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 }