hs-test: fixed timed out tests passing in the CI
[vpp.git] / extras / hs-test / vppinstance.go
1 package main
2
3 import (
4         "fmt"
5         "os"
6         "os/exec"
7         "os/signal"
8         "strconv"
9         "strings"
10         "syscall"
11         "time"
12
13         "github.com/edwarnicke/exechelper"
14         . "github.com/onsi/ginkgo/v2"
15
16         "go.fd.io/govpp"
17         "go.fd.io/govpp/api"
18         "go.fd.io/govpp/binapi/af_packet"
19         interfaces "go.fd.io/govpp/binapi/interface"
20         "go.fd.io/govpp/binapi/interface_types"
21         "go.fd.io/govpp/binapi/session"
22         "go.fd.io/govpp/binapi/tapv2"
23         "go.fd.io/govpp/binapi/vpe"
24         "go.fd.io/govpp/core"
25 )
26
27 const vppConfigTemplate = `unix {
28   nodaemon
29   log %[1]s%[4]s
30   full-coredump
31   cli-listen %[1]s%[2]s
32   runtime-dir %[1]s/var/run
33   gid vpp
34 }
35
36 api-trace {
37   on
38 }
39
40 api-segment {
41   gid vpp
42 }
43
44 socksvr {
45   socket-name %[1]s%[3]s
46 }
47
48 statseg {
49   socket-name %[1]s/var/run/vpp/stats.sock
50 }
51
52 plugins {
53   plugin default { disable }
54
55   plugin unittest_plugin.so { enable }
56   plugin quic_plugin.so { enable }
57   plugin af_packet_plugin.so { enable }
58   plugin hs_apps_plugin.so { enable }
59   plugin http_plugin.so { enable }
60   plugin http_static_plugin.so { enable }
61   plugin prom_plugin.so { enable }
62   plugin tlsopenssl_plugin.so { enable }
63   plugin ping_plugin.so { enable }
64   plugin nsim_plugin.so { enable }
65 }
66
67 logging {
68   default-log-level debug
69   default-syslog-log-level debug
70 }
71
72 `
73
74 const (
75         defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
76         defaultApiSocketFilePath = "/var/run/vpp/api.sock"
77         defaultLogFilePath       = "/var/log/vpp/vpp.log"
78 )
79
80 type VppInstance struct {
81         container        *Container
82         additionalConfig []Stanza
83         connection       *core.Connection
84         apiChannel       api.Channel
85         cpus             []int
86 }
87
88 func (vpp *VppInstance) getSuite() *HstSuite {
89         return vpp.container.suite
90 }
91
92 func (vpp *VppInstance) getCliSocket() string {
93         return fmt.Sprintf("%s%s", vpp.container.getContainerWorkDir(), defaultCliSocketFilePath)
94 }
95
96 func (vpp *VppInstance) getRunDir() string {
97         return vpp.container.getContainerWorkDir() + "/var/run/vpp"
98 }
99
100 func (vpp *VppInstance) getLogDir() string {
101         return vpp.container.getContainerWorkDir() + "/var/log/vpp"
102 }
103
104 func (vpp *VppInstance) getEtcDir() string {
105         return vpp.container.getContainerWorkDir() + "/etc/vpp"
106 }
107
108 func (vpp *VppInstance) start() error {
109         // Create folders
110         containerWorkDir := vpp.container.getContainerWorkDir()
111
112         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
113         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
114         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
115
116         // Create startup.conf inside the container
117         configContent := fmt.Sprintf(
118                 vppConfigTemplate,
119                 containerWorkDir,
120                 defaultCliSocketFilePath,
121                 defaultApiSocketFilePath,
122                 defaultLogFilePath,
123         )
124         configContent += vpp.generateCpuConfig()
125         for _, c := range vpp.additionalConfig {
126                 configContent += c.toString()
127         }
128         startupFileName := vpp.getEtcDir() + "/startup.conf"
129         vpp.container.createFile(startupFileName, configContent)
130
131         // create wrapper script for vppctl with proper CLI socket path
132         cliContent := "#!/usr/bin/bash\nvppctl -s " + vpp.getRunDir() + "/cli.sock"
133         vppcliFileName := "/usr/bin/vppcli"
134         vpp.container.createFile(vppcliFileName, cliContent)
135         vpp.container.exec("chmod 0755 " + vppcliFileName)
136
137         vpp.getSuite().log("starting vpp")
138         if *isVppDebug {
139                 sig := make(chan os.Signal, 1)
140                 signal.Notify(sig, syscall.SIGQUIT)
141                 cont := make(chan bool, 1)
142                 go func() {
143                         <-sig
144                         cont <- true
145                 }()
146
147                 vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
148                 fmt.Println("run following command in different terminal:")
149                 fmt.Println("docker exec -it " + vpp.container.name + " gdb -ex \"attach $(docker exec " + vpp.container.name + " pidof vpp)\"")
150                 fmt.Println("Afterwards press CTRL+\\ to continue")
151                 <-cont
152                 fmt.Println("continuing...")
153         } else {
154                 // Start VPP
155                 vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
156         }
157
158         vpp.getSuite().log("connecting to vpp")
159         // Connect to VPP and store the connection
160         sockAddress := vpp.container.getHostWorkDir() + defaultApiSocketFilePath
161         conn, connEv, err := govpp.AsyncConnect(
162                 sockAddress,
163                 core.DefaultMaxReconnectAttempts,
164                 core.DefaultReconnectInterval)
165         if err != nil {
166                 fmt.Println("async connect error: ", err)
167                 return err
168         }
169         vpp.connection = conn
170
171         // ... wait for Connected event
172         e := <-connEv
173         if e.State != core.Connected {
174                 fmt.Println("connecting to VPP failed: ", e.Error)
175         }
176
177         // ... check compatibility of used messages
178         ch, err := conn.NewAPIChannel()
179         if err != nil {
180                 fmt.Println("creating channel failed: ", err)
181                 return err
182         }
183         if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
184                 fmt.Println("compatibility error: ", err)
185                 return err
186         }
187         if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
188                 fmt.Println("compatibility error: ", err)
189                 return err
190         }
191         vpp.apiChannel = ch
192
193         return nil
194 }
195
196 func (vpp *VppInstance) vppctl(command string, arguments ...any) string {
197         vppCliCommand := fmt.Sprintf(command, arguments...)
198         containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
199                 vpp.container.name, vpp.getCliSocket(), vppCliCommand)
200         vpp.getSuite().log(containerExecCommand)
201         output, err := exechelper.CombinedOutput(containerExecCommand)
202         vpp.getSuite().assertNil(err)
203
204         return string(output)
205 }
206
207 func (vpp *VppInstance) GetSessionStat(stat string) int {
208         o := vpp.vppctl("show session stats")
209         vpp.getSuite().log(o)
210         for _, line := range strings.Split(o, "\n") {
211                 if strings.Contains(line, stat) {
212                         tokens := strings.Split(strings.TrimSpace(line), " ")
213                         val, err := strconv.Atoi(tokens[0])
214                         if err != nil {
215                                 Fail("failed to parse stat value %s" + fmt.Sprint(err))
216                                 return 0
217                         }
218                         return val
219                 }
220         }
221         return 0
222 }
223
224 func (vpp *VppInstance) waitForApp(appName string, timeout int) {
225         vpp.getSuite().log("waiting for app " + appName)
226         for i := 0; i < timeout; i++ {
227                 o := vpp.vppctl("show app")
228                 if strings.Contains(o, appName) {
229                         return
230                 }
231                 time.Sleep(1 * time.Second)
232         }
233         vpp.getSuite().assertNil(1, "Timeout while waiting for app '%s'", appName)
234 }
235
236 func (vpp *VppInstance) createAfPacket(
237         veth *NetInterface,
238 ) (interface_types.InterfaceIndex, error) {
239         createReq := &af_packet.AfPacketCreateV2{
240                 UseRandomHwAddr: true,
241                 HostIfName:      veth.Name(),
242         }
243         if veth.hwAddress != (MacAddress{}) {
244                 createReq.UseRandomHwAddr = false
245                 createReq.HwAddr = veth.hwAddress
246         }
247         createReply := &af_packet.AfPacketCreateV2Reply{}
248
249         vpp.getSuite().log("create af-packet interface " + veth.Name())
250         if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
251                 return 0, err
252         }
253         veth.index = createReply.SwIfIndex
254
255         // Set to up
256         upReq := &interfaces.SwInterfaceSetFlags{
257                 SwIfIndex: veth.index,
258                 Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
259         }
260         upReply := &interfaces.SwInterfaceSetFlagsReply{}
261
262         vpp.getSuite().log("set af-packet interface " + veth.Name() + " up")
263         if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
264                 return 0, err
265         }
266
267         // Add address
268         if veth.addressWithPrefix() == (AddressWithPrefix{}) {
269                 var err error
270                 var ip4Address string
271                 if ip4Address, err = veth.ip4AddrAllocator.NewIp4InterfaceAddress(veth.peer.networkNumber); err == nil {
272                         veth.ip4Address = ip4Address
273                 } else {
274                         return 0, err
275                 }
276         }
277         addressReq := &interfaces.SwInterfaceAddDelAddress{
278                 IsAdd:     true,
279                 SwIfIndex: veth.index,
280                 Prefix:    veth.addressWithPrefix(),
281         }
282         addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
283
284         vpp.getSuite().log("af-packet interface " + veth.Name() + " add address " + veth.ip4Address)
285         if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
286                 return 0, err
287         }
288
289         return veth.index, nil
290 }
291
292 func (vpp *VppInstance) addAppNamespace(
293         secret uint64,
294         ifx interface_types.InterfaceIndex,
295         namespaceId string,
296 ) error {
297         req := &session.AppNamespaceAddDelV2{
298                 Secret:      secret,
299                 SwIfIndex:   ifx,
300                 NamespaceID: namespaceId,
301         }
302         reply := &session.AppNamespaceAddDelV2Reply{}
303
304         vpp.getSuite().log("add app namespace " + namespaceId)
305         if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
306                 return err
307         }
308
309         sessionReq := &session.SessionEnableDisable{
310                 IsEnable: true,
311         }
312         sessionReply := &session.SessionEnableDisableReply{}
313
314         vpp.getSuite().log("enable app namespace " + namespaceId)
315         if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
316                 return err
317         }
318
319         return nil
320 }
321
322 func (vpp *VppInstance) createTap(
323         tap *NetInterface,
324         tapId ...uint32,
325 ) error {
326         var id uint32 = 1
327         if len(tapId) > 0 {
328                 id = tapId[0]
329         }
330         createTapReq := &tapv2.TapCreateV2{
331                 ID:               id,
332                 HostIfNameSet:    true,
333                 HostIfName:       tap.Name(),
334                 HostIP4PrefixSet: true,
335                 HostIP4Prefix:    tap.ip4AddressWithPrefix(),
336         }
337         createTapReply := &tapv2.TapCreateV2Reply{}
338
339         vpp.getSuite().log("create tap interface " + tap.Name())
340         // Create tap interface
341         if err := vpp.apiChannel.SendRequest(createTapReq).ReceiveReply(createTapReply); err != nil {
342                 return err
343         }
344
345         // Add address
346         addAddressReq := &interfaces.SwInterfaceAddDelAddress{
347                 IsAdd:     true,
348                 SwIfIndex: createTapReply.SwIfIndex,
349                 Prefix:    tap.peer.addressWithPrefix(),
350         }
351         addAddressReply := &interfaces.SwInterfaceAddDelAddressReply{}
352
353         vpp.getSuite().log("tap interface " + tap.Name() + " add address " + tap.peer.ip4Address)
354         if err := vpp.apiChannel.SendRequest(addAddressReq).ReceiveReply(addAddressReply); err != nil {
355                 return err
356         }
357
358         // Set interface to up
359         upReq := &interfaces.SwInterfaceSetFlags{
360                 SwIfIndex: createTapReply.SwIfIndex,
361                 Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
362         }
363         upReply := &interfaces.SwInterfaceSetFlagsReply{}
364
365         vpp.getSuite().log("set tap interface " + tap.Name() + " up")
366         if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
367                 return err
368         }
369
370         return nil
371 }
372
373 func (vpp *VppInstance) saveLogs() {
374         logTarget := vpp.container.getLogDirPath() + "vppinstance-" + vpp.container.name + ".log"
375         logSource := vpp.container.getHostWorkDir() + defaultLogFilePath
376         cmd := exec.Command("cp", logSource, logTarget)
377         vpp.getSuite().log(cmd.String())
378         cmd.Run()
379 }
380
381 func (vpp *VppInstance) disconnect() {
382         vpp.connection.Disconnect()
383         vpp.apiChannel.Close()
384 }
385
386 func (vpp *VppInstance) generateCpuConfig() string {
387         var c Stanza
388         var s string
389         if len(vpp.cpus) < 1 {
390                 return ""
391         }
392         c.newStanza("cpu").
393                 append(fmt.Sprintf("main-core %d", vpp.cpus[0]))
394         workers := vpp.cpus[1:]
395
396         if len(workers) > 0 {
397                 for i := 0; i < len(workers); i++ {
398                         if i != 0 {
399                                 s = s + ", "
400                         }
401                         s = s + fmt.Sprintf("%d", workers[i])
402                 }
403                 c.append(fmt.Sprintf("corelist-workers %s", s))
404         }
405         return c.close().toString()
406 }