Skip to content

Commit 686320e

Browse files
committed
Add memif tests
1 parent 165a06b commit 686320e

File tree

5 files changed

+461
-1
lines changed

5 files changed

+461
-1
lines changed

calico-vpp-agent/cni/cni_pod_test.go

Lines changed: 321 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"os/exec"
2222
"strings"
2323
"syscall"
24+
"time"
2425

2526
. "github.com/onsi/ginkgo"
2627
. "github.com/onsi/gomega"
@@ -38,6 +39,8 @@ import (
3839
"github.com/projectcalico/vpp-dataplane/v3/config"
3940
"github.com/projectcalico/vpp-dataplane/v3/vpplink"
4041
"github.com/projectcalico/vpp-dataplane/v3/vpplink/types"
42+
43+
gomemif "go.fd.io/govpp/extras/gomemif/memif"
4144
)
4245

4346
const (
@@ -245,6 +248,75 @@ var _ = Describe("Pod-related functionality of CNI", func() {
245248
Expect(pblClientStr).To(ContainSubstring(
246249
fmt.Sprintf("udp ports: %d-%d", memifUDPPortStart, memifUDPPortEnd)),
247250
"UDP port range is not correctly configured for memif interface")
251+
252+
By("Checking socket creation")
253+
memif_socket, err := gomemif.NewSocket("gomemif_example", "@vpp/memif-newInterface")
254+
Expect(err).ToNot(HaveOccurred())
255+
256+
By("Checking slave connection to master")
257+
memifErrChan := make(chan error)
258+
quitChan := make(chan int)
259+
// Start master polling
260+
go func() {
261+
for {
262+
select {
263+
case <-quitChan:
264+
return
265+
default:
266+
memif_socket.StartPolling(memifErrChan)
267+
time.Sleep(100 * time.Millisecond)
268+
}
269+
}
270+
}()
271+
272+
sockChannel := make(chan *gomemif.Interface, 1)
273+
274+
slave := func() error {
275+
args := &gomemif.Arguments{
276+
IsMaster: false,
277+
Name: "memif",
278+
ConnectedFunc: func(i *gomemif.Interface) error { return nil },
279+
DisconnectedFunc: func(i *gomemif.Interface) error { return nil },
280+
}
281+
282+
i, err := memif_socket.NewInterface(args)
283+
if err != nil {
284+
return err
285+
}
286+
287+
retry := 5
288+
for !i.IsConnecting() {
289+
err = i.RequestConnection()
290+
if err != nil {
291+
retry--
292+
}
293+
if retry == 0 {
294+
sockChannel <- nil
295+
return err
296+
}
297+
time.Sleep(100 * time.Millisecond)
298+
}
299+
sockChannel <- i
300+
return nil
301+
}
302+
// Create slave socket in container net space
303+
err = cni.NetNsExec("pid:"+containerPidStr, slave)
304+
Expect(err).ToNot(HaveOccurred())
305+
i := <-sockChannel
306+
307+
By("Sending a ARP request")
308+
srcMac := net.HardwareAddr{0x01, 0x66, 0x38, 0xa1, 0x12, 0x46}
309+
srcIp := net.IPv4(1, 2, 3, 4)
310+
dstIp := net.IPv4(1, 2, 3, 5)
311+
312+
arp, err := cni.NewArpRequestPacket(srcMac, srcIp, dstIp)
313+
Expect(err).ToNot(HaveOccurred())
314+
txq, err := i.GetTxQueue(0)
315+
Expect(err).ToNot(HaveOccurred())
316+
317+
bytesWritten := txq.WritePacket(arp)
318+
Expect(bytesWritten).To(Equal(len(arp)))
319+
quitChan <- 1
248320
})
249321

250322
})
@@ -287,8 +359,256 @@ var _ = Describe("Pod-related functionality of CNI", func() {
287359
pubSubHandlerMock.Start()
288360
})
289361

