Developer documentation ‹ 10. cloonix-gui-server and the canvas › 12. Writing a demo
Four places, in this order:
lib_rpc_clownix/src/json_interface.c: the send_xxx, its parser, and a RECV_DEFAULT line at the bottom
lib_rpc_clownix/include/rpc_clownix.h: the declaration, a type_hop value if a new kind of peer
the sender
the receiver, in the binary that answers it
A receiver is a weak default in the library -- it warns and drops -- until a binary defines it; no binary carries a stub for what it does not answer. The same holds for doors_recv_xxx in json_doors.c and rpct_recv_xxx in lib_io_clownix/src/rpct.c. A receiver mis-spelled in a binary is not a link error any more: debug/deadcode.sh lists it as dead.
Two halves that disagree fail at run time with a parse error naming the fctident. A value added to glob_wire.h bumps CLOONIX_WIRE_VERSION and the copy in the python client; debug/check_wire.sh compares them.
A helper the main server starts: copy ovs.c, socket path in argv, retry connecting on a timer, a callback for it dying in automates.c.
A setuid operation: a verb of suid-power, on the socket that exists. Not a second binary.
A client verb: one entry in to_json() of cloonix/cloonix, its completion in cloonix_completion.py, the message in json_interface.c.
The hidden options of the client, all suppressed from its usage: --wait, -s, --cfg, --raw, --completion, and three for the machinery: --via-unix <path> connects straight to a cloonix-cxp-srv unix socket with no cloonix in between, --complete and --complete-usage are what the tab hook calls.
A client is answered status_ok when what it asked for is, status_ko when it cannot be, and never on acceptance: an ok sent when the request was taken reaches the client before the refusal that follows and hides it, and the demos then wait for a machine that never existed. Two shapes exist:
the answer arrives on a message: the record holds cli_llid and cli_tid, the handler of the reply calls utils_send_status_ok or utils_send_status_ko, and a heartbeat counter on the record turns a reply that never comes into a ko -- crun.c for a cnt on crun_start_resp_ok, utils_finish_vm_init for a kvm once qemu has a pid, ovs_a2b.c, ovs_nat_main.c, ovs_tap.c, ovs_phy.c on the pid of their satellite
no message says it: ack_pending() of ack_pending.c keeps the client and polls a test every period until it answers 1, -1 with a reason, or the limit is reached, which is a ko TIMEOUT <verb>; a client gone meanwhile is dropped. cnf ... sniff=on waits this way for the snf process to be up or gone (ovs_snf_state), add c2c for the c2c process to have a pid (ovs_c2c_up_test).
The settings the switch holds itself, x= y=, angle=, mac_mangle=, answer at once, the value being set.
A helper defined between two lines of another function compiles as a nested function under gcc and fails elsewhere: check the brace depth.
glob_include/pthexec.c holds every absolute path of the delivery once, relative to a prefix that pthexec_running_in_crun() chooses at init, /cloonix in the toy and /usr/libexec/cloonix in the bundle: a new path is one X line of PTHEXEC_LIST and its prototype in pthexec.h.
A directory of executables copy_libs does not scan: silent at build, fatal at run.
A qcow2 only grows; qemu-img convert -c compacts one.
A user namespace refuses what capabilities suggest: setgroups denied, chown to an unmapped id EINVAL, a write refused with the full mask.
A text built with a path goes through snprintf with the size of its destination: a sprintf of a 299 byte name into an info or err of MAX_PATH_LEN overran the stack in recv_from_client.c and system_callers.c.
A port open to the network receives anything. fct_seqtap_rx_udp returns kind_seqtap_error with buf NULL on any mismatch of magic, kind or length, and the udp side of a c2c drops that datagram with one warning per period; the stream callers stop, a stream out of step being unrecoverable. debug/sub_c2c_udp_garbage.py holds the case.
A unix socket name is bounded by sun_path, 108 bytes, not by MAX_PATH_LEN: unix_sock() of util_sock.c refuses a longer one.
A fact tested from the wrong namespace is not the fact: if_nametoindex on a guest tap from the main server sees nothing, because the taps live in cloonix_<net>; /proc/<vswitchd_pid>/net/dev lists them.
the nat item and the nat inside proxymous are two things.
cloonix-xwy-srv and cloonix-xwycli in an old log or diagram are the container console before console_crun of suid-power.
KOUT the process cannot continue
KERR an error, or an important warning, through the gate below
KTRC a normal event a reader may one day want; compiled out unless CLOONIX_TRACE
KTRC_ON(flag) the same, behind one file's own flag
Every branch that is not nominal says so with a KERR, WARNING or ERROR first, with the cause (strerror(errno) after a system call). A branch is nominal when it is the expected end of a thing: a peer that closes, a reset from a client killed, a lookup of an object deleted meanwhile. Informational output does not reach syslog by default.
The gate. Each KERR call site has its own gate, glob_include/kerr_gate.h: at most KERR_BURST (10) lines per KERR_WINDOW_MS (10 s); the others are counted, and the next line of that site that passes ends with:
[N line(s) of this site not logged]
A site that repeats is therefore never silent and never a flood. Two sites never share a gate. The loggers of the guest agents, cxp_log of cloonix-cxp-srv, agent_vlog of the windows agent and srv_log of the windows cxp server, use the same gate kept per format string. A counter in the message itself (N dropped so far) stays the way to say how much happened between two lines.
A block of lines that tells one thing, a history, goes through one gate: KERR_HEAD(passed, ...) is its first line and sets passed, and each KERR_MORE(passed, ...) after it is written or left with it, so a history is never cut in its middle. snf_hist_dump() of ovs_snf.c and lan_history_dump() of lan_to_name.c are written that way.
The net name is written once. KERR, KOUT and KTRC stamp the net of the process ahead of the file name, so the text after them names the object and never the net again.
A message in a library and again in its caller, a message naming the functions on the way rather than the event, or a loss told by both ends of one pipe, are the habits that produce volume. The tap child of a2b, c2c and nat and its parent number their frames; on that pipe only the side that drops a frame writes it, with its count, and the side that finds the gap in the numbers says nothing more of it (nat's parent keeps a line: its own state, why it was not reading).
All off but FANOUT_TRACE, which writes into the log of its capture and not into syslog; each in the file whose behaviour it explains, turning one on rebuilds that binary only. The udp sequence gap of a c2c is a WARNING at all times, rate-limited:
ENDP_TRACE uml_cloonix_switch/src/recv_add_lan.c a lan refused because its endpoint was not there
C2C_KILL_TRACE uml_cloonix_switch/src/ovs_c2c.c whether the switch had a handle on a c2c it destroyed
LAN_TRACE uml_cloonix_switch/src/recv_add_lan.c the cabling sequence of a lan
A2B_TRACE uml_cloonix_switch/src/ovs_a2b.c the per-a2b chatter
PCAP_TRACE uml_cloonix_switch/src/pcap_relay.h the capture relay
CRUN_TRACE suid_power/src/crun.c each step of a container create and delete
C2C_TRACE proxymous_passt/src/c2c_peer.c per-attempt c2c peering
PASST_TRACE proxymous_passt/src/passt_spawn.c per-nat passt spawn and stop
DEMO_TRACE proxymous_passt/src/demo_exec.c each step of playing a demo
NAT_TRACE ovs_nat_passt/src/ovs_nat_passt.c the nat's own path
BACKDOOR_TRACE doorways/src/llid_backdoor.c the backdoor legs
FANOUT_TRACE jscloonix/jscloonix/webshark/cloonix_fanout.c on: the capture fan-out, into <n>_<num>_pcap_log, not syslog
A study: rebuild the one binary, run debug/soak.py long enough for the event to happen a handful of times, read the trace lines next to the warning they explain, turn the flag off, and put an answer that is needed again into the message itself. lan_history_dump is the other shape, a 64 entry ring printed by the anomaly itself.
Every fixed delay was measured against the fact it guessed at:
ovs-start wait before qemu -500 ms per guest get_daemon_done() was the test
guest eth ready 250 ms -> the tap seen in /proc/<vswitchd>/net/dev
container delete settle 544 -> 397 ms median 3 beats -> 1, delete_mount_retry never fired
snf kil delay 500 -> 200 ms
kil: tail of shutdown stage 1 -900 ms
kil: ovs destroy countdown -1650 ms a kil went from 4.80 s to 1.24 s
Measured over the CRUN_TRACE timestamps of one run:
CREATE overlay mount -> start median 54 ms
DELETE asked -> freed median 397 ms p90 1057 ms
What is left of a delete is umounts and the crun delete spawn, not a constant.
Run by debug/testitall.sh, no server needed:
debug/check_demos.py tree, summary line, commands printed, images,
guest sources, capture filter words, the
angle= of an eth facing its lan
debug/check_docs.sh binaries named, versions, capture filter words
debug/check_doc_words.sh judgement and filler words
debug/check_cli.py verbs as the client reads them
debug/check_doc_cli.py every *cloonix cli* line of the books and pages,
parsed offline by the client's own code
debug/check_site.py links of the pages, figures named and present,
every svg well-formed and shown by its book
The figure of a demo in the user book is drawn from the LAYOUT, ETHS and WIRING of the demo itself:
debug/demo_svg.py ../demos/routing/babel.py
debug/demo_svg.py --missing
Developer documentation ‹ 10. cloonix-gui-server and the canvas › 12. Writing a demo