X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=extras%2Fhs-test%2Fhst_suite.go;h=7f93b15f50a52cf67466a35c36b731c1957ea7b2;hb=refs%2Fchanges%2F53%2F40253%2F2;hp=286536da3058e7ac7caea9f2ceba42f2a9bb73bf;hpb=7550dd268f80334cbb9127feefe35319b9c7e572;p=vpp.git diff --git a/extras/hs-test/hst_suite.go b/extras/hs-test/hst_suite.go index 286536da305..7f93b15f50a 100644 --- a/extras/hs-test/hst_suite.go +++ b/extras/hs-test/hst_suite.go @@ -1,9 +1,13 @@ package main import ( + "errors" + "flag" "fmt" - "io/ioutil" "os" + "os/exec" + "strings" + "time" "github.com/edwarnicke/exechelper" "github.com/stretchr/testify/assert" @@ -12,24 +16,47 @@ import ( ) const ( - defaultNamespaceName string = "default" + DEFAULT_NETWORK_NUM int = 1 ) -func IsPersistent() bool { - return os.Getenv("HST_PERSIST") == "1" +var isPersistent = flag.Bool("persist", false, "persists topology config") +var isVerbose = flag.Bool("verbose", false, "verbose test output") +var isUnconfiguring = flag.Bool("unconfigure", false, "remove topology") +var isVppDebug = flag.Bool("debug", false, "attach gdb to vpp") +var nConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp") +var vppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory") + +type HstSuite struct { + suite.Suite + containers map[string]*Container + volumes []string + netConfigs []NetConfig + netInterfaces map[string]*NetInterface + ip4AddrAllocator *Ip4AddressAllocator + testIds map[string]string + cpuAllocator *CpuAllocatorT + cpuContexts []*CpuContext + cpuPerVpp int } -func IsVerbose() bool { - return os.Getenv("HST_VERBOSE") == "1" +func (s *HstSuite) SetupSuite() { + var err error + s.cpuAllocator, err = CpuAllocator() + if err != nil { + s.FailNow("failed to init cpu allocator: %v", err) + } + s.cpuPerVpp = *nConfiguredCpus } -type HstSuite struct { - suite.Suite - containers map[string]*Container - volumes []string - netConfigs []NetConfig - netInterfaces map[string]NetInterface - addresser *Addresser +func (s *HstSuite) AllocateCpus() []int { + cpuCtx, err := s.cpuAllocator.Allocate(s.cpuPerVpp) + s.assertNil(err) + s.AddCpuContext(cpuCtx) + return cpuCtx.cpus +} + +func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) { + s.cpuContexts = append(s.cpuContexts, cpuCtx) } func (s *HstSuite) TearDownSuite() { @@ -37,19 +64,29 @@ func (s *HstSuite) TearDownSuite() { } func (s *HstSuite) TearDownTest() { - if IsPersistent() { + if *isPersistent { return } - s.ResetContainers() - s.RemoveVolumes() + for _, c := range s.cpuContexts { + c.Release() + } + s.resetContainers() + s.removeVolumes() +} + +func (s *HstSuite) skipIfUnconfiguring() { + if *isUnconfiguring { + s.skip("skipping to unconfigure") + } } func (s *HstSuite) SetupTest() { - s.SetupVolumes() - s.SetupContainers() + s.skipIfUnconfiguring() + s.setupVolumes() + s.setupContainers() } -func (s *HstSuite) SetupVolumes() { +func (s *HstSuite) setupVolumes() { for _, volume := range s.volumes { cmd := "docker volume create --name=" + volume s.log(cmd) @@ -57,9 +94,9 @@ func (s *HstSuite) SetupVolumes() { } } -func (s *HstSuite) SetupContainers() { +func (s *HstSuite) setupContainers() { for _, container := range s.containers { - if container.isOptional == false { + if !container.isOptional { container.run() } } @@ -105,8 +142,15 @@ func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArg } } +func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) { + if !assert.NotEmpty(s.T(), object, msgAndArgs...) { + s.hstFail() + } +} + func (s *HstSuite) log(args ...any) { - if IsVerbose() { + if *isVerbose { + s.T().Helper() s.T().Log(args...) } } @@ -116,13 +160,33 @@ func (s *HstSuite) skip(args ...any) { s.T().SkipNow() } -func (s *HstSuite) ResetContainers() { +func (s *HstSuite) SkipIfMultiWorker(args ...any) { + if *nConfiguredCpus > 1 { + s.skip("test case not supported with multiple vpp workers") + } +} + +func (s *HstSuite) SkipUnlessExtendedTestsBuilt() { + imageName := "hs-test/nginx-http3" + + cmd := exec.Command("docker", "images", imageName) + byteOutput, err := cmd.CombinedOutput() + if err != nil { + s.log("error while searching for docker image") + return + } + if !strings.Contains(string(byteOutput), imageName) { + s.skip("extended tests not built") + } +} + +func (s *HstSuite) resetContainers() { for _, container := range s.containers { container.stop() } } -func (s *HstSuite) RemoveVolumes() { +func (s *HstSuite) removeVolumes() { for _, volumeName := range s.volumes { cmd := "docker volume rm " + volumeName exechelper.Run(cmd) @@ -144,7 +208,7 @@ func (s *HstSuite) getTransientContainerByName(name string) *Container { } func (s *HstSuite) loadContainerTopology(topologyName string) { - data, err := ioutil.ReadFile(ContainerTopologyDir + topologyName + ".yaml") + data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml") if err != nil { s.T().Fatalf("read error: %v", err) } @@ -157,23 +221,24 @@ func (s *HstSuite) loadContainerTopology(topologyName string) { for _, elem := range yamlTopo.Volumes { volumeMap := elem["volume"].(VolumeConfig) hostDir := volumeMap["host-dir"].(string) + workingVolumeDir := logDir + s.T().Name() + volumeDir + volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir) + hostDir = volDirReplacer.Replace(hostDir) s.volumes = append(s.volumes, hostDir) } s.containers = make(map[string]*Container) for _, elem := range yamlTopo.Containers { - newContainer, err := NewContainer(elem) - newContainer.suite = s + newContainer, err := newContainer(s, elem) if err != nil { s.T().Fatalf("container config error: %v", err) } - s.log(newContainer.getRunCommand()) s.containers[newContainer.name] = newContainer } } func (s *HstSuite) loadNetworkTopology(topologyName string) { - data, err := ioutil.ReadFile(NetworkTopologyDir + topologyName + ".yaml") + data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml") if err != nil { s.T().Fatalf("read error: %v", err) } @@ -183,39 +248,30 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) { s.T().Fatalf("unmarshal error: %v", err) } - s.addresser = NewAddresser(s) - s.netInterfaces = make(map[string]NetInterface) + s.ip4AddrAllocator = NewIp4AddressAllocator() + s.netInterfaces = make(map[string]*NetInterface) for _, elem := range yamlTopo.Devices { switch elem["type"].(string) { case NetNs: { - if namespace, err := NewNetNamespace(elem); err == nil { + if namespace, err := newNetNamespace(elem); err == nil { s.netConfigs = append(s.netConfigs, &namespace) } else { s.T().Fatalf("network config error: %v", err) } } - case Veth: - { - if veth, err := NewVeth(elem, s.addresser); err == nil { - s.netConfigs = append(s.netConfigs, &veth) - s.netInterfaces[veth.Name()] = &veth - } else { - s.T().Fatalf("network config error: %v", err) - } - } - case Tap: + case Veth, Tap: { - if tap, err := NewTap(elem, s.addresser); err == nil { - s.netConfigs = append(s.netConfigs, &tap) - s.netInterfaces[tap.Name()] = &tap + if netIf, err := newNetworkInterface(elem, s.ip4AddrAllocator); err == nil { + s.netConfigs = append(s.netConfigs, netIf) + s.netInterfaces[netIf.Name()] = netIf } else { s.T().Fatalf("network config error: %v", err) } } case Bridge: { - if bridge, err := NewBridge(elem); err == nil { + if bridge, err := newBridge(elem); err == nil { s.netConfigs = append(s.netConfigs, &bridge) } else { s.T().Fatalf("network config error: %v", err) @@ -228,63 +284,114 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) { func (s *HstSuite) configureNetworkTopology(topologyName string) { s.loadNetworkTopology(topologyName) + if *isUnconfiguring { + return + } + for _, nc := range s.netConfigs { - if err := nc.Configure(); err != nil { + if err := nc.configure(); err != nil { s.T().Fatalf("network config error: %v", err) } } } func (s *HstSuite) unconfigureNetworkTopology() { - if IsPersistent() { + if *isPersistent { return } for _, nc := range s.netConfigs { - nc.Unconfigure() + nc.unconfigure() } } -type NamespaceAddresses struct { - namespace string - numberOfAddresses int -} +func (s *HstSuite) getTestId() string { + testName := s.T().Name() -type Addresser struct { - namespaces []*NamespaceAddresses - suite *HstSuite -} + if s.testIds == nil { + s.testIds = map[string]string{} + } -func (a *Addresser) AddNamespace(name string) { - var newNamespace = &NamespaceAddresses{ - namespace: name, - numberOfAddresses: 0, + if _, ok := s.testIds[testName]; !ok { + s.testIds[testName] = time.Now().Format("2006-01-02_15-04-05") } - a.namespaces = append(a.namespaces, newNamespace) + + return s.testIds[testName] } -func (a *Addresser) NewIp4Address() (string, error) { - return a.NewIp4AddressWithNamespace(defaultNamespaceName) +func (s *HstSuite) startServerApp(running chan error, done chan struct{}, env []string) { + cmd := exec.Command("iperf3", "-4", "-s") + if env != nil { + cmd.Env = env + } + s.log(cmd) + err := cmd.Start() + if err != nil { + msg := fmt.Errorf("failed to start iperf server: %v", err) + running <- msg + return + } + running <- nil + <-done + cmd.Process.Kill() } -func (a *Addresser) NewIp4AddressWithNamespace(namespace string) (string, error) { - for i, val := range a.namespaces { - if val.namespace != namespace { - continue +func (s *HstSuite) startClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) { + defer func() { + clnCh <- nil + }() + + nTries := 0 + + for { + cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g") + if env != nil { + cmd.Env = env } - if val.numberOfAddresses == 255 { - return "", fmt.Errorf("no available IPv4 addresses") + s.log(cmd) + o, err := cmd.CombinedOutput() + if err != nil { + if nTries > 5 { + clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o) + return + } + time.Sleep(1 * time.Second) + nTries++ + continue + } else { + clnRes <- fmt.Sprintf("Client output: %s", o) } - address := fmt.Sprintf("10.10.%v.%v/24", i, val.numberOfAddresses+1) - val.numberOfAddresses++ - return address, nil + break } - a.AddNamespace(namespace) - return a.NewIp4AddressWithNamespace(namespace) } -func NewAddresser(suite *HstSuite) *Addresser { - var addresser = new(Addresser) - addresser.suite = suite - addresser.AddNamespace(defaultNamespaceName) - return addresser +func (s *HstSuite) startHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) { + cmd := newCommand([]string{"./http_server", addressPort}, netNs) + err := cmd.Start() + s.log(cmd) + if err != nil { + fmt.Println("Failed to start http server") + return + } + running <- struct{}{} + <-done + cmd.Process.Kill() +} + +func (s *HstSuite) startWget(finished chan error, server_ip, port, query, netNs string) { + defer func() { + finished <- errors.New("wget error") + }() + + cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query}, + netNs) + s.log(cmd) + o, err := cmd.CombinedOutput() + if err != nil { + finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o) + return + } else if !strings.Contains(string(o), "200 OK") { + finished <- fmt.Errorf("wget error: response not 200 OK") + return + } + finished <- nil }