github.com/prysmaticlabs/prysm@v1.4.4/endtoend/helpers/helpers.go (about)

     1  // Package helpers defines helper functions to peer into
     2  // end to end processes and kill processes as needed.
     3  package helpers
     4  
     5  import (
     6  	"bufio"
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	e2e "github.com/prysmaticlabs/prysm/endtoend/params"
    20  	e2etypes "github.com/prysmaticlabs/prysm/endtoend/types"
    21  	eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    22  	"github.com/prysmaticlabs/prysm/shared/params"
    23  	"github.com/prysmaticlabs/prysm/shared/slotutil"
    24  	log "github.com/sirupsen/logrus"
    25  	"golang.org/x/sync/errgroup"
    26  	"google.golang.org/grpc"
    27  )
    28  
    29  const (
    30  	maxPollingWaitTime  = 60 * time.Second // A minute so timing out doesn't take very long.
    31  	filePollingInterval = 500 * time.Millisecond
    32  	memoryHeapFileName  = "node_heap_%d.pb.gz"
    33  	cpuProfileFileName  = "node_cpu_profile_%d.pb.gz"
    34  	fileBufferSize      = 64 * 1024
    35  	maxFileBufferSize   = 1024 * 1024
    36  )
    37  
    38  // Graffiti is a list of sample graffiti strings.
    39  var Graffiti = []string{"Sushi", "Ramen", "Takoyaki"}
    40  
    41  // DeleteAndCreateFile checks if the file path given exists, if it does, it deletes it and creates a new file.
    42  // If not, it just creates the requested file.
    43  func DeleteAndCreateFile(tmpPath, fileName string) (*os.File, error) {
    44  	filePath := path.Join(tmpPath, fileName)
    45  	if _, err := os.Stat(filePath); os.IsExist(err) {
    46  		if err = os.Remove(filePath); err != nil {
    47  			return nil, err
    48  		}
    49  	}
    50  	newFile, err := os.Create(path.Join(tmpPath, fileName))
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	return newFile, nil
    55  }
    56  
    57  // WaitForTextInFile checks a file every polling interval for the text requested.
    58  func WaitForTextInFile(file *os.File, text string) error {
    59  	d := time.Now().Add(maxPollingWaitTime)
    60  	ctx, cancel := context.WithDeadline(context.Background(), d)
    61  	defer cancel()
    62  
    63  	// Use a ticker with a deadline to poll a given file.
    64  	ticker := time.NewTicker(filePollingInterval)
    65  	defer ticker.Stop()
    66  	for {
    67  		select {
    68  		case <-ctx.Done():
    69  			contents, err := ioutil.ReadAll(file)
    70  			if err != nil {
    71  				return err
    72  			}
    73  			return fmt.Errorf("could not find requested text \"%s\" in logs:\n%s", text, contents)
    74  		case <-ticker.C:
    75  			fileScanner := bufio.NewScanner(file)
    76  			buf := make([]byte, 0, fileBufferSize)
    77  			fileScanner.Buffer(buf, maxFileBufferSize)
    78  			for fileScanner.Scan() {
    79  				scanned := fileScanner.Text()
    80  				if strings.Contains(scanned, text) {
    81  					return nil
    82  				}
    83  			}
    84  			if err := fileScanner.Err(); err != nil {
    85  				return err
    86  			}
    87  			_, err := file.Seek(0, io.SeekStart)
    88  			if err != nil {
    89  				return err
    90  			}
    91  		}
    92  	}
    93  }
    94  
    95  // GraffitiYamlFile outputs graffiti YAML file into a testing directory.
    96  func GraffitiYamlFile(testDir string) (string, error) {
    97  	b := []byte(`default: "Rice"
    98  random: 
    99    - "Sushi"
   100    - "Ramen"
   101    - "Takoyaki"
   102  `)
   103  	f := filepath.Join(testDir, "graffiti.yaml")
   104  	if err := ioutil.WriteFile(f, b, os.ModePerm); err != nil {
   105  		return "", err
   106  	}
   107  	return f, nil
   108  }
   109  
   110  // LogOutput logs the output of all log files made.
   111  func LogOutput(t *testing.T, config *e2etypes.E2EConfig) {
   112  	// Log out errors from beacon chain nodes.
   113  	for i := 0; i < e2e.TestParams.BeaconNodeCount; i++ {
   114  		beaconLogFile, err := os.Open(path.Join(e2e.TestParams.LogPath, fmt.Sprintf(e2e.BeaconNodeLogFileName, i)))
   115  		if err != nil {
   116  			t.Fatal(err)
   117  		}
   118  		LogErrorOutput(t, beaconLogFile, "beacon chain node", i)
   119  
   120  		validatorLogFile, err := os.Open(path.Join(e2e.TestParams.LogPath, fmt.Sprintf(e2e.ValidatorLogFileName, i)))
   121  		if err != nil {
   122  			t.Fatal(err)
   123  		}
   124  		LogErrorOutput(t, validatorLogFile, "validator client", i)
   125  
   126  		if config.TestSlasher {
   127  			slasherLogFile, err := os.Open(path.Join(e2e.TestParams.LogPath, fmt.Sprintf(e2e.SlasherLogFileName, i)))
   128  			if err != nil {
   129  				t.Fatal(err)
   130  			}
   131  			LogErrorOutput(t, slasherLogFile, "slasher client", i)
   132  		}
   133  	}
   134  	t.Logf("Ending time: %s\n", time.Now().String())
   135  }
   136  
   137  // LogErrorOutput logs the output of a specific file.
   138  func LogErrorOutput(t *testing.T, file io.Reader, title string, index int) {
   139  	var errorLines []string
   140  
   141  	scanner := bufio.NewScanner(file)
   142  	for scanner.Scan() {
   143  		currentLine := scanner.Text()
   144  		if strings.Contains(currentLine, "level=error") {
   145  			errorLines = append(errorLines, currentLine)
   146  		}
   147  	}
   148  	if len(errorLines) < 1 {
   149  		return
   150  	}
   151  
   152  	t.Logf("==================== Start of %s %d error output ==================\n", title, index)
   153  	for _, err := range errorLines {
   154  		t.Log(err)
   155  	}
   156  }
   157  
   158  // WritePprofFiles writes the memory heap and cpu profile files to the test path.
   159  func WritePprofFiles(testDir string, index int) error {
   160  	url := fmt.Sprintf("http://127.0.0.1:%d/debug/pprof/heap", e2e.TestParams.BeaconNodeRPCPort+50+index)
   161  	filePath := filepath.Join(testDir, fmt.Sprintf(memoryHeapFileName, index))
   162  	if err := writeURLRespAtPath(url, filePath); err != nil {
   163  		return err
   164  	}
   165  	url = fmt.Sprintf("http://127.0.0.1:%d/debug/pprof/profile", e2e.TestParams.BeaconNodeRPCPort+50+index)
   166  	filePath = filepath.Join(testDir, fmt.Sprintf(cpuProfileFileName, index))
   167  	return writeURLRespAtPath(url, filePath)
   168  }
   169  
   170  func writeURLRespAtPath(url, filePath string) error {
   171  	resp, err := http.Get(url)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	defer func() {
   176  		if err = resp.Body.Close(); err != nil {
   177  			return
   178  		}
   179  	}()
   180  
   181  	body, err := ioutil.ReadAll(resp.Body)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	file, err := os.Create(filePath)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	if _, err = file.Write(body); err != nil {
   190  		return err
   191  	}
   192  	return nil
   193  }
   194  
   195  // NewLocalConnection creates and returns GRPC connection on a given localhost port.
   196  func NewLocalConnection(ctx context.Context, port int) (*grpc.ClientConn, error) {
   197  	endpoint := fmt.Sprintf("127.0.0.1:%d", port)
   198  	dialOpts := []grpc.DialOption{
   199  		grpc.WithInsecure(),
   200  	}
   201  	conn, err := grpc.DialContext(ctx, endpoint, dialOpts...)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	return conn, nil
   206  }
   207  
   208  // NewLocalConnections returns number of GRPC connections, along with function to close all of them.
   209  func NewLocalConnections(ctx context.Context, numConns int) ([]*grpc.ClientConn, func(), error) {
   210  	conns := make([]*grpc.ClientConn, numConns)
   211  	for i := 0; i < len(conns); i++ {
   212  		conn, err := NewLocalConnection(ctx, e2e.TestParams.BeaconNodeRPCPort+i)
   213  		if err != nil {
   214  			return nil, nil, err
   215  		}
   216  		conns[i] = conn
   217  	}
   218  	return conns, func() {
   219  		for _, conn := range conns {
   220  			if err := conn.Close(); err != nil {
   221  				log.Error(err)
   222  			}
   223  		}
   224  	}, nil
   225  }
   226  
   227  // ComponentsStarted checks, sequentially, each provided component, blocks until all of the components are ready.
   228  func ComponentsStarted(ctx context.Context, comps []e2etypes.ComponentRunner) error {
   229  	for _, comp := range comps {
   230  		select {
   231  		case <-ctx.Done():
   232  			return ctx.Err()
   233  		case <-comp.Started():
   234  			continue
   235  		}
   236  	}
   237  	return nil
   238  }
   239  
   240  // EpochTickerStartTime calculates the best time to start epoch ticker for a given genesis.
   241  func EpochTickerStartTime(genesis *eth.Genesis) time.Time {
   242  	epochSeconds := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
   243  	epochSecondsHalf := time.Duration(int64(epochSeconds*1000)/2) * time.Millisecond
   244  	// Adding a half slot here to ensure the requests are in the middle of an epoch.
   245  	middleOfEpoch := epochSecondsHalf + slotutil.DivideSlotBy(2 /* half a slot */)
   246  	genesisTime := time.Unix(genesis.GenesisTime.Seconds, 0)
   247  	// Offsetting the ticker from genesis so it ticks in the middle of an epoch, in order to keep results consistent.
   248  	return genesisTime.Add(middleOfEpoch)
   249  }
   250  
   251  // WaitOnNodes waits on nodes to complete execution, accepts function that will be called when all nodes are ready.
   252  func WaitOnNodes(ctx context.Context, nodes []e2etypes.ComponentRunner, nodesStarted func()) error {
   253  	// Start nodes.
   254  	g, ctx := errgroup.WithContext(ctx)
   255  	for _, node := range nodes {
   256  		node := node
   257  		g.Go(func() error {
   258  			return node.Start(ctx)
   259  		})
   260  	}
   261  
   262  	// Mark set as ready (happens when all contained nodes report as started).
   263  	go func() {
   264  		for _, node := range nodes {
   265  			select {
   266  			case <-ctx.Done():
   267  				return
   268  			case <-node.Started():
   269  				continue
   270  			}
   271  		}
   272  		// When all nodes are done, signal the client. Client handles unresponsive components by setting up
   273  		// a deadline for passed in context, and this ensures that nothing breaks if function below is never called.
   274  		nodesStarted()
   275  	}()
   276  
   277  	return g.Wait()
   278  }