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