diff --git a/cmd/supervisor/main.go b/cmd/supervisor/main.go new file mode 100644 index 0000000..61e9b8b --- /dev/null +++ b/cmd/supervisor/main.go @@ -0,0 +1,10 @@ +package main + +import "net/http" + +func main() { + _ = http.ListenAndServe(":8088", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("lifeboat-ready\n")) + })) +} diff --git a/dev/ACTIVE_TASK.json b/dev/ACTIVE_TASK.json index 10cd1ed..04d3594 100644 --- a/dev/ACTIVE_TASK.json +++ b/dev/ACTIVE_TASK.json @@ -1 +1 @@ -{"task_file":"dev/tasks/GEN2-020-separate-execution-receipt-from-verified-world-outcome.json","task_id":"GEN2-020","task_revision":1} \ No newline at end of file +{"task_file":"dev/tasks/GEN2-021-add-independent-supervisor-lifeboat-for-release-health-and-rollback.json","task_id":"GEN2-021","task_revision":1} \ No newline at end of file diff --git a/runtime/supervisor/supervisor.go b/runtime/supervisor/supervisor.go new file mode 100644 index 0000000..7ea0210 --- /dev/null +++ b/runtime/supervisor/supervisor.go @@ -0,0 +1,62 @@ +package supervisor + +type ReleaseState string + +const ( + ReleaseCandidate ReleaseState = "CANDIDATE" + ReleaseHealthy ReleaseState = "HEALTHY" + ReleaseFailed ReleaseState = "FAILED" + ReleaseRolledBack ReleaseState = "ROLLED_BACK" +) + +type Release struct { + ID string + Version string + State ReleaseState + LastKnownGood bool +} +type Observation struct { + ReleaseID string + Ready bool + Reason string + ObservedAtUnixMillis int64 +} +type Lifeboat struct { + Current Release + LastKnownGood Release + Observations []Observation +} + +func (l *Lifeboat) Start(candidate Release) bool { + if candidate.ID == "" || candidate.Version == "" { + return false + } + candidate.State = ReleaseCandidate + l.Current = candidate + return true +} +func (l *Lifeboat) Record(o Observation) bool { + if o.ReleaseID == "" { + return false + } + l.Observations = append(l.Observations, o) + if o.ReleaseID != l.Current.ID { + return true + } + if o.Ready { + l.Current.State = ReleaseHealthy + l.Current.LastKnownGood = true + l.LastKnownGood = l.Current + } else { + l.Current.State = ReleaseFailed + } + return true +} +func (l *Lifeboat) Rollback() bool { + if l.LastKnownGood.ID == "" { + return false + } + l.Current = l.LastKnownGood + l.Current.State = ReleaseRolledBack + return true +} diff --git a/runtime/supervisor/supervisor_test.go b/runtime/supervisor/supervisor_test.go new file mode 100644 index 0000000..6aa8704 --- /dev/null +++ b/runtime/supervisor/supervisor_test.go @@ -0,0 +1,17 @@ +package supervisor_test + +import ( + "git.cin.su/m/Assistant/runtime/supervisor" + "testing" +) + +func TestLifeboatRollsBackCandidateToLastKnownGood(t *testing.T) { + l := supervisor.Lifeboat{} + l.Start(supervisor.Release{ID: "good", Version: "1"}) + l.Record(supervisor.Observation{ReleaseID: "good", Ready: true}) + l.Start(supervisor.Release{ID: "candidate", Version: "2"}) + l.Record(supervisor.Observation{ReleaseID: "candidate", Ready: false}) + if !l.Rollback() || l.Current.ID != "good" || l.Current.State != supervisor.ReleaseRolledBack { + t.Fatalf("%#v", l) + } +}