hs-test: fix install/build on new ubuntu instance
[vpp.git] / extras / hs-test / vppinstance.go
1 package main
2
3 import (
4         "fmt"
5         "github.com/edwarnicke/exechelper"
6         "strings"
7         "time"
8
9         "go.fd.io/govpp"
10         "go.fd.io/govpp/api"
11         "go.fd.io/govpp/binapi/af_packet"
12         interfaces "go.fd.io/govpp/binapi/interface"
13         "go.fd.io/govpp/binapi/interface_types"
14         "go.fd.io/govpp/binapi/session"
15         "go.fd.io/govpp/binapi/tapv2"
16         "go.fd.io/govpp/binapi/vpe"
17         "go.fd.io/govpp/core"
18 )
19
20 const vppConfigTemplate = `unix {
21   nodaemon
22   log %[1]s/var/log/vpp/vpp.log
23   full-coredump
24   cli-listen %[1]s%[2]s
25   runtime-dir %[1]s/var/run
26   gid vpp
27 }
28
29 api-trace {
30   on
31 }
32
33 api-segment {
34   gid vpp
35 }
36
37 socksvr {
38   socket-name %[1]s%[3]s
39 }
40
41 statseg {
42   socket-name %[1]s/var/run/vpp/stats.sock
43 }
44
45 plugins {
46   plugin default { disable }
47
48   plugin unittest_plugin.so { enable }
49   plugin quic_plugin.so { enable }
50   plugin af_packet_plugin.so { enable }
51   plugin hs_apps_plugin.so { enable }
52   plugin http_plugin.so { enable }
53 }
54
55 `
56
57 const (
58         defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
59         defaultApiSocketFilePath = "/var/run/vpp/api.sock"
60 )
61
62 type VppInstance struct {
63         container        *Container
64         additionalConfig Stanza
65         connection       *core.Connection
66         apiChannel       api.Channel
67 }
68
69 func (vpp *VppInstance) Suite() *HstSuite {
70         return vpp.container.suite
71 }
72
73 func (vpp *VppInstance) getCliSocket() string {
74         return fmt.Sprintf("%s%s", vpp.container.GetContainerWorkDir(), defaultCliSocketFilePath)
75 }
76
77 func (vpp *VppInstance) getRunDir() string {
78         return vpp.container.GetContainerWorkDir() + "/var/run/vpp"
79 }
80
81 func (vpp *VppInstance) getLogDir() string {
82         return vpp.container.GetContainerWorkDir() + "/var/log/vpp"
83 }
84
85 func (vpp *VppInstance) getEtcDir() string {
86         return vpp.container.GetContainerWorkDir() + "/etc/vpp"
87 }
88
89 func (vpp *VppInstance) start() error {
90         // Create folders
91         containerWorkDir := vpp.container.GetContainerWorkDir()
92
93         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
94         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
95         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
96
97         // Create startup.conf inside the container
98         configContent := fmt.Sprintf(
99                 vppConfigTemplate,
100                 containerWorkDir,
101                 defaultCliSocketFilePath,
102                 defaultApiSocketFilePath,
103         )
104         configContent += vpp.additionalConfig.ToString()
105         startupFileName := vpp.getEtcDir() + "/startup.conf"
106         vpp.container.createFile(startupFileName, configContent)
107
108         // Start VPP
109         vpp.container.execServer("vpp -c " + startupFileName)
110
111         // Connect to VPP and store the connection
112         sockAddress := vpp.container.GetHostWorkDir() + defaultApiSocketFilePath
113         conn, connEv, err := govpp.AsyncConnect(
114                 sockAddress,
115                 core.DefaultMaxReconnectAttempts,
116                 core.DefaultReconnectInterval)
117         if err != nil {
118                 fmt.Println("async connect error: ", err)
119         }
120         vpp.connection = conn
121
122         // ... wait for Connected event
123         e := <-connEv
124         if e.State != core.Connected {
125                 fmt.Println("connecting to VPP failed: ", e.Error)
126         }
127
128         // ... check compatibility of used messages
129         ch, err := conn.NewAPIChannel()
130         if err != nil {
131                 fmt.Println("creating channel failed: ", err)
132         }
133         if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
134                 fmt.Println("compatibility error: ", err)
135         }
136         if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
137                 fmt.Println("compatibility error: ", err)
138         }
139         vpp.apiChannel = ch
140
141         return nil
142 }
143
144 func (vpp *VppInstance) vppctl(command string, arguments ...any) string {
145         vppCliCommand := fmt.Sprintf(command, arguments...)
146         containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
147                 vpp.container.name, vpp.getCliSocket(), vppCliCommand)
148         vpp.Suite().log(containerExecCommand)
149         output, err := exechelper.CombinedOutput(containerExecCommand)
150         vpp.Suite().assertNil(err)
151
152         return string(output)
153 }
154
155 func (vpp *VppInstance) waitForApp(appName string, timeout int) error {
156         for i := 0; i < timeout; i++ {
157                 o := vpp.vppctl("show app")
158                 if strings.Contains(o, appName) {
159                         return nil
160                 }
161                 time.Sleep(1 * time.Second)
162         }
163         return fmt.Errorf("timeout while waiting for app '%s'", appName)
164 }
165
166 func (vpp *VppInstance) createAfPacket(
167         netInterface NetInterface,
168 ) (interface_types.InterfaceIndex, error) {
169         veth := netInterface.(*NetworkInterfaceVeth)
170
171         createReq := &af_packet.AfPacketCreateV2{
172                 UseRandomHwAddr: true,
173                 HostIfName:      veth.Name(),
174         }
175         if veth.HwAddress() != (MacAddress{}) {
176                 createReq.UseRandomHwAddr = false
177                 createReq.HwAddr = veth.HwAddress()
178         }
179         createReply := &af_packet.AfPacketCreateV2Reply{}
180
181         if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
182                 return 0, err
183         }
184         veth.SetIndex(createReply.SwIfIndex)
185
186         // Set to up
187         upReq := &interfaces.SwInterfaceSetFlags{
188                 SwIfIndex: veth.Index(),
189                 Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
190         }
191         upReply := &interfaces.SwInterfaceSetFlagsReply{}
192
193         if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
194                 return 0, err
195         }
196
197         // Add address
198         if veth.AddressWithPrefix() == (AddressWithPrefix{}) {
199                 var err error
200                 var ip4Address string
201                 if ip4Address, err = veth.addresser.NewIp4Address(veth.peerNetworkNumber); err == nil {
202                         veth.SetAddress(ip4Address)
203                 } else {
204                         return 0, err
205                 }
206         }
207         addressReq := &interfaces.SwInterfaceAddDelAddress{
208                 IsAdd:     true,
209                 SwIfIndex: veth.Index(),
210                 Prefix:    veth.AddressWithPrefix(),
211         }
212         addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
213
214         if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
215                 return 0, err
216         }
217
218         return veth.Index(), nil
219 }
220
221 func (vpp *VppInstance) addAppNamespace(
222         secret uint64,
223         ifx interface_types.InterfaceIndex,
224         namespaceId string,
225 ) error {
226         req := &session.AppNamespaceAddDelV2{
227                 Secret:      secret,
228                 SwIfIndex:   ifx,
229                 NamespaceID: namespaceId,
230         }
231         reply := &session.AppNamespaceAddDelV2Reply{}
232
233         if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
234                 return err
235         }
236
237         sessionReq := &session.SessionEnableDisable{
238                 IsEnable: true,
239         }
240         sessionReply := &session.SessionEnableDisableReply{}
241
242         if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
243                 return err
244         }
245
246         return nil
247 }
248
249 func (vpp *VppInstance) createTap(
250         hostInterfaceName string,
251         hostIp4Address IP4AddressWithPrefix,
252         vppIp4Address AddressWithPrefix,
253 ) error {
254         createTapReq := &tapv2.TapCreateV2{
255                 HostIfNameSet:    true,
256                 HostIfName:       hostInterfaceName,
257                 HostIP4PrefixSet: true,
258                 HostIP4Prefix:    hostIp4Address,
259         }
260         createTapReply := &tapv2.TapCreateV2Reply{}
261
262         // Create tap interface
263         if err := vpp.apiChannel.SendRequest(createTapReq).ReceiveReply(createTapReply); err != nil {
264                 return err
265         }
266
267         // Add address
268         addAddressReq := &interfaces.SwInterfaceAddDelAddress{
269                 IsAdd:     true,
270                 SwIfIndex: createTapReply.SwIfIndex,
271                 Prefix:    vppIp4Address,
272         }
273         addAddressReply := &interfaces.SwInterfaceAddDelAddressReply{}
274
275         if err := vpp.apiChannel.SendRequest(addAddressReq).ReceiveReply(addAddressReply); err != nil {
276                 return err
277         }
278
279         // Set interface to up
280         upReq := &interfaces.SwInterfaceSetFlags{
281                 SwIfIndex: createTapReply.SwIfIndex,
282                 Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
283         }
284         upReply := &interfaces.SwInterfaceSetFlagsReply{}
285
286         if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
287                 return err
288         }
289
290         return nil
291 }
292
293 func (vpp *VppInstance) disconnect() {
294         vpp.connection.Disconnect()
295         vpp.apiChannel.Close()
296 }