Internals
Read from the source at commit
39a0c02. Every claim here points at a file and line.
Code map
| Path | Responsibility |
|---|---|
cli/main.go | Process entry; calls CmdInit() then cobra Execute() |
cli/cmd/cmd.go | Registers the static top-level commands |
cli/cmd/create.go | create command, run closures, sync/async paths, auto-recovery |
cli/cmd/exp.go | Spec-driven command tree, executor registry, flag binding |
cli/cmd/command.go | Record building, uid generation, SQLite insert |
exec/os/ | OS executor that shells out to the chaos_os binary |
data/ | SQLite-backed experiment and preparation store |
version/ | Version info (generated by scripts/version.sh) |
Core data structures
data.ExperimentModel (data/experiment.go:30) is the persisted experiment record. Its fields are Uid, Command, SubCommand, Flag, Status, Error, CreateTime, and UpdateTime:
type ExperimentModel struct {
Uid string
Command string
SubCommand string
Flag string
Status string
Error string
CreateTime string
UpdateTime string
}The table it maps to is declared inline as expTableDDL (data/experiment.go:71), where uid carries a UNIQUE constraint:
const expTableDDL = `CREATE TABLE IF NOT EXISTS experiment (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid VARCHAR(32) UNIQUE,
command VARCHAR NOT NULL,
sub_command VARCHAR,
flag VARCHAR,
status VARCHAR,
error VARCHAR,
create_time VARCHAR,
update_time VARCHAR
)`The store interface is SourceI (data/source.go:36), composed of ExperimentSource (data/experiment.go:41) and PreparationSource, with the concrete Source holding a *sql.DB (data/source.go:41).
The runtime experiment representation is spec.ExpModel (from the chaosblade-spec-go SDK), built at cli/cmd/exp.go:435 with Target, Scope, ActionName, and an ActionFlags map. The command service that owns the registries is baseExpCommandService (cli/cmd/exp.go:91); executor keys are derived by createExecutorKey (cli/cmd/exp.go:452).
A path worth tracing
Follow blade create cpu load --cpu-percent 60 from flag parsing to fault injection.
The leaf command's RunE is actionRunEFunc (cli/cmd/create.go:104). It first turns cobra flags into a spec.ExpModel:
expModel := createExpModel(target, scope, actionCommandSpec.Name(), cmd)createExpModel (cli/cmd/exp.go:435) copies every non-false flag into the map:
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Value.String() == "false" {
return
}
expModel.ActionFlags[flag.Name] = flag.Value.String()
})The record is then persisted. recordExpModel (cli/cmd/command.go:76) generates a uid when none is supplied (cli/cmd/command.go:82, generator declared at cli/cmd/command.go:122), builds the data.ExperimentModel (cli/cmd/command.go:96), and inserts it (cli/cmd/command.go:106).
On the synchronous branch the executor runs inline:
executor := actionCommandSpec.Executor()
executor.SetChannel(channel.NewLocalChannel())
ctx := context.WithValue(context.Background(), spec.Uid, model.Uid)
response := executor.Exec(model.Uid, ctx, expModel)That block is at cli/cmd/create.go:180 through cli/cmd/create.go:183. The OS executor's Exec (exec/os/executor.go:42) decides create versus destroy via spec.IsDestroy(ctx), assembles argsArray, and resolves the external binary:
chaosOsBin := path.Join(util.GetProgramPath(), "bin", spec.ChaosOsBin)
command := os_exec.CommandContext(ctx, chaosOsBin, argsArray...)A hang-style fault returns the child PID after command.Start() (exec/os/executor.go:71); other faults run command.CombinedOutput() (exec/os/executor.go:78) and decode with spec.Decode(outMsg, nil) (exec/os/executor.go:84). Back in create.go, a successful run updates the record with GetDS().UpdateExperimentModelByUid(model.Uid, Success, response.Err) (cli/cmd/create.go:222), implemented at data/experiment.go:164.
Things that surprised me
The CLI never injects a fault itself. Even a one-line CPU load goes out to a separate chaos_os process (exec/os/executor.go:66 and exec/os/executor.go:67), and the scenario grammar is loaded from YAML at runtime (cli/cmd/exp.go:140), not compiled in. Reading the build clarifies why: Makefile:341 clones chaosblade-exec-os and runs its make to produce that binary and its YAML.
Auto-recovery is wired through cobra's PostRunE. actionPostRunEFunc (cli/cmd/create.go:252) reads the timeout flag and, when set, schedules a detached recovery:
args := fmt.Sprintf("nohup /bin/sh -c 'sleep %d; %s destroy %s' > /dev/null 2>&1 &",
timeout, script, actionCommand.uid)That snippet is at cli/cmd/create.go:275. For container and pod scopes it pads the timeout by 60 seconds (cli/cmd/create.go:271). The timeout flag is not something the user must remember to add; addTimeoutFlag (cli/cmd/exp.go:405) appends it to every action automatically.
One more detail: the SQLite driver is the cgo-free github.com/glebarez/sqlite (data/source.go:28), so blade ships as a static Go binary with no external database and no C toolchain at build time.