15 "github.com/edwarnicke/exechelper"
16 . "github.com/onsi/ginkgo/v2"
17 "github.com/sirupsen/logrus"
21 "go.fd.io/govpp/binapi/af_packet"
22 interfaces "go.fd.io/govpp/binapi/interface"
23 "go.fd.io/govpp/binapi/interface_types"
24 "go.fd.io/govpp/binapi/session"
25 "go.fd.io/govpp/binapi/tapv2"
29 const vppConfigTemplate = `unix {
34 runtime-dir %[1]s/var/run
47 socket-name %[1]s%[3]s
51 socket-name %[1]s/var/run/vpp/stats.sock
55 plugin default { disable }
57 plugin unittest_plugin.so { enable }
58 plugin quic_plugin.so { enable }
59 plugin af_packet_plugin.so { enable }
60 plugin hs_apps_plugin.so { enable }
61 plugin http_plugin.so { enable }
62 plugin http_static_plugin.so { enable }
63 plugin prom_plugin.so { enable }
64 plugin tlsopenssl_plugin.so { enable }
65 plugin ping_plugin.so { enable }
66 plugin nsim_plugin.so { enable }
70 default-log-level debug
71 default-syslog-log-level debug
77 defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
78 defaultApiSocketFilePath = "/var/run/vpp/api.sock"
79 defaultLogFilePath = "/var/log/vpp/vpp.log"
82 type VppInstance struct {
84 additionalConfig []Stanza
85 connection *core.Connection
90 func (vpp *VppInstance) getSuite() *HstSuite {
91 return vpp.container.suite
94 func (vpp *VppInstance) getCliSocket() string {
95 return fmt.Sprintf("%s%s", vpp.container.getContainerWorkDir(), defaultCliSocketFilePath)
98 func (vpp *VppInstance) getRunDir() string {
99 return vpp.container.getContainerWorkDir() + "/var/run/vpp"
102 func (vpp *VppInstance) getLogDir() string {
103 return vpp.container.getContainerWorkDir() + "/var/log/vpp"
106 func (vpp *VppInstance) getEtcDir() string {
107 return vpp.container.getContainerWorkDir() + "/etc/vpp"
110 func (vpp *VppInstance) start() error {
111 maxReconnectAttempts := 3
112 // Replace default logger in govpp with our own
113 govppLogger := logrus.New()
114 govppLogger.SetOutput(io.MultiWriter(vpp.getSuite().logger.Writer(), GinkgoWriter))
115 core.SetLogger(govppLogger)
117 containerWorkDir := vpp.container.getContainerWorkDir()
119 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
120 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
121 vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
123 // Create startup.conf inside the container
124 configContent := fmt.Sprintf(
127 defaultCliSocketFilePath,
128 defaultApiSocketFilePath,
131 configContent += vpp.generateCpuConfig()
132 for _, c := range vpp.additionalConfig {
133 configContent += c.toString()
135 startupFileName := vpp.getEtcDir() + "/startup.conf"
136 vpp.container.createFile(startupFileName, configContent)
138 // create wrapper script for vppctl with proper CLI socket path
139 cliContent := "#!/usr/bin/bash\nvppctl -s " + vpp.getRunDir() + "/cli.sock"
140 vppcliFileName := "/usr/bin/vppcli"
141 vpp.container.createFile(vppcliFileName, cliContent)
142 vpp.container.exec("chmod 0755 " + vppcliFileName)
144 vpp.getSuite().log("starting vpp")
146 // default = 3; VPP will timeout while debugging if there are not enough attempts
147 maxReconnectAttempts = 5000
148 sig := make(chan os.Signal, 1)
149 signal.Notify(sig, syscall.SIGQUIT)
150 cont := make(chan bool, 1)
156 vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
157 fmt.Println("run following command in different terminal:")
158 fmt.Println("docker exec -it " + vpp.container.name + " gdb -ex \"attach $(docker exec " + vpp.container.name + " pidof vpp)\"")
159 fmt.Println("Afterwards press CTRL+\\ to continue")
161 fmt.Println("continuing...")
164 vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
167 vpp.getSuite().log("connecting to vpp")
168 // Connect to VPP and store the connection
169 sockAddress := vpp.container.getHostWorkDir() + defaultApiSocketFilePath
170 conn, connEv, err := govpp.AsyncConnect(
172 maxReconnectAttempts,
173 core.DefaultReconnectInterval)
175 vpp.getSuite().log("async connect error: " + fmt.Sprint(err))
178 vpp.connection = conn
180 // ... wait for Connected event
182 if e.State != core.Connected {
183 vpp.getSuite().log("connecting to VPP failed: " + fmt.Sprint(e.Error))
186 ch, err := conn.NewStream(
187 context.Background(),
188 core.WithRequestSize(50),
189 core.WithReplySize(50),
190 core.WithReplyTimeout(time.Second*5))
192 vpp.getSuite().log("creating stream failed: " + fmt.Sprint(err))
200 func (vpp *VppInstance) vppctl(command string, arguments ...any) string {
201 vppCliCommand := fmt.Sprintf(command, arguments...)
202 containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
203 vpp.container.name, vpp.getCliSocket(), vppCliCommand)
204 vpp.getSuite().log(containerExecCommand)
205 output, err := exechelper.CombinedOutput(containerExecCommand)
206 vpp.getSuite().assertNil(err)
208 return string(output)
211 func (vpp *VppInstance) GetSessionStat(stat string) int {
212 o := vpp.vppctl("show session stats")
213 vpp.getSuite().log(o)
214 for _, line := range strings.Split(o, "\n") {
215 if strings.Contains(line, stat) {
216 tokens := strings.Split(strings.TrimSpace(line), " ")
217 val, err := strconv.Atoi(tokens[0])
219 Fail("failed to parse stat value %s" + fmt.Sprint(err))
228 func (vpp *VppInstance) waitForApp(appName string, timeout int) {
229 vpp.getSuite().log("waiting for app " + appName)
230 for i := 0; i < timeout; i++ {
231 o := vpp.vppctl("show app")
232 if strings.Contains(o, appName) {
235 time.Sleep(1 * time.Second)
237 vpp.getSuite().assertNil(1, "Timeout while waiting for app '%s'", appName)
240 func (vpp *VppInstance) createAfPacket(
242 ) (interface_types.InterfaceIndex, error) {
243 createReq := &af_packet.AfPacketCreateV3{
245 UseRandomHwAddr: true,
246 HostIfName: veth.Name(),
247 Flags: af_packet.AfPacketFlags(11),
249 if veth.hwAddress != (MacAddress{}) {
250 createReq.UseRandomHwAddr = false
251 createReq.HwAddr = veth.hwAddress
254 vpp.getSuite().log("create af-packet interface " + veth.Name())
255 if err := vpp.apiStream.SendMsg(createReq); err != nil {
256 vpp.getSuite().hstFail()
259 replymsg, err := vpp.apiStream.RecvMsg()
263 reply := replymsg.(*af_packet.AfPacketCreateV3Reply)
264 err = api.RetvalToVPPApiError(reply.Retval)
269 veth.index = reply.SwIfIndex
272 upReq := &interfaces.SwInterfaceSetFlags{
273 SwIfIndex: veth.index,
274 Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
277 vpp.getSuite().log("set af-packet interface " + veth.Name() + " up")
278 if err := vpp.apiStream.SendMsg(upReq); err != nil {
281 replymsg, err = vpp.apiStream.RecvMsg()
285 reply2 := replymsg.(*interfaces.SwInterfaceSetFlagsReply)
286 if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
291 if veth.addressWithPrefix() == (AddressWithPrefix{}) {
293 var ip4Address string
294 if ip4Address, err = veth.ip4AddrAllocator.NewIp4InterfaceAddress(veth.peer.networkNumber); err == nil {
295 veth.ip4Address = ip4Address
300 addressReq := &interfaces.SwInterfaceAddDelAddress{
302 SwIfIndex: veth.index,
303 Prefix: veth.addressWithPrefix(),
306 vpp.getSuite().log("af-packet interface " + veth.Name() + " add address " + veth.ip4Address)
307 if err := vpp.apiStream.SendMsg(addressReq); err != nil {
310 replymsg, err = vpp.apiStream.RecvMsg()
314 reply3 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply)
315 err = api.RetvalToVPPApiError(reply3.Retval)
320 return veth.index, nil
323 func (vpp *VppInstance) addAppNamespace(
325 ifx interface_types.InterfaceIndex,
328 req := &session.AppNamespaceAddDelV4{
332 NamespaceID: namespaceId,
333 SockName: defaultApiSocketFilePath,
336 vpp.getSuite().log("add app namespace " + namespaceId)
337 if err := vpp.apiStream.SendMsg(req); err != nil {
340 replymsg, err := vpp.apiStream.RecvMsg()
344 reply := replymsg.(*session.AppNamespaceAddDelV4Reply)
345 if err = api.RetvalToVPPApiError(reply.Retval); err != nil {
349 sessionReq := &session.SessionEnableDisable{
353 vpp.getSuite().log("enable app namespace " + namespaceId)
354 if err := vpp.apiStream.SendMsg(sessionReq); err != nil {
357 replymsg, err = vpp.apiStream.RecvMsg()
361 reply2 := replymsg.(*session.SessionEnableDisableReply)
362 if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
369 func (vpp *VppInstance) createTap(
377 createTapReq := &tapv2.TapCreateV3{
380 HostIfName: tap.Name(),
381 HostIP4PrefixSet: true,
382 HostIP4Prefix: tap.ip4AddressWithPrefix(),
385 vpp.getSuite().log("create tap interface " + tap.Name())
386 // Create tap interface
387 if err := vpp.apiStream.SendMsg(createTapReq); err != nil {
390 replymsg, err := vpp.apiStream.RecvMsg()
394 reply := replymsg.(*tapv2.TapCreateV3Reply)
395 if err = api.RetvalToVPPApiError(reply.Retval); err != nil {
400 addAddressReq := &interfaces.SwInterfaceAddDelAddress{
402 SwIfIndex: reply.SwIfIndex,
403 Prefix: tap.peer.addressWithPrefix(),
406 vpp.getSuite().log("tap interface " + tap.Name() + " add address " + tap.peer.ip4Address)
407 if err := vpp.apiStream.SendMsg(addAddressReq); err != nil {
410 replymsg, err = vpp.apiStream.RecvMsg()
414 reply2 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply)
415 if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
419 // Set interface to up
420 upReq := &interfaces.SwInterfaceSetFlags{
421 SwIfIndex: reply.SwIfIndex,
422 Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
425 vpp.getSuite().log("set tap interface " + tap.Name() + " up")
426 if err := vpp.apiStream.SendMsg(upReq); err != nil {
429 replymsg, err = vpp.apiStream.RecvMsg()
433 reply3 := replymsg.(*interfaces.SwInterfaceSetFlagsReply)
434 if err = api.RetvalToVPPApiError(reply3.Retval); err != nil {
441 func (vpp *VppInstance) saveLogs() {
442 logTarget := vpp.container.getLogDirPath() + "vppinstance-" + vpp.container.name + ".log"
443 logSource := vpp.container.getHostWorkDir() + defaultLogFilePath
444 cmd := exec.Command("cp", logSource, logTarget)
445 vpp.getSuite().log(cmd.String())
449 func (vpp *VppInstance) disconnect() {
450 vpp.connection.Disconnect()
451 vpp.apiStream.Close()
454 func (vpp *VppInstance) generateCpuConfig() string {
457 if len(vpp.cpus) < 1 {
461 append(fmt.Sprintf("main-core %d", vpp.cpus[0]))
462 vpp.getSuite().log(fmt.Sprintf("main-core %d", vpp.cpus[0]))
463 workers := vpp.cpus[1:]
465 if len(workers) > 0 {
466 for i := 0; i < len(workers); i++ {
470 s = s + fmt.Sprintf("%d", workers[i])
472 c.append(fmt.Sprintf("corelist-workers %s", s))
473 vpp.getSuite().log("corelist-workers " + s)
475 return c.close().toString()