hs-test: more debug output in http3 test
[vpp.git] / extras / hs-test / vppinstance.go
1 package main
2
3 import (
4         "context"
5         "fmt"
6         "io"
7         "os"
8         "os/exec"
9         "os/signal"
10         "strconv"
11         "strings"
12         "syscall"
13         "time"
14
15         "github.com/edwarnicke/exechelper"
16         . "github.com/onsi/ginkgo/v2"
17         "github.com/sirupsen/logrus"
18
19         "go.fd.io/govpp"
20         "go.fd.io/govpp/api"
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"
26         "go.fd.io/govpp/core"
27 )
28
29 const vppConfigTemplate = `unix {
30   nodaemon
31   log %[1]s%[4]s
32   full-coredump
33   cli-listen %[1]s%[2]s
34   runtime-dir %[1]s/var/run
35   gid vpp
36 }
37
38 api-trace {
39   on
40 }
41
42 api-segment {
43   gid vpp
44 }
45
46 socksvr {
47   socket-name %[1]s%[3]s
48 }
49
50 statseg {
51   socket-name %[1]s/var/run/vpp/stats.sock
52 }
53
54 plugins {
55   plugin default { disable }
56
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 }
67 }
68
69 logging {
70   default-log-level debug
71   default-syslog-log-level debug
72 }
73
74 `
75
76 const (
77         defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
78         defaultApiSocketFilePath = "/var/run/vpp/api.sock"
79         defaultLogFilePath       = "/var/log/vpp/vpp.log"
80 )
81
82 type VppInstance struct {
83         container        *Container
84         additionalConfig []Stanza
85         connection       *core.Connection
86         apiStream        api.Stream
87         cpus             []int
88 }
89
90 func (vpp *VppInstance) getSuite() *HstSuite {
91         return vpp.container.suite
92 }
93
94 func (vpp *VppInstance) getCliSocket() string {
95         return fmt.Sprintf("%s%s", vpp.container.getContainerWorkDir(), defaultCliSocketFilePath)
96 }
97
98 func (vpp *VppInstance) getRunDir() string {
99         return vpp.container.getContainerWorkDir() + "/var/run/vpp"
100 }
101
102 func (vpp *VppInstance) getLogDir() string {
103         return vpp.container.getContainerWorkDir() + "/var/log/vpp"
104 }
105
106 func (vpp *VppInstance) getEtcDir() string {
107         return vpp.container.getContainerWorkDir() + "/etc/vpp"
108 }
109
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)
116         // Create folders
117         containerWorkDir := vpp.container.getContainerWorkDir()
118
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())
122
123         // Create startup.conf inside the container
124         configContent := fmt.Sprintf(
125                 vppConfigTemplate,
126                 containerWorkDir,
127                 defaultCliSocketFilePath,
128                 defaultApiSocketFilePath,
129                 defaultLogFilePath,
130         )
131         configContent += vpp.generateCpuConfig()
132         for _, c := range vpp.additionalConfig {
133                 configContent += c.toString()
134         }
135         startupFileName := vpp.getEtcDir() + "/startup.conf"
136         vpp.container.createFile(startupFileName, configContent)
137
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)
143
144         vpp.getSuite().log("starting vpp")
145         if *isVppDebug {
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)
151                 go func() {
152                         <-sig
153                         cont <- true
154                 }()
155
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")
160                 <-cont
161                 fmt.Println("continuing...")
162         } else {
163                 // Start VPP
164                 vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
165         }
166
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(
171                 sockAddress,
172                 maxReconnectAttempts,
173                 core.DefaultReconnectInterval)
174         if err != nil {
175                 vpp.getSuite().log("async connect error: " + fmt.Sprint(err))
176                 return err
177         }
178         vpp.connection = conn
179
180         // ... wait for Connected event
181         e := <-connEv
182         if e.State != core.Connected {
183                 vpp.getSuite().log("connecting to VPP failed: " + fmt.Sprint(e.Error))
184         }
185
186         ch, err := conn.NewStream(
187                 context.Background(),
188                 core.WithRequestSize(50),
189                 core.WithReplySize(50),
190                 core.WithReplyTimeout(time.Second*5))
191         if err != nil {
192                 vpp.getSuite().log("creating stream failed: " + fmt.Sprint(err))
193                 return err
194         }
195         vpp.apiStream = ch
196
197         return nil
198 }
199
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)
207
208         return string(output)
209 }
210
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])
218                         if err != nil {
219                                 Fail("failed to parse stat value %s" + fmt.Sprint(err))
220                                 return 0
221                         }
222                         return val
223                 }
224         }
225         return 0
226 }
227
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) {
233                         return
234                 }
235                 time.Sleep(1 * time.Second)
236         }
237         vpp.getSuite().assertNil(1, "Timeout while waiting for app '%s'", appName)
238 }
239
240 func (vpp *VppInstance) createAfPacket(
241         veth *NetInterface,
242 ) (interface_types.InterfaceIndex, error) {
243         createReq := &af_packet.AfPacketCreateV3{
244                 Mode:            1,
245                 UseRandomHwAddr: true,
246                 HostIfName:      veth.Name(),
247                 Flags:           af_packet.AfPacketFlags(11),
248         }
249         if veth.hwAddress != (MacAddress{}) {
250                 createReq.UseRandomHwAddr = false
251                 createReq.HwAddr = veth.hwAddress
252         }
253
254         vpp.getSuite().log("create af-packet interface " + veth.Name())
255         if err := vpp.apiStream.SendMsg(createReq); err != nil {
256                 vpp.getSuite().hstFail()
257                 return 0, err
258         }
259         replymsg, err := vpp.apiStream.RecvMsg()
260         if err != nil {
261                 return 0, err
262         }
263         reply := replymsg.(*af_packet.AfPacketCreateV3Reply)
264         err = api.RetvalToVPPApiError(reply.Retval)
265         if err != nil {
266                 return 0, err
267         }
268
269         veth.index = reply.SwIfIndex
270
271         // Set to up
272         upReq := &interfaces.SwInterfaceSetFlags{
273                 SwIfIndex: veth.index,
274                 Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
275         }
276
277         vpp.getSuite().log("set af-packet interface " + veth.Name() + " up")
278         if err := vpp.apiStream.SendMsg(upReq); err != nil {
279                 return 0, err
280         }
281         replymsg, err = vpp.apiStream.RecvMsg()
282         if err != nil {
283                 return 0, err
284         }
285         reply2 := replymsg.(*interfaces.SwInterfaceSetFlagsReply)
286         if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
287                 return 0, err
288         }
289
290         // Add address
291         if veth.addressWithPrefix() == (AddressWithPrefix{}) {
292                 var err error
293                 var ip4Address string
294                 if ip4Address, err = veth.ip4AddrAllocator.NewIp4InterfaceAddress(veth.peer.networkNumber); err == nil {
295                         veth.ip4Address = ip4Address
296                 } else {
297                         return 0, err
298                 }
299         }
300         addressReq := &interfaces.SwInterfaceAddDelAddress{
301                 IsAdd:     true,
302                 SwIfIndex: veth.index,
303                 Prefix:    veth.addressWithPrefix(),
304         }
305
306         vpp.getSuite().log("af-packet interface " + veth.Name() + " add address " + veth.ip4Address)
307         if err := vpp.apiStream.SendMsg(addressReq); err != nil {
308                 return 0, err
309         }
310         replymsg, err = vpp.apiStream.RecvMsg()
311         if err != nil {
312                 return 0, err
313         }
314         reply3 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply)
315         err = api.RetvalToVPPApiError(reply3.Retval)
316         if err != nil {
317                 return 0, err
318         }
319
320         return veth.index, nil
321 }
322
323 func (vpp *VppInstance) addAppNamespace(
324         secret uint64,
325         ifx interface_types.InterfaceIndex,
326         namespaceId string,
327 ) error {
328         req := &session.AppNamespaceAddDelV4{
329                 IsAdd:       true,
330                 Secret:      secret,
331                 SwIfIndex:   ifx,
332                 NamespaceID: namespaceId,
333                 SockName:    defaultApiSocketFilePath,
334         }
335
336         vpp.getSuite().log("add app namespace " + namespaceId)
337         if err := vpp.apiStream.SendMsg(req); err != nil {
338                 return err
339         }
340         replymsg, err := vpp.apiStream.RecvMsg()
341         if err != nil {
342                 return err
343         }
344         reply := replymsg.(*session.AppNamespaceAddDelV4Reply)
345         if err = api.RetvalToVPPApiError(reply.Retval); err != nil {
346                 return err
347         }
348
349         sessionReq := &session.SessionEnableDisable{
350                 IsEnable: true,
351         }
352
353         vpp.getSuite().log("enable app namespace " + namespaceId)
354         if err := vpp.apiStream.SendMsg(sessionReq); err != nil {
355                 return err
356         }
357         replymsg, err = vpp.apiStream.RecvMsg()
358         if err != nil {
359                 return err
360         }
361         reply2 := replymsg.(*session.SessionEnableDisableReply)
362         if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
363                 return err
364         }
365
366         return nil
367 }
368
369 func (vpp *VppInstance) createTap(
370         tap *NetInterface,
371         tapId ...uint32,
372 ) error {
373         var id uint32 = 1
374         if len(tapId) > 0 {
375                 id = tapId[0]
376         }
377         createTapReq := &tapv2.TapCreateV3{
378                 ID:               id,
379                 HostIfNameSet:    true,
380                 HostIfName:       tap.Name(),
381                 HostIP4PrefixSet: true,
382                 HostIP4Prefix:    tap.ip4AddressWithPrefix(),
383         }
384
385         vpp.getSuite().log("create tap interface " + tap.Name())
386         // Create tap interface
387         if err := vpp.apiStream.SendMsg(createTapReq); err != nil {
388                 return err
389         }
390         replymsg, err := vpp.apiStream.RecvMsg()
391         if err != nil {
392                 return err
393         }
394         reply := replymsg.(*tapv2.TapCreateV3Reply)
395         if err = api.RetvalToVPPApiError(reply.Retval); err != nil {
396                 return err
397         }
398
399         // Add address
400         addAddressReq := &interfaces.SwInterfaceAddDelAddress{
401                 IsAdd:     true,
402                 SwIfIndex: reply.SwIfIndex,
403                 Prefix:    tap.peer.addressWithPrefix(),
404         }
405
406         vpp.getSuite().log("tap interface " + tap.Name() + " add address " + tap.peer.ip4Address)
407         if err := vpp.apiStream.SendMsg(addAddressReq); err != nil {
408                 return err
409         }
410         replymsg, err = vpp.apiStream.RecvMsg()
411         if err != nil {
412                 return err
413         }
414         reply2 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply)
415         if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
416                 return err
417         }
418
419         // Set interface to up
420         upReq := &interfaces.SwInterfaceSetFlags{
421                 SwIfIndex: reply.SwIfIndex,
422                 Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
423         }
424
425         vpp.getSuite().log("set tap interface " + tap.Name() + " up")
426         if err := vpp.apiStream.SendMsg(upReq); err != nil {
427                 return err
428         }
429         replymsg, err = vpp.apiStream.RecvMsg()
430         if err != nil {
431                 return err
432         }
433         reply3 := replymsg.(*interfaces.SwInterfaceSetFlagsReply)
434         if err = api.RetvalToVPPApiError(reply3.Retval); err != nil {
435                 return err
436         }
437
438         return nil
439 }
440
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())
446         cmd.Run()
447 }
448
449 func (vpp *VppInstance) disconnect() {
450         vpp.connection.Disconnect()
451         vpp.apiStream.Close()
452 }
453
454 func (vpp *VppInstance) generateCpuConfig() string {
455         var c Stanza
456         var s string
457         if len(vpp.cpus) < 1 {
458                 return ""
459         }
460         c.newStanza("cpu").
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:]
464
465         if len(workers) > 0 {
466                 for i := 0; i < len(workers); i++ {
467                         if i != 0 {
468                                 s = s + ", "
469                         }
470                         s = s + fmt.Sprintf("%d", workers[i])
471                 }
472                 c.append(fmt.Sprintf("corelist-workers %s", s))
473                 vpp.getSuite().log("corelist-workers " + s)
474         }
475         return c.close().toString()
476 }