Skip to content

Commit

Permalink
test: ipam sync routine unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fra98 committed Nov 12, 2024
1 parent ea7f48c commit a104509
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 10 deletions.
43 changes: 43 additions & 0 deletions pkg/ipam/ipam_suite_test.go
Original file line number Diff line number Diff line change
@@ -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())
})
34 changes: 24 additions & 10 deletions pkg/ipam/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,27 @@ func (lipam *LiqoIPAM) sync(ctx context.Context, syncFrequency time.Duration) {

func (lipam *LiqoIPAM) syncNetworks(ctx context.Context, expiredThreshold time.Time) error {
// List all networks present in the cluster.
nets, err := listNetworksOnCluster(ctx, lipam.Client)
clusterNetworks, err := listNetworksOnCluster(ctx, lipam.Client)
if err != nil {
return err
}

// Create a set for faster lookup.
nwSet := make(map[string]struct{})
for _, net := range nets {
nwSet[net] = struct{}{}
// Create the set of networks present in the cluster (for faster lookup later).
setClusterNetworks := make(map[string]struct{})

// Add networks that are present in the cluster but not in the cache.
for _, net := range clusterNetworks {
if _, inCache := lipam.cacheNetworks[net]; !inCache {
if err := lipam.reserveNetwork(net); err != nil {
return err
}
}
setClusterNetworks[net] = struct{}{} // add network to the set
}

// Remove networks that are present in the cache but not in the cluster, and were added before the threshold.
for key := range lipam.cacheNetworks {
if _, ok := nwSet[key]; !ok && lipam.cacheNetworks[key].creationTimestamp.Before(expiredThreshold) {
if _, inCluster := setClusterNetworks[key]; !inCluster && lipam.cacheNetworks[key].creationTimestamp.Before(expiredThreshold) {
lipam.freeNetwork(lipam.cacheNetworks[key].cidr)
}
}
Expand All @@ -83,15 +90,22 @@ func (lipam *LiqoIPAM) syncIPs(ctx context.Context, expiredThreshold time.Time)
return err
}

// Create a set for faster lookup.
ipSet := make(map[string]struct{})
// Create the set of IPs present in the cluster (for faster lookup later).
setClusterIPs := make(map[string]struct{})

// Add IPs that are present in the cluster but not in the cache.
for _, ip := range ips {
ipSet[ip.String()] = struct{}{}
if _, inCache := lipam.cacheIPs[ip.String()]; !inCache {
if err := lipam.reserveIP(ip); err != nil {
return err
}
}
setClusterIPs[ip.String()] = struct{}{} // add IP to the set
}

// Remove IPs that are present in the cache but not in the cluster, and were added before the threshold.
for key := range lipam.cacheIPs {
if _, ok := ipSet[key]; !ok && lipam.cacheIPs[key].creationTimestamp.Before(expiredThreshold) {
if _, inCluster := setClusterIPs[key]; !inCluster && lipam.cacheIPs[key].creationTimestamp.Before(expiredThreshold) {
lipam.freeIP(lipam.cacheIPs[key].ipCidr)
}
}
Expand Down
175 changes: 175 additions & 0 deletions pkg/ipam/sync_test.go
Original file line number Diff line number Diff line change
@@ -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
newEntryThreshold time.Time
notNewTimestamp 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()
newEntryThreshold = now.Add(-syncFrequency)
notNewTimestamp = newEntryThreshold.Add(-time.Minute)
})

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", notNewTimestamp)
addNetowrkToCache(fakeIpamServer, "10.3.0.0/16", notNewTimestamp)
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, newEntryThreshold)).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 new entry threshold
Expect(fakeIpamServer.cacheNetworks).To(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 new entry threshold
Expect(fakeIpamServer.cacheNetworks).To(HaveKey("10.4.0.0/16")) // network not in cluster but in cache after new entry 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", notNewTimestamp)
addIPToCache(fakeIpamServer, "10.0.0.3", "10.0.0.0/24", notNewTimestamp)
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, newEntryThreshold)).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 new entry threshold
Expect(fakeIpamServer.cacheIPs).To(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 new entry 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 new entry threshold
})
})
})
})

0 comments on commit a104509

Please sign in to comment.