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 }