hs-test: refactor test cases from no-topo suite
[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/var/run/vpp/api.sock
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         config         *VppConfig
65         actionFuncName string
66         connection     *core.Connection
67         apiChannel     api.Channel
68 }
69
70 type VppConfig struct {
71         Variant           string
72         CliSocketFilePath string
73         additionalConfig  Stanza
74 }
75
76 func (vc *VppConfig) getTemplate() string {
77         return fmt.Sprintf(vppConfigTemplate, "%[1]s", vc.CliSocketFilePath)
78 }
79
80 func (vpp *VppInstance) Suite() *HstSuite {
81         return vpp.container.suite
82 }
83
84 func (vpp *VppInstance) setCliSocket(filePath string) {
85         vpp.config.CliSocketFilePath = filePath
86 }
87
88 func (vpp *VppInstance) getCliSocket() string {
89         return fmt.Sprintf("%s%s", vpp.container.GetContainerWorkDir(), vpp.config.CliSocketFilePath)
90 }
91
92 func (vpp *VppInstance) getRunDir() string {
93         return vpp.container.GetContainerWorkDir() + "/var/run/vpp"
94 }
95
96 func (vpp *VppInstance) getLogDir() string {
97         return vpp.container.GetContainerWorkDir() + "/var/log/vpp"
98 }
99
100 func (vpp *VppInstance) getEtcDir() string {
101         return vpp.container.GetContainerWorkDir() + "/etc/vpp"
102 }
103
104 func (vpp *VppInstance) start() error {
105         // Create folders
106         containerWorkDir := vpp.container.GetContainerWorkDir()
107
108         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
109         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
110         vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
111
112         // Create startup.conf inside the container
113         configContent := fmt.Sprintf(vppConfigTemplate, containerWorkDir, vpp.config.CliSocketFilePath)
114         configContent += vpp.config.additionalConfig.ToString()
115         startupFileName := vpp.getEtcDir() + "/startup.conf"
116         vpp.container.createFile(startupFileName, configContent)
117
118         // Start VPP
119         vpp.container.execServer("vpp -c " + startupFileName)
120
121         // Connect to VPP and store the connection
122         sockAddress := vpp.container.GetHostWorkDir() + defaultApiSocketFilePath
123         conn, connEv, err := govpp.AsyncConnect(
124                 sockAddress,
125                 core.DefaultMaxReconnectAttempts,
126                 core.DefaultReconnectInterval)
127         if err != nil {
128                 fmt.Println("async connect error: ", err)
129         }
130         vpp.connection = conn
131
132         // ... wait for Connected event
133         e := <-connEv
134         if e.State != core.Connected {
135                 fmt.Println("connecting to VPP failed: ", e.Error)
136         }
137
138         // ... check compatibility of used messages
139         ch, err := conn.NewAPIChannel()
140         if err != nil {
141                 fmt.Println("creating channel failed: ", err)
142         }
143         if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
144                 fmt.Println("compatibility error: ", err)
145         }
146         if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
147                 fmt.Println("compatibility error: ", err)
148         }
149         vpp.apiChannel = ch
150
151         return nil
152 }
153
154 func (vpp *VppInstance) vppctl(command string, arguments ...any) string {
155         vppCliCommand := fmt.Sprintf(command, arguments...)
156         containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
157                 vpp.container.name, vpp.getCliSocket(), vppCliCommand)
158         vpp.Suite().log(containerExecCommand)
159         output, err := exechelper.CombinedOutput(containerExecCommand)
160         vpp.Suite().assertNil(err)
161
162         return string(output)
163 }
164
165 func (vpp *VppInstance) waitForApp(appName string, timeout int) error {
166         for i := 0; i < timeout; i++ {
167                 o := vpp.vppctl("show app")
168                 if strings.Contains(o, appName) {
169                         return nil
170                 }
171                 time.Sleep(1 * time.Second)
172         }
173         return fmt.Errorf("Timeout while waiting for app '%s'", appName)
174 }
175
176 func (vpp *VppInstance) createAfPacket(
177         netInterface NetInterface,
178 ) (interface_types.InterfaceIndex, error) {
179         var veth *NetworkInterfaceVeth
180         veth = netInterface.(*NetworkInterfaceVeth)
181
182         createReq := &af_packet.AfPacketCreateV2{
183                 UseRandomHwAddr: true,
184                 HostIfName:      veth.Name(),
185         }
186         if veth.HwAddress() != (MacAddress{}) {
187                 createReq.UseRandomHwAddr = false
188                 createReq.HwAddr = veth.HwAddress()
189         }
190         createReply := &af_packet.AfPacketCreateV2Reply{}
191
192         if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
193                 return 0, err
194         }
195         veth.SetIndex(createReply.SwIfIndex)
196
197         // Set to up
198         upReq := &interfaces.SwInterfaceSetFlags{
199                 SwIfIndex: veth.Index(),
200                 Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
201         }
202         upReply := &interfaces.SwInterfaceSetFlagsReply{}
203
204         if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
205                 return 0, err
206         }
207
208         // Add address
209         if veth.AddressWithPrefix() == (AddressWithPrefix{}) {
210                 var err error
211                 var ip4Address string
212                 if veth.peerNetworkNamespace != "" {
213                         ip4Address, err = veth.addresser.
214                                 NewIp4AddressWithNamespace(veth.peerNetworkNamespace)
215                 } else {
216                         ip4Address, err = veth.addresser.
217                                 NewIp4Address()
218                 }
219                 if err == nil {
220                         veth.SetAddress(ip4Address)
221                 } else {
222                         return 0, err
223                 }
224         }
225         addressReq := &interfaces.SwInterfaceAddDelAddress{
226                 IsAdd:     true,
227                 SwIfIndex: veth.Index(),
228                 Prefix:    veth.AddressWithPrefix(),
229         }
230         addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
231
232         if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
233                 return 0, err
234         }
235
236         return veth.Index(), nil
237 }
238
239 func (vpp *VppInstance) addAppNamespace(
240         secret uint64,
241         ifx interface_types.InterfaceIndex,
242         namespaceId string,
243 ) error {
244         req := &session.AppNamespaceAddDelV2{
245                 Secret:      secret,
246                 SwIfIndex:   ifx,
247                 NamespaceID: namespaceId,
248         }
249         reply := &session.AppNamespaceAddDelV2Reply{}
250
251         if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
252                 return err
253         }
254
255         sessionReq := &session.SessionEnableDisable{
256                 IsEnable: true,
257         }
258         sessionReply := &session.SessionEnableDisableReply{}
259
260         if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
261                 return err
262         }
263
264         return nil
265 }
266
267 func (vpp *VppInstance) createTap(
268         hostInterfaceName string,
269         hostIp4Address IP4AddressWithPrefix,
270         vppIp4Address AddressWithPrefix,
271 ) error {
272         createTapReq := &tapv2.TapCreateV2{
273                 HostIfNameSet:    true,
274                 HostIfName:       hostInterfaceName,
275                 HostIP4PrefixSet: true,
276                 HostIP4Prefix:    hostIp4Address,
277         }
278         createTapReply := &tapv2.TapCreateV2Reply{}
279
280         // Create tap interface
281         if err := vpp.apiChannel.SendRequest(createTapReq).ReceiveReply(createTapReply); err != nil {
282                 return err
283         }
284
285         // Add address
286         addAddressReq := &interfaces.SwInterfaceAddDelAddress{
287                 IsAdd:     true,
288                 SwIfIndex: createTapReply.SwIfIndex,
289                 Prefix:    vppIp4Address,
290         }
291         addAddressReply := &interfaces.SwInterfaceAddDelAddressReply{}
292
293         if err := vpp.apiChannel.SendRequest(addAddressReq).ReceiveReply(addAddressReply); err != nil {
294                 return err
295         }
296
297         // Set interface to up
298         upReq := &interfaces.SwInterfaceSetFlags{
299                 SwIfIndex: createTapReply.SwIfIndex,
300                 Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
301         }
302         upReply := &interfaces.SwInterfaceSetFlagsReply{}
303
304         if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
305                 return err
306         }
307
308         return nil
309 }
310
311 func (vpp *VppInstance) disconnect() {
312         vpp.connection.Disconnect()
313         vpp.apiChannel.Close()
314 }