go.ligato.io/vpp-agent/v3@v3.5.0/tests/e2e/e2etest/e2e.go (about)

     1  //  Copyright (c) 2018 Cisco and/or its affiliates.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at:
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package e2etest
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"log"
    23  	"net"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	docker "github.com/fsouza/go-dockerclient"
    31  	"github.com/onsi/gomega"
    32  	"go.ligato.io/cn-infra/v2/logging"
    33  	"google.golang.org/grpc"
    34  	"google.golang.org/protobuf/proto"
    35  
    36  	"go.ligato.io/vpp-agent/v3/client"
    37  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    38  	nslinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls"
    39  	"go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler"
    40  )
    41  
    42  var Debug bool
    43  
    44  const (
    45  	checkPollingInterval = time.Millisecond * 100
    46  	checkTimeout         = time.Second * 6
    47  	logDir               = "/testlogs"
    48  	shareVolumeName      = "share-for-vpp-agent-e2e-tests"
    49  
    50  	DefaultShareDir      = "/test-share"
    51  	DefaultMainAgentName = "agent0"
    52  
    53  	// VPP input nodes for packet tracing (uncomment when needed)
    54  	Tapv2InputNode = "virtio-input"
    55  	// Tapv1InputNode    = "tapcli-rx"
    56  	// AfPacketInputNode = "af-packet-input"
    57  	// MemifInputNode    = "memif-input"
    58  )
    59  
    60  // TestCtx represents data context fur currently running test
    61  type TestCtx struct {
    62  	*gomega.WithT
    63  
    64  	Agent     *Agent // the main agent (first agent in multi-agent test scenario)
    65  	Etcd      *Etcd
    66  	DNSServer *DNSServer
    67  
    68  	DataDir  string
    69  	ShareDir string
    70  
    71  	logWriter io.Writer
    72  	Logger    *log.Logger
    73  
    74  	t      *testing.T
    75  	ctx    context.Context
    76  	cancel context.CancelFunc
    77  
    78  	agents        map[string]*Agent
    79  	dockerClient  *docker.Client
    80  	microservices map[string]*Microservice
    81  	nsCalls       nslinuxcalls.NetworkNamespaceAPI
    82  	vppVersion    string
    83  
    84  	outputBuf *bytes.Buffer
    85  	traceBuf  *bytes.Buffer
    86  }
    87  
    88  // ComponentRuntime represents running instance of test topology component. Different implementation can
    89  // handle test topology components in different environments (docker container, k8s pods, VMs,...)
    90  type ComponentRuntime interface {
    91  	CommandExecutor
    92  
    93  	// Start starts instance of test topology component
    94  	Start(options interface{}) error
    95  
    96  	// Stop stops instance of test topology component
    97  	Stop(options ...interface{}) error
    98  
    99  	// IPAddress provides ip address for connecting to the component
   100  	IPAddress() string
   101  
   102  	// TODO replace PID() this with some namespace handler(that is what it is used for) because this
   103  	//  won't help in certain runtime implementations (non-local runtimes)
   104  
   105  	// PID provides process id of the main process in component
   106  	PID() int
   107  }
   108  
   109  // CommandExecutor gives test topology components the ability to perform (linux) commands
   110  type CommandExecutor interface {
   111  	// ExecCmd executes command inside runtime environment
   112  	ExecCmd(cmd string, args ...string) (stdout, stderr string, err error)
   113  }
   114  
   115  // Pinger gives test topology components the ability to perform pinging (pinging from them to other places)
   116  type Pinger interface {
   117  	CommandExecutor
   118  
   119  	// Ping <destAddress> from inside of the container.
   120  	Ping(destAddress string, opts ...PingOptModifier) error
   121  
   122  	// PingAsCallback can be used to ping repeatedly inside the assertions "Eventually"
   123  	// and "Consistently" from Omega.
   124  	PingAsCallback(destAddress string, opts ...PingOptModifier) func() error
   125  }
   126  
   127  // Diger gives test topology components the ability to perform dig command (DNS-query linux tool)
   128  type Diger interface {
   129  	CommandExecutor
   130  
   131  	// Dig calls linux tool "dig" that query DNS server for domain name (queryDomain) and return records associated
   132  	// of given type (requestedInfo) associated with the domain name.
   133  	Dig(dnsServer net.IP, queryDomain string, requestedInfo DNSRecordType) ([]net.IP, error)
   134  }
   135  
   136  // NewTest creates new TestCtx for given runnin test
   137  func NewTest(t *testing.T) *TestCtx {
   138  	g := gomega.NewWithT(t)
   139  
   140  	g.SetDefaultEventuallyPollingInterval(checkPollingInterval)
   141  	g.SetDefaultEventuallyTimeout(checkTimeout)
   142  
   143  	logging.Debugf("Environ:\n%v", strings.Join(os.Environ(), "\n"))
   144  
   145  	outputBuf := new(bytes.Buffer)
   146  	var logWriter io.Writer
   147  	if Debug {
   148  		logWriter = io.MultiWriter(outputBuf, os.Stderr)
   149  	} else {
   150  		logWriter = outputBuf
   151  	}
   152  
   153  	prefix := fmt.Sprintf("[E2E-TEST::%v] ", t.Name())
   154  	logger := log.New(logWriter, prefix, log.Lshortfile|log.Lmicroseconds)
   155  
   156  	te := &TestCtx{
   157  		WithT:         g,
   158  		t:             t,
   159  		DataDir:       os.Getenv("TESTDATA_DIR"),
   160  		ShareDir:      DefaultShareDir,
   161  		agents:        make(map[string]*Agent),
   162  		microservices: make(map[string]*Microservice),
   163  		nsCalls:       nslinuxcalls.NewSystemHandler(),
   164  		outputBuf:     outputBuf,
   165  		traceBuf:      new(bytes.Buffer),
   166  		logWriter:     logWriter,
   167  		Logger:        logger,
   168  	}
   169  	te.ctx, te.cancel = context.WithCancel(context.Background())
   170  	return te
   171  }
   172  
   173  // Setup setups the testing environment according to options
   174  func Setup(t *testing.T, optMods ...SetupOptModifier) *TestCtx {
   175  	testCtx := NewTest(t)
   176  
   177  	// prepare setup options
   178  	opts := DefaultSetupOpt(testCtx)
   179  	for _, mod := range optMods {
   180  		mod(opts)
   181  	}
   182  
   183  	// connect to the docker daemon
   184  	var err error
   185  	testCtx.dockerClient, err = docker.NewClientFromEnv()
   186  	if err != nil {
   187  		t.Fatalf("failed to get docker client instance from the environment variables: %v", err)
   188  	}
   189  	if Debug {
   190  		t.Logf("Using docker client endpoint: %+v", testCtx.dockerClient.Endpoint())
   191  	}
   192  
   193  	// make sure there are no containers left from the previous run
   194  	removeDanglingAgents(t, testCtx.dockerClient)
   195  	removeDanglingMicroservices(t, testCtx.dockerClient)
   196  
   197  	// if setupE2E fails we need to stop started containers
   198  	defer func() {
   199  		if testCtx.t.Failed() || Debug {
   200  			testCtx.dumpLog()
   201  		}
   202  		if testCtx.t.Failed() {
   203  			if testCtx.Agent != nil {
   204  				if err := testCtx.Agent.Stop(); err != nil {
   205  					t.Logf("failed to stop vpp-agent: %v", err)
   206  				}
   207  			}
   208  			if testCtx.Etcd != nil {
   209  				if err := testCtx.Etcd.Stop(); err != nil {
   210  					t.Logf("failed to stop etcd due to: %v", err)
   211  				}
   212  			}
   213  			if testCtx.DNSServer != nil {
   214  				if err := testCtx.DNSServer.Stop(); err != nil {
   215  					t.Logf("failed to stop DNS server due to: %v", err)
   216  				}
   217  			}
   218  		}
   219  	}()
   220  
   221  	// setup DNS server
   222  	if opts.SetupDNSServer {
   223  		testCtx.DNSServer, err = NewDNSServer(testCtx, opts.DNSOptMods...)
   224  		testCtx.Expect(err).ShouldNot(gomega.HaveOccurred())
   225  	}
   226  
   227  	// setup Etcd
   228  	if opts.SetupEtcd {
   229  		testCtx.Etcd, err = NewEtcd(testCtx, opts.EtcdOptMods...)
   230  		testCtx.Expect(err).ShouldNot(gomega.HaveOccurred())
   231  	}
   232  
   233  	// setup main VPP-Agent
   234  	if opts.SetupAgent {
   235  		testCtx.Agent = testCtx.StartAgent(DefaultMainAgentName, opts.AgentOptMods...)
   236  
   237  		// fill VPP version (this depends on agentctl and that depends on agent to be set up)
   238  		if version, err := testCtx.Agent.ExecVppctl("show version"); err != nil {
   239  			testCtx.t.Fatalf("Retrieving VPP version via vppctl failed: %v", err)
   240  		} else {
   241  			versionParts := strings.SplitN(version, " ", 3)
   242  			if len(versionParts) > 1 {
   243  				testCtx.vppVersion = version
   244  				testCtx.t.Logf("VPP version: %v", testCtx.vppVersion)
   245  			} else {
   246  				testCtx.t.Logf("invalid VPP version: %q", version)
   247  			}
   248  		}
   249  	}
   250  
   251  	return testCtx
   252  }
   253  
   254  // AgentInstanceName provides instance name of VPP-Agent that is created by setup by default. This name is
   255  // used i.e. in ETCD key prefix.
   256  func AgentInstanceName(testCtx *TestCtx) string {
   257  	// TODO API boundaries becomes blurry as tests and support structures are in the same package and there
   258  	// is strong temptation to misuse it and create an unmaintainable dependency mesh -> create different
   259  	// package for test supporting files (setup/teardown/util stuff) and define clear boundaries
   260  	if testCtx.Agent != nil {
   261  		return testCtx.Agent.name
   262  	}
   263  	return DefaultMainAgentName
   264  }
   265  
   266  // Teardown perform test cleanup
   267  func (test *TestCtx) Teardown() {
   268  	if test.t.Failed() || Debug {
   269  		defer test.dumpLog()
   270  		defer test.dumpPacketTrace()
   271  	}
   272  
   273  	if test.cancel != nil {
   274  		test.cancel()
   275  		test.cancel = nil
   276  	}
   277  
   278  	// terminate all agents and close their clients
   279  	for name, agent := range test.agents {
   280  		if err := agent.Stop(); err != nil {
   281  			test.t.Logf("failed to stop agent %s: %v", name, err)
   282  		}
   283  	}
   284  
   285  	// stop all microservices
   286  	for name, ms := range test.microservices {
   287  		if err := ms.Stop(); err != nil {
   288  			test.t.Logf("failed to stop microservice %s: %v", name, err)
   289  		}
   290  	}
   291  
   292  	// terminate etcd
   293  	if test.Etcd != nil {
   294  		if err := test.Etcd.Stop(); err != nil {
   295  			test.t.Logf("failed to stop ETCD: %v", err)
   296  		}
   297  	}
   298  
   299  	// terminate DNS server
   300  	if test.DNSServer != nil {
   301  		if err := test.DNSServer.Stop(); err != nil {
   302  			test.t.Logf("failed to stop DNS server: %v", err)
   303  		}
   304  	}
   305  }
   306  
   307  func (test *TestCtx) dumpLog() {
   308  	if test.outputBuf.Len() == 0 {
   309  		return
   310  	}
   311  	defer test.outputBuf.Reset()
   312  	path := filepath.Join(logDir, fmt.Sprintf("%s_%s_e2e.log", test.VppRelease(), test.t.Name()))
   313  	f, err := os.Create(path)
   314  	if err != nil {
   315  		test.t.Errorf("failed to create test log file: %v", err)
   316  	}
   317  	_, err = f.Write(test.outputBuf.Bytes())
   318  	if err != nil {
   319  		test.t.Errorf("failed to write into test log file: %v", err)
   320  	}
   321  	if err = f.Close(); err != nil {
   322  		test.t.Errorf("failed to close test log file: %v", err)
   323  	}
   324  	if !Debug {
   325  		output := test.outputBuf.String()
   326  		test.t.Logf("OUTPUT:\n------------------\n%s\n------------------\n\n", output)
   327  	}
   328  }
   329  
   330  func (test *TestCtx) dumpPacketTrace() {
   331  	if test.traceBuf.Len() == 0 {
   332  		return
   333  	}
   334  	defer test.traceBuf.Reset()
   335  	path := filepath.Join(logDir, fmt.Sprintf("%s_%s_e2e_packettrace.log", test.VppRelease(), test.t.Name()))
   336  	f, err := os.Create(path)
   337  	if err != nil {
   338  		test.t.Errorf("failed to create packet trace log file: %v", err)
   339  	}
   340  	_, err = f.Write(test.traceBuf.Bytes())
   341  	if err != nil {
   342  		test.t.Errorf("failed to write into packet trace log file: %v", err)
   343  	}
   344  	if err = f.Close(); err != nil {
   345  		test.t.Errorf("failed to close packet trace log file: %v", err)
   346  	}
   347  }
   348  
   349  // VppRelease provides VPP version of VPP in default VPP-Agent test component
   350  func (test *TestCtx) VppRelease() string {
   351  	version := test.vppVersion
   352  	version = strings.TrimPrefix(version, "vpp ")
   353  	if len(version) > 5 {
   354  		return version[1:6]
   355  	}
   356  	return version
   357  }
   358  
   359  // GenericClient provides generic client for communication with default VPP-Agent test component
   360  func (test *TestCtx) GenericClient() client.GenericClient {
   361  	test.t.Helper()
   362  	return test.Agent.GenericClient()
   363  }
   364  
   365  // GRPCConn provides GRPC client connection for communication with default VPP-Agent test component
   366  func (test *TestCtx) GRPCConn() *grpc.ClientConn {
   367  	test.t.Helper()
   368  	return test.Agent.GRPCConn()
   369  }
   370  
   371  // AgentInSync checks if the agent NB config and the SB state (VPP+Linux) are in-sync.
   372  func (test *TestCtx) AgentInSync() bool {
   373  	test.t.Helper()
   374  	return test.Agent.IsInSync()
   375  }
   376  
   377  // ExecCmd executes command in agent and returns stdout, stderr as strings and error.
   378  func (test *TestCtx) ExecCmd(cmd string, args ...string) (stdout, stderr string, err error) {
   379  	test.t.Helper()
   380  	return test.Agent.ExecCmd(cmd, args...)
   381  }
   382  
   383  // ExecVppctl returns output from vppctl for given action and arguments.
   384  func (test *TestCtx) ExecVppctl(action string, args ...string) (string, error) {
   385  	test.t.Helper()
   386  	return test.Agent.ExecVppctl(action, args...)
   387  }
   388  
   389  // StartMicroservice starts microservice according to given options
   390  func (test *TestCtx) StartMicroservice(name string, optMods ...MicroserviceOptModifier) *Microservice {
   391  	test.t.Helper()
   392  
   393  	if _, ok := test.microservices[name]; ok {
   394  		test.t.Fatalf("microservice %s already started", name)
   395  	}
   396  	ms, err := NewMicroservice(test, name, test.nsCalls, optMods...)
   397  	if err != nil {
   398  		test.t.Fatalf("creating microservice %s failed: %v", name, err)
   399  	}
   400  	test.microservices[name] = ms
   401  	return ms
   402  }
   403  
   404  // StopMicroservice stops microservice with given name
   405  func (test *TestCtx) StopMicroservice(name string) {
   406  	test.t.Helper()
   407  
   408  	ms, found := test.microservices[name]
   409  	if !found {
   410  		// bug inside a test
   411  		test.t.Logf("ERROR: cannot stop unknown microservice %s", name)
   412  	}
   413  	if err := ms.Stop(); err != nil {
   414  		test.t.Logf("ERROR: stopping microservice %s failed: %v", name, err)
   415  	}
   416  	delete(test.microservices, name)
   417  }
   418  
   419  // StartAgent starts new VPP-Agent with given name and according to options
   420  func (test *TestCtx) StartAgent(name string, optMods ...AgentOptModifier) *Agent {
   421  	test.t.Helper()
   422  
   423  	if _, ok := test.agents[name]; ok {
   424  		test.t.Fatalf("agent %s already started", name)
   425  	}
   426  	agent, err := NewAgent(test, name, optMods...)
   427  	if err != nil {
   428  		test.t.Fatalf("creating agent %s failed: %v", name, err)
   429  	}
   430  	if test.Agent == nil {
   431  		test.Agent = agent
   432  	}
   433  	test.agents[name] = agent
   434  	return agent
   435  }
   436  
   437  // StopAgent stops VPP-Agent with given name
   438  func (test *TestCtx) StopAgent(name string) {
   439  	test.t.Helper()
   440  
   441  	agent, found := test.agents[name]
   442  	if !found {
   443  		// bug inside a test
   444  		test.t.Logf("ERROR: cannot stop unknown agent %s", name)
   445  	}
   446  	if err := agent.Stop(); err != nil {
   447  		test.t.Logf("ERROR: stopping agent %s failed: %v", name, err)
   448  	}
   449  	if test.Agent.name == name {
   450  		test.Agent = nil
   451  	}
   452  	delete(test.agents, name)
   453  }
   454  
   455  // GetRunningMicroservice retrieves already running microservice by its name.
   456  func (test *TestCtx) GetRunningMicroservice(msName string) *Microservice {
   457  	test.t.Helper()
   458  	ms, found := test.microservices[msName]
   459  	if !found {
   460  		// bug inside a test
   461  		test.t.Fatalf("cannot ping from unknown microservice '%s'", msName)
   462  	}
   463  	return ms
   464  }
   465  
   466  // PingFromMs pings <dstAddress> from the microservice <msName>
   467  // Deprecated: use ctx.AlreadyRunningMicroservice(msName).Ping(dstAddress, opts...) instead (or
   468  // ms := ctx.StartMicroservice; ms.Ping(dstAddress, opts...))
   469  func (test *TestCtx) PingFromMs(msName, dstAddress string, opts ...PingOptModifier) error {
   470  	test.t.Helper()
   471  	return test.GetRunningMicroservice(msName).Ping(dstAddress, opts...)
   472  }
   473  
   474  // PingFromMsClb can be used to ping repeatedly inside the assertions "Eventually"
   475  // and "Consistently" from Omega.
   476  // Deprecated: use ctx.AlreadyRunningMicroservice(msName).PingAsCallback(dstAddress, opts...) instead (or
   477  // ms := ctx.StartMicroservice; ms.PingAsCallback(dstAddress, opts...))
   478  func (test *TestCtx) PingFromMsClb(msName, dstAddress string, opts ...PingOptModifier) func() error {
   479  	test.t.Helper()
   480  	return test.GetRunningMicroservice(msName).PingAsCallback(dstAddress, opts...)
   481  }
   482  
   483  // PingFromVPP pings <dstAddress> from inside the VPP.
   484  func (test *TestCtx) PingFromVPP(destAddress string) error {
   485  	test.t.Helper()
   486  	return test.Agent.PingFromVPP(destAddress)
   487  }
   488  
   489  // PingFromVPPClb can be used to ping repeatedly inside the assertions "Eventually"
   490  // and "Consistently" from Omega.
   491  func (test *TestCtx) PingFromVPPClb(destAddress string) func() error {
   492  	test.t.Helper()
   493  	return test.Agent.PingFromVPPAsCallback(destAddress)
   494  }
   495  
   496  // TestConnection starts a simple TCP or UPD server and client, sends some data
   497  // and stops the client and server.
   498  //
   499  // If upd is true and there was no prior traffic (TPC/UDP/ICMP) between the
   500  // endpoints, ARP glean may happen and TestConnection may fail.
   501  func (test *TestCtx) TestConnection(
   502  	fromMs, toMs, toAddr, listenAddr string,
   503  	toPort, listenPort uint16, udp bool,
   504  	traceVPPNodes ...string,
   505  ) error {
   506  	test.t.Helper()
   507  
   508  	const (
   509  		connTimeout    = 3 * time.Second
   510  		srvExitTimeout = 500 * time.Millisecond
   511  		reqData        = "Hi server!"
   512  		respData       = "Hi client!"
   513  		timeFormat     = "2006-01-02 15:04:05.00000"
   514  	)
   515  
   516  	protocol := "TCP"
   517  	if udp {
   518  		protocol = "UDP"
   519  	}
   520  
   521  	clientMs, found := test.microservices[fromMs]
   522  	if !found {
   523  		// bug inside a test
   524  		test.t.Fatalf("client microservice %q not found", fromMs)
   525  	}
   526  	serverMs, found := test.microservices[toMs]
   527  	if !found {
   528  		// bug inside a test
   529  		test.t.Fatalf("server microservice %q not found", toMs)
   530  	}
   531  
   532  	serverAddr := fmt.Sprintf("%s:%d", listenAddr, listenPort)
   533  	clientAddr := fmt.Sprintf("%s:%d", toAddr, toPort)
   534  
   535  	srvRet := make(chan error, 1)
   536  	srvCtx, cancelSrv := context.WithCancel(context.Background())
   537  	udpSrvReady := make(chan error, 1)
   538  	defer close(udpSrvReady)
   539  	runServer := func() {
   540  		defer close(srvRet)
   541  		if udp {
   542  			simpleUDPServer(srvCtx, serverMs, serverAddr, reqData, respData, srvRet, udpSrvReady, test.Logger)
   543  		} else {
   544  			simpleTCPServer(srvCtx, serverMs, serverAddr, reqData, respData, srvRet, test.Logger)
   545  		}
   546  	}
   547  
   548  	clientRet := make(chan error, 1)
   549  	runClient := func() {
   550  		defer close(clientRet)
   551  		if udp {
   552  			simpleUDPClient(clientMs, clientAddr,
   553  				reqData, respData, connTimeout, clientRet, udpSrvReady, test.Logger)
   554  		} else {
   555  			simpleTCPClient(clientMs, clientAddr,
   556  				reqData, respData, connTimeout, clientRet, test.Logger)
   557  		}
   558  	}
   559  
   560  	stopPacketTrace := test.startPacketTrace(traceVPPNodes...)
   561  	// log info about connection
   562  	info := fmt.Sprintf("%s connection <time=%s, from-ms=%s, dest=%s:%d, to-ms=%s, server=%s:%d>\n",
   563  		protocol, time.Now().Format(timeFormat), fromMs, toAddr, toPort, toMs, listenAddr, listenPort)
   564  	test.Logger.Print(info)
   565  	test.traceBuf.WriteString(info)
   566  
   567  	go runServer()
   568  	go runClient()
   569  	err := <-clientRet
   570  
   571  	// give server some time to exit gracefully, then force it to stop
   572  	var srvErr error
   573  	select {
   574  	case srvErr = <-srvRet:
   575  		// now that the client has read all the data, the server is safe to stop
   576  		// and close the connection
   577  		cancelSrv()
   578  	case <-time.After(srvExitTimeout):
   579  		cancelSrv()
   580  		srvErr = <-srvRet
   581  	}
   582  	// wait until server actually stops
   583  	<-srvRet
   584  
   585  	outcome := "OK"
   586  	if err != nil || srvErr != nil {
   587  		time.Sleep(50 * time.Millisecond) // give other goroutines time to print logs
   588  		err = fmt.Errorf("server: <%v>, client: <%v>", srvErr, err)
   589  		outcome = err.Error()
   590  	}
   591  
   592  	stopPacketTrace()
   593  	// log info about connection
   594  	info = fmt.Sprintf("%s connection <time=%s, from-ms=%s, dest=%s:%d, to-ms=%s, server=%s:%d> => outcome: %s\n",
   595  		protocol, time.Now().Format(timeFormat), fromMs, toAddr, toPort, toMs, listenAddr, listenPort, outcome)
   596  	test.Logger.Print(info)
   597  	test.traceBuf.WriteString(info)
   598  	return err
   599  }
   600  
   601  // NumValues returns number of values found under the given model
   602  func (test *TestCtx) NumValues(value proto.Message, view kvs.View) int {
   603  	test.t.Helper()
   604  	return test.Agent.NumValues(value, view)
   605  }
   606  
   607  // GetValue retrieves value(s) as seen by the given view
   608  func (test *TestCtx) GetValue(value proto.Message, view kvs.View) proto.Message {
   609  	test.t.Helper()
   610  	return test.Agent.GetValue(value, view)
   611  }
   612  
   613  // GetValueMetadata retrieves metadata associated with the given value.
   614  func (test *TestCtx) GetValueMetadata(value proto.Message, view kvs.View) (metadata interface{}) {
   615  	test.t.Helper()
   616  	return test.Agent.GetValueMetadata(value, view)
   617  }
   618  
   619  func (test *TestCtx) GetValueState(value proto.Message) kvscheduler.ValueState {
   620  	test.t.Helper()
   621  	return test.Agent.GetValueState(value)
   622  }
   623  
   624  func (test *TestCtx) GetValueStateByKey(key string) kvscheduler.ValueState {
   625  	test.t.Helper()
   626  	return test.Agent.GetValueStateByKey(key)
   627  }
   628  
   629  func (test *TestCtx) GetDerivedValueState(baseValue proto.Message, derivedKey string) kvscheduler.ValueState {
   630  	test.t.Helper()
   631  	return test.Agent.GetDerivedValueState(baseValue, derivedKey)
   632  }
   633  
   634  // GetValueStateClb can be used to repeatedly check value state inside the assertions
   635  // "Eventually" and "Consistently" from Omega.
   636  func (test *TestCtx) GetValueStateClb(value proto.Message) func() kvscheduler.ValueState {
   637  	return func() kvscheduler.ValueState {
   638  		return test.GetValueState(value)
   639  	}
   640  }
   641  
   642  // GetDerivedValueStateClb can be used to repeatedly check derived value state inside
   643  // the assertions "Eventually" and "Consistently" from Omega.
   644  func (test *TestCtx) GetDerivedValueStateClb(baseValue proto.Message, derivedKey string) func() kvscheduler.ValueState {
   645  	return func() kvscheduler.ValueState {
   646  		return test.GetDerivedValueState(baseValue, derivedKey)
   647  	}
   648  }
   649  
   650  func (test *TestCtx) startPacketTrace(nodes ...string) (stopTrace func()) {
   651  	const tracePacketsMax = 100
   652  	for i, node := range nodes {
   653  		if i == 0 {
   654  			_, err := test.Agent.ExecVppctl("clear trace")
   655  			if err != nil {
   656  				test.t.Errorf("Failed to clear the packet trace: %v", err)
   657  			}
   658  		}
   659  		_, err := test.Agent.ExecVppctl("trace add", fmt.Sprintf("%s %d", node, tracePacketsMax))
   660  		if err != nil {
   661  			test.t.Errorf("Failed to add packet trace for node '%s': %v", node, err)
   662  		}
   663  	}
   664  	return func() {
   665  		if len(nodes) == 0 {
   666  			return
   667  		}
   668  		traces, err := test.Agent.ExecVppctl("show trace")
   669  		if err != nil {
   670  			test.t.Errorf("Failed to show packet trace: %v", err)
   671  			return
   672  		}
   673  		_, err = test.traceBuf.WriteString(fmt.Sprintf("Packet trace:\n%s\n", traces))
   674  		if err != nil {
   675  			test.t.Errorf("Failed to write packet trace into buffer: %v", err)
   676  			return
   677  		}
   678  	}
   679  }
   680  
   681  func SupportsLinuxVRF() bool {
   682  	if os.Getenv("GITHUB_WORKFLOW") != "" {
   683  		// Linux VRFs are not enabled by default in the github workflow runners
   684  		// Notes:
   685  		// generally, run this to check system support for VRFs:
   686  		//  	modinfo vrf
   687  		// in the container, you can check if kernel module for VRFs is loaded:
   688  		//  	ls /sys/module/vrf
   689  		// TODO: figure out how to enable support for linux VRFs
   690  		return false
   691  	}
   692  	if os.Getenv("TRAVIS") != "" {
   693  		// Linux VRFs are seemingly not supported on Ubuntu Xenial, which is used in Travis CI to run the tests.
   694  		// TODO: remove once we upgrade to Ubuntu Bionic or newer
   695  		return false
   696  	}
   697  	return true
   698  }