Skip to content

Internals

Read from the source at commit 39a0c02. Every claim here points at a file and line.

Code map

PathResponsibility
cli/main.goProcess entry; calls CmdInit() then cobra Execute()
cli/cmd/cmd.goRegisters the static top-level commands
cli/cmd/create.gocreate command, run closures, sync/async paths, auto-recovery
cli/cmd/exp.goSpec-driven command tree, executor registry, flag binding
cli/cmd/command.goRecord 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:

go
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:

go
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:

go
expModel := createExpModel(target, scope, actionCommandSpec.Name(), cmd)

createExpModel (cli/cmd/exp.go:435) copies every non-false flag into the map:

go
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:

go
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:

go
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:

go
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.