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