From 35247ced644c5f18e21eec70378e0d5224ea2435 Mon Sep 17 00:00:00 2001 From: Francesco Torta <62566275+fra98@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:55:48 +0100 Subject: [PATCH] refactor: updated IP controller for new ipam --- apis/ipam/v1alpha1/ip_types.go | 12 +- apis/ipam/v1alpha1/zz_generated.deepcopy.go | 17 +- cmd/liqo-controller-manager/main.go | 2 +- .../modules/networking.go | 16 +- .../liqo-crds/crds/ipam.liqo.io_ips.yaml | 68 ++++-- docs/advanced/external-ip-remapping.md | 4 +- pkg/ipam/ips.go | 7 +- pkg/ipam/sync_test.go | 4 +- pkg/ipam/utils/doc.go | 16 ++ pkg/ipam/utils/utils.go | 109 ++++++++++ pkg/ipam/utils/utils_suite_test.go | 27 +++ pkg/ipam/utils/utils_test.go | 44 ++++ pkg/ipamold/ipam.go | 3 +- pkg/ipamold/utils/utils.go | 13 -- .../ipmapping/configuration_controller.go | 4 +- .../external-network/remapping/finalizer.go | 8 +- .../remapping/ip_controller.go | 6 +- .../configuration-controller/firewall.go | 4 +- .../route/internalnode_controller.go | 3 +- .../networking/ip-controller/ip_controller.go | 200 +++++++----------- pkg/liqoctl/authenticate/cluster.go | 6 +- pkg/liqoctl/test/network/setup/ip.go | 2 +- pkg/utils/getters/k8sGetters.go | 8 +- pkg/utils/ipam/ips.go | 19 +- pkg/utils/ipam/networks.go | 76 +++++-- pkg/virtualKubelet/provider/provider.go | 9 +- .../reflection/exposition/endpointslice.go | 6 +- .../exposition/endpointslice_test.go | 4 +- 28 files changed, 455 insertions(+), 242 deletions(-) create mode 100644 pkg/ipam/utils/doc.go create mode 100644 pkg/ipam/utils/utils.go create mode 100644 pkg/ipam/utils/utils_suite_test.go create mode 100644 pkg/ipam/utils/utils_test.go diff --git a/apis/ipam/v1alpha1/ip_types.go b/apis/ipam/v1alpha1/ip_types.go index 4dad966d9c..213ad392be 100644 --- a/apis/ipam/v1alpha1/ip_types.go +++ b/apis/ipam/v1alpha1/ip_types.go @@ -49,10 +49,10 @@ type IPSpec struct { // IP is the local IP. // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="IP field is immutable" IP networkingv1beta1.IP `json:"ip"` - // CIDR is the network CIDR where the desired IP should be allocated from. + // NetworkRef is the reference to the Network CR containing the CIDR where the desired IP should be allocated from. // It is optional, if left empty the IP will be allocated in a default network CIDR (e.g., external CIDR). // +kubebuilder:validation:Optional - CIDR *networkingv1beta1.CIDR `json:"cidr,omitempty"` + NetworkRef *v1.ObjectReference `json:"cidr,omitempty"` // ServiceTemplate contains the template to create the associated service (and endpointslice) for the IP endopoint. // If empty the creation of the service is disabled (default). // +kubebuilder:validation:Optional @@ -65,12 +65,11 @@ type IPSpec struct { // IPStatus defines remapped IPs. type IPStatus struct { - // IPMappings contains the mapping of the local IP for each remote cluster. - IPMappings map[string]networkingv1beta1.IP `json:"ipMappings,omitempty"` // IP is the remapped IP. // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="IP field is immutable" - IP networkingv1beta1.IP `json:"ip"` + IP networkingv1beta1.IP `json:"ip,omitempty"` // CIDR is the network CIDR where the IP is allocated. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="CIDR field is immutable" CIDR networkingv1beta1.CIDR `json:"cidr,omitempty"` } @@ -78,8 +77,9 @@ type IPStatus struct { // +kubebuilder:resource:categories=liqo // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Local IP",type=string,JSONPath=`.spec.ip` +// +kubebuilder:printcolumn:name="Remapped IP",type=string,JSONPath=`.status.ip` +// +kubebuilder:printcolumn:name="Remapped IP CIDR",type=string,JSONPath=`.status.cidr` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// +kubebuilder:printcolumn:name="Remapped IPs",type=string,JSONPath=`.status.ipMappings`,priority=1 // +genclient // IP is the Schema for the IP API. diff --git a/apis/ipam/v1alpha1/zz_generated.deepcopy.go b/apis/ipam/v1alpha1/zz_generated.deepcopy.go index 36b4697c16..7affb37385 100644 --- a/apis/ipam/v1alpha1/zz_generated.deepcopy.go +++ b/apis/ipam/v1alpha1/zz_generated.deepcopy.go @@ -19,7 +19,7 @@ package v1alpha1 import ( - "github.com/liqotech/liqo/apis/networking/v1beta1" + "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -66,7 +66,7 @@ func (in *IP) DeepCopyInto(out *IP) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) + out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IP. @@ -122,9 +122,9 @@ func (in *IPList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPSpec) DeepCopyInto(out *IPSpec) { *out = *in - if in.CIDR != nil { - in, out := &in.CIDR, &out.CIDR - *out = new(v1beta1.CIDR) + if in.NetworkRef != nil { + in, out := &in.NetworkRef, &out.NetworkRef + *out = new(v1.ObjectReference) **out = **in } if in.ServiceTemplate != nil { @@ -152,13 +152,6 @@ func (in *IPSpec) DeepCopy() *IPSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPStatus) DeepCopyInto(out *IPStatus) { *out = *in - if in.IPMappings != nil { - in, out := &in.IPMappings, &out.IPMappings - *out = make(map[string]v1beta1.IP, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPStatus. diff --git a/cmd/liqo-controller-manager/main.go b/cmd/liqo-controller-manager/main.go index 48add3e4df..000290e989 100644 --- a/cmd/liqo-controller-manager/main.go +++ b/cmd/liqo-controller-manager/main.go @@ -260,7 +260,7 @@ func main() { ipamClient = ipam.NewIPAMClient(conn) } - if err := modules.SetupNetworkingModule(ctx, mgr, &modules.NetworkingOption{ + if err := modules.SetupNetworkingModule(mgr, &modules.NetworkingOption{ DynClient: dynClient, Factory: factory, diff --git a/cmd/liqo-controller-manager/modules/networking.go b/cmd/liqo-controller-manager/modules/networking.go index 3305a4c5db..6135df9ae1 100644 --- a/cmd/liqo-controller-manager/modules/networking.go +++ b/cmd/liqo-controller-manager/modules/networking.go @@ -15,8 +15,6 @@ package modules import ( - "context" - "k8s.io/client-go/dynamic" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -35,6 +33,7 @@ import ( nodecontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/internal-network/node-controller" "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/internal-network/route" internalservercontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/internal-network/server-controller" + ipctrl "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/ip-controller" networkctrl "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/network-controller" dynamicutils "github.com/liqotech/liqo/pkg/utils/dynamic" ) @@ -60,19 +59,18 @@ type NetworkingOption struct { } // SetupNetworkingModule setup the networking module and initializes its controllers . -func SetupNetworkingModule(_ context.Context, mgr manager.Manager, opts *NetworkingOption) error { +func SetupNetworkingModule(mgr manager.Manager, opts *NetworkingOption) error { networkReconciler := networkctrl.NewNetworkReconciler(mgr.GetClient(), mgr.GetScheme(), opts.IpamClient) if err := networkReconciler.SetupWithManager(mgr, opts.NetworkWorkers); err != nil { klog.Errorf("Unable to start the networkReconciler: %v", err) return err } - // TODO: refactor IP reconciler with the new IPAM client. - // ipReconciler := ipctrl.NewIPReconciler(mgr.GetClient(), mgr.GetScheme(), opts.IpamClient) - // if err := ipReconciler.SetupWithManager(ctx, mgr, opts.IPWorkers); err != nil { - // klog.Errorf("Unable to start the ipReconciler: %v", err) - // return err - // } + ipReconciler := ipctrl.NewIPReconciler(mgr.GetClient(), mgr.GetScheme(), opts.IpamClient) + if err := ipReconciler.SetupWithManager(mgr, opts.IPWorkers); err != nil { + klog.Errorf("Unable to start the ipReconciler: %v", err) + return err + } cfgReconciler := configuration.NewConfigurationReconciler(mgr.GetClient(), mgr.GetScheme(), mgr.GetEventRecorderFor("configuration-controller")) diff --git a/deployments/liqo/charts/liqo-crds/crds/ipam.liqo.io_ips.yaml b/deployments/liqo/charts/liqo-crds/crds/ipam.liqo.io_ips.yaml index c2f8e96c6c..a7fbed5b69 100644 --- a/deployments/liqo/charts/liqo-crds/crds/ipam.liqo.io_ips.yaml +++ b/deployments/liqo/charts/liqo-crds/crds/ipam.liqo.io_ips.yaml @@ -20,13 +20,15 @@ spec: - jsonPath: .spec.ip name: Local IP type: string + - jsonPath: .status.ip + name: Remapped IP + type: string + - jsonPath: .status.cidr + name: Remapped IP CIDR + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - - jsonPath: .status.ipMappings - name: Remapped IPs - priority: 1 - type: string name: v1alpha1 schema: openAPIV3Schema: @@ -54,10 +56,49 @@ spec: properties: cidr: description: |- - CIDR is the network CIDR where the desired IP should be allocated from. + NetworkRef is the reference to the Network CR containing the CIDR where the desired IP should be allocated from. It is optional, if left empty the IP will be allocated in a default network CIDR (e.g., external CIDR). - format: cidr - type: string + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic ip: description: IP is the local IP. format: ipv4 @@ -449,6 +490,9 @@ spec: description: CIDR is the network CIDR where the IP is allocated. format: cidr type: string + x-kubernetes-validations: + - message: CIDR field is immutable + rule: self == oldSelf ip: description: IP is the remapped IP. format: ipv4 @@ -456,16 +500,6 @@ spec: x-kubernetes-validations: - message: IP field is immutable rule: self == oldSelf - ipMappings: - additionalProperties: - description: IP defines a syntax validated IP. - format: ipv4 - type: string - description: IPMappings contains the mapping of the local IP for each - remote cluster. - type: object - required: - - ip type: object required: - spec diff --git a/docs/advanced/external-ip-remapping.md b/docs/advanced/external-ip-remapping.md index 89fac7ae9b..2f7e6f8775 100644 --- a/docs/advanced/external-ip-remapping.md +++ b/docs/advanced/external-ip-remapping.md @@ -59,8 +59,8 @@ apiVersion: ipam.liqo.io/v1alpha1 kind: IP ... status: - ipMappings: - cluster1: + ip: + cidr: ``` diff --git a/pkg/ipam/ips.go b/pkg/ipam/ips.go index 0a9117985c..5784cb7ef9 100644 --- a/pkg/ipam/ips.go +++ b/pkg/ipam/ips.go @@ -18,6 +18,7 @@ import ( "context" "time" + "github.com/google/nftables" klog "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" @@ -64,8 +65,12 @@ func (lipam *LiqoIPAM) acquireIP(cidr string) (string, error) { if lipam.cacheIPs == nil { lipam.cacheIPs = make(map[string]ipInfo) } + firstIP, _, err := nftables.NetFirstAndLastIP(cidr) + if err != nil { + return "", err + } ip := ipCidr{ - ip: "", + ip: firstIP.String(), cidr: cidr, } lipam.cacheIPs[ip.String()] = ipInfo{ diff --git a/pkg/ipam/sync_test.go b/pkg/ipam/sync_test.go index 8d9c54e794..4ff86ec37e 100644 --- a/pkg/ipam/sync_test.go +++ b/pkg/ipam/sync_test.go @@ -22,7 +22,6 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" - "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client/fake" ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" @@ -81,8 +80,7 @@ var _ = Describe("Sync routine tests", func() { Namespace: testNamespace, }, Spec: ipamv1alpha1.IPSpec{ - IP: networkingv1beta1.IP(ip), - CIDR: ptr.To(networkingv1beta1.CIDR(cidr)), + IP: networkingv1beta1.IP(ip), }, Status: ipamv1alpha1.IPStatus{ IP: networkingv1beta1.IP(ip), diff --git a/pkg/ipam/utils/doc.go b/pkg/ipam/utils/doc.go new file mode 100644 index 0000000000..9efbc00809 --- /dev/null +++ b/pkg/ipam/utils/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2024 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 utils contain utility functions for the IPAM package. +package utils diff --git a/pkg/ipam/utils/utils.go b/pkg/ipam/utils/utils.go new file mode 100644 index 0000000000..3a49415ae5 --- /dev/null +++ b/pkg/ipam/utils/utils.go @@ -0,0 +1,109 @@ +// Copyright 2019-2024 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 utils + +import ( + "fmt" + "net" + "net/netip" + + "go4.org/netipx" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + "github.com/liqotech/liqo/pkg/consts" +) + +// MapIPToNetwork creates a new IP address obtained by means of the old IP address and the new network. +func MapIPToNetwork(newNetwork, oldIP string) (newIP string, err error) { + if newNetwork == consts.DefaultCIDRValue { + return oldIP, nil + } + // Parse newNetwork + ip, network, err := net.ParseCIDR(newNetwork) + if err != nil { + return "", err + } + // Get mask + mask := network.Mask + // Get slice of bytes for newNetwork + // Type net.IP has underlying type []byte + parsedNewIP := ip.To4() + // Get oldIP as slice of bytes + parsedOldIP := net.ParseIP(oldIP) + if parsedOldIP == nil { + return "", fmt.Errorf("cannot parse oldIP") + } + parsedOldIP = parsedOldIP.To4() + // Substitute the last 32-mask bits of newNetwork with bits taken by the old ip + for i := 0; i < len(mask); i++ { + // Step 1: NOT(mask[i]) = mask[i] ^ 0xff. They are the 'host' bits + // Step 2: BITWISE AND between the host bits and parsedOldIP[i] zeroes the network bits in parsedOldIP[i] + // Step 3: BITWISE OR copies the result of step 2 in newIP[i] + parsedNewIP[i] |= (mask[i] ^ 0xff) & parsedOldIP[i] + } + newIP = parsedNewIP.String() + return +} + +// GetMask retrieves the mask from a CIDR. +func GetMask(network string) uint8 { + _, subnet, err := net.ParseCIDR(network) + utilruntime.Must(err) + ones, _ := subnet.Mask.Size() + return uint8(ones) +} + +// SetMask forges a new cidr from a network cidr and a mask. +func SetMask(network string, mask uint8) string { + _, n, err := net.ParseCIDR(network) + utilruntime.Must(err) + newMask := net.CIDRMask(int(mask), 32) + n.Mask = newMask + return n.String() +} + +// Next used to get the second half of a given network. +func Next(network string) string { + prefix, err := netip.ParsePrefix(network) + utilruntime.Must(err) + // Step 1: Get last IP address of network + // Step 2: Get next IP address + firstIP := netipx.RangeOfPrefix(prefix).To().Next() + prefix = netip.PrefixFrom(firstIP, prefix.Bits()) + return prefix.String() +} + +// IsValidCIDR returns an error if the received CIDR is invalid. +func IsValidCIDR(cidr string) error { + _, _, err := net.ParseCIDR(cidr) + return err +} + +// SplitNetwork returns the two halves that make up a given network. +func SplitNetwork(network string) []string { + halves := make([]string, 2) + + // Get halves mask length. + mask := GetMask(network) + mask++ + + // Get first half CIDR. + halves[0] = SetMask(network, mask) + + // Get second half CIDR. + halves[1] = Next(halves[0]) + + return halves +} diff --git a/pkg/ipam/utils/utils_suite_test.go b/pkg/ipam/utils/utils_suite_test.go new file mode 100644 index 0000000000..67d903d40c --- /dev/null +++ b/pkg/ipam/utils/utils_suite_test.go @@ -0,0 +1,27 @@ +// Copyright 2019-2024 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 utils_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestUtils(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Utils Suite") +} diff --git a/pkg/ipam/utils/utils_test.go b/pkg/ipam/utils/utils_test.go new file mode 100644 index 0000000000..30b79cba90 --- /dev/null +++ b/pkg/ipam/utils/utils_test.go @@ -0,0 +1,44 @@ +// Copyright 2019-2024 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 utils_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/liqotech/liqo/pkg/ipam/utils" +) + +var _ = Describe("Mapping", func() { + DescribeTable("MapIPToNetwork", + func(oldIp, newPodCidr, expectedIP string, expectedErr string) { + ip, err := utils.MapIPToNetwork(oldIp, newPodCidr) + if expectedErr != "" { + Expect(err.Error()).To(Equal(expectedErr)) + } else { + Expect(err).ToNot(HaveOccurred()) + } + Expect(ip).To(Equal(expectedIP)) + }, + Entry("Mapping 10.2.1.3 to 10.0.4.0/24", "10.0.4.0/24", "10.2.1.3", "10.0.4.3", ""), + Entry("Mapping 10.2.1.128 to 10.0.4.0/24", "10.0.4.0/24", "10.2.1.128", "10.0.4.128", ""), + Entry("Mapping 10.2.1.1 to 10.0.4.0/24", "10.0.4.0/24", "10.2.1.1", "10.0.4.1", ""), + Entry("Mapping 10.2.127.128 to 10.0.128.0/23", "10.0.128.0/23", "10.2.127.128", "10.0.129.128", ""), + Entry("Mapping 10.2.128.128 to 10.0.126.0/23", "10.0.127.0/23", "10.2.128.128", "10.0.127.128", ""), + Entry("Mapping 10.2.128.128 to 10.0.126.0/25", "10.0.126.0/25", "10.2.128.128", "10.0.126.0", ""), + Entry("Using an invalid newPodCidr", "10.0..0/25", "10.2.128.128", "", "invalid CIDR address: 10.0..0/25"), + Entry("Using an invalid oldIp", "10.0.0.0/25", "10.2...128", "", "cannot parse oldIP"), + ) +}) diff --git a/pkg/ipamold/ipam.go b/pkg/ipamold/ipam.go index ad32a7e91e..46ad253dcd 100644 --- a/pkg/ipamold/ipam.go +++ b/pkg/ipamold/ipam.go @@ -36,6 +36,7 @@ import ( "github.com/liqotech/liqo/pkg/consts" ipamerrors "github.com/liqotech/liqo/pkg/ipamold/errors" ipamutils "github.com/liqotech/liqo/pkg/ipamold/utils" + utils "github.com/liqotech/liqo/pkg/utils/ipam" ) // Ipam Interface. @@ -415,7 +416,7 @@ func (liqoIPAM *IPAM) GetOrSetExternalCIDR(ctx context.Context, getOrSetExtCIDRR // Acquire the UnknownSourceIP // It must be the first ip in the externalCIDR - unknownSourceIP, err := ipamutils.GetUnknownSourceIP(externalCIDR) + unknownSourceIP, err := utils.GetUnknownSourceIP(externalCIDR) if err != nil { return &GetOrSetExtCIDRResponse{}, fmt.Errorf("cannot get the UnknownSourceIP: %w", err) } diff --git a/pkg/ipamold/utils/utils.go b/pkg/ipamold/utils/utils.go index 580f3914ce..68b1bb8ef1 100644 --- a/pkg/ipamold/utils/utils.go +++ b/pkg/ipamold/utils/utils.go @@ -19,7 +19,6 @@ import ( "net" "net/netip" - "github.com/google/nftables" "go4.org/netipx" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -117,15 +116,3 @@ func SplitNetwork(network string) []string { return halves } - -// GetUnknownSourceIP returns the IP address used to map unknown sources. -func GetUnknownSourceIP(extCIDR string) (string, error) { - if extCIDR == "" { - return "", fmt.Errorf("ExternalCIDR not set") - } - firstExtCIDRip, _, err := nftables.NetFirstAndLastIP(extCIDR) - if err != nil { - return "", fmt.Errorf("cannot get first IP of ExternalCIDR") - } - return firstExtCIDRip.String(), nil -} diff --git a/pkg/liqo-controller-manager/ipmapping/configuration_controller.go b/pkg/liqo-controller-manager/ipmapping/configuration_controller.go index c6d6c7a7bd..153f568ee1 100644 --- a/pkg/liqo-controller-manager/ipmapping/configuration_controller.go +++ b/pkg/liqo-controller-manager/ipmapping/configuration_controller.go @@ -32,8 +32,8 @@ import ( ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1" "github.com/liqotech/liqo/pkg/consts" - "github.com/liqotech/liqo/pkg/ipamold/utils" configuration "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/external-network/configuration" + ipamutils "github.com/liqotech/liqo/pkg/utils/ipam" ) // cluster-role @@ -74,7 +74,7 @@ func (r *ConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Reques klog.V(4).Infof("Reconciling configuration %q", req.NamespacedName) extCIDR := cfg.Status.Remote.CIDR.External - remoteUnknownSourceIP, err := utils.GetUnknownSourceIP(string(extCIDR)) + remoteUnknownSourceIP, err := ipamutils.GetUnknownSourceIP(string(extCIDR)) if err != nil { return ctrl.Result{}, fmt.Errorf("unable to get the unknown source IP: %w", err) } diff --git a/pkg/liqo-controller-manager/networking/external-network/remapping/finalizer.go b/pkg/liqo-controller-manager/networking/external-network/remapping/finalizer.go index 6be80ca193..881651225b 100644 --- a/pkg/liqo-controller-manager/networking/external-network/remapping/finalizer.go +++ b/pkg/liqo-controller-manager/networking/external-network/remapping/finalizer.go @@ -23,18 +23,18 @@ import ( ) const ( - // ipMappingsControllerFinalizer is the finalizer added to IP resources (related to mapping) to allow the controller to clean up. - ipMappingsControllerFinalizer = "ipmapping-nat-controller.liqo.io/finalizer" + // ipMappingControllerFinalizer is the finalizer added to IP resources (related to mapping) to allow the controller to clean up. + ipMappingControllerFinalizer = "ipmapping-nat-controller.liqo.io/finalizer" ) func (r *IPReconciler) ensureIPMappingFinalizerPresence( ctx context.Context, ip *ipamv1alpha1.IP) error { - controllerutil.AddFinalizer(ip, ipMappingsControllerFinalizer) + controllerutil.AddFinalizer(ip, ipMappingControllerFinalizer) return r.Client.Update(ctx, ip) } func (r *IPReconciler) ensureIPMappingFinalizerAbsence( ctx context.Context, ip *ipamv1alpha1.IP) error { - controllerutil.RemoveFinalizer(ip, ipMappingsControllerFinalizer) + controllerutil.RemoveFinalizer(ip, ipMappingControllerFinalizer) return r.Client.Update(ctx, ip) } diff --git a/pkg/liqo-controller-manager/networking/external-network/remapping/ip_controller.go b/pkg/liqo-controller-manager/networking/external-network/remapping/ip_controller.go index 4116545297..b5e6b151f8 100644 --- a/pkg/liqo-controller-manager/networking/external-network/remapping/ip_controller.go +++ b/pkg/liqo-controller-manager/networking/external-network/remapping/ip_controller.go @@ -65,7 +65,7 @@ func (r *IPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re klog.V(4).Infof("Reconciling IP %s", req.String()) deleting := !ip.DeletionTimestamp.IsZero() - containsFinalizer := controllerutil.ContainsFinalizer(ip, ipMappingsControllerFinalizer) + containsFinalizer := controllerutil.ContainsFinalizer(ip, ipMappingControllerFinalizer) if !deleting { if !containsFinalizer { if err := r.ensureIPMappingFinalizerPresence(ctx, ip); err != nil { @@ -86,8 +86,8 @@ func (r *IPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re return ctrl.Result{}, nil } - if len(ip.Status.IPMappings) == 0 { - klog.Warningf("IP %s has no IP mappings yet", req.String()) + if ip.Status.IP != "" { + klog.Warningf("IP %q has no IP assigned yet", req.String()) return ctrl.Result{}, nil } diff --git a/pkg/liqo-controller-manager/networking/internal-network/configuration-controller/firewall.go b/pkg/liqo-controller-manager/networking/internal-network/configuration-controller/firewall.go index edcb2f64a6..004e27269a 100644 --- a/pkg/liqo-controller-manager/networking/internal-network/configuration-controller/firewall.go +++ b/pkg/liqo-controller-manager/networking/internal-network/configuration-controller/firewall.go @@ -27,7 +27,7 @@ import ( networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1" firewallapi "github.com/liqotech/liqo/apis/networking/v1beta1/firewall" "github.com/liqotech/liqo/pkg/fabric" - "github.com/liqotech/liqo/pkg/ipamold/utils" + ipamutils "github.com/liqotech/liqo/pkg/utils/ipam" ) func (r *ConfigurationReconciler) ensureFirewallConfiguration(ctx context.Context, @@ -93,7 +93,7 @@ func forgeFirewallChain() *firewallapi.Chain { } func forgeFirewallNatRule(cfg *networkingv1beta1.Configuration, opts *Options) (natrules []firewallapi.NatRule, err error) { - unknownSourceIP, err := utils.GetUnknownSourceIP(cfg.Spec.Local.CIDR.External.String()) + unknownSourceIP, err := ipamutils.GetUnknownSourceIP(cfg.Spec.Local.CIDR.External.String()) if err != nil { return nil, fmt.Errorf("unable to get first IP from CIDR: %w", err) } diff --git a/pkg/liqo-controller-manager/networking/internal-network/route/internalnode_controller.go b/pkg/liqo-controller-manager/networking/internal-network/route/internalnode_controller.go index ab8d69525c..b8426505c1 100644 --- a/pkg/liqo-controller-manager/networking/internal-network/route/internalnode_controller.go +++ b/pkg/liqo-controller-manager/networking/internal-network/route/internalnode_controller.go @@ -32,7 +32,6 @@ import ( ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1" "github.com/liqotech/liqo/pkg/consts" - "github.com/liqotech/liqo/pkg/ipamold/utils" configuration "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/external-network/configuration" "github.com/liqotech/liqo/pkg/utils/getters" "github.com/liqotech/liqo/pkg/utils/ipam" @@ -117,7 +116,7 @@ func (r *InternalNodeReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, err } - unknownSourceIP, err := utils.GetUnknownSourceIP(extCIDR) + unknownSourceIP, err := ipam.GetUnknownSourceIP(extCIDR) if err != nil { return ctrl.Result{}, err } diff --git a/pkg/liqo-controller-manager/networking/ip-controller/ip_controller.go b/pkg/liqo-controller-manager/networking/ip-controller/ip_controller.go index b7bb641b9c..7788a2e501 100644 --- a/pkg/liqo-controller-manager/networking/ip-controller/ip_controller.go +++ b/pkg/liqo-controller-manager/networking/ip-controller/ip_controller.go @@ -16,29 +16,23 @@ package ipctrl import ( "context" - "slices" + "fmt" v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1" "github.com/liqotech/liqo/pkg/consts" - ipam "github.com/liqotech/liqo/pkg/ipamold" - "github.com/liqotech/liqo/pkg/utils/getters" + "github.com/liqotech/liqo/pkg/ipam" ipamutils "github.com/liqotech/liqo/pkg/utils/ipam" - "github.com/liqotech/liqo/pkg/utils/slice" ) const ( @@ -50,34 +44,30 @@ type IPReconciler struct { client.Client Scheme *runtime.Scheme - ipamClient ipam.IpamClient - externalCIDRSet bool + ipamClient ipam.IPAMClient } // NewIPReconciler returns a new IPReconciler. -func NewIPReconciler(cl client.Client, s *runtime.Scheme, ipamClient ipam.IpamClient) *IPReconciler { +func NewIPReconciler(cl client.Client, s *runtime.Scheme, ipamClient ipam.IPAMClient) *IPReconciler { return &IPReconciler{ Client: cl, Scheme: s, - ipamClient: ipamClient, - externalCIDRSet: false, + ipamClient: ipamClient, } } // +kubebuilder:rbac:groups=ipam.liqo.io,resources=ips,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=ipam.liqo.io,resources=ips/status,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=ipam.liqo.io,resources=ips/finalizers,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=networking.liqo.io,resources=configurations,verbs=get;list;watch +// +kubebuilder:rbac:groups=ipam.liqo.io,resources=networks,verbs=get;list;watch // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=discovery.k8s.io,resources=endpointslices,verbs=get;list;watch;create;update;patch;delete // Reconcile Ip objects. func (r *IPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var ip ipamv1alpha1.IP - var desiredIP networkingv1beta1.IP - // Fetch the IP instance + var ip ipamv1alpha1.IP if err := r.Get(ctx, req.NamespacedName, &ip); err != nil { if apierrors.IsNotFound(err) { klog.V(4).Infof(" %q not found", req.NamespacedName) @@ -87,34 +77,15 @@ 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.GetExternalCIDR(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 - configurations, err := getters.ListConfigurationsByLabel(ctx, r.Client, labels.Everything()) + _, cidr, err := r.handleNetworkRef(ctx, &ip) if err != nil { - klog.Errorf("error while listing virtual nodes: %v", err) + klog.Errorf("error while handling NetworkRef for IP %q: %v", req.NamespacedName, err) return ctrl.Result{}, err } - clusterIDs := getters.RetrieveClusterIDsFromObjectsLabels(slice.ToPointerSlice(configurations.Items)) - if ip.GetDeletionTimestamp().IsZero() { if !controllerutil.ContainsFinalizer(&ip, ipamIPFinalizer) { - // Add finalizer to prevent deletion without unmapping the IP. + // Add finalizer to prevent deletion without releasing the IP. controllerutil.AddFinalizer(&ip, ipamIPFinalizer) // Update the IP object @@ -128,29 +99,30 @@ func (r *IPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re return ctrl.Result{}, nil } - needUpdate, err := r.forgeIPMappings(ctx, clusterIDs, desiredIP, &ip) - if err != nil { - return ctrl.Result{}, err - } + if ip.Status.IP == "" { + if err := r.forgeIPStatus(ctx, &ip, cidr); err != nil { + klog.Errorf("error while forging IP status for IP %q: %v", req.NamespacedName, err) + return ctrl.Result{}, err + } - // Update resource status if needed - if needUpdate { if err := r.Client.Status().Update(ctx, &ip); err != nil { - klog.Errorf("error while updating IP %q status: %v", req.NamespacedName, err) + klog.Errorf("error while updating IP %q: %v", req.NamespacedName, err) return ctrl.Result{}, err } - klog.Infof("updated IP %q status", req.NamespacedName) + klog.Infof("updated IP %q", req.NamespacedName) } // Create service and associated endpointslice if the template is defined if err := r.handleAssociatedService(ctx, &ip); err != nil { + klog.Errorf("error while handling associated service for IP %q: %v", req.NamespacedName, err) return ctrl.Result{}, err } } else if controllerutil.ContainsFinalizer(&ip, ipamIPFinalizer) { // the resource is being deleted, but the finalizer is present: - // - unmap the remapped IPs + // - release the IP from the IPAM. // - remove finalizer from the resource. - if err := r.handleDelete(ctx, clusterIDs, desiredIP, &ip); err != nil { + if err := r.handleDelete(ctx, &ip); err != nil { + klog.Errorf("error while handling deletion of IP %q: %v", req.NamespacedName, err) return ctrl.Result{}, err } @@ -169,122 +141,112 @@ func (r *IPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re } // SetupWithManager monitors IP resources. -func (r *IPReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, workers int) error { - // List all IP resources and enqueue them. - enqueuer := func(_ context.Context, _ client.Object) []reconcile.Request { - var ipList ipamv1alpha1.IPList - if err := r.List(ctx, &ipList); err != nil { - klog.Errorf("error while listing IPs: %v", err) - return nil - } - var requests []reconcile.Request - for i := range ipList.Items { - requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ - Namespace: ipList.Items[i].Namespace, Name: ipList.Items[i].Name}}) - } - return requests - } - +func (r *IPReconciler) SetupWithManager(mgr ctrl.Manager, workers int) error { return ctrl.NewControllerManagedBy(mgr).Named(consts.CtrlIP). For(&ipamv1alpha1.IP{}). Owns(&v1.Service{}). Owns(&discoveryv1.EndpointSlice{}). - Watches(&networkingv1beta1.Configuration{}, handler.EnqueueRequestsFromMapFunc(enqueuer)). WithOptions(controller.Options{MaxConcurrentReconciles: workers}). Complete(r) } -// forgeIPMappings forge the IP mappings for each remote cluster. Return true if the status must be updated, false otherwise. -func (r *IPReconciler) forgeIPMappings(ctx context.Context, clusterIDs []string, desiredIP networkingv1beta1.IP, ip *ipamv1alpha1.IP) (bool, error) { - // Update IP status for each remote cluster - needUpdate := false - - if ip.Status.IPMappings == nil { - ip.Status.IPMappings = make(map[string]networkingv1beta1.IP) +// handleNetworkRef get the CIDR of the Network referenced by the IP, or default to the +// external CIDR of the local cluster if the IP has no NetworkRef set. +func (r *IPReconciler) handleNetworkRef(ctx context.Context, ip *ipamv1alpha1.IP) (*ipamv1alpha1.Network, networkingv1beta1.CIDR, error) { + // If the IP has not set a reference to a Network CIDR, we remap it on the external CIDR of the local cluster. + if ip.Spec.NetworkRef == nil { + network, err := ipamutils.GetExternalCIDRNetwork(ctx, r.Client) + if err != nil { + return nil, "", err + } + // The externalCIDR Network has no CIDR set yet, we return an error. + if network.Status.CIDR == "" { + return nil, "", fmt.Errorf("externalCIDR is not set yet. Configure it to correctly handle IP mapping") + } + return network, network.Status.CIDR, nil } - for i := range clusterIDs { - remoteClusterID := &clusterIDs[i] - // Update IP status if it is not set yet - // The IPAM function that maps IPs is not idempotent, so we avoid to call it - // 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) - if err != nil { - return false, err - } - ip.Status.IPMappings[*remoteClusterID] = remappedIP - needUpdate = true - } + // Retrieve the Network object referenced by the IP. + var network ipamv1alpha1.Network + if err := r.Get(ctx, client.ObjectKey{Namespace: ip.Spec.NetworkRef.Namespace, Name: ip.Spec.NetworkRef.Name}, &network); err != nil { + return nil, "", err } + if network.Status.CIDR != "" { + return nil, "", fmt.Errorf("network %s/%s has no CIDR set yet", network.Namespace, network.Name) + } + return &network, network.Status.CIDR, nil +} - // Check if the IPMappings has entries associated to clusters that have been deleted (i.e., the virtualNode is missing) - for entry := range ip.Status.IPMappings { - if !slices.Contains(clusterIDs, entry) { - // We ignore eventual errors from the IPAM because the entries in the IpamStorage for that cluster - // may have been already removed. - _ = deleteRemappedIP(ctx, r.ipamClient, entry, desiredIP) - delete(ip.Status.IPMappings, entry) - needUpdate = true +// forgeIPStatus forge the IP status. +func (r *IPReconciler) forgeIPStatus(ctx context.Context, ip *ipamv1alpha1.IP, cidr networkingv1beta1.CIDR) error { + // Update IP status if it is not set yet. + // The IPAM function that maps IPs is not idempotent, so we avoid to call it + // multiple times by checking if the IP is already set. + if ip.Status.IP == "" { + acquiredIP, err := acquireIP(ctx, r.ipamClient, cidr) + if err != nil { + return err } + ip.Status.IP = acquiredIP + ip.Status.CIDR = cidr } - return needUpdate, nil + return nil } -// handleDelete handles the deletion of the IP resource. It call the IPAM to unmap the IPs of each remote cluster. -func (r *IPReconciler) handleDelete(ctx context.Context, clusterIDs []string, desiredIP networkingv1beta1.IP, ip *ipamv1alpha1.IP) error { - for i := range clusterIDs { - remoteClusterID := &clusterIDs[i] - if err := deleteRemappedIP(ctx, r.ipamClient, *remoteClusterID, desiredIP); err != nil { +// handleDelete handles the deletion of the IP resource. It call the IPAM to release the IP. +func (r *IPReconciler) handleDelete(ctx context.Context, ip *ipamv1alpha1.IP) error { + if ip.Status.IP != "" && ip.Status.CIDR != "" { + if err := deleteIP(ctx, r.ipamClient, ip.Status.IP, ip.Status.CIDR); err != nil { return err } - delete(ip.Status.IPMappings, *remoteClusterID) } // Remove status and finalizer, and update the object. - ip.Status.IPMappings = nil + ip.Status.IP = "" + ip.Status.CIDR = "" controllerutil.RemoveFinalizer(ip, ipamIPFinalizer) return nil } -// getRemappedIP returns the remapped IP for the given IP and remote clusterID. -func getRemappedIP(ctx context.Context, ipamClient ipam.IpamClient, remoteClusterID string, - desiredIP networkingv1beta1.IP) (networkingv1beta1.IP, error) { +// acquireIP acquire a free IP of a given CIDR from the IPAM. +func acquireIP(ctx context.Context, ipamClient ipam.IPAMClient, cidr networkingv1beta1.CIDR) (networkingv1beta1.IP, error) { switch ipamClient.(type) { case nil: - // IPAM is not enabled, use original IP from spec - return desiredIP, nil + // IPAM is not enabled, return an error. + return "", fmt.Errorf("IPAM is not enabled") default: // interact with the IPAM to retrieve the correct mapping. - response, err := ipamClient.MapEndpointIP(ctx, &ipam.MapRequest{ - ClusterID: remoteClusterID, Ip: desiredIP.String()}) + response, err := ipamClient.IPAcquire(ctx, &ipam.IPAcquireRequest{ + Cidr: string(cidr), + }) if err != nil { - klog.Errorf("IPAM: error while mapping IP %s for remote cluster %q: %v", desiredIP, remoteClusterID, err) + klog.Errorf("IPAM: error while acquiring IP from CIDR %q: %v", cidr, err) return "", err } - klog.Infof("IPAM: mapped IP %s to %s for remote cluster %q", desiredIP, response.Ip, remoteClusterID) + klog.Infof("IPAM: acquired IP %q from CIDR %q", response.Ip, cidr) return networkingv1beta1.IP(response.Ip), nil } } -// deleteRemappedIP unmaps the IP for the given remote clusterID. -func deleteRemappedIP(ctx context.Context, ipamClient ipam.IpamClient, remoteClusterID string, desiredIP networkingv1beta1.IP) error { +// deleteIP release a IP of a given CIDR from the IPAM. +func deleteIP(ctx context.Context, ipamClient ipam.IPAMClient, ip networkingv1beta1.IP, cidr networkingv1beta1.CIDR) error { switch ipamClient.(type) { case nil: - // If the IPAM is not enabled we do not need to release the translation. + // If the IPAM is not enabled we do not need to release any IP. return nil default: // Interact with the IPAM to release the translation. - _, err := ipamClient.UnmapEndpointIP(ctx, &ipam.UnmapRequest{ - ClusterID: remoteClusterID, Ip: desiredIP.String()}) + _, err := ipamClient.IPRelease(ctx, &ipam.IPReleaseRequest{ + Ip: string(ip), + Cidr: string(cidr), + }) if err != nil { - klog.Errorf("IPAM: error while unmapping IP %s for remote cluster %q: %v", desiredIP, remoteClusterID, err) + klog.Errorf("IPAM: error while de allocating IP %q (cidr: %q): %v", ip, cidr, err) return err } - klog.Infof("IPAM: unmapped IP %s for remote cluster %q", desiredIP, remoteClusterID) + klog.Infof("IPAM: de-allocated IP %q (cidr: %q)", ip, cidr) return nil } } diff --git a/pkg/liqoctl/authenticate/cluster.go b/pkg/liqoctl/authenticate/cluster.go index 0d1015e2b5..0dd73418d2 100644 --- a/pkg/liqoctl/authenticate/cluster.go +++ b/pkg/liqoctl/authenticate/cluster.go @@ -226,11 +226,11 @@ func (c *Cluster) GetAPIServerProxyRemappedIP(ctx context.Context) (string, erro return "", err } - for _, ipam := range ip.Status.IPMappings { - return ipam.String(), nil + if ip.Status.IP != "" { + return "", fmt.Errorf("no IP found, make sure the Liqo Networking module is enabled and working") } - return "", fmt.Errorf("no IP found, make sure the Liqo Networking module is enabled and working") + return string(ip.Status.IP), nil } // RemapIPExternalCIDR remaps the given IP address to the external CIDR of the remote cluster. diff --git a/pkg/liqoctl/test/network/setup/ip.go b/pkg/liqoctl/test/network/setup/ip.go index 9fd42e29ab..e0bbefa34e 100644 --- a/pkg/liqoctl/test/network/setup/ip.go +++ b/pkg/liqoctl/test/network/setup/ip.go @@ -75,7 +75,7 @@ func WaitIPRemapped(ctx context.Context, cl ctrlclient.Client) error { if err := cl.Get(ctx, ctrlclient.ObjectKey{Name: IPName, Namespace: NamespaceName}, &ip); err != nil { return false, err } - if len(ip.Status.IPMappings) == 0 { + if ip.Status.IP == "" { return false, nil } return true, nil diff --git a/pkg/utils/getters/k8sGetters.go b/pkg/utils/getters/k8sGetters.go index 298c502bea..c7287625f1 100644 --- a/pkg/utils/getters/k8sGetters.go +++ b/pkg/utils/getters/k8sGetters.go @@ -948,21 +948,21 @@ func GetUniqueNetworkByLabel(ctx context.Context, cl client.Client, lSelector la return nil, err } - switch len(networks.Items) { + switch len(networks) { case 0: return nil, kerrors.NewNotFound(ipamv1alpha1.NetworkGroupResource, ipamv1alpha1.NetworkResource) case 1: - return &networks.Items[0], nil + return &networks[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) { +func GetNetworksByLabel(ctx context.Context, cl client.Client, lSelector labels.Selector) ([]ipamv1alpha1.Network, error) { var networks ipamv1alpha1.NetworkList if err := cl.List(ctx, &networks, &client.ListOptions{LabelSelector: lSelector}); err != nil { return nil, err } - return &networks, nil + return networks.Items, nil } diff --git a/pkg/utils/ipam/ips.go b/pkg/utils/ipam/ips.go index 0b5f2abea3..cdebfb931d 100644 --- a/pkg/utils/ipam/ips.go +++ b/pkg/utils/ipam/ips.go @@ -15,6 +15,10 @@ package ipam import ( + "fmt" + + "github.com/google/nftables" + ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1" "github.com/liqotech/liqo/pkg/consts" @@ -34,8 +38,17 @@ func IsAPIServerProxyIP(ip *ipamv1alpha1.IP) bool { // GetRemappedIP returns the remapped IP of the given IP resource. func GetRemappedIP(ip *ipamv1alpha1.IP) networkingv1beta1.IP { - for _, ipremapped := range ip.Status.IPMappings { - return ipremapped + return ip.Status.IP +} + +// GetUnknownSourceIP returns the IP address used to map unknown sources. +func GetUnknownSourceIP(extCIDR string) (string, error) { + if extCIDR == "" { + return "", fmt.Errorf("ExternalCIDR not set") + } + firstExtCIDRip, _, err := nftables.NetFirstAndLastIP(extCIDR) + if err != nil { + return "", fmt.Errorf("cannot get first IP of ExternalCIDR") } - return "" + return firstExtCIDRip.String(), nil } diff --git a/pkg/utils/ipam/networks.go b/pkg/utils/ipam/networks.go index 7e1118ef18..577d6d2c58 100644 --- a/pkg/utils/ipam/networks.go +++ b/pkg/utils/ipam/networks.go @@ -30,35 +30,58 @@ import ( 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{ +// GetPodCIDRNetwork retrieves the Network resource of type PodCIDR. +func GetPodCIDRNetwork(ctx context.Context, cl client.Client) (*ipamv1alpha1.Network, error) { + return liqogetters.GetUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ consts.NetworkTypeLabelKey: string(consts.NetworkTypePodCIDR), })) +} + +// GetPodCIDR retrieves the podCIDR of the local cluster. +func GetPodCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := GetPodCIDRNetwork(ctx, cl) if err != nil { return "", err } - return nw.Spec.CIDR.String(), nil + if nw.Status.CIDR == "" { + return "", fmt.Errorf("the pod CIDR is not yet configured: missing status on the Network resource") + } + + return nw.Status.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{ +// GetServiceCIDRNetwork retrieves the Network resource of type ServiceCIDR. +func GetServiceCIDRNetwork(ctx context.Context, cl client.Client) (*ipamv1alpha1.Network, error) { + return liqogetters.GetUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ consts.NetworkTypeLabelKey: string(consts.NetworkTypeServiceCIDR), })) +} + +// GetServiceCIDR retrieves the serviceCIDR of the local cluster. +func GetServiceCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := GetServiceCIDRNetwork(ctx, cl) if err != nil { return "", err } - return nw.Spec.CIDR.String(), nil + if nw.Status.CIDR == "" { + return "", fmt.Errorf("the service CIDR is not yet configured: missing status on the Network resource") + } + + return nw.Status.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{ +// GetExternalCIDRNetwork retrieves the Network resource of type ExternalCIDR. +func GetExternalCIDRNetwork(ctx context.Context, cl client.Client) (*ipamv1alpha1.Network, error) { + return liqogetters.GetUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ consts.NetworkTypeLabelKey: string(consts.NetworkTypeExternalCIDR), })) +} + +// GetExternalCIDR retrieves the externalCIDR of the local cluster. +func GetExternalCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := GetExternalCIDRNetwork(ctx, cl) if err != nil { return "", err } @@ -70,11 +93,16 @@ func GetExternalCIDR(ctx context.Context, cl client.Client) (string, error) { return nw.Status.CIDR.String(), nil } -// GetInternalCIDR retrieves the internalCIDR of the local cluster. -func GetInternalCIDR(ctx context.Context, cl client.Client) (string, error) { - nw, err := liqogetters.GetUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ +// GetInternalCIDRNetwork retrieves the Network resource of type InternalCIDR. +func GetInternalCIDRNetwork(ctx context.Context, cl client.Client) (*ipamv1alpha1.Network, error) { + return liqogetters.GetUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ consts.NetworkTypeLabelKey: string(consts.NetworkTypeInternalCIDR), })) +} + +// GetInternalCIDR retrieves the internalCIDR of the local cluster. +func GetInternalCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := GetInternalCIDRNetwork(ctx, cl) if err != nil { return "", err } @@ -86,19 +114,25 @@ func GetInternalCIDR(ctx context.Context, cl client.Client) (string, error) { 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{ +// GetReservedSubnetNetworks retrieves the Network resources of type Reserved. +func GetReservedSubnetNetworks(ctx context.Context, cl client.Client) ([]ipamv1alpha1.Network, error) { + return liqogetters.GetNetworksByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ consts.NetworkTypeLabelKey: string(consts.NetworkTypeReserved), })) +} + +// GetReservedSubnets retrieves the reserved subnets of the local cluster. +func GetReservedSubnets(ctx context.Context, cl client.Client) ([]string, error) { + networks, err := GetReservedSubnetNetworks(ctx, cl) if err != nil { return nil, err } - for i := range networks.Items { - reservedSubnets = append(reservedSubnets, networks.Items[i].Spec.CIDR.String()) + var reservedSubnets []string + for i := range networks { + if networks[i].Status.CIDR != "" { + reservedSubnets = append(reservedSubnets, networks[i].Status.CIDR.String()) + } } return reservedSubnets, nil diff --git a/pkg/virtualKubelet/provider/provider.go b/pkg/virtualKubelet/provider/provider.go index 4bcfa12d2b..5271f6a491 100644 --- a/pkg/virtualKubelet/provider/provider.go +++ b/pkg/virtualKubelet/provider/provider.go @@ -126,16 +126,11 @@ func NewLiqoProvider(ctx context.Context, cfg *InitConfig, eb record.EventBroadc return "", err } - if ip.Status.IPMappings == nil { - return "", errors.New("no IP mappings found for the API server") - } - - v, ok := ip.Status.IPMappings[string(cfg.RemoteCluster)] - if !ok { + if ip.Status.IP == "" { return "", errors.New("no IP mapping found for the remote cluster API server") } - return string(v), nil + return string(ip.Status.IP), nil }, NetConfiguration: cfg.NetConfiguration, } diff --git a/pkg/virtualKubelet/reflection/exposition/endpointslice.go b/pkg/virtualKubelet/reflection/exposition/endpointslice.go index e0d7745c8c..6eab45aa6a 100644 --- a/pkg/virtualKubelet/reflection/exposition/endpointslice.go +++ b/pkg/virtualKubelet/reflection/exposition/endpointslice.go @@ -267,10 +267,10 @@ func (ner *NamespacedEndpointSliceReflector) MapEndpointIPFromIPResource(origina for i := range ips { if ips[i].Spec.IP.String() == original { remappedIP := ipamutils.GetRemappedIP(ips[i]) - if len(ips[i].Status.IPMappings) > 0 { - return remappedIP.String(), nil + if remappedIP == "" { + return "", fmt.Errorf("resource IP %q (%q) has not been mapped yet", ips[i].Name, ips[i].Spec.IP) } - return "", fmt.Errorf("resource IP %s has not been mapped yet", ips[i].Name) + return remappedIP.String(), nil } } return original, fmt.Errorf("resource IP %s not found", original) diff --git a/pkg/virtualKubelet/reflection/exposition/endpointslice_test.go b/pkg/virtualKubelet/reflection/exposition/endpointslice_test.go index 18b5da1622..6544273a46 100644 --- a/pkg/virtualKubelet/reflection/exposition/endpointslice_test.go +++ b/pkg/virtualKubelet/reflection/exposition/endpointslice_test.go @@ -106,9 +106,7 @@ var _ = Describe("EndpointSlice Reflection Tests", func() { ipamIP, err = liqoClient.IpamV1alpha1().IPs(namespace).Create(ctx, ipamIP, metav1.CreateOptions{}) Expect(err).ToNot(HaveOccurred()) ipamIP.Status = ipamv1alpha1.IPStatus{ - IPMappings: map[string]networkingv1beta1.IP{ - RemoteClusterID: networkingv1beta1.IP(remappedIP), - }, + IP: networkingv1beta1.IP(remappedIP), } ipamIP, err = liqoClient.IpamV1alpha1().IPs(namespace).UpdateStatus(ctx, ipamIP, metav1.UpdateOptions{}) Expect(err).ToNot(HaveOccurred())