feat: add harness runner (scenario executor, alert receiver, action executor, judge, revert)
This commit is contained in:
1 parent
8f5378b6ea
commit
5daf13b669
16 files changed
+2550
No files matched your search
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// runStep executes one injection/revert step; the returned map is recorded as injections.params and reused by the revert.
|
||||
func (a *App) runStep(ctx context.Context, sc *Scenario, st Step, runID string, prev map[string]any) (map[string]any, error) {
|
||||
res := map[string]any{"method": st.Method}
|
||||
var err error
|
||||
switch st.Method {
|
||||
case "none":
|
||||
case "chaos":
|
||||
name := strings.ToLower(sc.ID) + "-" + runID[len(runID)-6:]
|
||||
var ref map[string]any
|
||||
ref, err = a.applyChaos(ctx, st.Manifest, name)
|
||||
for k, v := range ref {
|
||||
res[k] = v
|
||||
}
|
||||
case "chaos_delete":
|
||||
err = a.deleteChaos(ctx, prev)
|
||||
case "kubectl_scale":
|
||||
res["namespace"], res["deployment"], res["replicas"] = st.Namespace, st.Deployment, st.Replicas
|
||||
err = a.scaleDeployment(ctx, st.Namespace, st.Deployment, st.Replicas)
|
||||
case "ssh":
|
||||
res["host"], res["command"] = st.Host, st.Command
|
||||
var out string
|
||||
out, err = a.sshRun(ctx, st.Host, st.Command)
|
||||
res["output"] = truncate(out, 2000)
|
||||
case "gcloud":
|
||||
var r map[string]any
|
||||
r, err = a.gcpOp(ctx, st.Op, st.Instance, st.MIG, st.Size)
|
||||
for k, v := range r {
|
||||
res[k] = v
|
||||
}
|
||||
case "psql":
|
||||
res["host"], res["database"], res["sql"] = st.Host, st.Database, st.SQL
|
||||
var out string
|
||||
out, err = a.psqlExec(ctx, st.Host, st.Database, st.SQL)
|
||||
res["output"] = truncate(out, 2000)
|
||||
default:
|
||||
err = fmt.Errorf("unknown step method %q", st.Method)
|
||||
}
|
||||
if err != nil {
|
||||
res["error"] = err.Error()
|
||||
return res, err
|
||||
}
|
||||
if st.WaitS > 0 {
|
||||
select {
|
||||
case <-time.After(time.Duration(st.WaitS) * time.Second):
|
||||
case <-ctx.Done():
|
||||
return res, ctx.Err()
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// psqlExec runs SQL on db-primary / db-replica as the harness role over hostssl (pg_hba: harness from 10.20.0.0/20).
|
||||
func (a *App) psqlExec(ctx context.Context, host, database, sqlText string) (string, error) {
|
||||
ip := host
|
||||
if net.ParseIP(host) == nil {
|
||||
in, err := a.resolveInstance(ctx, host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ip = in.IP
|
||||
}
|
||||
pw, err := readFileTrim(a.cfg.DBPassFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("db password: %w", err)
|
||||
}
|
||||
if database == "" {
|
||||
database = "postgres"
|
||||
}
|
||||
cfg, err := pgx.ParseConfig(fmt.Sprintf("postgres://harness@%s:5432/%s?sslmode=require&connect_timeout=10", ip, database))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cfg.Password = pw
|
||||
conn, err := pgx.ConnectConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer conn.Close(ctx)
|
||||
rows, err := conn.Query(ctx, sqlText)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rows.Close()
|
||||
var sb strings.Builder
|
||||
for rows.Next() {
|
||||
vals, err := rows.Values()
|
||||
if err != nil {
|
||||
return sb.String(), err
|
||||
}
|
||||
sb.WriteString(fmt.Sprint(vals...))
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
return sb.String(), rows.Err()
|
||||
}
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
if len(s) > n {
|
||||
return s[:n] + "...(truncated)"
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in new issue
Block a user