github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/k8s/environment/artifacts.go (about)

     1  package environment
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/smartcontractkit/chainlink-testing-framework/libs/k8s/client"
    13  
    14  	"github.com/rs/zerolog/log"
    15  	coreV1 "k8s.io/api/core/v1"
    16  	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/client-go/kubernetes/scheme"
    18  	clientV1 "k8s.io/client-go/kubernetes/typed/core/v1"
    19  	"k8s.io/client-go/tools/remotecommand"
    20  )
    21  
    22  // Artifacts is an artifacts dumping structure that copies logs and database dumps for all deployed pods
    23  type Artifacts struct {
    24  	Namespace  string
    25  	DBName     string
    26  	Client     *client.K8sClient
    27  	podsClient clientV1.PodInterface
    28  }
    29  
    30  // NewArtifacts create new artifacts instance for provided environment
    31  func NewArtifacts(client *client.K8sClient, namespace string) (*Artifacts, error) {
    32  	return &Artifacts{
    33  		Namespace:  namespace,
    34  		Client:     client,
    35  		podsClient: client.ClientSet.CoreV1().Pods(namespace),
    36  	}, nil
    37  }
    38  
    39  // DumpTestResult dumps all pods logs and db dump in a separate test dir
    40  func (a *Artifacts) DumpTestResult(testDir string, dbName string) error {
    41  	a.DBName = dbName
    42  	if err := MkdirIfNotExists(testDir); err != nil {
    43  		return err
    44  	}
    45  	return a.writePodArtifacts(testDir)
    46  }
    47  
    48  func (a *Artifacts) writePodArtifacts(testDir string) error {
    49  	log.Info().
    50  		Str("Test", testDir).
    51  		Msg("Writing test artifacts")
    52  	podsList, err := a.podsClient.List(context.Background(), metaV1.ListOptions{})
    53  	if err != nil {
    54  		log.Err(err).
    55  			Str("Namespace", a.Namespace).
    56  			Msg("Error retrieving pod list from K8s environment")
    57  		return err
    58  	}
    59  	for _, pod := range podsList.Items {
    60  		log.Info().
    61  			Str("Pod", pod.Name).
    62  			Msg("Writing pod artifacts")
    63  		appName := pod.Labels[client.AppLabel]
    64  		instance := pod.Labels["instance"]
    65  		appDir := filepath.Join(testDir, fmt.Sprintf("%s_%s", appName, instance))
    66  		if err := MkdirIfNotExists(appDir); err != nil {
    67  			return err
    68  		}
    69  		err = a.writePodLogs(pod, appDir)
    70  		if err != nil {
    71  			log.Err(err).
    72  				Str("Namespace", a.Namespace).
    73  				Str("Pod", pod.Name).
    74  				Msg("Error writing logs for pod")
    75  		}
    76  	}
    77  	return nil
    78  }
    79  
    80  func (a *Artifacts) dumpDB(pod coreV1.Pod, container coreV1.Container) (string, error) {
    81  	postRequestBase := a.Client.ClientSet.CoreV1().RESTClient().Post().
    82  		Namespace(pod.Namespace).Resource("pods").Name(pod.Name).SubResource("exec")
    83  	exportDBRequest := postRequestBase.VersionedParams(
    84  		&coreV1.PodExecOptions{
    85  			Container: container.Name,
    86  			Command:   []string{"/bin/sh", "-c", "pg_dump", a.DBName},
    87  			Stdin:     true,
    88  			Stdout:    true,
    89  			Stderr:    true,
    90  			TTY:       false,
    91  		}, scheme.ParameterCodec)
    92  	exec, err := remotecommand.NewSPDYExecutor(a.Client.RESTConfig, "POST", exportDBRequest.URL())
    93  	if err != nil {
    94  		return "", err
    95  	}
    96  	outBuff, errBuff := &bytes.Buffer{}, &bytes.Buffer{}
    97  	err = exec.Stream(remotecommand.StreamOptions{
    98  		Stdin:  &bytes.Reader{},
    99  		Stdout: outBuff,
   100  		Stderr: errBuff,
   101  		Tty:    false,
   102  	})
   103  	if err != nil || errBuff.Len() > 0 {
   104  		return "", fmt.Errorf("error in dumping DB contents | STDOUT: %v | STDERR: %v", outBuff.String(),
   105  			errBuff.String())
   106  	}
   107  	return outBuff.String(), err
   108  }
   109  
   110  func (a *Artifacts) writePostgresDump(podDir string, pod coreV1.Pod, cont coreV1.Container) error {
   111  	dumpContents, err := a.dumpDB(pod, cont)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	logFile, err := os.Create(filepath.Join(podDir, fmt.Sprintf("%s_dump.sql", cont.Name)))
   116  	if err != nil {
   117  		return err
   118  	}
   119  	_, err = logFile.WriteString(dumpContents)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	return logFile.Close()
   124  }
   125  
   126  func (a *Artifacts) writeContainerLogs(podDir string, pod coreV1.Pod, cont coreV1.Container) error {
   127  	logFile, err := os.Create(filepath.Join(podDir, cont.Name) + ".log")
   128  	if err != nil {
   129  		return err
   130  	}
   131  	podLogRequest := a.podsClient.GetLogs(pod.Name, &coreV1.PodLogOptions{Container: cont.Name})
   132  	podLogs, err := podLogRequest.Stream(context.Background())
   133  	if err != nil {
   134  		return err
   135  	}
   136  	buf := new(bytes.Buffer)
   137  	_, err = io.Copy(buf, podLogs)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	_, err = logFile.Write(buf.Bytes())
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	if err = logFile.Close(); err != nil {
   147  		return err
   148  	}
   149  	return podLogs.Close()
   150  }
   151  
   152  // Writes logs for each container in a pod
   153  func (a *Artifacts) writePodLogs(pod coreV1.Pod, appDir string) error {
   154  	for _, c := range pod.Spec.Containers {
   155  		log.Info().
   156  			Str("Container", c.Name).
   157  			Msg("Writing container artifacts")
   158  		if err := a.writeContainerLogs(appDir, pod, c); err != nil {
   159  			return err
   160  		}
   161  		if strings.Contains(c.Image, "postgres") {
   162  			if err := a.writePostgresDump(appDir, pod, c); err != nil {
   163  				return err
   164  			}
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  func MkdirIfNotExists(dirName string) error {
   171  	if _, err := os.Stat(dirName); os.IsNotExist(err) {
   172  		if err = os.MkdirAll(dirName, os.ModePerm); err != nil {
   173  			return fmt.Errorf("failed to create directory: %s err: %w", dirName, err)
   174  		}
   175  	}
   176  	return nil
   177  }