diff --git a/pkg/ipam/ipam_suite_test.go b/pkg/ipam/ipam_suite_test.go new file mode 100644 index 0000000000..15bd4dec7c --- /dev/null +++ b/pkg/ipam/ipam_suite_test.go @@ -0,0 +1,43 @@ +// 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 ipam + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes/scheme" + + liqov1beta1 "github.com/liqotech/liqo/apis/core/v1beta1" + ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" + networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1" + "github.com/liqotech/liqo/pkg/utils/testutil" +) + +func TestIpam(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Ipam Suite") +} + +var _ = BeforeSuite(func() { + testutil.LogsToGinkgoWriter() + + Expect(corev1.AddToScheme(scheme.Scheme)).To(Succeed()) + Expect(liqov1beta1.AddToScheme(scheme.Scheme)).To(Succeed()) + Expect(networkingv1beta1.AddToScheme(scheme.Scheme)).To(Succeed()) + Expect(ipamv1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) +}) diff --git a/pkg/ipam/sync_test.go b/pkg/ipam/sync_test.go new file mode 100644 index 0000000000..607c98626b --- /dev/null +++ b/pkg/ipam/sync_test.go @@ -0,0 +1,175 @@ +// 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 ipam + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo/v2" + . "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" + networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1" +) + +var _ = Describe("Sync routine tests", func() { + const ( + syncFrequency = 3 * time.Second + testNamespace = "test" + ) + + var ( + ctx context.Context + fakeClientBuilder *fake.ClientBuilder + now time.Time + expiredThreshold time.Time + expired time.Time + + fakeIpamServer *LiqoIPAM + + addNetowrkToCache = func(ipamServer *LiqoIPAM, cidr string, creationTimestamp time.Time) { + ipamServer.cacheNetworks[cidr] = networkInfo{ + cidr: cidr, + creationTimestamp: creationTimestamp, + } + } + + addIPToCache = func(ipamServer *LiqoIPAM, ip, cidr string, creationTimestamp time.Time) { + ipC := ipCidr{ip: ip, cidr: cidr} + ipamServer.cacheIPs[ipC.String()] = ipInfo{ + ipCidr: ipC, + creationTimestamp: creationTimestamp, + } + } + + newNetwork = func(name, cidr string) *ipamv1alpha1.Network { + return &ipamv1alpha1.Network{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Spec: ipamv1alpha1.NetworkSpec{ + CIDR: networkingv1beta1.CIDR(cidr), + }, + Status: ipamv1alpha1.NetworkStatus{ + CIDR: networkingv1beta1.CIDR(cidr), + }, + } + } + + newIP = func(name, ip, cidr string) *ipamv1alpha1.IP { + return &ipamv1alpha1.IP{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Spec: ipamv1alpha1.IPSpec{ + IP: networkingv1beta1.IP(ip), + CIDR: ptr.To(networkingv1beta1.CIDR(cidr)), + }, + Status: ipamv1alpha1.IPStatus{ + IP: networkingv1beta1.IP(ip), + CIDR: networkingv1beta1.CIDR(cidr), + }, + } + } + ) + + BeforeEach(func() { + ctx = context.Background() + fakeClientBuilder = fake.NewClientBuilder().WithScheme(scheme.Scheme) + now = time.Now() + expiredThreshold = now.Add(-syncFrequency) + expired = now.Add(-2 * syncFrequency) + }) + + Describe("Testing the sync routine", func() { + Context("Sync Networks", func() { + BeforeEach(func() { + // Add in-cluster networks + client := fakeClientBuilder.WithObjects( + newNetwork("net1", "10.0.0.0/16"), + newNetwork("net2", "10.1.0.0/16"), + newNetwork("net3", "10.2.0.0/16"), + ).Build() + + // Populate the cache + fakeIpamServer = &LiqoIPAM{ + Client: client, + cacheNetworks: make(map[string]networkInfo), + } + addNetowrkToCache(fakeIpamServer, "10.0.0.0/16", now) + addNetowrkToCache(fakeIpamServer, "10.1.0.0/16", expired) + addNetowrkToCache(fakeIpamServer, "10.3.0.0/16", expired) + addNetowrkToCache(fakeIpamServer, "10.4.0.0/16", now) + }) + + It("should remove networks from cache if they are not present in the cluster", func() { + // Run sync + Expect(fakeIpamServer.syncNetworks(ctx, expiredThreshold)).To(Succeed()) + + // Check the cache + Expect(fakeIpamServer.cacheNetworks).To(HaveKey("10.0.0.0/16")) // network in cluster and cache + Expect(fakeIpamServer.cacheNetworks).To(HaveKey("10.1.0.0/16")) // network in cluster and cache before expired threshold + Expect(fakeIpamServer.cacheNetworks).NotTo(HaveKey("10.2.0.0/16")) // network in cluster but not in cache + Expect(fakeIpamServer.cacheNetworks).NotTo(HaveKey("10.3.0.0/16")) // network not in cluster but in cache before expired threshold + Expect(fakeIpamServer.cacheNetworks).To(HaveKey("10.4.0.0/16")) // network not in cluster but in cache after expired threshold + }) + }) + + Context("Sync IPs", func() { + BeforeEach(func() { + // Add in-cluster IPs + client := fakeClientBuilder.WithObjects( + newIP("ip1", "10.0.0.0", "10.0.0.0/24"), + newIP("ip2", "10.0.0.1", "10.0.0.0/24"), + newIP("ip3", "10.0.0.2", "10.0.0.0/24"), + ).Build() + + // Populate the cache + fakeIpamServer = &LiqoIPAM{ + Client: client, + cacheIPs: make(map[string]ipInfo), + } + addIPToCache(fakeIpamServer, "10.0.0.0", "10.0.0.0/24", now) + addIPToCache(fakeIpamServer, "10.0.0.1", "10.0.0.0/24", expired) + addIPToCache(fakeIpamServer, "10.0.0.3", "10.0.0.0/24", expired) + addIPToCache(fakeIpamServer, "10.0.0.4", "10.0.0.0/24", now) + }) + + It("should remove IPs from cache if they are not present in the cluster", func() { + // Run sync + Expect(fakeIpamServer.syncIPs(ctx, expiredThreshold)).To(Succeed()) + + // Check the cache + Expect(fakeIpamServer.cacheIPs).To(HaveKey( + ipCidr{ip: "10.0.0.0", cidr: "10.0.0.0/24"}.String())) // IP in cluster and cache + Expect(fakeIpamServer.cacheIPs).To(HaveKey( + ipCidr{ip: "10.0.0.1", cidr: "10.0.0.0/24"}.String())) // IP in cluster and cache before expired threshold + Expect(fakeIpamServer.cacheIPs).NotTo(HaveKey( + ipCidr{ip: "10.0.0.2", cidr: "10.0.0.0/24"}.String())) // IP in cluster but not in cache + Expect(fakeIpamServer.cacheIPs).NotTo(HaveKey( + ipCidr{ip: "10.0.0.3", cidr: "10.0.0.0/24"}.String())) // IP not in cluster but in cache before expired threshold + Expect(fakeIpamServer.cacheIPs).To(HaveKey( + ipCidr{ip: "10.0.0.4", cidr: "10.0.0.0/24"}.String())) // IP not in cluster but in cache after expired threshold + }) + }) + }) +})