Skip to content

Commit 556f57d

Browse files
committed
runner: run package — executor, leg.json sentinels, and resume
Sequential executor over the plan: leg.json completion sentinels in every timed --out dir, --resume that trusts them, keep-going semantics, dataset preparation, and the persistent stellar-rpc source/build clone.
1 parent 742f704 commit 556f57d

10 files changed

Lines changed: 1984 additions & 0 deletions

File tree

runner/internal/run/dataset.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package run
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/stellar/stellar-rpc-benchmarks/runner/internal/config"
11+
"github.com/stellar/stellar-rpc-benchmarks/runner/internal/plan"
12+
)
13+
14+
// runDataset converges one dataset on a local cold pack root, whatever kind it
15+
// is: the legs downstream all read <root>/ledgers and neither know nor care
16+
// whether it was fetched, backfilled, generated, or already there.
17+
//
18+
// Everything a kind materializes lands in <root>.partial and is renamed onto
19+
// <root> only once whole, so an interrupted preparation can never be mistaken
20+
// for a finished one — the golden-present check below is exactly that
21+
// distinction, and `rm -rf <root>` is the documented lever to force a re-fetch.
22+
func runDataset(s plan.Step, opts Options) StepResult {
23+
if s.Dataset == nil {
24+
return failure(s, errors.New("dataset step has no dataset spec"))
25+
}
26+
if err := prepareDataset(s, opts.Output); err != nil {
27+
return failure(s, err)
28+
}
29+
return StepResult{ID: s.ID, Status: StatusOK}
30+
}
31+
32+
func prepareDataset(s plan.Step, out io.Writer) error {
33+
d := s.Dataset
34+
partial := d.Root + ".partial"
35+
36+
switch d.Kind {
37+
case config.KindPacksLocal:
38+
Notef(out, "dataset %s: local cold pack root %s", d.Name, d.Root)
39+
if !dirExists(filepath.Join(d.Root, "ledgers")) {
40+
return fmt.Errorf("dataset '%s': %s/ledgers not found — location must be a cold pack root", d.Name, d.Root)
41+
}
42+
43+
case config.KindPacksGS:
44+
if goldenPresent(d.Root) {
45+
Notef(out, "dataset %s: golden packs already at %s — skipping fetch", d.Name, d.Root)
46+
break
47+
}
48+
Notef(out, "dataset %s: fetch %s", d.Name, d.Location)
49+
// golden_present was false, so root is absent or an empty leftover, and
50+
// the plan's pre_clean says to clear it — but not the partial, which
51+
// rsync resumes into.
52+
if err := preClean(s, out); err != nil {
53+
return err
54+
}
55+
if err := mkdirAll(out, partial); err != nil {
56+
return err
57+
}
58+
if err := runDatasetCommands(s, out); err != nil {
59+
return err
60+
}
61+
if err := rename(out, partial, d.Root); err != nil {
62+
return err
63+
}
64+
65+
case config.KindBSBS3:
66+
if goldenPresent(d.Root) {
67+
Notef(out, "dataset %s: golden packs already at %s — skipping backfill", d.Name, d.Root)
68+
break
69+
}
70+
Notef(out, "dataset %s: golden backfill of %s from S3 (untimed)", d.Name, d.Location)
71+
if err := preClean(s, out); err != nil {
72+
return err
73+
}
74+
// One untimed cold backfill per chunk. The step's env carries
75+
// AWS_EC2_METADATA_DISABLED=true: without it the SDK signs requests
76+
// with the machine's IAM role and the public bucket 403s, but setting
77+
// it for the whole campaign would also hide those same instance-role
78+
// credentials from the publish step's `aws s3` calls.
79+
if err := runDatasetCommands(s, out); err != nil {
80+
return err
81+
}
82+
if err := rename(out, partial, d.Root); err != nil {
83+
return err
84+
}
85+
86+
case config.KindFixture:
87+
if goldenPresent(d.Root) {
88+
Notef(out, "dataset %s: golden packs already at %s — skipping generation", d.Name, d.Root)
89+
break
90+
}
91+
if d.Stage == "" {
92+
// The generation commands write into the staging tree, and the
93+
// plan's pre_clean is derived from it; a spec without one describes
94+
// a preparation nobody can reason about.
95+
return fmt.Errorf("dataset '%s': fixture step has no staging pack dir", d.Name)
96+
}
97+
Notef(out, "dataset %s: generate a fixture pack tree", d.Name)
98+
if err := preClean(s, out); err != nil {
99+
return err
100+
}
101+
// Generate every chunk into the staging pack tree, then freeze every
102+
// chunk into the golden packs — both untimed.
103+
if err := runDatasetCommands(s, out); err != nil {
104+
return err
105+
}
106+
if err := rename(out, partial, d.Root); err != nil {
107+
return err
108+
}
109+
110+
default:
111+
return fmt.Errorf("dataset '%s': unknown kind %q", d.Name, d.Kind)
112+
}
113+
114+
if !dirExists(filepath.Join(d.Root, "ledgers")) {
115+
return fmt.Errorf("dataset '%s': %s/ledgers missing after preparation", d.Name, d.Root)
116+
}
117+
return nil
118+
}
119+
120+
// preClean wipes what the plan says to wipe before a dataset materializes.
121+
// Which directories a kind clears — and, for packs-gs, which it deliberately
122+
// keeps — is a property of the kind, so the plan owns the list and a dry run
123+
// prints exactly the wipes the run performs.
124+
func preClean(s plan.Step, out io.Writer) error {
125+
if len(s.PreClean) == 0 {
126+
return nil
127+
}
128+
return removeAll(out, s.PreClean...)
129+
}
130+
131+
// runDatasetCommands runs the step's commands in order, stopping at the first
132+
// failure: what they build together is one pack tree, and half of one is worth
133+
// nothing.
134+
func runDatasetCommands(s plan.Step, out io.Writer) error {
135+
for _, argv := range s.Argv {
136+
if err := runCommand(argv, s.Env, out); err != nil {
137+
return err
138+
}
139+
}
140+
return nil
141+
}
142+
143+
// goldenPresent reports whether a pack root is there and non-empty, the port of
144+
// bash's golden_present.
145+
func goldenPresent(dir string) bool {
146+
f, err := os.Open(dir)
147+
if err != nil {
148+
return false
149+
}
150+
defer f.Close()
151+
names, err := f.Readdirnames(1)
152+
return err == nil && len(names) > 0
153+
}
154+
155+
func dirExists(path string) bool {
156+
fi, err := os.Stat(path)
157+
return err == nil && fi.IsDir()
158+
}
159+
160+
// mkdirAll and rename log themselves as the commands bash ran, so a campaign
161+
// log shows every filesystem move the runner made, not just the ones that
162+
// happened to be external processes.
163+
func mkdirAll(out io.Writer, dir string) error {
164+
fmt.Fprintf(out, " $ mkdir -p %s\n", dir)
165+
if err := os.MkdirAll(dir, 0o755); err != nil {
166+
return fmt.Errorf("mkdir -p %s: %w", dir, err)
167+
}
168+
return nil
169+
}
170+
171+
func rename(out io.Writer, from, to string) error {
172+
fmt.Fprintf(out, " $ mv %s %s\n", from, to)
173+
if err := os.Rename(from, to); err != nil {
174+
return fmt.Errorf("mv %s %s: %w", from, to, err)
175+
}
176+
return nil
177+
}

0 commit comments

Comments
 (0)