blob: f0ebdca5919f5268dc5a1c0a91aef217c32ff5d6 [file] [log] [blame]
Maros Ondrejickaffa3f602023-01-26 10:07:29 +01001package main
2
3import (
Filip Tehlar4b3598e2023-09-02 08:54:21 +02004 "errors"
Filip Tehlar671cf512023-01-31 10:34:18 +01005 "flag"
Filip Tehlar4b3598e2023-09-02 08:54:21 +02006 "fmt"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +01007 "io/ioutil"
8 "os"
Filip Tehlar31eaea92023-06-15 10:06:57 +02009 "os/exec"
10 "strings"
Maros Ondrejickaa2d52622023-02-24 11:26:39 +010011 "time"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010012
13 "github.com/edwarnicke/exechelper"
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/suite"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010016 "gopkg.in/yaml.v3"
17)
18
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +010019const (
Filip Tehlar608d0062023-04-28 10:29:47 +020020 DEFAULT_NETWORK_NUM int = 1
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +010021)
22
Maros Ondrejickae7625d02023-02-28 16:55:01 +010023var isPersistent = flag.Bool("persist", false, "persists topology config")
24var isVerbose = flag.Bool("verbose", false, "verbose test output")
25var isUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
26var isVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
Filip Tehlar608d0062023-04-28 10:29:47 +020027var nConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
Filip Tehlar109f3ce2023-09-05 15:36:28 +020028var vppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010029
30type HstSuite struct {
31 suite.Suite
Filip Tehlar3a910ab2023-06-08 17:39:39 +020032 containers map[string]*Container
33 volumes []string
34 netConfigs []NetConfig
35 netInterfaces map[string]*NetInterface
36 ip4AddrAllocator *Ip4AddressAllocator
37 testIds map[string]string
38 cpuAllocator *CpuAllocatorT
39 cpuContexts []*CpuContext
40 cpuPerVpp int
Filip Tehlar608d0062023-04-28 10:29:47 +020041}
42
43func (s *HstSuite) SetupSuite() {
44 var err error
45 s.cpuAllocator, err = CpuAllocator()
46 if err != nil {
47 s.FailNow("failed to init cpu allocator: %v", err)
48 }
49 s.cpuPerVpp = *nConfiguredCpus
50}
51
52func (s *HstSuite) AllocateCpus() []int {
53 cpuCtx, err := s.cpuAllocator.Allocate(s.cpuPerVpp)
54 s.assertNil(err)
55 s.AddCpuContext(cpuCtx)
56 return cpuCtx.cpus
57}
58
59func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
60 s.cpuContexts = append(s.cpuContexts, cpuCtx)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010061}
62
63func (s *HstSuite) TearDownSuite() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010064 s.unconfigureNetworkTopology()
65}
66
67func (s *HstSuite) TearDownTest() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +010068 if *isPersistent {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010069 return
70 }
Filip Tehlar608d0062023-04-28 10:29:47 +020071 for _, c := range s.cpuContexts {
72 c.Release()
73 }
Maros Ondrejickae7625d02023-02-28 16:55:01 +010074 s.resetContainers()
75 s.removeVolumes()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010076}
77
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010078func (s *HstSuite) skipIfUnconfiguring() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +010079 if *isUnconfiguring {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010080 s.skip("skipping to unconfigure")
81 }
82}
83
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010084func (s *HstSuite) SetupTest() {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010085 s.skipIfUnconfiguring()
Maros Ondrejickae7625d02023-02-28 16:55:01 +010086 s.setupVolumes()
87 s.setupContainers()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010088}
89
Maros Ondrejickae7625d02023-02-28 16:55:01 +010090func (s *HstSuite) setupVolumes() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010091 for _, volume := range s.volumes {
92 cmd := "docker volume create --name=" + volume
93 s.log(cmd)
94 exechelper.Run(cmd)
95 }
96}
97
Maros Ondrejickae7625d02023-02-28 16:55:01 +010098func (s *HstSuite) setupContainers() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010099 for _, container := range s.containers {
Filip Tehlar608d0062023-04-28 10:29:47 +0200100 if !container.isOptional {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100101 container.run()
102 }
103 }
104}
105
106func (s *HstSuite) hstFail() {
107 s.T().FailNow()
108}
109
110func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
111 if !assert.Nil(s.T(), object, msgAndArgs...) {
112 s.hstFail()
113 }
114}
115
116func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
117 if !assert.NotNil(s.T(), object, msgAndArgs...) {
118 s.hstFail()
119 }
120}
121
122func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
123 if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
124 s.hstFail()
125 }
126}
127
128func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
129 if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
130 s.hstFail()
131 }
132}
133
134func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
135 if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
136 s.hstFail()
137 }
138}
139
140func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
141 if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
142 s.hstFail()
143 }
144}
145
Maros Ondrejicka2ddb2fd2023-02-15 17:44:46 +0100146func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
147 if !assert.NotEmpty(s.T(), object, msgAndArgs...) {
148 s.hstFail()
149 }
150}
151
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100152func (s *HstSuite) log(args ...any) {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100153 if *isVerbose {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +0100154 s.T().Helper()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100155 s.T().Log(args...)
156 }
157}
158
159func (s *HstSuite) skip(args ...any) {
160 s.log(args...)
161 s.T().SkipNow()
162}
163
Filip Tehlar608d0062023-04-28 10:29:47 +0200164func (s *HstSuite) SkipIfMultiWorker(args ...any) {
165 if *nConfiguredCpus > 1 {
166 s.skip("test case not supported with multiple vpp workers")
167 }
168}
169
Filip Tehlar31eaea92023-06-15 10:06:57 +0200170func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
171 imageName := "hs-test/nginx-http3"
172
173 cmd := exec.Command("docker", "images", imageName)
174 byteOutput, err := cmd.CombinedOutput()
175 if err != nil {
176 s.log("error while searching for docker image")
177 return
178 }
179 if !strings.Contains(string(byteOutput), imageName) {
180 s.skip("extended tests not built")
181 }
182}
183
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100184func (s *HstSuite) resetContainers() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100185 for _, container := range s.containers {
186 container.stop()
187 }
188}
189
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100190func (s *HstSuite) removeVolumes() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100191 for _, volumeName := range s.volumes {
192 cmd := "docker volume rm " + volumeName
193 exechelper.Run(cmd)
194 os.RemoveAll(volumeName)
195 }
196}
197
198func (s *HstSuite) getContainerByName(name string) *Container {
199 return s.containers[name]
200}
201
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100202/*
203 * Create a copy and return its address, so that individial tests which call this
204 * are not able to modify the original container and affect other tests by doing that
205 */
206func (s *HstSuite) getTransientContainerByName(name string) *Container {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100207 containerCopy := *s.containers[name]
208 return &containerCopy
209}
210
211func (s *HstSuite) loadContainerTopology(topologyName string) {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100212 data, err := ioutil.ReadFile(containerTopologyDir + topologyName + ".yaml")
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100213 if err != nil {
214 s.T().Fatalf("read error: %v", err)
215 }
216 var yamlTopo YamlTopology
217 err = yaml.Unmarshal(data, &yamlTopo)
218 if err != nil {
219 s.T().Fatalf("unmarshal error: %v", err)
220 }
221
222 for _, elem := range yamlTopo.Volumes {
223 volumeMap := elem["volume"].(VolumeConfig)
224 hostDir := volumeMap["host-dir"].(string)
225 s.volumes = append(s.volumes, hostDir)
226 }
227
228 s.containers = make(map[string]*Container)
229 for _, elem := range yamlTopo.Containers {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100230 newContainer, err := newContainer(elem)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100231 newContainer.suite = s
232 if err != nil {
233 s.T().Fatalf("container config error: %v", err)
234 }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100235 s.containers[newContainer.name] = newContainer
236 }
237}
238
239func (s *HstSuite) loadNetworkTopology(topologyName string) {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100240 data, err := ioutil.ReadFile(networkTopologyDir + topologyName + ".yaml")
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100241 if err != nil {
242 s.T().Fatalf("read error: %v", err)
243 }
244 var yamlTopo YamlTopology
245 err = yaml.Unmarshal(data, &yamlTopo)
246 if err != nil {
247 s.T().Fatalf("unmarshal error: %v", err)
248 }
249
Filip Tehlar3a910ab2023-06-08 17:39:39 +0200250 s.ip4AddrAllocator = NewIp4AddressAllocator()
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100251 s.netInterfaces = make(map[string]*NetInterface)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100252 for _, elem := range yamlTopo.Devices {
253 switch elem["type"].(string) {
254 case NetNs:
255 {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100256 if namespace, err := newNetNamespace(elem); err == nil {
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100257 s.netConfigs = append(s.netConfigs, &namespace)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100258 } else {
259 s.T().Fatalf("network config error: %v", err)
260 }
261 }
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100262 case Veth, Tap:
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100263 {
Filip Tehlar3a910ab2023-06-08 17:39:39 +0200264 if netIf, err := newNetworkInterface(elem, s.ip4AddrAllocator); err == nil {
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100265 s.netConfigs = append(s.netConfigs, netIf)
266 s.netInterfaces[netIf.Name()] = netIf
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100267 } else {
268 s.T().Fatalf("network config error: %v", err)
269 }
270 }
271 case Bridge:
272 {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100273 if bridge, err := newBridge(elem); err == nil {
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100274 s.netConfigs = append(s.netConfigs, &bridge)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100275 } else {
276 s.T().Fatalf("network config error: %v", err)
277 }
278 }
279 }
280 }
281}
282
283func (s *HstSuite) configureNetworkTopology(topologyName string) {
284 s.loadNetworkTopology(topologyName)
285
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100286 if *isUnconfiguring {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +0100287 return
288 }
289
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100290 for _, nc := range s.netConfigs {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100291 if err := nc.configure(); err != nil {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100292 s.T().Fatalf("network config error: %v", err)
293 }
294 }
295}
296
297func (s *HstSuite) unconfigureNetworkTopology() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100298 if *isPersistent {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100299 return
300 }
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100301 for _, nc := range s.netConfigs {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100302 nc.unconfigure()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100303 }
304}
305
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100306func (s *HstSuite) getTestId() string {
307 testName := s.T().Name()
308
309 if s.testIds == nil {
310 s.testIds = map[string]string{}
311 }
312
313 if _, ok := s.testIds[testName]; !ok {
Filip Tehlar75776f02023-03-24 13:47:45 +0100314 s.testIds[testName] = time.Now().Format("2006-01-02_15-04-05")
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100315 }
316
317 return s.testIds[testName]
318}
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200319
320func (s *HstSuite) startServerApp(running chan error, done chan struct{}, env []string) {
321 cmd := exec.Command("iperf3", "-4", "-s")
322 if env != nil {
323 cmd.Env = env
324 }
325 s.log(cmd)
326 err := cmd.Start()
327 if err != nil {
328 msg := fmt.Errorf("failed to start iperf server: %v", err)
329 running <- msg
330 return
331 }
332 running <- nil
333 <-done
334 cmd.Process.Kill()
335}
336
337func (s *HstSuite) startClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
338 defer func() {
339 clnCh <- nil
340 }()
341
342 nTries := 0
343
344 for {
345 cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g")
346 if env != nil {
347 cmd.Env = env
348 }
349 s.log(cmd)
350 o, err := cmd.CombinedOutput()
351 if err != nil {
352 if nTries > 5 {
353 clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
354 return
355 }
356 time.Sleep(1 * time.Second)
357 nTries++
358 continue
359 } else {
360 clnRes <- fmt.Sprintf("Client output: %s", o)
361 }
362 break
363 }
364}
365
366func (s *HstSuite) startHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
367 cmd := newCommand([]string{"./http_server", addressPort}, netNs)
368 err := cmd.Start()
369 s.log(cmd)
370 if err != nil {
371 fmt.Println("Failed to start http server")
372 return
373 }
374 running <- struct{}{}
375 <-done
376 cmd.Process.Kill()
377}
378
379func (s *HstSuite) startWget(finished chan error, server_ip, port, query, netNs string) {
380 defer func() {
381 finished <- errors.New("wget error")
382 }()
383
384 cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
385 netNs)
386 s.log(cmd)
387 o, err := cmd.CombinedOutput()
388 if err != nil {
389 finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
390 return
391 } else if !strings.Contains(string(o), "200 OK") {
392 finished <- fmt.Errorf("wget error: response not 200 OK")
393 return
394 }
395 finished <- nil
396}