github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/internal/operator/orbiter/takeoff.go (about) 1 package orbiter 2 3 import ( 4 "errors" 5 "net/http" 6 "time" 7 8 "github.com/prometheus/client_golang/prometheus" 9 "github.com/prometheus/client_golang/prometheus/promhttp" 10 "gopkg.in/yaml.v3" 11 12 "github.com/caos/orbos/internal/operator/common" 13 "github.com/caos/orbos/mntr" 14 "github.com/caos/orbos/pkg/git" 15 "github.com/caos/orbos/pkg/orb" 16 "github.com/caos/orbos/pkg/secret" 17 "github.com/caos/orbos/pkg/tree" 18 ) 19 20 func ToEnsureResult(done bool, err error) *EnsureResult { 21 return &EnsureResult{ 22 Err: err, 23 Done: done, 24 } 25 } 26 27 type EnsureResult struct { 28 Err error 29 Done bool 30 } 31 32 type ConfigureFunc func(orb orb.Orb) error 33 34 func NoopConfigure(orb orb.Orb) error { 35 return nil 36 } 37 38 type QueryFunc func(nodeAgentsCurrent *common.CurrentNodeAgents, nodeAgentsDesired *common.DesiredNodeAgents, queried map[string]interface{}) (EnsureFunc, error) 39 40 type EnsureFunc func(pdf func(monitor mntr.Monitor) error) *EnsureResult 41 42 func NoopEnsure(_ func(monitor mntr.Monitor) error) *EnsureResult { 43 return &EnsureResult{Done: true} 44 } 45 46 type event struct { 47 commit string 48 files []git.File 49 } 50 51 func Instrument(monitor mntr.Monitor, healthyChan chan bool) { 52 defer func() { monitor.RecoverPanic(recover()) }() 53 54 healthy := true 55 56 prometheus.MustRegister(prometheus.NewBuildInfoCollector()) 57 http.Handle("/metrics", promhttp.Handler()) 58 http.HandleFunc("/health", func(writer http.ResponseWriter, request *http.Request) { 59 msg := "OK" 60 status := 200 61 if !healthy { 62 msg = "ORBITER is not healthy. See the logs." 63 status = 404 64 } 65 writer.WriteHeader(status) 66 writer.Write([]byte(msg)) 67 }) 68 69 go func() { 70 timeout := 10 * time.Minute 71 ticker := time.NewTimer(timeout) 72 for { 73 select { 74 case newHealthiness := <-healthyChan: 75 ticker.Reset(timeout) 76 if newHealthiness == healthy { 77 continue 78 } 79 healthy = newHealthiness 80 if !newHealthiness { 81 monitor.Error(errors.New("ORBITER is unhealthy now")) 82 continue 83 } 84 monitor.Info("ORBITER is healthy now") 85 case <-ticker.C: 86 monitor.Error(errors.New("ORBITER is unhealthy now as it did not report healthiness for 10 minutes")) 87 healthy = false 88 } 89 } 90 }() 91 92 if err := http.ListenAndServe(":9000", nil); err != nil { 93 panic(err) 94 } 95 } 96 97 func Adapt(gitClient *git.Client, monitor mntr.Monitor, finished chan struct{}, adapt AdaptFunc) (QueryFunc, DestroyFunc, ConfigureFunc, bool, *tree.Tree, *tree.Tree, map[string]*secret.Secret, error) { 98 99 treeDesired, err := gitClient.ReadTree(git.OrbiterFile) 100 if err != nil { 101 return nil, nil, nil, false, nil, nil, nil, err 102 } 103 treeCurrent := &tree.Tree{} 104 105 query, destroy, configure, migrate, secrets, err := adapt(monitor, finished, treeDesired, treeCurrent) 106 return query, destroy, configure, migrate, treeDesired, treeCurrent, secrets, err 107 } 108 109 func Takeoff(monitor mntr.Monitor, conf *Config, healthyChan chan bool) func() { 110 111 return func() { 112 113 var err error 114 defer func() { 115 go func() { 116 if err != nil { 117 healthyChan <- false 118 return 119 } 120 healthyChan <- true 121 }() 122 }() 123 124 query, _, _, migrate, treeDesired, treeCurrent, _, err := Adapt(conf.GitClient, monitor, conf.FinishedChan, conf.Adapt) 125 if err != nil { 126 monitor.Error(err) 127 return 128 } 129 130 desiredNodeAgents := common.NodeAgentsDesiredKind{ 131 Kind: "nodeagent.caos.ch/NodeAgents", 132 Version: "v0", 133 Spec: common.NodeAgentsSpec{ 134 Commit: conf.OrbiterCommit, 135 }, 136 } 137 138 marshalCurrentFiles := func() []git.File { 139 return []git.File{{ 140 Path: "caos-internal/orbiter/current.yml", 141 Content: common.MarshalYAML(treeCurrent), 142 }, { 143 Path: "caos-internal/orbiter/node-agents-desired.yml", 144 Content: common.MarshalYAML(desiredNodeAgents), 145 }} 146 } 147 148 if migrate { 149 if err = conf.GitClient.PushGitDesiredStates(monitor, "Desired state migrated", []git.GitDesiredState{{ 150 Desired: treeDesired, 151 Path: git.OrbiterFile, 152 }}); err != nil { 153 monitor.Error(err) 154 return 155 } 156 } 157 158 currentNodeAgents := common.NodeAgentsCurrentKind{} 159 if err := yaml.Unmarshal(conf.GitClient.Read("caos-internal/orbiter/node-agents-current.yml"), ¤tNodeAgents); err != nil { 160 monitor.Error(err) 161 return 162 } 163 164 handleAdapterError := func(err error) { 165 monitor.Error(err) 166 if commitErr := conf.GitClient.Commit(mntr.CommitRecord([]*mntr.Field{{Pos: 0, Key: "err", Value: err.Error()}})); commitErr != nil { 167 monitor.Error(err) 168 return 169 } 170 monitor.Error(conf.GitClient.Push()) 171 } 172 173 ensure, err := query(¤tNodeAgents.Current, &desiredNodeAgents.Spec.NodeAgents, nil) 174 if err != nil { 175 handleAdapterError(err) 176 return 177 } 178 179 if err := conf.GitClient.Clone(); err != nil { 180 monitor.Error(err) 181 return 182 } 183 184 reconciledCurrentStateMsg := "Current state reconciled" 185 186 if err := conf.GitClient.UpdateRemote(reconciledCurrentStateMsg, func() []git.File { 187 return []git.File{marshalCurrentFiles()[0]} 188 }); err != nil { 189 monitor.Error(err) 190 return 191 } 192 193 result := ensure(conf.GitClient.PushDesiredFunc(git.OrbiterFile, treeDesired)) 194 if result.Err != nil { 195 handleAdapterError(result.Err) 196 return 197 } 198 199 if result.Done { 200 monitor.Info("Desired state is ensured") 201 } else { 202 monitor.Info("Desired state is not yet ensured") 203 } 204 205 conf.GitClient.UpdateRemote("Current state changed", func() []git.File { 206 pushFiles := marshalCurrentFiles() 207 if result.Done { 208 var deletedACurrentNodeAgent bool 209 for currentNA := range currentNodeAgents.Current.NA { 210 if _, ok := desiredNodeAgents.Spec.NodeAgents.NA[currentNA]; !ok { 211 currentNodeAgents.Current.NA[currentNA] = nil 212 delete(currentNodeAgents.Current.NA, currentNA) 213 deletedACurrentNodeAgent = true 214 } 215 } 216 if deletedACurrentNodeAgent { 217 monitor.Info("Clearing node agents current states") 218 pushFiles = append(pushFiles, git.File{ 219 Path: "caos-internal/orbiter/node-agents-current.yml", 220 Content: common.MarshalYAML(currentNodeAgents), 221 }) 222 } 223 } 224 return pushFiles 225 }) 226 } 227 }