package diagnostics import ( "context" "encoding/json" "fmt" "os" ospath "path" "strings" "testing" dockertypes "github.com/docker/docker/api/types" mobycli "github.com/moby/moby/client" "github.com/cosmos/ibc-go/e2e/dockerutil" "github.com/cosmos/ibc-go/e2e/internal/directories" ) const ( dockerInspectFileName = "docker-inspect.json" defaultFilePerm = 0o750 ) // Collect can be used in `t.Cleanup` and will copy all the of the container logs and relevant files // into e2e//.log. These log files will be uploaded to GH upon test failure. func Collect(t *testing.T, dc *mobycli.Client, debugModeEnabled bool, suiteName string, chainNames ...string) { t.Helper() if !debugModeEnabled { // when we are not forcing log collection, we only upload upon test failing. if !t.Failed() { t.Logf("test passed, not uploading logs") return } } t.Logf("writing logs for test: %s", t.Name()) ctx := context.TODO() e2eDir, err := directories.E2E() if err != nil { t.Logf("failed finding log directory: %s", err) return } logsDir := fmt.Sprintf("%s/diagnostics", e2eDir) if err := os.MkdirAll(fmt.Sprintf("%s/%s", logsDir, t.Name()), defaultFilePerm); err != nil { t.Logf("failed creating logs directory in test cleanup: %s", err) return } testContainers, err := dockerutil.GetTestContainers(ctx, suiteName, dc) if err != nil { t.Logf("failed listing containers during test cleanup: %s", err) return } for _, container := range testContainers { containerName := getContainerName(t, container) containerDir := fmt.Sprintf("%s/%s/%s", logsDir, t.Name(), containerName) if err := os.MkdirAll(containerDir, defaultFilePerm); err != nil { t.Logf("failed creating logs directory for container %s: %s", containerDir, err) continue } logsBz, err := dockerutil.GetContainerLogs(ctx, dc, container.ID) if err != nil { t.Logf("failed reading logs in test cleanup: %s", err) continue } logFile := fmt.Sprintf("%s/%s.log", containerDir, containerName) if err := os.WriteFile(logFile, logsBz, defaultFilePerm); err != nil { continue } t.Logf("successfully wrote log file %s", logFile) var diagnosticFiles []string for _, chainName := range chainNames { diagnosticFiles = append(diagnosticFiles, chainDiagnosticAbsoluteFilePaths(chainName)...) } diagnosticFiles = append(diagnosticFiles, relayerDiagnosticAbsoluteFilePaths()...) for _, absoluteFilePathInContainer := range diagnosticFiles { localFilePath := ospath.Join(containerDir, ospath.Base(absoluteFilePathInContainer)) if err := fetchAndWriteDiagnosticsFile(ctx, dc, container.ID, localFilePath, absoluteFilePathInContainer); err != nil { continue } t.Logf("successfully wrote diagnostics file %s", absoluteFilePathInContainer) } localFilePath := ospath.Join(containerDir, dockerInspectFileName) if err := fetchAndWriteDockerInspectOutput(ctx, dc, container.ID, localFilePath); err != nil { continue } t.Logf("successfully wrote docker inspect output") } } // getContainerName returns an either the ID of the container or stripped down human-readable // version of the name if the name is non-empty. // // Note: You should still always use the ID when interacting with the docker client. func getContainerName(t *testing.T, container dockertypes.Container) string { t.Helper() // container will always have an id, by may not have a name. containerName := container.ID if len(container.Names) > 0 { containerName = container.Names[0] // remove the test name from the container as the folder structure will provide this // information already. containerName = strings.TrimRight(containerName, "-"+t.Name()) containerName = strings.TrimLeft(containerName, "/") } return containerName } // fetchAndWriteDiagnosticsFile fetches the contents of a single file from the given container id and writes // the contents of the file to a local path provided. func fetchAndWriteDiagnosticsFile(ctx context.Context, dc *mobycli.Client, containerID, localPath, absoluteFilePathInContainer string) error { fileBz, err := dockerutil.GetFileContentsFromContainer(ctx, dc, containerID, absoluteFilePathInContainer) if err != nil { return err } return os.WriteFile(localPath, fileBz, defaultFilePerm) } // fetchAndWriteDockerInspectOutput writes the contents of docker inspect to the specified file. func fetchAndWriteDockerInspectOutput(ctx context.Context, dc *mobycli.Client, containerID, localPath string) error { containerJSON, err := dc.ContainerInspect(ctx, containerID) if err != nil { return err } fileBz, err := json.MarshalIndent(containerJSON, "", "\t") if err != nil { return err } return os.WriteFile(localPath, fileBz, defaultFilePerm) } // chainDiagnosticAbsoluteFilePaths returns a slice of absolute file paths (in the containers) which are the files that should be // copied locally when fetching diagnostics. func chainDiagnosticAbsoluteFilePaths(chainName string) []string { return []string{ fmt.Sprintf("/var/cosmos-chain/%s/config/genesis.json", chainName), fmt.Sprintf("/var/cosmos-chain/%s/config/app.toml", chainName), fmt.Sprintf("/var/cosmos-chain/%s/config/config.toml", chainName), fmt.Sprintf("/var/cosmos-chain/%s/config/client.toml", chainName), } } // relayerDiagnosticAbsoluteFilePaths returns a slice of absolute file paths (in the containers) which are the files that should be // copied locally when fetching diagnostics. func relayerDiagnosticAbsoluteFilePaths() []string { return []string{ "/home/hermes/.hermes/config.toml", } }