290-
// TODO test multinet(additional network for pod) with MEMIF interface
362+
Context("With default memif interface configured for secondary(multinet) tunnel to pod", func() {
363+
BeforeEach(func() {
364+
config.GetCalicoVppFeatureGates().MemifEnabled = &config.True
365+
})
366+
It("should have properly configured both interfaces tunnels to VPP", func() {
367+
const (
368+
ipAddress = "1.2.3.44" // main TAP tunnel (=not multinet)
369+
mainInterfaceName = "mainInterface" // name must be <=16 characters long due to tap name size on pod linux side
370+
secondaryInterfaceName = "memif2nd" // name must be <=16 characters long due to tap name size on pod linux side
371+
memifTCPPortStart = 2222
372+
memifTCPPortEnd = 33333
373+
memifUDPPortStart = 4444
374+
memifUDPPortEnd = 55555
375+
)
376+
377+
By("Getting Pod mock container's PID")
378+
containerPidOutput, err := exec.Command("docker", "inspect", "-f", "{{.State.Pid}}",
379+
PodMockContainerName).Output()
380+
Expect(err).Should(BeNil(), "Failed to get pod mock container's PID string")
381+
containerPidStr := strings.ReplaceAll(string(containerPidOutput), "\n", "")
382+
383+
By("Adding Pod to primary network using CNI server")
384+
newPodForPrimaryNetwork := &cniproto.AddRequest{
385+
InterfaceName: mainInterfaceName,
386+
Netns: fmt.Sprintf("/proc/%s/ns/net", containerPidStr), // expecting mount of "/proc" from host
387+
ContainerIps: []*cniproto.IPConfig{{Address: ipAddress + "/24"}},
388+
Workload: &cniproto.WorkloadIDs{},
389+
}
390+
common.VppManagerInfo = &config.VppManagerInfo{}
391+
reply, err := cniServer.Add(context.Background(), newPodForPrimaryNetwork)
392+
Expect(err).ToNot(HaveOccurred(), "Pod addition to primary network failed")
393+
Expect(reply.Successful).To(BeTrue(),
394+
fmt.Sprintf("Pod addition to primary network failed due to: %s", reply.ErrorMessage))
395+
396+
By("Adding Pod to secondary(multinet) network using CNI server")
397+
secondaryIPAddress := test.FirstIPinIPRange(networkDefinition.Range).String()
398+
newPodForSecondaryNetwork := &cniproto.AddRequest{
399+
InterfaceName: secondaryInterfaceName,
400+
Netns: fmt.Sprintf("/proc/%s/ns/net", containerPidStr), // expecting mount of "/proc" from host
401+
ContainerIps: []*cniproto.IPConfig{{
402+
Address: secondaryIPAddress + "/24",
403+
}},
404+
//Workload: &cniproto.WorkloadIDs{},
405+
DataplaneOptions: map[string]string{
406+
test.DpoNetworkNameFieldName(): networkDefinition.Name,
407+
},
408+
Workload: &cniproto.WorkloadIDs{
409+
Annotations: map[string]string{
410+
// needed just for setting up steering of traffic to default Tun/Tap and to secondary Memif
411+
cni.VppAnnotationPrefix + cni.MemifPortAnnotation: fmt.Sprintf("tcp:%d-%d,udp:%d-%d",
412+
memifTCPPortStart, memifTCPPortEnd, memifUDPPortStart, memifUDPPortEnd),
413+
},
414+
},
415+
}
416+
reply, err = cniServer.Add(context.Background(), newPodForSecondaryNetwork)
417+
Expect(err).ToNot(HaveOccurred(), "Pod addition to secondary network failed")
418+
Expect(reply.Successful).To(BeTrue(),
419+
fmt.Sprintf("Pod addition to secondary network failed due to: %s", reply.ErrorMessage))
420+
421+
By("Checking existence of main tun interface tunnel to pod (at VPP's end)")
422+
mainSwIfIndex := test.AssertTunInterfaceExistence(vpp, newPodForPrimaryNetwork)
423+
424+
By("Checking main tunnel's tun interface for common interface attributes")
425+
test.AssertTunnelInterfaceIPAddress(vpp, mainSwIfIndex, ipAddress)
426+
test.AssertTunnelInterfaceMTU(vpp, mainSwIfIndex)
427+
428+
//By("Checking secondary tunnel's tun interface for existence")
429+
//secondarySwIfIndex := test.AssertTunInterfaceExistence(vpp, newPodForSecondaryNetwork)
430+
By("Checking secondary tunnel's memif interface for existence")
431+
memifSwIfIndex, err := vpp.SearchInterfaceWithTag(
432+
test.InterfaceTagForLocalMemifTunnel(newPodForSecondaryNetwork.InterfaceName, newPodForSecondaryNetwork.Netns))
433+
Expect(err).ShouldNot(HaveOccurred(), "Failed to get memif interface at VPP's end")
434+
435+
By("Checking secondary tunnel's memif interface for common interface attributes")
436+
test.AssertTunnelInterfaceIPAddress(vpp, memifSwIfIndex, secondaryIPAddress)
437+
test.AssertTunnelInterfaceMTU(vpp, memifSwIfIndex)
438+
test.AssertInterfaceGSO(memifSwIfIndex, "secondary tunnel's memif interface", vpp)
439+
440+
By("Checking secondary tunnel's memif interface for memif attributes")
441+
memifs, err := vpp.ListMemifInterfaces()
442+
Expect(err).ToNot(HaveOccurred(), "failed to get memif interfaces")
443+
Expect(memifs).ToNot(BeEmpty(), "no memif interfaces retrieved")
444+
Expect(memifs[0].Role).To(Equal(types.MemifMaster))
445+
Expect(memifs[0].Mode).To(Equal(types.MemifModeEthernet))
446+
Expect(memifs[0].Flags&types.MemifAdminUp > 0).To(BeTrue())
447+
// Note: queues are allocated only when a client is listening
448+
// Expect(memifs[0].QueueSize).To(Equal(config.GetCalicoVppInterfaces().DefaultPodIfSpec.RxQueueSize))
449+
//Note:Memif.NumRxQueues and Memif.NumTxQueues is not dumped by VPP binary API dump -> can't test it
450+
451+
By("Checking secondary tunnel's memif socket file") // checking only VPP setting, not file socket presence
452+
socket, err := vpp.MemifsocketByID(memifs[0].SocketId)
453+
Expect(err).ToNot(HaveOccurred(), "failed to get memif socket")
454+
Expect(socket.SocketFilename).To(Equal(
455+
fmt.Sprintf("@netns:%s@%s", newPodForSecondaryNetwork.Netns, newPodForSecondaryNetwork.InterfaceName)),
456+
"memif socket file is not configured correctly")
457+
458+
test.RunInPod(newPodForSecondaryNetwork.Netns, func() {
459+
By("Checking main tunnel's tun interface on pod side")
460+
_, err := netlink.LinkByName(mainInterfaceName)
461+
Expect(err).ToNot(HaveOccurred(), "can't find main interface in pod")
462+
463+
By("Checking secondary tunnel's tun interface on pod side")
464+
secTunLink, err := netlink.LinkByName(secondaryInterfaceName)
465+
Expect(err).ToNot(HaveOccurred(), "can't find secondary(multinet) interface in pod")
466+
467+
By("Checking multinet related routes on pod side")
468+
secTunLinkRoutes, err := netlink.RouteList(secTunLink, syscall.AF_INET) // Ipv4 routes only
469+
Expect(err).ToNot(HaveOccurred(), "can't get routes from pod")
470+
Expect(secTunLinkRoutes).To(ContainElements(
471+
gs.MatchFields(gs.IgnoreExtras, gs.Fields{
472+
"Dst": gs.PointTo(Equal(*test.IpNet(networkDefinition.Range))),
473+
}),
474+
), "can't find route in pod that steers all multinet network "+
475+
"traffic into multinet tunnel interface in pod")
476+
})
477+
478+
By("checking pushing of LocalPodAddressAdded event for BGP pod network announcing")
479+
// Note: BGP is not tested here, only that event for it was sent
480+
Expect(pubSubHandlerMock.ReceivedEvents).To(ContainElements(
481+
gs.MatchFields(gs.IgnoreExtras, gs.Fields{
482+
"Type": Equal(common.LocalPodAddressAdded),
483+
"New": gs.MatchFields(gs.IgnoreExtras, gs.Fields{
484+
"ContainerIP": gs.PointTo(Equal(*test.IpNetWithIPInIPv6Format(ipAddress + "/32"))),
485+
}),
486+
}),
487+
gs.MatchFields(gs.IgnoreExtras, gs.Fields{
488+
"Type": Equal(common.LocalPodAddressAdded),
489+
"New": gs.MatchFields(gs.IgnoreExtras, gs.Fields{
490+
"ContainerIP": gs.PointTo(Equal(*test.IpNetWithIPInIPv6Format(secondaryIPAddress + "/32"))),
491+
}),
492+
}),
493+
))
291494

495+
By("Checking default route from pod-specific VRF to multinet network-specific vrf")
496+
podVrf4ID, podVrf6ID, err := test.PodVRFs(secondaryInterfaceName, newPodForSecondaryNetwork.Netns, vpp)
497+
Expect(err).ToNot(HaveOccurred(), "can't find pod-specific VRFs")
498+
for idx, ipFamily := range vpplink.IpFamilies {
499+
podVrfID := podVrf4ID
500+
zeroIPNet := &net.IPNet{IP: net.IPv4zero.To4(), Mask: net.IPMask(net.IPv4zero.To4())}
501+
if ipFamily.IsIp6 {
502+
podVrfID = podVrf6ID
503+
zeroIPNet = &net.IPNet{IP: net.IPv6zero, Mask: net.IPMask(net.IPv6zero)}
504+
}
505+
routes, err := vpp.GetRoutes(podVrfID, ipFamily.IsIp6)
506+
Expect(err).ToNot(HaveOccurred(),
507+
fmt.Sprintf("can't get %s routes in pod-specific VRF", ipFamily.Str))
508+
Expect(routes).To(ContainElements(
509+
types.Route{
510+
Paths: []types.RoutePath{{
511+
Table: networkDefinition.VRF.Tables[idx],
512+
SwIfIndex: types.InvalidID,
513+
Gw: zeroIPNet.IP,
514+
}},
515+
Dst: zeroIPNet,
516+
Table: podVrfID,
517+
},
518+
), "can't find default route from pod-specific VRF to multinet "+
519+
"network-specific vrf")
520+
}
521+
522+
By("Checking steering route in multinet network VRF leading to pod " +
523+
"using multinet tunnel interface")
524+
// Note: should be checked for all container IPs of multinet, but we have only one
525+
multinetVRFID := networkDefinition.VRF.Tables[test.IpFamilyIndex(vpplink.IpFamilyV4)] // secondaryIPAddress is from IpFamilyV4
526+
routes, err := vpp.GetRoutes(multinetVRFID, false)
527+
Expect(err).ToNot(HaveOccurred(),
528+
"can't get ipv4 routes in multinet network-specific VRF")
529+
Expect(routes).To(ContainElements(
530+
types.Route{
531+
Dst: test.IpNet(secondaryIPAddress + "/32"),
532+
Paths: []types.RoutePath{{
533+
SwIfIndex: memifSwIfIndex,
534+
Gw: test.IpNet(secondaryIPAddress + "/32").IP,
535+
}},
536+
Table: multinetVRFID,
537+
},
538+
), "can't find steering route in multinet network VRF leading "+
539+
"to pod using multinet tunnel interface")
540+
541+
By("Checking socket creation")
542+
memif_socket, err := gomemif.NewSocket("gomemif_example", "@"+secondaryInterfaceName)
543+
Expect(err).ToNot(HaveOccurred())
544+
545+
By("Checking slave connection to master")
546+
memifErrChan := make(chan error)
547+
quitChan := make(chan int)
548+
// Start master polling
549+
go func() {
550+
for {
551+
select {
552+
case <-quitChan:
553+
return
554+
default:
555+
memif_socket.StartPolling(memifErrChan)
556+
time.Sleep(100 * time.Millisecond)
557+
}
558+
}
559+
}()
560+
561+
sockChannel := make(chan *gomemif.Interface, 1)
562+
563+
slave := func() error {
564+
args := &gomemif.Arguments{
565+
IsMaster: false,
566+
Name: "memif",
567+
ConnectedFunc: func(i *gomemif.Interface) error { return nil },
568+
DisconnectedFunc: func(i *gomemif.Interface) error { return nil },
569+
}
570+
571+
i, err := memif_socket.NewInterface(args)
572+
if err != nil {
573+
return err
574+
}
575+
576+
retry := 5
577+
for !i.IsConnecting() {
578+
err = i.RequestConnection()
579+
if err != nil {
580+
retry--
581+
}
582+
if retry == 0 {
583+
sockChannel <- nil
584+
return err
585+
}
586+
time.Sleep(100 * time.Millisecond)
587+
}
588+
sockChannel <- i
589+
return nil
590+
}
591+
// Create slave socket in container net space
592+
err = cni.NetNsExec("pid:"+containerPidStr, slave)
593+
Expect(err).ToNot(HaveOccurred())
594+
i := <-sockChannel
595+
Expect(i).ToNot(BeNil())
596+
597+
By("Sending a ARP request")
598+
srcMac := net.HardwareAddr{0x01, 0x66, 0x38, 0xa1, 0x12, 0x46}
599+
srcIp := net.IPv4(1, 2, 3, 4)
600+
dstIp := net.IPv4(1, 2, 3, 5)
601+
602+
arp, err := cni.NewArpRequestPacket(srcMac, srcIp, dstIp)
603+
Expect(err).ToNot(HaveOccurred())
604+
txq, err := i.GetTxQueue(0)
605+
Expect(err).ToNot(HaveOccurred())
606+
607+
bytesWritten := txq.WritePacket(arp)
608+
Expect(bytesWritten).To(Equal(len(arp)))
609+
quitChan <- 1
610+
})
611+
})
292612
Context("With default (TAP) interface configured for secondary(multinet) tunnel to pod", func() {
293613
It("should have properly configured both TAP interface tunnels to VPP", func() {
294614
const (

0 commit comments

Comments
 (0)