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 }