github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/cmd/chore/e2e/run/ensured.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os/exec"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/caos/orbos/internal/helpers"
    14  
    15  	"github.com/caos/orbos/internal/operator/common"
    16  
    17  	"gopkg.in/yaml.v3"
    18  )
    19  
    20  func awaitCondition(
    21  	ctx context.Context,
    22  	settings programSettings,
    23  	orbctl newOrbctlCommandFunc,
    24  	kubectl newKubectlCommandFunc,
    25  	downloadKubeconfigFunc downloadKubeconfig,
    26  	step uint8,
    27  	condition *condition,
    28  ) error {
    29  
    30  	awaitCtx, awaitCancel := context.WithTimeout(ctx, condition.watcher.timeout)
    31  	defer awaitCancel()
    32  
    33  	if err := downloadKubeconfigFunc(awaitCtx, orbctl); err != nil {
    34  		return err
    35  	}
    36  
    37  	triggerCheck := make(chan struct{})
    38  	trigger := func() { triggerCheck <- struct{}{} }
    39  	// show initial state and tracking progress begins
    40  	go trigger()
    41  
    42  	go watchLogs(awaitCtx, settings, kubectl, condition.watcher, triggerCheck)
    43  
    44  	started := time.Now()
    45  
    46  	// Check each minute if the desired state is ensured
    47  	ticker := time.NewTicker(time.Minute)
    48  	defer ticker.Stop()
    49  
    50  	var err error
    51  
    52  	for {
    53  		select {
    54  		case <-ticker.C:
    55  			go trigger()
    56  		case <-awaitCtx.Done():
    57  			return helpers.Concat(awaitCtx.Err(), err)
    58  		case <-triggerCheck:
    59  
    60  			if err = isEnsured(awaitCtx, settings, orbctl, kubectl, condition); err != nil {
    61  				printProgress(condition.watcher.logPrefix, settings, strconv.Itoa(int(step)), started, condition.watcher.timeout)
    62  				settings.logger.Warnf("desired state is not yet ensured: %s", err.Error())
    63  				continue
    64  			}
    65  			return nil
    66  		}
    67  	}
    68  }
    69  
    70  func watchLogs(ctx context.Context, settings programSettings, kubectl newKubectlCommandFunc, watcher watcher, lineFound chan<- struct{}) {
    71  
    72  	select {
    73  	case <-ctx.Done():
    74  		return
    75  	default:
    76  		// goon
    77  	}
    78  
    79  	err := runCommand(settings, watcher.logPrefix.strPtr(), nil, func(line string) {
    80  		// Check if the desired state is ensured when orbiter prints so
    81  		if strings.Contains(line, watcher.checkWhenLogContains) {
    82  			go func() { lineFound <- struct{}{} }()
    83  		}
    84  	}, kubectl(ctx), "logs", "--namespace", "caos-system", "--selector", watcher.selector, "--since", "10s", "--follow")
    85  
    86  	if err != nil {
    87  		settings.logger.Warnf("watching logs failed: %s. trying again", err.Error())
    88  	}
    89  
    90  	time.Sleep(1 * time.Second)
    91  
    92  	watchLogs(ctx, settings, kubectl, watcher, lineFound)
    93  }
    94  
    95  func isEnsured(ctx context.Context, settings programSettings, newOrbctl newOrbctlCommandFunc, newKubectl newKubectlCommandFunc, condition *condition) error {
    96  
    97  	if condition.checks == nil {
    98  		return nil
    99  	}
   100  
   101  	var (
   102  		orbiter    = currentOrbiter{}
   103  		nodeagents = common.NodeAgentsCurrentKind{}
   104  	)
   105  
   106  	if err := helpers.Fanout([]func() error{
   107  		func() error {
   108  			return checkPodsAreReady(ctx, settings, newKubectl, "caos-system", condition.watcher.selector, 1)
   109  		},
   110  		func() error {
   111  			return readYaml(ctx, settings, newOrbctl, &orbiter, "--gitops", "file", "print", "caos-internal/orbiter/current.yml")
   112  		},
   113  		func() error {
   114  			return readYaml(ctx, settings, newOrbctl, &nodeagents, "--gitops", "file", "print", "caos-internal/orbiter/node-agents-current.yml")
   115  		},
   116  	})(); err != nil {
   117  		return err
   118  	}
   119  
   120  	return condition.checks(ctx, newKubectl, orbiter, nodeagents)
   121  }
   122  
   123  func readYaml(ctx context.Context, settings programSettings, binFunc func(context.Context) *exec.Cmd, into interface{}, args ...string) error {
   124  
   125  	orbctlCtx, orbctlCancel := context.WithTimeout(ctx, 10*time.Second)
   126  	defer orbctlCancel()
   127  
   128  	buf := new(bytes.Buffer)
   129  	defer buf.Reset()
   130  
   131  	if err := runCommand(settings, nil, buf, nil, binFunc(orbctlCtx), args...); err != nil {
   132  		return fmt.Errorf("reading orbiters current state failed: %w", err)
   133  	}
   134  
   135  	currentBytes, err := ioutil.ReadAll(buf)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	return yaml.Unmarshal(currentBytes, into)
   141  }
   142  
   143  func checkPodsAreReady(ctx context.Context, settings programSettings, kubectl newKubectlCommandFunc, namespace, selector string, expectedPodsCount uint8) (err error) {
   144  
   145  	defer func() {
   146  		if err != nil {
   147  			err = fmt.Errorf(`check for ready pods in namespace %s with selector "%s"" failed: %w`, namespace, selector, err)
   148  		}
   149  	}()
   150  
   151  	pods := struct {
   152  		Items []struct {
   153  			Metadata struct {
   154  				Name string
   155  			}
   156  			Status struct {
   157  				Conditions []struct {
   158  					Type   string
   159  					Status string
   160  				}
   161  			}
   162  		}
   163  	}{}
   164  
   165  	args := []string{
   166  		"get", "pods",
   167  		"--namespace", namespace,
   168  		"--output", "yaml",
   169  	}
   170  
   171  	if selector != "" {
   172  		args = append(args, "--selector", selector)
   173  	}
   174  
   175  	if err := readYaml(ctx, settings, kubectl, &pods, args...); err != nil {
   176  		return err
   177  	}
   178  
   179  	podsCount := uint8(len(pods.Items))
   180  	if podsCount != expectedPodsCount {
   181  		return fmt.Errorf("%d pods are existing instead of %d", podsCount, expectedPodsCount)
   182  	}
   183  
   184  	for i := range pods.Items {
   185  		pod := pods.Items[i]
   186  		isReady := false
   187  		for j := range pod.Status.Conditions {
   188  			condition := pod.Status.Conditions[j]
   189  			if condition.Type != "Ready" {
   190  				continue
   191  			}
   192  			if condition.Status != "True" {
   193  				return fmt.Errorf("pod %s has Ready condition %s", pod.Metadata.Name, condition.Status)
   194  			}
   195  			isReady = true
   196  			break
   197  		}
   198  		if !isReady {
   199  			return fmt.Errorf("pod %s has no Ready condition", pod.Metadata.Name)
   200  		}
   201  	}
   202  
   203  	return nil
   204  }