hs-test: improve test infra
[vpp.git] / extras / hs-test / hst_suite.go
1 package main
2
3 import (
4         "flag"
5         "fmt"
6         "io/ioutil"
7         "os"
8
9         "github.com/edwarnicke/exechelper"
10         "github.com/stretchr/testify/assert"
11         "github.com/stretchr/testify/suite"
12         "gopkg.in/yaml.v3"
13 )
14
15 const (
16         defaultNetworkNumber int = 1
17 )
18
19 var IsPersistent = flag.Bool("persist", false, "persists topology config")
20 var IsVerbose = flag.Bool("verbose", false, "verbose test output")
21
22 type HstSuite struct {
23         suite.Suite
24         containers    map[string]*Container
25         volumes       []string
26         netConfigs    []NetConfig
27         netInterfaces map[string]NetInterface
28         addresser     *Addresser
29 }
30
31 func (s *HstSuite) TearDownSuite() {
32         s.unconfigureNetworkTopology()
33 }
34
35 func (s *HstSuite) TearDownTest() {
36         if *IsPersistent {
37                 return
38         }
39         s.ResetContainers()
40         s.RemoveVolumes()
41 }
42
43 func (s *HstSuite) SetupTest() {
44         s.SetupVolumes()
45         s.SetupContainers()
46 }
47
48 func (s *HstSuite) SetupVolumes() {
49         for _, volume := range s.volumes {
50                 cmd := "docker volume create --name=" + volume
51                 s.log(cmd)
52                 exechelper.Run(cmd)
53         }
54 }
55
56 func (s *HstSuite) SetupContainers() {
57         for _, container := range s.containers {
58                 if container.isOptional == false {
59                         container.run()
60                 }
61         }
62 }
63
64 func (s *HstSuite) hstFail() {
65         s.T().FailNow()
66 }
67
68 func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
69         if !assert.Nil(s.T(), object, msgAndArgs...) {
70                 s.hstFail()
71         }
72 }
73
74 func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
75         if !assert.NotNil(s.T(), object, msgAndArgs...) {
76                 s.hstFail()
77         }
78 }
79
80 func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
81         if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
82                 s.hstFail()
83         }
84 }
85
86 func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
87         if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
88                 s.hstFail()
89         }
90 }
91
92 func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
93         if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
94                 s.hstFail()
95         }
96 }
97
98 func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
99         if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
100                 s.hstFail()
101         }
102 }
103
104 func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
105         if !assert.NotEmpty(s.T(), object, msgAndArgs...) {
106                 s.hstFail()
107         }
108 }
109
110 func (s *HstSuite) log(args ...any) {
111         if *IsVerbose {
112                 s.T().Log(args...)
113         }
114 }
115
116 func (s *HstSuite) skip(args ...any) {
117         s.log(args...)
118         s.T().SkipNow()
119 }
120
121 func (s *HstSuite) ResetContainers() {
122         for _, container := range s.containers {
123                 container.stop()
124         }
125 }
126
127 func (s *HstSuite) RemoveVolumes() {
128         for _, volumeName := range s.volumes {
129                 cmd := "docker volume rm " + volumeName
130                 exechelper.Run(cmd)
131                 os.RemoveAll(volumeName)
132         }
133 }
134
135 func (s *HstSuite) getContainerByName(name string) *Container {
136         return s.containers[name]
137 }
138
139 /*
140  * Create a copy and return its address, so that individial tests which call this
141  * are not able to modify the original container and affect other tests by doing that
142  */
143 func (s *HstSuite) getTransientContainerByName(name string) *Container {
144         containerCopy := *s.containers[name]
145         return &containerCopy
146 }
147
148 func (s *HstSuite) loadContainerTopology(topologyName string) {
149         data, err := ioutil.ReadFile(ContainerTopologyDir + topologyName + ".yaml")
150         if err != nil {
151                 s.T().Fatalf("read error: %v", err)
152         }
153         var yamlTopo YamlTopology
154         err = yaml.Unmarshal(data, &yamlTopo)
155         if err != nil {
156                 s.T().Fatalf("unmarshal error: %v", err)
157         }
158
159         for _, elem := range yamlTopo.Volumes {
160                 volumeMap := elem["volume"].(VolumeConfig)
161                 hostDir := volumeMap["host-dir"].(string)
162                 s.volumes = append(s.volumes, hostDir)
163         }
164
165         s.containers = make(map[string]*Container)
166         for _, elem := range yamlTopo.Containers {
167                 newContainer, err := NewContainer(elem)
168                 newContainer.suite = s
169                 if err != nil {
170                         s.T().Fatalf("container config error: %v", err)
171                 }
172                 s.containers[newContainer.name] = newContainer
173         }
174 }
175
176 func (s *HstSuite) loadNetworkTopology(topologyName string) {
177         data, err := ioutil.ReadFile(NetworkTopologyDir + topologyName + ".yaml")
178         if err != nil {
179                 s.T().Fatalf("read error: %v", err)
180         }
181         var yamlTopo YamlTopology
182         err = yaml.Unmarshal(data, &yamlTopo)
183         if err != nil {
184                 s.T().Fatalf("unmarshal error: %v", err)
185         }
186
187         s.addresser = NewAddresser(s)
188         s.netInterfaces = make(map[string]NetInterface)
189         for _, elem := range yamlTopo.Devices {
190                 switch elem["type"].(string) {
191                 case NetNs:
192                         {
193                                 if namespace, err := NewNetNamespace(elem); err == nil {
194                                         s.netConfigs = append(s.netConfigs, &namespace)
195                                 } else {
196                                         s.T().Fatalf("network config error: %v", err)
197                                 }
198                         }
199                 case Veth:
200                         {
201                                 if veth, err := NewVeth(elem, s.addresser); err == nil {
202                                         s.netConfigs = append(s.netConfigs, &veth)
203                                         s.netInterfaces[veth.Name()] = &veth
204                                 } else {
205                                         s.T().Fatalf("network config error: %v", err)
206                                 }
207                         }
208                 case Tap:
209                         {
210                                 if tap, err := NewTap(elem, s.addresser); err == nil {
211                                         s.netConfigs = append(s.netConfigs, &tap)
212                                         s.netInterfaces[tap.Name()] = &tap
213                                 } else {
214                                         s.T().Fatalf("network config error: %v", err)
215                                 }
216                         }
217                 case Bridge:
218                         {
219                                 if bridge, err := NewBridge(elem); err == nil {
220                                         s.netConfigs = append(s.netConfigs, &bridge)
221                                 } else {
222                                         s.T().Fatalf("network config error: %v", err)
223                                 }
224                         }
225                 }
226         }
227 }
228
229 func (s *HstSuite) configureNetworkTopology(topologyName string) {
230         s.loadNetworkTopology(topologyName)
231
232         for _, nc := range s.netConfigs {
233                 if err := nc.Configure(); err != nil {
234                         s.T().Fatalf("network config error: %v", err)
235                 }
236         }
237 }
238
239 func (s *HstSuite) unconfigureNetworkTopology() {
240         if *IsPersistent {
241                 return
242         }
243         for _, nc := range s.netConfigs {
244                 nc.Unconfigure()
245         }
246 }
247
248 type NetworkAddresses struct {
249         network           int
250         numberOfAddresses int
251 }
252
253 type AddressCounter = int
254
255 type Addresser struct {
256         networks map[int]AddressCounter
257         suite    *HstSuite
258 }
259
260 func (a *Addresser) AddNetwork(networkNumber int) {
261         a.networks[networkNumber] = 1
262 }
263
264 func (a *Addresser) NewIp4Address(inputNetworkNumber ...int) (string, error) {
265         var networkNumber int = 0
266         if len(inputNetworkNumber) > 0 {
267                 networkNumber = inputNetworkNumber[0]
268         }
269
270         if _, ok := a.networks[networkNumber]; !ok {
271                 a.AddNetwork(networkNumber)
272         }
273
274         numberOfAddresses := a.networks[networkNumber]
275
276         if numberOfAddresses == 254 {
277                 return "", fmt.Errorf("no available IPv4 addresses")
278         }
279
280         address := fmt.Sprintf("10.10.%v.%v/24", networkNumber, numberOfAddresses)
281         a.networks[networkNumber] = numberOfAddresses + 1
282
283         return address, nil
284 }
285
286 func NewAddresser(suite *HstSuite) *Addresser {
287         var addresser = new(Addresser)
288         addresser.suite = suite
289         addresser.networks = make(map[int]AddressCounter)
290         addresser.AddNetwork(0)
291         return addresser
292 }