hs-test: add nginx+quic test
[vpp.git] / extras / hs-test / hst_suite.go
1 package main
2
3 import (
4         "flag"
5         "io/ioutil"
6         "os"
7         "os/exec"
8         "strings"
9         "time"
10
11         "github.com/edwarnicke/exechelper"
12         "github.com/stretchr/testify/assert"
13         "github.com/stretchr/testify/suite"
14         "gopkg.in/yaml.v3"
15 )
16
17 const (
18         DEFAULT_NETWORK_NUM int = 1
19 )
20
21 var isPersistent = flag.Bool("persist", false, "persists topology config")
22 var isVerbose = flag.Bool("verbose", false, "verbose test output")
23 var isUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
24 var isVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
25 var nConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
26
27 type HstSuite struct {
28         suite.Suite
29         containers       map[string]*Container
30         volumes          []string
31         netConfigs       []NetConfig
32         netInterfaces    map[string]*NetInterface
33         ip4AddrAllocator *Ip4AddressAllocator
34         testIds          map[string]string
35         cpuAllocator     *CpuAllocatorT
36         cpuContexts      []*CpuContext
37         cpuPerVpp        int
38 }
39
40 func (s *HstSuite) SetupSuite() {
41         var err error
42         s.cpuAllocator, err = CpuAllocator()
43         if err != nil {
44                 s.FailNow("failed to init cpu allocator: %v", err)
45         }
46         s.cpuPerVpp = *nConfiguredCpus
47 }
48
49 func (s *HstSuite) AllocateCpus() []int {
50         cpuCtx, err := s.cpuAllocator.Allocate(s.cpuPerVpp)
51         s.assertNil(err)
52         s.AddCpuContext(cpuCtx)
53         return cpuCtx.cpus
54 }
55
56 func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
57         s.cpuContexts = append(s.cpuContexts, cpuCtx)
58 }
59
60 func (s *HstSuite) TearDownSuite() {
61         s.unconfigureNetworkTopology()
62 }
63
64 func (s *HstSuite) TearDownTest() {
65         if *isPersistent {
66                 return
67         }
68         for _, c := range s.cpuContexts {
69                 c.Release()
70         }
71         s.resetContainers()
72         s.removeVolumes()
73 }
74
75 func (s *HstSuite) skipIfUnconfiguring() {
76         if *isUnconfiguring {
77                 s.skip("skipping to unconfigure")
78         }
79 }
80
81 func (s *HstSuite) SetupTest() {
82         s.skipIfUnconfiguring()
83         s.setupVolumes()
84         s.setupContainers()
85 }
86
87 func (s *HstSuite) setupVolumes() {
88         for _, volume := range s.volumes {
89                 cmd := "docker volume create --name=" + volume
90                 s.log(cmd)
91                 exechelper.Run(cmd)
92         }
93 }
94
95 func (s *HstSuite) setupContainers() {
96         for _, container := range s.containers {
97                 if !container.isOptional {
98                         container.run()
99                 }
100         }
101 }
102
103 func (s *HstSuite) hstFail() {
104         s.T().FailNow()
105 }
106
107 func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
108         if !assert.Nil(s.T(), object, msgAndArgs...) {
109                 s.hstFail()
110         }
111 }
112
113 func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
114         if !assert.NotNil(s.T(), object, msgAndArgs...) {
115                 s.hstFail()
116         }
117 }
118
119 func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
120         if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
121                 s.hstFail()
122         }
123 }
124
125 func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
126         if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
127                 s.hstFail()
128         }
129 }
130
131 func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
132         if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
133                 s.hstFail()
134         }
135 }
136
137 func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
138         if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
139                 s.hstFail()
140         }
141 }
142
143 func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
144         if !assert.NotEmpty(s.T(), object, msgAndArgs...) {
145                 s.hstFail()
146         }
147 }
148
149 func (s *HstSuite) log(args ...any) {
150         if *isVerbose {
151                 s.T().Helper()
152                 s.T().Log(args...)
153         }
154 }
155
156 func (s *HstSuite) skip(args ...any) {
157         s.log(args...)
158         s.T().SkipNow()
159 }
160
161 func (s *HstSuite) SkipIfMultiWorker(args ...any) {
162         if *nConfiguredCpus > 1 {
163                 s.skip("test case not supported with multiple vpp workers")
164         }
165 }
166
167 func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
168         imageName := "hs-test/nginx-http3"
169
170         cmd := exec.Command("docker", "images", imageName)
171         byteOutput, err := cmd.CombinedOutput()
172         if err != nil {
173                 s.log("error while searching for docker image")
174                 return
175         }
176         if !strings.Contains(string(byteOutput), imageName) {
177                 s.skip("extended tests not built")
178         }
179 }
180
181 func (s *HstSuite) resetContainers() {
182         for _, container := range s.containers {
183                 container.stop()
184         }
185 }
186
187 func (s *HstSuite) removeVolumes() {
188         for _, volumeName := range s.volumes {
189                 cmd := "docker volume rm " + volumeName
190                 exechelper.Run(cmd)
191                 os.RemoveAll(volumeName)
192         }
193 }
194
195 func (s *HstSuite) getContainerByName(name string) *Container {
196         return s.containers[name]
197 }
198
199 /*
200  * Create a copy and return its address, so that individial tests which call this
201  * are not able to modify the original container and affect other tests by doing that
202  */
203 func (s *HstSuite) getTransientContainerByName(name string) *Container {
204         containerCopy := *s.containers[name]
205         return &containerCopy
206 }
207
208 func (s *HstSuite) loadContainerTopology(topologyName string) {
209         data, err := ioutil.ReadFile(containerTopologyDir + topologyName + ".yaml")
210         if err != nil {
211                 s.T().Fatalf("read error: %v", err)
212         }
213         var yamlTopo YamlTopology
214         err = yaml.Unmarshal(data, &yamlTopo)
215         if err != nil {
216                 s.T().Fatalf("unmarshal error: %v", err)
217         }
218
219         for _, elem := range yamlTopo.Volumes {
220                 volumeMap := elem["volume"].(VolumeConfig)
221                 hostDir := volumeMap["host-dir"].(string)
222                 s.volumes = append(s.volumes, hostDir)
223         }
224
225         s.containers = make(map[string]*Container)
226         for _, elem := range yamlTopo.Containers {
227                 newContainer, err := newContainer(elem)
228                 newContainer.suite = s
229                 if err != nil {
230                         s.T().Fatalf("container config error: %v", err)
231                 }
232                 s.containers[newContainer.name] = newContainer
233         }
234 }
235
236 func (s *HstSuite) loadNetworkTopology(topologyName string) {
237         data, err := ioutil.ReadFile(networkTopologyDir + topologyName + ".yaml")
238         if err != nil {
239                 s.T().Fatalf("read error: %v", err)
240         }
241         var yamlTopo YamlTopology
242         err = yaml.Unmarshal(data, &yamlTopo)
243         if err != nil {
244                 s.T().Fatalf("unmarshal error: %v", err)
245         }
246
247         s.ip4AddrAllocator = NewIp4AddressAllocator()
248         s.netInterfaces = make(map[string]*NetInterface)
249         for _, elem := range yamlTopo.Devices {
250                 switch elem["type"].(string) {
251                 case NetNs:
252                         {
253                                 if namespace, err := newNetNamespace(elem); err == nil {
254                                         s.netConfigs = append(s.netConfigs, &namespace)
255                                 } else {
256                                         s.T().Fatalf("network config error: %v", err)
257                                 }
258                         }
259                 case Veth, Tap:
260                         {
261                                 if netIf, err := newNetworkInterface(elem, s.ip4AddrAllocator); err == nil {
262                                         s.netConfigs = append(s.netConfigs, netIf)
263                                         s.netInterfaces[netIf.Name()] = netIf
264                                 } else {
265                                         s.T().Fatalf("network config error: %v", err)
266                                 }
267                         }
268                 case Bridge:
269                         {
270                                 if bridge, err := newBridge(elem); err == nil {
271                                         s.netConfigs = append(s.netConfigs, &bridge)
272                                 } else {
273                                         s.T().Fatalf("network config error: %v", err)
274                                 }
275                         }
276                 }
277         }
278 }
279
280 func (s *HstSuite) configureNetworkTopology(topologyName string) {
281         s.loadNetworkTopology(topologyName)
282
283         if *isUnconfiguring {
284                 return
285         }
286
287         for _, nc := range s.netConfigs {
288                 if err := nc.configure(); err != nil {
289                         s.T().Fatalf("network config error: %v", err)
290                 }
291         }
292 }
293
294 func (s *HstSuite) unconfigureNetworkTopology() {
295         if *isPersistent {
296                 return
297         }
298         for _, nc := range s.netConfigs {
299                 nc.unconfigure()
300         }
301 }
302
303 func (s *HstSuite) getTestId() string {
304         testName := s.T().Name()
305
306         if s.testIds == nil {
307                 s.testIds = map[string]string{}
308         }
309
310         if _, ok := s.testIds[testName]; !ok {
311                 s.testIds[testName] = time.Now().Format("2006-01-02_15-04-05")
312         }
313
314         return s.testIds[testName]
315 }