Skip to content
This repository was archived by the owner on Apr 8, 2025. It is now read-only.

Commit 77804b1

Browse files
author
Eoghan Russell
authored
Adding VPP Unit tests (#227)
* Adding VPP Unit tests Added unit tests for vpp Added new Dockerfile for unit test container to run Fixed bug found in code fixed some logging to return as a useful error when failing Signed-off-by: Eoghan Russell <[email protected]>
1 parent 61ac834 commit 77804b1

File tree

6 files changed

+368
-2
lines changed

6 files changed

+368
-2
lines changed

Diff for: .github/workflows/unittest.yml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: unit-tests
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
unit-tests:
13+
name: unit-tests
14+
runs-on: hugepage-runner
15+
steps:
16+
- name: Set up Go
17+
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
18+
with:
19+
go-version: 1.20.1
20+
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
21+
- name: make unit-test
22+
run: make unit-test
23+

Diff for: Makefile

+9
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,12 @@ undeploy:
4646
testpmd:
4747
@$(IMAGE_BUILDER) build -t $(IMAGE_REGISTRY)testpmd:latest -f ./docker/testpmd/Dockerfile ./docker/testpmd/
4848
@$(IMAGE_BUILDER) push $(IMAGE_REGISTRY)testpmd:latest
49+
50+
unit-test:
51+
@$(IMAGE_BUILDER) rm -f userspacecni-unittest
52+
@$(IMAGE_BUILDER) build . -f ./docker/userspacecni/Dockerfile.unittest -t userspacecni-unittest:latest
53+
@$(IMAGE_BUILDER) run -m 100g --privileged -v ./examples/sample-vpp-host-config/startup.conf:/etc/vpp/startup.conf --name userspacecni-unittest -itd userspacecni-unittest:latest
54+
@$(IMAGE_BUILDER) cp userspacecni-unittest:/root/userspace-cni-network-plugin/cnivpp ./
55+
@$(IMAGE_BUILDER) exec userspacecni-unittest bash -c "go test ./cnivpp/ -v -cover"
56+
@$(IMAGE_BUILDER) rm -f userspacecni-unittest
57+

Diff for: cnivpp/cnivpp.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ func (cniVpp CniVpp) AddOnHost(conf *types.NetConf,
149149
err = errors.New("ERROR: Unknown HostConf.NetType:" + conf.HostConf.NetType)
150150
logging.Debugf("AddOnHost(vpp): %v", err)
151151
return err
152+
} else {
153+
return fmt.Errorf("ERROR: NetType must be provided")
152154
}
153155

154156
//
@@ -321,7 +323,7 @@ func delLocalDeviceMemif(vppCh vppinfra.ConnectionData, conf *types.NetConf, arg
321323
err = vppmemif.DeleteMemifInterface(vppCh.Ch, interface_types.InterfaceIndex(data.InterfaceSwIfIndex))
322324
if err != nil {
323325
logging.Debugf("delLocalDeviceMemif(vpp): Error deleting memif inteface: %v", err)
324-
return
326+
return logging.Errorf("delLocalDeviceMemif(vpp): Error deleting memif inteface: %v", err)
325327
} else {
326328
if dbgInterface {
327329
logging.Verbosef("INTERFACE %d deleted\n", data.InterfaceSwIfIndex)

Diff for: cnivpp/cnivpp_test.go

+318
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
package cnivpp
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"path"
8+
"path/filepath"
9+
"testing"
10+
11+
"github.com/containernetworking/cni/pkg/skel"
12+
current "github.com/containernetworking/cni/pkg/types/100"
13+
"github.com/google/uuid"
14+
"github.com/intel/userspace-cni-network-plugin/pkg/types"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
v1 "k8s.io/api/core/v1"
18+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
apitypes "k8s.io/apimachinery/pkg/types"
20+
"k8s.io/client-go/kubernetes/fake"
21+
)
22+
23+
func GetTestPod(sharedDir string) *v1.Pod {
24+
id, _ := uuid.NewUUID()
25+
pod := &v1.Pod{
26+
TypeMeta: metav1.TypeMeta{
27+
Kind: "Pod",
28+
APIVersion: "v1",
29+
},
30+
ObjectMeta: metav1.ObjectMeta{
31+
UID: apitypes.UID(id.String()),
32+
Name: fmt.Sprintf("pod-%v", id[:8]),
33+
Namespace: fmt.Sprintf("namespace-%v", id[:8]),
34+
},
35+
}
36+
if sharedDir != "" {
37+
pod.Spec.Volumes = append(pod.Spec.Volumes,
38+
v1.Volume{
39+
Name: "shared-dir",
40+
VolumeSource: v1.VolumeSource{
41+
HostPath: &v1.HostPathVolumeSource{
42+
Path: sharedDir,
43+
},
44+
},
45+
})
46+
pod.Spec.Containers = append(pod.Spec.Containers,
47+
v1.Container{
48+
Name: "container",
49+
VolumeMounts: []v1.VolumeMount{{Name: "shared-dir", MountPath: sharedDir}},
50+
})
51+
}
52+
return pod
53+
}
54+
55+
func GetTestArgs() *skel.CmdArgs {
56+
id, _ := uuid.NewUUID()
57+
return &skel.CmdArgs{
58+
ContainerID: id.String(),
59+
IfName: fmt.Sprintf("eth%v", int(id[7])),
60+
StdinData: []byte("{}"),
61+
}
62+
}
63+
64+
func TestGetMemifSocketfileName(t *testing.T) {
65+
t.Run("get Memif Socker File Name", func(t *testing.T) {
66+
args := GetTestArgs()
67+
68+
sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cniovs-")
69+
require.NoError(t, dirErr, "Can't create temporary directory")
70+
defer os.RemoveAll(sharedDir)
71+
72+
memifSockFileName := getMemifSocketfileName(&types.NetConf{}, sharedDir, args.ContainerID, args.IfName)
73+
assert.Equal(t, filepath.Join(sharedDir, fmt.Sprintf("memif-%s-%s.sock", args.ContainerID[:12], args.IfName)), memifSockFileName, "Unexpected error")
74+
75+
conf := &types.NetConf{}
76+
conf.HostConf.MemifConf.Socketfile = "socketFile.sock"
77+
78+
memifSockFileName = getMemifSocketfileName(conf, sharedDir, args.ContainerID, args.IfName)
79+
assert.Equal(t, filepath.Join(sharedDir, conf.HostConf.MemifConf.Socketfile), memifSockFileName, "Unexpected error")
80+
})
81+
}
82+
83+
func TestAddOnContainer(t *testing.T) {
84+
t.Run("save container data to file", func(t *testing.T) {
85+
var result *current.Result
86+
args := GetTestArgs()
87+
cniVpp := CniVpp{}
88+
89+
sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cniovs-")
90+
require.NoError(t, dirErr, "Can't create temporary directory")
91+
defer os.RemoveAll(sharedDir)
92+
93+
pod := GetTestPod(sharedDir)
94+
resPod, resErr := cniVpp.AddOnContainer(&types.NetConf{}, args, nil, sharedDir, pod, result)
95+
assert.NoError(t, resErr, "Unexpected error")
96+
assert.Equal(t, pod, resPod, "Unexpected change of pod data")
97+
fileName := fmt.Sprintf("configData-%s-%s.json", args.ContainerID[:12], args.IfName)
98+
assert.FileExists(t, path.Join(sharedDir, fileName), "Container data were not saved to file")
99+
})
100+
}
101+
102+
func TestDelOnContainer(t *testing.T) {
103+
t.Run("remove container configuration", func(t *testing.T) {
104+
args := GetTestArgs()
105+
cniVpp := CniVpp{}
106+
107+
sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cniovs-")
108+
require.NoError(t, dirErr, "Can't create temporary directory")
109+
// just in case DelFromContainer fails
110+
defer os.RemoveAll(sharedDir)
111+
112+
err := cniVpp.DelFromContainer(&types.NetConf{}, args, sharedDir, nil)
113+
assert.NoError(t, err, "Unexpected error")
114+
assert.NoDirExists(t, sharedDir, "Container data were not removed")
115+
})
116+
}
117+
118+
func TestAddOnHost(t *testing.T) {
119+
cniVpp := CniVpp{}
120+
121+
testCases := []struct {
122+
name string
123+
netConf *types.NetConf
124+
fakeErr error
125+
expErr error
126+
}{
127+
{
128+
name: "Happy path",
129+
netConf: &types.NetConf{
130+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "interface",
131+
VhostConf: types.VhostConf{Mode: "client"},
132+
MemifConf: types.MemifConf{
133+
Role: "master", // Role of memif: master|slave
134+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
135+
}}},
136+
expErr: nil,
137+
},
138+
{
139+
name: "Invalid MEMIF Role",
140+
netConf: &types.NetConf{
141+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "interface",
142+
VhostConf: types.VhostConf{Mode: "client"},
143+
MemifConf: types.MemifConf{
144+
Role: "", // Role of memif: master|slave
145+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
146+
}}},
147+
expErr: errors.New("ERROR: Invalid MEMIF Role"),
148+
},
149+
{
150+
name: "Unknown IfType",
151+
netConf: &types.NetConf{
152+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "", NetType: "interface",
153+
VhostConf: types.VhostConf{Mode: "client"},
154+
MemifConf: types.MemifConf{
155+
Role: "", // Role of memif: master|slave
156+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
157+
}}},
158+
expErr: errors.New("Unknown HostConf.IfType"),
159+
},
160+
{
161+
name: "Unknown NetType",
162+
netConf: &types.NetConf{
163+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "UnkownNetType",
164+
VhostConf: types.VhostConf{Mode: "client"},
165+
MemifConf: types.MemifConf{
166+
Role: "master", // Role of memif: master|slave
167+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
168+
}}},
169+
expErr: errors.New("Unknown HostConf.NetType"),
170+
},
171+
{
172+
name: "Bridge already exists",
173+
netConf: &types.NetConf{
174+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "bridge",
175+
VhostConf: types.VhostConf{Mode: "client"},
176+
MemifConf: types.MemifConf{
177+
Role: "master", // Role of memif: master|slave
178+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
179+
}}},
180+
expErr: errors.New("Bridge domain already exists"),
181+
},
182+
{
183+
name: "Create 12345 Bridge",
184+
netConf: &types.NetConf{
185+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "bridge",
186+
BridgeConf: types.BridgeConf{
187+
BridgeName: "12345",
188+
BridgeId: 12345,
189+
},
190+
VhostConf: types.VhostConf{Mode: "client"},
191+
MemifConf: types.MemifConf{
192+
Role: "master", // Role of memif: master|slave
193+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
194+
}}},
195+
expErr: nil,
196+
},
197+
{
198+
name: "NetType set to empty",
199+
netConf: &types.NetConf{
200+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "",
201+
VhostConf: types.VhostConf{Mode: "client"},
202+
MemifConf: types.MemifConf{
203+
Role: "master", // Role of memif: master|slave
204+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
205+
}}},
206+
expErr: errors.New("ERROR: NetType must be provided"),
207+
},
208+
{
209+
name: "interface slave and ip mode",
210+
netConf: &types.NetConf{
211+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "interface",
212+
VhostConf: types.VhostConf{Mode: "client"},
213+
MemifConf: types.MemifConf{
214+
Role: "slave", // Role of memif: master|slave
215+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
216+
}}},
217+
expErr: nil,
218+
},
219+
}
220+
for _, tc := range testCases {
221+
t.Run(tc.name, func(t *testing.T) {
222+
var result *current.Result
223+
args := GetTestArgs()
224+
225+
sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cnivpp-")
226+
require.NoError(t, dirErr, "Can't create temporary directory")
227+
defer os.RemoveAll(sharedDir)
228+
229+
pod := GetTestPod(sharedDir)
230+
kubeClient := fake.NewSimpleClientset(pod)
231+
232+
err := cniVpp.AddOnHost(tc.netConf, args, kubeClient, sharedDir, result)
233+
if tc.expErr == nil {
234+
assert.Equal(t, tc.expErr, err, "Unexpected result")
235+
// on success there shall be saved ovs data
236+
var data VppSavedData
237+
assert.NoError(t, LoadVppConfig(tc.netConf, args, &data))
238+
assert.NotEmpty(t, data.MemifSocketId)
239+
} else {
240+
require.Error(t, err, "Unexpected result")
241+
assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected result")
242+
}
243+
})
244+
}
245+
}
246+
247+
func TestDelFromHost(t *testing.T) {
248+
cniVpp := CniVpp{}
249+
250+
testCases := []struct {
251+
name string
252+
netConf *types.NetConf
253+
savedData string
254+
fakeErr error
255+
expErr error
256+
}{
257+
{
258+
name: "Happy path",
259+
netConf: &types.NetConf{
260+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "interface",
261+
VhostConf: types.VhostConf{Mode: "client"},
262+
MemifConf: types.MemifConf{
263+
Role: "master", // Role of memif: master|slave
264+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
265+
}}},
266+
expErr: nil,
267+
},
268+
{
269+
name: "Unknown HostConf Type",
270+
netConf: &types.NetConf{
271+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "Unknown", NetType: "interface",
272+
VhostConf: types.VhostConf{Mode: "client"},
273+
MemifConf: types.MemifConf{
274+
Role: "master", // Role of memif: master|slave
275+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
276+
}}},
277+
expErr: fmt.Errorf("ERROR: Unknown HostConf.Type"),
278+
},
279+
{
280+
name: "Delete Bridge with IfType set to vhostUser",
281+
netConf: &types.NetConf{
282+
HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "vhostuser", NetType: "bridge",
283+
VhostConf: types.VhostConf{Mode: "client"},
284+
BridgeConf: types.BridgeConf{
285+
BridgeName: "12345",
286+
BridgeId: 12345,
287+
},
288+
MemifConf: types.MemifConf{
289+
Role: "master", // Role of memif: master|slave
290+
Mode: "ip", // Mode of memif: ip|ethernet|inject-punt
291+
}}},
292+
expErr: fmt.Errorf("GOOD: Found HostConf.Type:vhostuser"),
293+
},
294+
}
295+
for _, tc := range testCases {
296+
t.Run(tc.name, func(t *testing.T) {
297+
args := GetTestArgs()
298+
sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cnivpp-")
299+
require.NoError(t, dirErr, "Can't create temporary directory")
300+
defer os.RemoveAll(sharedDir)
301+
302+
var result *current.Result
303+
304+
pod := GetTestPod(sharedDir)
305+
kubeClient := fake.NewSimpleClientset(pod)
306+
307+
_ = cniVpp.AddOnHost(tc.netConf, args, kubeClient, sharedDir, result)
308+
309+
err := cniVpp.DelFromHost(tc.netConf, args, sharedDir)
310+
if tc.expErr == nil {
311+
assert.Equal(t, tc.expErr, err, "Unexpected result")
312+
} else {
313+
require.Error(t, err, "Unexpected result")
314+
assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected result")
315+
}
316+
})
317+
}
318+
}

Diff for: docker/userspacecni/Dockerfile.unittest

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM ligato/vpp-base:23.06@sha256:f68272b0aebe106673c7fffe94b6e6ccd06ecc9afd123ebcbbdc22b350bd2774 as builder
2+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
3+
COPY . /root/userspace-cni-network-plugin
4+
WORKDIR /root/userspace-cni-network-plugin
5+
RUN apt-get update -y \
6+
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y binutils bash wget make git \
7+
&& wget -qO- https://golang.org/dl/go1.20.1.linux-amd64.tar.gz | tar -C /usr/local -xz \
8+
&& rm -rf /var/lib/apt/lists/*
9+
ENV PATH="${PATH}:/usr/local/go/bin"
10+
RUN go mod download \
11+
&& go get go.fd.io/govpp/binapigen/[email protected] \
12+
&& make generate \
13+
&& go mod tidy \
14+
&& make generate-bin

0 commit comments

Comments
 (0)