gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/dut/dut.go (about)

     1  // Copyright 2021 The gVisor Authors.
     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  //go:build linux
    16  // +build linux
    17  
    18  // Package dut provides common definitions and utilities to be shared by DUTs.
    19  package dut
    20  
    21  import (
    22  	"bufio"
    23  	"context"
    24  	"encoding/json"
    25  	"flag"
    26  	"fmt"
    27  	"io"
    28  	"os"
    29  	"os/signal"
    30  	"strings"
    31  
    32  	"golang.org/x/sync/errgroup"
    33  	"golang.org/x/sys/unix"
    34  	"gvisor.dev/gvisor/test/packetimpact/testbench"
    35  )
    36  
    37  const (
    38  	// completeFd is used for notifying the parent for the completion of setup.
    39  	completeFd = 3
    40  	// PosixServerPort is the port the posix server should listen on.
    41  	PosixServerPort = 54321
    42  	// CtrlIface is the command switch name for passing name of the control interface.
    43  	CtrlIface = "ctrl_iface"
    44  	// TestIface is the command switch name for passing name of the test interface.
    45  	TestIface = "test_iface"
    46  )
    47  
    48  // Ifaces describe the names of the interfaces on DUT.
    49  type Ifaces struct {
    50  	// Ctrl is the name of the control interface.
    51  	Ctrl string
    52  	// Test is the name of the test interface.
    53  	Test string
    54  }
    55  
    56  // Init puts the current process into the target network namespace, the user of
    57  // this library should call this function in the beginning.
    58  func Init(fs *flag.FlagSet) (Ifaces, error) {
    59  	// The DUT might create child processes, we don't want this fd to leak into
    60  	// those processes as it keeps the pipe open and the testbench will hang
    61  	// waiting for an EOF on the pipe.
    62  	unix.CloseOnExec(completeFd)
    63  	var ifaces Ifaces
    64  	// Parse command line flags that is defined by the caller and us.
    65  	fs.StringVar(&ifaces.Ctrl, CtrlIface, "", "the name of the control interface")
    66  	fs.StringVar(&ifaces.Test, TestIface, "", "the name of the test interface")
    67  	if err := fs.Parse(os.Args[1:]); err != nil {
    68  		return Ifaces{}, err
    69  	}
    70  	return ifaces, nil
    71  }
    72  
    73  // DUT is an interface for different platforms of DUTs.
    74  type DUT interface {
    75  	// Bootstrap starts a DUT and returns the collected DUTInfo and a function
    76  	// for the caller to call to wait for the completion of the DUT.
    77  	Bootstrap(ctx context.Context) (testbench.DUTInfo, func() error, error)
    78  	// Cleanup stops the DUT and cleans up the resources being used.
    79  	Cleanup()
    80  }
    81  
    82  // Run is the provided function that calls dut's Bootstrap and Cleanup
    83  // methods and returns the DUT information to the parent through the pipe.
    84  func Run(dut DUT) error {
    85  	defer dut.Cleanup()
    86  
    87  	// Register for cleanup signals.
    88  	stopSigs := make(chan os.Signal, 1)
    89  	signal.Notify(stopSigs, unix.SIGTERM, unix.SIGINT)
    90  	defer signal.Stop(stopSigs)
    91  
    92  	// Start bootstrapping the DUT.
    93  	g, ctx := errgroup.WithContext(context.Background())
    94  	ctx, cancel := context.WithCancel(ctx)
    95  	defer cancel()
    96  	g.Go(func() error {
    97  		info, waitFn, err := dut.Bootstrap(ctx)
    98  		if err != nil {
    99  			return err
   100  		}
   101  		bytes, err := json.Marshal(info)
   102  		if err != nil {
   103  			return fmt.Errorf("failed to marshal DUT info into json: %w", err)
   104  		}
   105  		// Send the DUT information to the parent through the pipe.
   106  		completeFile := os.NewFile(completeFd, "complete")
   107  		for len(bytes) > 0 {
   108  			n, err := completeFile.Write(bytes)
   109  			if err != nil && err != io.ErrShortWrite {
   110  				return fmt.Errorf("write(%s) = %d, %w", completeFile.Name(), n, err)
   111  			}
   112  			bytes = bytes[n:]
   113  		}
   114  		if err := completeFile.Close(); err != nil {
   115  			return fmt.Errorf("close(%s) = %w", completeFile.Name(), err)
   116  		}
   117  		return waitFn()
   118  	})
   119  
   120  	select {
   121  	case <-ctx.Done():
   122  		// The only reason for our context to be cancelled is the propagation of
   123  		// the cancellation from the errgroup g, which means DUT returned an error
   124  		// so we report it with g.Wait().
   125  		if ctx.Err() == context.Canceled {
   126  			return fmt.Errorf("failed to bootstrap DUT: %w", g.Wait())
   127  		}
   128  		panic(fmt.Sprintf("unknown reason for the context to be cancelled: %s, g.Wait() = %s", ctx.Err(), g.Wait()))
   129  	// An signal occurred, we should exit.
   130  	case <-stopSigs:
   131  		return nil
   132  	}
   133  }
   134  
   135  // WaitForServer waits for a pattern to occur in posix_server's logs.
   136  func WaitForServer(output io.Reader) error {
   137  	// Scanning log lines is not the most robust way, we could pass a file
   138  	// descriptor to signal the event for native/runsc DUTs, however, it is not
   139  	// possible for a fuchsia DUT as it lives inside a qemu instance.
   140  	scanner := bufio.NewScanner(output)
   141  	for scanner.Scan() {
   142  		if text := scanner.Text(); strings.HasPrefix(text, "Server listening on") {
   143  			return nil
   144  		}
   145  	}
   146  	return scanner.Err()
   147  }