From 57f788f4dc1468807a2d78f7effbbaddcae6f36c Mon Sep 17 00:00:00 2001
From: John Estabrook <jestabro@vyos.io>
Date: Sun, 16 Mar 2025 22:59:46 -0500
Subject: T7121: add commitd client operation and test function

---
 src/vycall_client.ml | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 124 insertions(+)
 create mode 100644 src/vycall_client.ml

(limited to 'src/vycall_client.ml')

diff --git a/src/vycall_client.ml b/src/vycall_client.ml
new file mode 100644
index 0000000..c7fc811
--- /dev/null
+++ b/src/vycall_client.ml
@@ -0,0 +1,124 @@
+(* send commit data to Python commit daemon *)
+
+open Vycall_message.Vycall_pbt
+open Vyconfd_config.Commit
+
+module CT = Vyos1x.Config_tree
+module IC = Vyos1x.Internal.Make(CT)
+module ST = Vyconfd_config.Startup
+module DF = Vyconfd_config.Defaults
+module FP = FilePath
+
+type t = {
+    ic: Lwt_io.input Lwt_io.channel;
+    oc: Lwt_io.output Lwt_io.channel;
+}
+
+(* explicit translation between commit data and commit protobuf
+ * to keep the commit data opaque to protobuf message definition.
+ * The commit daemon updates the (subset of) commit data with
+ * results of script execution in init/reply fields.
+ *)
+let node_data_to_call nd =
+    { script_name = nd.script_name;
+      tag_value = nd.tag_value;
+      arg_value = nd.arg_value;
+      reply = None
+    }
+
+let call_to_node_data ((c: call), (nd: node_data)) =
+    match c.reply with
+    | None -> nd
+    | Some r -> { nd with reply = Some { success = r.success; out = r.out }}
+
+let commit_data_to_commit_proto cd =
+    { session_id = cd.session_id;
+      named_active = cd.named_active;
+      named_proposed = cd.named_proposed;
+      dry_run = cd.dry_run;
+      atomic = cd.atomic;
+      background = cd.background;
+      init = None;
+      calls = List.map node_data_to_call cd.node_list;
+    }
+
+let commit_proto_to_commit_data (c: commit) (cd: commit_data) =
+    match c.init with
+    | None -> cd
+    | Some i ->
+        { cd with init = Some { success = i.success; out = i.out };
+          node_list =
+              List.map call_to_node_data (List.combine c.calls cd.node_list);
+        }
+
+(* read/write message from/to socket *)
+let call_write oc msg =
+    let length = Bytes.length msg in
+    let length' = Int32.of_int length in
+    if length' < 0l then failwith (Printf.sprintf "Bad message length: %d" length) else
+    let header = Bytes.create 4 in
+    let () = EndianBytes.BigEndian.set_int32 header 0 length' in
+    let%lwt () = Lwt_io.write_from_exactly oc header 0 4 in
+    Lwt_io.write_from_exactly oc msg 0 length
+
+let call_read ic =
+    let header = Bytes.create 4 in
+    let%lwt () = Lwt_io.read_into_exactly ic header 0 4 in
+    let length = EndianBytes.BigEndian.get_int32 header 0 |> Int32.to_int in
+    if length < 0 then failwith (Printf.sprintf "Bad message length: %d" length) else
+    let buffer = Bytes.create length in
+    let%lwt () = Lwt_io.read_into_exactly ic buffer 0 length in
+    Lwt.return buffer
+
+(* encode/decode commit data *)
+let do_call client request =
+    let enc = Pbrt.Encoder.create () in
+    let () = encode_pb_commit request enc in
+    let msg = Pbrt.Encoder.to_bytes enc in
+    let%lwt () = call_write client.oc msg in
+    let%lwt resp = call_read client.ic in
+    decode_pb_commit (Pbrt.Decoder.of_bytes resp) |> Lwt.return
+
+(* socket management and commit callback *)
+let create sockfile =
+    let open Lwt_unix in
+    let sock = socket PF_UNIX SOCK_STREAM 0 in
+    let%lwt () = connect sock (ADDR_UNIX sockfile) in
+    let ic = Lwt_io.of_fd ~mode:Lwt_io.Input sock in
+    let oc = Lwt_io.of_fd ~mode:Lwt_io.Output sock in
+    Lwt.return { ic=ic; oc=oc; }
+
+let update session_data =
+    Lwt.return (commit_store session_data)
+
+let do_commit session_data =
+    let session = commit_data_to_commit_proto session_data in
+    let run () =
+        let sockfile = "/run/vyos-commitd.sock" in
+        let%lwt client = create sockfile in
+        let%lwt resp = do_call client session in
+        let%lwt () = Lwt_io.close client.oc in
+        update (commit_proto_to_commit_data resp session_data)
+    in Lwt_main.run @@ run ()
+
+(* test function *)
+let test_commit at wt =
+    let vc =
+        ST.load_daemon_config DF.defaults.config_file in
+    let () =
+        IC.write_internal at (FP.concat vc.session_dir vc.running_cache) in
+    let () =
+        IC.write_internal wt (FP.concat vc.session_dir vc.session_cache) in
+    let rt_opt =
+        ST.read_reference_tree (FP.concat vc.reftree_dir vc.reference_tree)
+    in
+    match rt_opt with
+    | Error msg -> print_endline msg
+    | Ok rt ->
+        let del_list, add_list =
+            calculate_priority_lists rt at wt
+        in
+        let commit_session =
+            { default_commit_data with node_list = del_list @ add_list }
+        in
+        do_commit commit_session
-- 
cgit v1.2.3