skip to content
==> tests/reconcile.c <==
1/* reconcile.rs a_cycle_is_hellod_only_with_its_image_and_task; and passes against a fake driver: loads,
2 * unsupported programs, failures and the retry, a migration (load stopped, view, start), a lockstep
3 * epoch and a cycle with its task, placement on the leader. */
4#include "mesh/abi/reconcile.h"
5#include "mesh/check.h"
6#include "mesh/reconcile.h"
7#include "mesh/mt.h"
8
9#include <stdlib.h>
10
11static mfd_group group(mfd_op op) {
12    return (mfd_group){.name = "train", .program = "train", .epoch = 2, .cycle = 3, .op = op, .members = ml_of((uint32_t[]){0, 1, 2}, 3), .state = MFD_RUNNING};
13}
14
15static void cycle_ready(void) {
16    mfl_actual a = {.name = "train", .dir = "train", .hash = 0xa, .lockstep = true}, b = a;
17    b.hash = 0xb;
18    char *why = NULL;
19    mfd_group up = group((mfd_op){MFD_OP_UPGRADE, 0, 0});
20    CHECK(!mrc_cycle_ready(&up, 0xb, &a, 1, NULL, NULL, &why));
21    CHECK(mt_contains(why, "runs 000000000000000a here, the group 000000000000000b") && mt_contains(why, "paused"));
22    free(why);
23    CHECK(mrc_cycle_ready(&up, 0xb, &b, 1, NULL, NULL, &why));
24    CHECK(!mrc_cycle_ready(&up, 0xb, NULL, 0, NULL, NULL, &why) && mt_contains(why, "not loaded"));
25    free(why);
26    mfd_group t = group((mfd_op){MFD_OP_TASK, 3, 7});
27    bool no = false, yes = true;
28    CHECK(!mrc_cycle_ready(&t, 0xb, &b, 1, &no, "no result in 60s", &why) && mt_contains(why, "until the group's next epoch"));
29    free(why);
30    CHECK(!mrc_cycle_ready(&t, 0xb, &b, 1, NULL, NULL, &why) && mt_ends(why, "not run here"));
31    free(why);
32    CHECK(mrc_cycle_ready(&t, 0xb, &b, 1, &yes, NULL, &why));
33    mfd_group rb = group((mfd_op){MFD_OP_ROLLBACK, 0, 0});
34    CHECK(mrc_cycle_ready(&rb, 0xa, &a, 1, NULL, NULL, &why));
35    ml_free(&up.members), ml_free(&t.members), ml_free(&rb.members);
36}
37
38/* ---- a fake driver */
39
40typedef struct {
41    char name[16], dir[16];
42    uint64_t hash;
43    bool run;
44} prog;
45
46typedef struct {
47    prog p[16];
48    int n;
49    mt_strs calls; /* what the reconciler asked, in order */
50    uint64_t applied;
51    uint32_t last_status;
52    char *last_reason;
53    bool leader, want;
54    mpk_hello hello;
55    bool helloed;
56    uint32_t words[3];
57    const char *no_image; /* this program has no image here: unsupported */
58    const char *fail_load;
59} fake;
60
61static prog *find(fake *f, const char *name) {
62    for (int i = 0; i < f->n; i++)
63        if (!strcmp(f->p[i].name, name)) return &f->p[i];
64    return NULL;
65}
66
67static mfl_actual *inventory(void *ctx, size_t *n) {
68    fake *f = ctx;
69    mfl_actual *a = calloc((size_t)f->n + 1, sizeof *a);
70    for (int i = 0; i < f->n; i++) a[i] = mfl_actual_new(f->p[i].name, f->p[i].dir, f->p[i].hash, false, 0, f->p[i].run);
71    *n = (size_t)f->n;
72    return a;
73}
74
75typedef struct {
76    char name[16], dir[16];
77    uint64_t hash;
78} src_t;
79
80static bool source(void *ctx, const char *name, const char *dir, uint64_t hash, void **src, char **err) {
81    fake *f = ctx;
82    if (f->no_image && !strcmp(name, f->no_image)) {
83        *err = mt_fmt("unsupported: no image of %s for this member", name);
84        return false;
85    }
86    src_t *s = calloc(1, sizeof *s);
87    snprintf(s->name, sizeof s->name, "%s", name), snprintf(s->dir, sizeof s->dir, "%s", dir);
88    s->hash = hash;
89    *src = s;
90    return true;
91}
92
93static void src_free(void *ctx, void *src) { (void)ctx, free(src); }
94
95static bool load(void *ctx, void *src, bool run, char **text, char **err) {
96    fake *f = ctx;
97    src_t *s = src;
98    if (f->fail_load && !strcmp(s->name, f->fail_load)) {
99        *err = mt_strdup("jit: boom");
100        return false;
101    }
102    prog *p = &f->p[f->n++];
103    snprintf(p->name, sizeof p->name, "%s", s->name), snprintf(p->dir, sizeof p->dir, "%s", s->dir);
104    p->hash = s->hash, p->run = run;
105    mt_push_own(&f->calls, mt_fmt("load %s %s", s->name, run ? "running" : "stopped"));
106    *text = mt_strdup("slot 1");
107    return true;
108}
109
110static bool upgrade(void *ctx, const char *name, void *src, char **text, char **err) {
111    (void)err;
112    fake *f = ctx;
113    find(f, name)->hash = ((src_t *)src)->hash;
114    mt_push_own(&f->calls, mt_fmt("upgrade %s", name));
115    *text = mt_strdup("jit 1.0 ms");
116    return true;
117}
118
119static bool unload(void *ctx, const char *name, char **text, char **err) {
120    (void)err;
121    fake *f = ctx;
122    prog *p = find(f, name);
123    *p = f->p[--f->n];
124    mt_push_own(&f->calls, mt_fmt("unload %s", name));
125    *text = mt_strdup("");
126    return true;
127}
128
129static bool simple(fake *f, const char *what, const char *name, char **text) {
130    mt_push_own(&f->calls, mt_fmt("%s %s", what, name));
131    *text = mt_strdup("ok");
132    return true;
133}
134
135static bool rollback(void *ctx, const char *name, char **text, char **err) { return (void)err, simple(ctx, "rollback", name, text); }
136static bool reset(void *ctx, const char *name, char **text, char **err) { return (void)err, simple(ctx, "reset", name, text); }
137
138static bool prog_run(void *ctx, const char *name, bool on, char **text, char **err) {
139    (void)err;
140    fake *f = ctx;
141    find(f, name)->run = on;
142    return simple(f, on ? "start" : "stop", name, text);
143}
144
145static bool task(void *ctx, const char *name, uint32_t op, uint32_t arg, uint32_t *result, char **err) {
146    (void)err;
147    fake *f = ctx;
148    mt_push_own(&f->calls, mt_fmt("task %s %u %u", name, op, arg));
149    *result = op + arg;
150    return true;
151}
152
153static bool place(void *ctx, const char *name, const mt_strs *words, char **text, char **err) {
154    (void)err;
155    fake *f = ctx;
156    char *j = mt_join(words, " ");
157    mt_push_own(&f->calls, mt_fmt("place %s %s", name, j));
158    free(j);
159    *text = mt_strdup("done");
160    return true;
161}
162
163static bool fetch_view(void *ctx, const char *name, uint64_t hash, uint32_t from, uint32_t *abi, mpw_bytes *b, char **err) {
164    (void)hash, (void)err;
165    fake *f = ctx;
166    mt_push_own(&f->calls, mt_fmt("fetch view %s from %u", name, from));
167    *abi = 1;
168    mpw_put(b, "view", 4);
169    return true;
170}
171
172static bool view_put(void *ctx, const char *name, uint32_t abi, const mpw_bytes *b, char **text, char **err) {
173    (void)err;
174    fake *f = ctx;
175    mt_push_own(&f->calls, mt_fmt("view put %s %u %zu", name, abi, b->n));
176    *text = mt_strdup("");
177    return true;
178}
179
180static void quiesce(void *ctx, bool mirror, bool member) {
181    fake *f = ctx;
182    f->want = false;
183    mt_push_own(&f->calls, mt_fmt("quiesce mirror %d member %d", mirror, member));
184}
185
186static bool want_set(void *ctx) { return ((fake *)ctx)->want; }
187
188static void group_words(void *ctx, uint32_t rank, uint32_t n, uint32_t epoch) {
189    fake *f = ctx;
190    f->words[0] = rank, f->words[1] = n, f->words[2] = epoch;
191}
192
193static void ready(void *ctx, const mpk_hello *spec) {
194    fake *f = ctx;
195    f->hello = *spec;
196    f->helloed = f->want = true;
197}
198
199static bool is_leader(void *ctx) { return ((fake *)ctx)->leader; }
200
201static void status(void *ctx, uint64_t applied, uint32_t st, const char *reason, const mst_outcome *m, size_t nm) {
202    (void)m, (void)nm;
203    fake *f = ctx;
204    f->applied = applied, f->last_status = st;
205    mt_set(&f->last_reason, reason);
206}
207
208static void fails(void *ctx, char *const *n, const uint64_t *h, const bool *u, char *const *w, size_t k) { (void)ctx, (void)n, (void)h, (void)u, (void)w, (void)k; }
209static uint64_t applied(void *ctx) { return ((fake *)ctx)->applied; }
210static void logline(void *ctx, bool error, const char *line) { (void)ctx, (void)error, (void)line; }
211static void sleep_ms(void *ctx, uint32_t ms) { (void)ctx, (void)ms; }
212
213static mrc_ops ops(fake *f, uint32_t me) {
214    return (mrc_ops){f, me, inventory, source, src_free, load, upgrade, unload, rollback, prog_run, reset, task, place, fetch_view, view_put,
215                     quiesce, want_set, group_words, ready, is_leader, status, fails, applied, logline, sleep_ms};
216}
217
218static mfd_doc doc(void) {
219    mfd_doc d = {.version = 3, .term = 1};
220    mesh_needs n = msn_default();
221    mfd_prog_put(&d, "hello", mfd_prog_new("hello", 0xabc, true, &n));
222    mfd_prog_put(&d, "tick", mfd_prog_new("tick", 0x123, false, &n));
223    mesh_needs_free(&n);
224    return d;
225}
226
227static bool called(fake *f, const char *what) { return mt_strs_has(&f->calls, what); }
228
229static void passes(void) {
230    fake f = {0};
231    mrc_ops o = ops(&f, 0);
232    mrc_rec *r = mrc_new(&o);
233    mfd_doc d = doc();
234    CHECK(!mrc_document(r, &d));
235    CHECK(called(&f, "load hello running") && called(&f, "load tick stopped") && f.last_status == MESH_RS_SYNC && f.applied == 3);
236    CHECK(mt_starts(f.last_reason, "ok: load hello (slot 1), load tick (slot 1)"));
237    /* In sync: nothing to do. */
238    mt_strs_free(&f.calls);
239    CHECK(!mrc_document(r, &d) && f.calls.n == 0 && !strcmp(f.last_reason, "ok"));
240    /* An upgrade by hash, a program placed elsewhere unloaded, and an unsupported one reported, not failed. */
241    mfd_doc e = mfd_doc_clone(&d);
242    e.version = 4;
243    mfd_prog_get(&e, "hello")->hash = 0xdef;
244    mfd_prog_get(&e, "tick")->at = ml_some((uint32_t[]){2}, 1);
245    mesh_needs n = msn_default();
246    mfd_prog_put(&e, "metal-only", mfd_prog_new("metal-only", 7, true, &n));
247    mesh_needs_free(&n);
248    f.no_image = "metal-only";
249    CHECK(!mrc_document(r, &e));
250    CHECK(called(&f, "upgrade hello") && called(&f, "unload tick") && f.last_status == MESH_RS_SYNC && mt_contains(f.last_reason, "unsupported here: metal-only"));
251    /* A failure: reported, skipped at that hash, and tried again on the retry. */
252    mfd_doc g = mfd_doc_clone(&e);
253    g.version = 5;
254    n = msn_default();
255    mfd_prog_put(&g, "flaky", mfd_prog_new("flaky", 9, true, &n));
256    mesh_needs_free(&n);
257    f.fail_load = "flaky";
258    CHECK(mrc_document(r, &g) && f.last_status == MESH_RS_FAILED && mt_contains(f.last_reason, "load flaky: jit: boom"));
259    mt_strs_free(&f.calls);
260    CHECK(!mrc_document(r, &g)); /* skipped (same version, same hash): nothing failed this pass */
261    CHECK(!called(&f, "load flaky running"));
262    f.fail_load = NULL;
263    CHECK(!mrc_retry(r) && called(&f, "load flaky running") && f.last_status == MESH_RS_SYNC);
264    /* Placement: the leader runs the requests; one dropped from the document goes back to local. */
265    f.leader = true;
266    mfd_doc h = mfd_doc_clone(&g);
267    h.version = 6;
268    mt_strs w = {0};
269    mt_push(&w, "pin");
270    mt_push(&w, "1");
271    mfd_place_put(&h, "hello", w);
272    mt_strs_free(&f.calls);
273    CHECK(!mrc_document(r, &h) && called(&f, "place hello pin 1"));
274    mfd_place_remove(&h, "hello");
275    h.version = 7;
276    mt_strs_free(&f.calls);
277    CHECK(!mrc_document(r, &h) && called(&f, "place hello local"));
278    mfd_doc_free(&d), mfd_doc_free(&e), mfd_doc_free(&g), mfd_doc_free(&h);
279    mrc_free(r);
280    mt_strs_free(&f.calls);
281    free(f.last_reason);
282}
283
284static void migration(void) {
285    fake f = {0};
286    mrc_ops o = ops(&f, 1);
287    mrc_rec *r = mrc_new(&o);
288    mfd_doc d = {.version = 8, .term = 1};
289    mesh_needs n = msn_default();
290    msn_strs_push(&n.feeds, &n.nfeeds, "pipehash");
291    mfd_prog src = mfd_prog_new("pipesrc", 1, true, &n);
292    mesh_needs_free(&n);
293    n = msn_default();
294    mfd_prog hash = mfd_prog_new("pipehash", 2, true, &n);
295    mesh_needs_free(&n);
296    src.at = ml_some((uint32_t[]){1}, 1), hash.at = ml_some((uint32_t[]){1}, 1);
297    src.has_from = hash.has_from = src.has_to = hash.has_to = true;
298    src.from = hash.from = 2, src.to = hash.to = 1, src.since = hash.since = 8;
299    mfd_prog_put(&d, "pipesrc", src);
300    mfd_prog_put(&d, "pipehash", hash);
301    CHECK(!mrc_document(r, &d));
302    /* Loaded stopped, views producers first, started consumers first. */
303    long ls = mt_strs_find(&f.calls, "load pipesrc stopped"), lh = mt_strs_find(&f.calls, "load pipehash stopped");
304    long vs = mt_strs_find(&f.calls, "fetch view pipesrc from 2"), vh = mt_strs_find(&f.calls, "fetch view pipehash from 2");
305    long ss = mt_strs_find(&f.calls, "start pipesrc"), sh = mt_strs_find(&f.calls, "start pipehash");
306    CHECK(ls >= 0 && lh > ls && vs > lh && vh > vs && sh > vh && ss > sh);
307    CHECK(called(&f, "view put pipehash 1 4") && mt_contains(f.last_reason, "migrate pipehash,pipesrc from 2: view 8 B"));
308    mfd_doc_free(&d);
309    mrc_free(r);
310    mt_strs_free(&f.calls);
311    free(f.last_reason);
312}
313
314static void lockstep(void) {
315    fake f = {0};
316    mrc_ops o = ops(&f, 2);
317    mrc_rec *r = mrc_new(&o);
318    mfd_doc d = {.version = 2, .term = 1};
319    mesh_needs n = msn_default();
320    mfd_prog t = mfd_prog_new("train", 0x77, true, &n);
321    t.lockstep = true;
322    mfd_prog_put(&d, "train", t);
323    mesh_needs_free(&n);
324    mfd_group_push(&d, (mfd_group){.name = mt_strdup("train"), .program = mt_strdup("train"), .epoch = 1, .members = ml_of((uint32_t[]){0, 2}, 2), .state = MFD_RUNNING});
325    CHECK(!mrc_document(r, &d));
326    CHECK(called(&f, "quiesce mirror 0 member 1") && called(&f, "load train running") && called(&f, "reset train"));
327    CHECK(f.words[0] == 1 && f.words[1] == 2 && f.words[2] == 1 && f.helloed && f.hello.epoch == 1 && f.hello.cycle == 0 && f.hello.mask == 5 && f.hello.hash == 0x77);
328    CHECK(f.hello.order[0] == 0 && f.hello.order[1] == 2 && f.hello.order[2] == 0xff);
329    /* A task cycle: quiesced with rounds mirrored, the task run, HELLOed. */
330    mfd_doc c = mfd_doc_clone(&d);
331    c.version = 3;
332    c.groups[0].cycle = 1;
333    c.groups[0].op = (mfd_op){MFD_OP_TASK, 3, 4};
334    f.helloed = false;
335    mt_strs_free(&f.calls);
336    CHECK(!mrc_document(r, &c));
337    CHECK(called(&f, "quiesce mirror 1 member 1") && called(&f, "task train 3 4") && f.helloed && f.hello.cycle == 1);
338    CHECK(mt_contains(f.last_reason, "task train -> 7 (cycle 1)"));
339    /* Paused: unfed, reported. */
340    mfd_doc p = mfd_doc_clone(&c);
341    p.version = 4;
342    p.groups[0].state = MFD_PAUSED;
343    f.want = true;
344    mt_strs_free(&f.calls);
345    CHECK(!mrc_document(r, &p) && called(&f, "quiesce mirror 0 member 1") && mt_contains(f.last_reason, "group train epoch 1: paused"));
346    /* The lockstep program leaves the document: its group goes too. */
347    mfd_doc z = {.version = 5, .term = 1};
348    f.want = false;
349    mt_strs_free(&f.calls);
350    CHECK(!mrc_document(r, &z));
351    mfd_doc_free(&d), mfd_doc_free(&c), mfd_doc_free(&p), mfd_doc_free(&z);
352    mrc_free(r);
353    mt_strs_free(&f.calls);
354    free(f.last_reason);
355}
356
357int main(void) {
358    cycle_ready();
359    passes();
360    migration();
361    lockstep();
362    return mesh_check_done();
363}