From 9835cb3213035f7602316e59c2a735b3fd879e3b Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 24 Jan 2025 10:06:54 +0100 Subject: [PATCH 01/36] function to post a request to update the binary --- assets/main.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/assets/main.js b/assets/main.js index 292812ab..2f216784 100644 --- a/assets/main.js +++ b/assets/main.js @@ -920,11 +920,43 @@ async function updateToken(value) { buttonLoading(tokenButton, false, "Update Token") } } catch (error) { - postAlert("bg-secondary-300", data.data); + postAlert("bg-secondary-300", error); buttonLoading(tokenButton, false, "Update Token") } } +async function updateUnikernel(job, build) { + const updateButton = document.getElementById("update-unikernel-button"); + const molly_csrf = document.getElementById("molly-csrf").value; + try { + buttonLoading(updateButton, true, "Updating...") + const response = await fetch("/api/unikernel/update", { + method: 'POST', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify( + { + "job": job, + "build": build, + "molly_csrf": molly_csrf + }) + }) + const data = await response.json(); + if (data.status === 200) { + postAlert("bg-primary-300", "Unikernel updated succesfully"); + setTimeout(() => window.location.reload(), 1000); + buttonLoading(updateButton, false, "Update to Latest") + } else { + postAlert("bg-secondary-300", data.data); + buttonLoading(updateButton, false, "Update to Latest") + } + } catch (error) { + postAlert("bg-secondary-300", error); + buttonLoading(updateButton, false, "Update to Latest") + } +} + function isValidName(s) { const length = s.length; if (length === 0 || length >= 64) return false; From 6516b855804b8283c6cba9c728e8671d0d6ed6ed Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 24 Jan 2025 10:07:14 +0100 Subject: [PATCH 02/36] add alert to unikernel update page --- unikernel_update.ml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/unikernel_update.ml b/unikernel_update.ml index 2d81774d..4ffb7760 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -265,6 +265,9 @@ let unikernel_update_layout unikernel current_time section ~a:[ a_class [ "col-span-10 p-4 bg-gray-50 my-1" ] ] [ + p + ~a:[ a_id "unikernel-update-form-alert"; a_class [ "my-4 hidden" ] ] + []; div ~a:[ a_id "unikernel-container"; a_class [ "p-4 rounded-md" ] ] [ From 75ef5f68e6afdc396b3ee38ef04b204f0b022b33 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 24 Jan 2025 10:07:33 +0100 Subject: [PATCH 03/36] use buttons and not anchor links --- unikernel_update.ml | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/unikernel_update.ml b/unikernel_update.ml index 4ffb7760..eaccd93c 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -305,18 +305,17 @@ let unikernel_update_layout unikernel current_time ]; div [ - a - ~a: + Utils.button_component + ~attribs: [ - a_href "/unikernel/update/"; - a_class - [ - "py-2 px-2 rounded hover:bg-primary-800 \ - text-gray-50 focus:outline-none \ - bg-primary-500 font-semibold"; - ]; + a_id "update-unikernel-button"; + a_onclick + ("updateUnikernel('" + ^ build_comparison.right.job ^ "','" + ^ build_comparison.right.uuid ^ "')"); ] - [ txt "Update to Latest" ]; + ~content:(txt "Update to Latest") + ~btn_type:`Primary_full (); ]; ]; div @@ -437,16 +436,14 @@ let unikernel_update_layout unikernel current_time ]; div [ - a - ~a: + Utils.button_component + ~attribs: [ - a_href "/unikernel/update/"; - a_class - [ - "py-2 px-2 rounded hover:bg-primary-800 text-gray-50 \ - focus:outline-none bg-primary-500 font-semibold"; - ]; + a_id "update-unikernel-button"; + a_onclick + ("updateUnikernel('" ^ build_comparison.right.job ^ "','" + ^ build_comparison.right.uuid ^ "')"); ] - [ txt "Update to Latest" ]; + ~content:(txt "Update to Latest") ~btn_type:`Primary_full (); ]; ]) From 14e568cc8f0b63f45a3281d165e94ed6ec3f8343 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 24 Jan 2025 10:07:52 +0100 Subject: [PATCH 04/36] add endpoint to process unikernel binary updates --- unikernel.ml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/unikernel.ml b/unikernel.ml index 79d6e13c..196cab24 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -2125,6 +2125,11 @@ struct authenticate store reqd (unikernel_prepare_update !albatross store unikernel_name reqd http_client)) + | "/api/unikernel/update" -> + check_meth `POST (fun () -> + authenticate ~check_token:true ~check_csrf:true ~api_meth:true + store reqd + (unikernel_update !albatross reqd http_client)) | _ -> let error = { From 9ef1fc2e55a19124c510245c41e7a789873eec09 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 24 Jan 2025 10:08:08 +0100 Subject: [PATCH 05/36] add function to process unikernel binary updates --- unikernel.ml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/unikernel.ml b/unikernel.ml index 196cab24..d8307bbf 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1240,6 +1240,34 @@ struct ~api_meth:false `Internal_server_error reqd ())))))) + let unikernel_update _albatross reqd http_client ~json_dict + (_user : User_model.user) = + match Utils.Json.(get "job" json_dict, get "build" json_dict) with + | Some (`String job), Some (`String build) -> ( + Builder_web.send_request http_client + ("/job/" ^ job ^ "/build/" ^ build ^ "/main-binary") + >>= function + | Error (`Msg err) -> + Logs.err (fun m -> + m + "builds.robur.coop: Error while fetching the binary of with \ + error: %s" + err); + Middleware.http_response reqd ~title:"Error" + ~data: + (`String + ("An error occured while fetching the binary from \ + builds.robur.coop with error " ^ err)) + `Internal_server_error + | Ok new_binary -> + Logs.err (fun m -> m "The binary is %s" new_binary); + Middleware.http_response reqd ~title:"Error" + ~data:(`String "Couldn't find job or build in json") `Bad_request) + | _ -> + Middleware.http_response reqd ~title:"Error" + ~data:(`String "Couldn't find job or build in json. Received ") + `Bad_request + let unikernel_destroy ~json_dict albatross reqd (user : User_model.user) = (* TODO use uuid in the future *) match Utils.Json.get "name" json_dict with From cd0b13420878d4ede7a5e2f390537569005fd664 Mon Sep 17 00:00:00 2001 From: Robur Team Date: Fri, 24 Jan 2025 10:38:55 +0000 Subject: [PATCH 06/36] check manifest diff of bridges and block devices --- albatross.ml | 31 +++++++++++++++++++++++++++++++ config.ml | 1 + 2 files changed, 32 insertions(+) diff --git a/albatross.ml b/albatross.ml index 5337ad4d..4533f93b 100644 --- a/albatross.ml +++ b/albatross.ml @@ -91,6 +91,37 @@ struct } | Error (`Msg err) -> Error err + let owee_buf_of_str b = + let buf = Bigarray.Array1.create Bigarray.Int8_unsigned Bigarray.c_layout (String.length b) in + for i = 0 to String.length b - 1 do + buf.{i} <- String.get_uint8 b i + done; + buf + + let manifest_devices_match ~bridges ~block_devices binary = + let mft : Solo5_elftool.mft = Solo5_elftool.query_manifest (owee_buf_of_str binary) in + let bridges = List.map (fun (name, _, _) -> name) bridges + and block_devices = List.map (fun (name, _, _) -> name) block_devices in + let bridges' = + List.filter_map + (function + | Solo5_elftool.Dev_net_basic name -> Some name + | _ -> None) + mft.Solo5_elftool.entries + and block_devices' = + List.filter_map + (function + | Solo5_elftool.Dev_block_basic -> Some name + | _ -> None) + mft.Solo5_elftool.entries + in + List.equal String.equal + (List.sort String.compare bridges) + (List.sort String.compare bridges') && + List.equal String.equal + (List.sort String.compare block_devices) + (List.sort String.compare block_devices') + let key_ids exts pub issuer = let open X509 in let auth = (Some (Public_key.id issuer), General_name.empty, None) in diff --git a/config.ml b/config.ml index 4b98b9ae..b57dbd64 100644 --- a/config.ml +++ b/config.ml @@ -23,6 +23,7 @@ let mollymawk = package "duration"; package ~min:"0.2.0" "ohex"; package "http-mirage-client"; + package "solo5-elftool"; ] in main ~packages "Unikernel.Main" From c752556a3a215f0e08698b92a82f0347ce79149b Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Wed, 29 Jan 2025 13:37:13 +0100 Subject: [PATCH 07/36] use solo5-elftool without owee --- albatross.ml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/albatross.ml b/albatross.ml index 4533f93b..6f8ef8a3 100644 --- a/albatross.ml +++ b/albatross.ml @@ -91,13 +91,6 @@ struct } | Error (`Msg err) -> Error err - let owee_buf_of_str b = - let buf = Bigarray.Array1.create Bigarray.Int8_unsigned Bigarray.c_layout (String.length b) in - for i = 0 to String.length b - 1 do - buf.{i} <- String.get_uint8 b i - done; - buf - let manifest_devices_match ~bridges ~block_devices binary = let mft : Solo5_elftool.mft = Solo5_elftool.query_manifest (owee_buf_of_str binary) in let bridges = List.map (fun (name, _, _) -> name) bridges From 4f62a043a17c4ee43bc36b312270aca80e3240fc Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Wed, 29 Jan 2025 13:37:41 +0100 Subject: [PATCH 08/36] pass bridge or block diffs in error message --- albatross.ml | 60 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/albatross.ml b/albatross.ml index 6f8ef8a3..d1284e5a 100644 --- a/albatross.ml +++ b/albatross.ml @@ -1,3 +1,7 @@ +let ( let* ) = Result.bind + +module String_set = Set.Make (String) + module Make (T : Mirage_time.S) (P : Mirage_clock.PCLOCK) (S : Tcpip.Stack.V4V6) = struct module TLS = Tls_mirage.Make (S.TCP) @@ -91,29 +95,39 @@ struct } | Error (`Msg err) -> Error err - let manifest_devices_match ~bridges ~block_devices binary = - let mft : Solo5_elftool.mft = Solo5_elftool.query_manifest (owee_buf_of_str binary) in - let bridges = List.map (fun (name, _, _) -> name) bridges - and block_devices = List.map (fun (name, _, _) -> name) block_devices in - let bridges' = - List.filter_map - (function - | Solo5_elftool.Dev_net_basic name -> Some name - | _ -> None) - mft.Solo5_elftool.entries - and block_devices' = - List.filter_map - (function - | Solo5_elftool.Dev_block_basic -> Some name - | _ -> None) - mft.Solo5_elftool.entries - in - List.equal String.equal - (List.sort String.compare bridges) - (List.sort String.compare bridges') && - List.equal String.equal - (List.sort String.compare block_devices) - (List.sort String.compare block_devices') + let manifest_devices_match ~bridges ~block_devices binary = + let* mft : Solo5_elftool.mft = + Solo5_elftool.query_manifest binary + in + let bridges = List.map (fun (name, _, _) -> name) bridges + and block_devices = List.map (fun (name, _, _) -> name) block_devices in + let bridges' = + List.filter_map + (function Solo5_elftool.Dev_net_basic name -> Some name | _ -> None) + mft.Solo5_elftool.entries + and block_devices' = + List.filter_map + (function Solo5_elftool.Dev_block_basic name -> Some name | _ -> None) + mft.Solo5_elftool.entries + in + let bridges_diff = + String_set.(diff (of_list bridges') (of_list bridges) |> elements) + in + let blocks_diff = + String_set.( + diff (of_list block_devices') (of_list block_devices) |> elements) + in + match (bridges_diff, blocks_diff) with + | [], [] -> Ok () + | [], blocks -> + Error (`Msg ("Block devices mismatch: " ^ String.concat "," blocks)) + | bridges, [] -> + Error (`Msg ("Network devices mismatch: " ^ String.concat "," bridges)) + | bridges, blocks -> + Error + (`Msg + ("Block devices mismatch: " ^ String.concat "," blocks + ^ " and network devices mismatch: " ^ String.concat "," bridges)) let key_ids exts pub issuer = let open X509 in From d1d33793fc914a4b1fe3c1eca2bc3bef9b85f0cc Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Wed, 29 Jan 2025 13:38:57 +0100 Subject: [PATCH 09/36] pass unikernel name in post request to unikernel update function --- assets/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/main.js b/assets/main.js index 2f216784..1466c3e9 100644 --- a/assets/main.js +++ b/assets/main.js @@ -925,7 +925,7 @@ async function updateToken(value) { } } -async function updateUnikernel(job, build) { +async function updateUnikernel(job, build, unikernel_name) { const updateButton = document.getElementById("update-unikernel-button"); const molly_csrf = document.getElementById("molly-csrf").value; try { @@ -939,6 +939,7 @@ async function updateUnikernel(job, build) { { "job": job, "build": build, + "unikernel_name": unikernel_name, "molly_csrf": molly_csrf }) }) From 81ccc1ae19fc237ef79554c7d6221db15888f8d0 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Wed, 29 Jan 2025 13:41:04 +0100 Subject: [PATCH 10/36] use unikernel name in update unikernel layout --- unikernel.ml | 2 +- unikernel_update.ml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/unikernel.ml b/unikernel.ml index d8307bbf..785215cd 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1203,7 +1203,7 @@ struct ^ " Update | Mollymawk") ~content: (Unikernel_update - .unikernel_update_layout + .unikernel_update_layout ~unikernel_name:name (unikernel_name, unikernel) now build_comparison) ~icon:"/images/robur.png" ()) diff --git a/unikernel_update.ml b/unikernel_update.ml index eaccd93c..d0f8560f 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -258,7 +258,7 @@ let opam_diff_table (diffs : Builder_web.o_diff list) = ]) diffs)) -let unikernel_update_layout unikernel current_time +let unikernel_update_layout ~unikernel_name unikernel current_time (build_comparison : Builder_web.compare) = let u_name, data = unikernel in Tyxml_html.( @@ -442,7 +442,7 @@ let unikernel_update_layout unikernel current_time a_id "update-unikernel-button"; a_onclick ("updateUnikernel('" ^ build_comparison.right.job ^ "','" - ^ build_comparison.right.uuid ^ "')"); + ^ build_comparison.right.uuid ^ "','"^unikernel_name^"')"); ] ~content:(txt "Update to Latest") ~btn_type:`Primary_full (); ]; From a14fd893f0ccb2482e0344146614a4f6050be7bb Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Wed, 29 Jan 2025 13:52:24 +0100 Subject: [PATCH 11/36] update to perform a manifest check before updating --- unikernel.ml | 119 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 21 deletions(-) diff --git a/unikernel.ml b/unikernel.ml index 785215cd..6c2f5392 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1240,29 +1240,106 @@ struct ~api_meth:false `Internal_server_error reqd ())))))) - let unikernel_update _albatross reqd http_client ~json_dict - (_user : User_model.user) = - match Utils.Json.(get "job" json_dict, get "build" json_dict) with - | Some (`String job), Some (`String build) -> ( - Builder_web.send_request http_client - ("/job/" ^ job ^ "/build/" ^ build ^ "/main-binary") - >>= function - | Error (`Msg err) -> - Logs.err (fun m -> - m - "builds.robur.coop: Error while fetching the binary of with \ - error: %s" - err); - Middleware.http_response reqd ~title:"Error" + let unikernel_update albatross reqd http_client ~json_dict + (user : User_model.user) = + match + Utils.Json. + ( get "job" json_dict, + get "build" json_dict, + get "unikernel_name" json_dict ) + with + | Some (`String job), Some (`String build), Some (`String unikernel_name) + -> ( + user_unikernel albatross ~user_name:user.name ~unikernel_name + >>= fun unikernel_info -> + match unikernel_info with + | Error err -> + Middleware.redirect_to_error ~data: (`String - ("An error occured while fetching the binary from \ - builds.robur.coop with error " ^ err)) - `Internal_server_error - | Ok new_binary -> - Logs.err (fun m -> m "The binary is %s" new_binary); - Middleware.http_response reqd ~title:"Error" - ~data:(`String "Couldn't find job or build in json") `Bad_request) + ("An error occured while fetching " ^ unikernel_name + ^ " from albatross with error " ^ err)) + ~title:"Albatross Error" ~api_meth:false `Internal_server_error + reqd () + | Ok + ( _, + Vmm_core.Unikernel. + { + bridges; + block_devices; + argv; + cpuid; + memory; + fail_behaviour; + typ = `Solo5 as typ; + _; + } ) -> ( + Builder_web.send_request http_client + ("/job/" ^ job ^ "/build/" ^ build ^ "/main-binary") + >>= function + | Error (`Msg err) -> + Logs.err (fun m -> + m + "builds.robur.coop: Error while fetching the binary of \ + with error: %s" + err); + Middleware.http_response reqd ~title:"Error" + ~data: + (`String + ("An error occured while fetching the binary from \ + builds.robur.coop with error " ^ err)) + `Internal_server_error + | Ok image -> ( + Logs.info (fun m -> m "The binary is %s" image); + let config = + { + Vmm_core.Unikernel.typ; + compressed = true; (*compressed here is assumed to be true.*) + image; + fail_behaviour; + cpuid; + memory; + block_devices; + bridges; + argv; + } + in + match + Albatross.manifest_devices_match ~bridges ~block_devices image + with + | Error (`Msg err) -> + Middleware.http_response reqd ~title:"Error" + ~data:(`String ("Manifest mismatch: " ^ err)) + `Bad_request + | Ok () -> ( + Albatross.query albatross ~domain:user.name + ~name:unikernel_name + (`Unikernel_cmd (`Unikernel_force_create config)) + >>= function + | Error msg -> + Logs.err (fun m -> m "Error querying albatross: %s" msg); + Middleware.http_response reqd ~title:"Error" + ~data:(`String ("Error querying albatross: " ^ msg)) + `Internal_server_error + | Ok (_hdr, res) -> ( + match Albatross_json.res res with + | Error (`String err) -> + Middleware.http_response reqd ~title:"Error" + ~data:(`String (String.escaped err)) + `Internal_server_error + | Ok res -> + Logs.err (fun m -> + m + "%s has been updated succesfully with \ + result: %s" + unikernel_name + (Yojson.Basic.to_string res)); + Middleware.http_response reqd ~title:"Error" + ~data: + (`String + (unikernel_name + ^ " has been updated to the latest build.")) + `OK))))) | _ -> Middleware.http_response reqd ~title:"Error" ~data:(`String "Couldn't find job or build in json. Received ") From 15af0fca678c088650bbd5eb007b8eb54b8d5748 Mon Sep 17 00:00:00 2001 From: "Automated ocamlformat GitHub action, developed by robur.coop" Date: Wed, 29 Jan 2025 13:02:37 +0000 Subject: [PATCH 12/36] formatted code --- albatross.ml | 4 +--- unikernel.ml | 6 ++++-- unikernel_update.ml | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/albatross.ml b/albatross.ml index d1284e5a..7f5cebfe 100644 --- a/albatross.ml +++ b/albatross.ml @@ -96,9 +96,7 @@ struct | Error (`Msg err) -> Error err let manifest_devices_match ~bridges ~block_devices binary = - let* mft : Solo5_elftool.mft = - Solo5_elftool.query_manifest binary - in + let* mft : Solo5_elftool.mft = Solo5_elftool.query_manifest binary in let bridges = List.map (fun (name, _, _) -> name) bridges and block_devices = List.map (fun (name, _, _) -> name) block_devices in let bridges' = diff --git a/unikernel.ml b/unikernel.ml index 6c2f5392..af0b95d4 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1203,7 +1203,8 @@ struct ^ " Update | Mollymawk") ~content: (Unikernel_update - .unikernel_update_layout ~unikernel_name:name + .unikernel_update_layout + ~unikernel_name:name (unikernel_name, unikernel) now build_comparison) ~icon:"/images/robur.png" ()) @@ -1294,7 +1295,8 @@ struct let config = { Vmm_core.Unikernel.typ; - compressed = true; (*compressed here is assumed to be true.*) + compressed = true; + (*compressed here is assumed to be true.*) image; fail_behaviour; cpuid; diff --git a/unikernel_update.ml b/unikernel_update.ml index d0f8560f..09e1f4dc 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -442,7 +442,8 @@ let unikernel_update_layout ~unikernel_name unikernel current_time a_id "update-unikernel-button"; a_onclick ("updateUnikernel('" ^ build_comparison.right.job ^ "','" - ^ build_comparison.right.uuid ^ "','"^unikernel_name^"')"); + ^ build_comparison.right.uuid ^ "','" ^ unikernel_name ^ "')" + ); ] ~content:(txt "Update to Latest") ~btn_type:`Primary_full (); ]; From 727f92dfeb137d50a134c34fa1363222df53bfa1 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 31 Jan 2025 06:57:05 +0100 Subject: [PATCH 13/36] minor refactorings --- unikernel.ml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/unikernel.ml b/unikernel.ml index af0b95d4..d08dd4fa 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1282,8 +1282,8 @@ struct Logs.err (fun m -> m "builds.robur.coop: Error while fetching the binary of \ - with error: %s" - err); + %s with error: %s" + unikernel_name err); Middleware.http_response reqd ~title:"Error" ~data: (`String @@ -1291,7 +1291,6 @@ struct builds.robur.coop with error " ^ err)) `Internal_server_error | Ok image -> ( - Logs.info (fun m -> m "The binary is %s" image); let config = { Vmm_core.Unikernel.typ; From de9bfbfa8ecc0007dff3ea1ebb2614619daf70a0 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 31 Jan 2025 06:57:22 +0100 Subject: [PATCH 14/36] properly handle device manifest --- albatross.ml | 94 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 19 deletions(-) diff --git a/albatross.ml b/albatross.ml index 7f5cebfe..9050730d 100644 --- a/albatross.ml +++ b/albatross.ml @@ -97,35 +97,91 @@ struct let manifest_devices_match ~bridges ~block_devices binary = let* mft : Solo5_elftool.mft = Solo5_elftool.query_manifest binary in - let bridges = List.map (fun (name, _, _) -> name) bridges - and block_devices = List.map (fun (name, _, _) -> name) block_devices in - let bridges' = + let req_bridges = + List.map (fun (name, _, _) -> name) bridges |> String_set.of_list + and req_block_devices = + List.map (fun (name, _, _) -> name) block_devices |> String_set.of_list + and mft_bridges = List.filter_map (function Solo5_elftool.Dev_net_basic name -> Some name | _ -> None) mft.Solo5_elftool.entries - and block_devices' = + |> String_set.of_list + and mft_block_devices = List.filter_map (function Solo5_elftool.Dev_block_basic name -> Some name | _ -> None) mft.Solo5_elftool.entries + |> String_set.of_list in - let bridges_diff = - String_set.(diff (of_list bridges') (of_list bridges) |> elements) + let req_only_bridges = String_set.(diff req_bridges mft_bridges |> elements) + and mft_only_bridges = String_set.(diff mft_bridges req_bridges |> elements) + and req_only_blocks = + String_set.(diff req_block_devices mft_block_devices |> elements) + and mft_only_blocks = + String_set.(diff mft_block_devices req_block_devices |> elements) in - let blocks_diff = - String_set.( - diff (of_list block_devices') (of_list block_devices) |> elements) - in - match (bridges_diff, blocks_diff) with - | [], [] -> Ok () - | [], blocks -> - Error (`Msg ("Block devices mismatch: " ^ String.concat "," blocks)) - | bridges, [] -> - Error (`Msg ("Network devices mismatch: " ^ String.concat "," bridges)) - | bridges, blocks -> + match + (req_only_bridges, mft_only_bridges, req_only_blocks, mft_only_blocks) + with + | [], [], [], [] -> Ok () + | req_only_bridges, [], [], [] -> + Error + (`Msg + ("Network devices missing from manifest: " + ^ String.concat "," req_only_bridges)) + | [], mft_only_bridges, [], [] -> + Error + (`Msg + ("Network devices only in manifest: " + ^ String.concat "," mft_only_bridges)) + | [], [], req_only_blocks, [] -> + Error + (`Msg + ("Block devices missing from manifest: " + ^ String.concat "," req_only_blocks)) + | [], [], [], mft_only_blocks -> + Error + (`Msg + ("Block devices only in manifest: " + ^ String.concat "," mft_only_blocks)) + | req_only_bridges, req_only_blocks, [], [] -> + Error + (`Msg + ("Network devices missing from manifest: " + ^ String.concat "," req_only_bridges + ^ " and block devices missing from manifest: " + ^ String.concat "," req_only_blocks)) + | [], req_only_blocks, mft_only_bridges, [] -> + Error + (`Msg + ("Network devices only in manifest: " + ^ String.concat "," mft_only_bridges + ^ " and block devices missing from manifest: " + ^ String.concat "," req_only_blocks)) + | [], [], mft_only_blocks, req_only_bridges -> + Error + (`Msg + ("Network devices missing from manifest: " + ^ String.concat "," req_only_bridges + ^ " and block devices only in manifest: " + ^ String.concat "," mft_only_blocks)) + | req_only_bridges, [], mft_only_blocks, [] -> + Error + (`Msg + ("Network devices missing from manifest: " + ^ String.concat "," req_only_bridges + ^ " and block devices only in manifest: " + ^ String.concat "," mft_only_blocks)) + | req_only_bridges, req_only_blocks, mft_only_bridges, mft_only_blocks -> Error (`Msg - ("Block devices mismatch: " ^ String.concat "," blocks - ^ " and network devices mismatch: " ^ String.concat "," bridges)) + ("Network devices missing from manifest: " + ^ String.concat "," req_only_bridges + ^ " and block devices missing from manifest: " + ^ String.concat "," req_only_blocks + ^ " and network devices only in manifest: " + ^ String.concat "," mft_only_bridges + ^ " and block devices only in manifest: " + ^ String.concat "," mft_only_blocks)) let key_ids exts pub issuer = let open X509 in From 7065a19578f28d8fd20c693a6c4076dfc617f1f4 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 31 Jan 2025 07:12:41 +0100 Subject: [PATCH 15/36] no upgrade button if no binary in latest build --- unikernel_update.ml | 74 ++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/unikernel_update.ml b/unikernel_update.ml index 09e1f4dc..c020c122 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -65,6 +65,15 @@ let build_table (build : Builder_web.build) = ~a:[ a_class [ "px-6 py-1 text-sm font-medium text-gray-800" ] ] [ txt (Utils.TimeHelper.string_of_ptime build.finish_time) ]; ]; + tr + [ + td + ~a:[ a_class [ "px-6 py-1 text-sm font-medium text-gray-800" ] ] + [ txt "Has binary" ]; + td + ~a:[ a_class [ "px-6 py-1 text-sm font-medium text-gray-800" ] ] + [ txt (string_of_bool build.main_binary) ]; + ]; tr [ td @@ -303,20 +312,26 @@ let unikernel_update_layout ~unikernel_name unikernel current_time ~a:[ a_class [ "text-sm" ] ] [ txt (Ohex.encode data.digest) ]; ]; - div - [ - Utils.button_component - ~attribs: - [ - a_id "update-unikernel-button"; - a_onclick - ("updateUnikernel('" - ^ build_comparison.right.job ^ "','" - ^ build_comparison.right.uuid ^ "')"); - ] - ~content:(txt "Update to Latest") - ~btn_type:`Primary_full (); - ]; + (if build_comparison.right.main_binary then + div + [ + Utils.button_component + ~attribs: + [ + a_id "update-unikernel-button"; + a_onclick + ("updateUnikernel('" + ^ build_comparison.right.job ^ "','" + ^ build_comparison.right.uuid ^ "','" + ^ unikernel_name ^ "')"); + ] + ~content:(txt "Update to Latest") + ~btn_type:`Primary_full (); + ] + else + p + ~a:[ a_class [ "text-secondary-500 font-semibold" ] ] + [ txt "Can't update. No binary in latest build." ]); ]; div ~a:[ a_class [ "grid grid-cols-2 divide-x-2 gap-4" ] ] @@ -434,17 +449,22 @@ let unikernel_update_layout ~unikernel_name unikernel current_time ]; ]; ]; - div - [ - Utils.button_component - ~attribs: - [ - a_id "update-unikernel-button"; - a_onclick - ("updateUnikernel('" ^ build_comparison.right.job ^ "','" - ^ build_comparison.right.uuid ^ "','" ^ unikernel_name ^ "')" - ); - ] - ~content:(txt "Update to Latest") ~btn_type:`Primary_full (); - ]; + (if build_comparison.right.main_binary then + div + [ + Utils.button_component + ~attribs: + [ + a_id "update-unikernel-button"; + a_onclick + ("updateUnikernel('" ^ build_comparison.right.job ^ "','" + ^ build_comparison.right.uuid ^ "','" ^ unikernel_name + ^ "')"); + ] + ~content:(txt "Update to Latest") ~btn_type:`Primary_full (); + ] + else + p + ~a:[ a_class [ "text-secondary-500 font-semibold" ] ] + [ txt "Can't update. No binary in latest build." ]); ]) From 316d8a7e696c47502687e8d22c5640fed8f074fb Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 31 Jan 2025 13:51:52 +0100 Subject: [PATCH 16/36] move string_or_none to utils and refactor --- user_model.ml | 13 +++---------- utils.ml | 8 ++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/user_model.ml b/user_model.ml index 4b96770d..ac527fc0 100644 --- a/user_model.ml +++ b/user_model.ml @@ -58,13 +58,6 @@ let cookie_to_json (cookie : cookie) = | None -> `Null ); ] -let string_or_none field = function - | None | Some `Null -> Ok None - | Some (`String v) -> Ok (Some v) - | Some json -> - Error - (`Msg ("invalid json for " ^ field ^ ": " ^ Yojson.Basic.to_string json)) - let ( let* ) = Result.bind let cookie_v1_of_json = function @@ -91,7 +84,7 @@ let cookie_v1_of_json = function created_at_str); Ptime.epoch in - let* uuid = string_or_none "uuid" uuid in + let* uuid = Utils.Json.string_or_none "uuid" uuid in Ok { name; @@ -150,8 +143,8 @@ let cookie_of_json = function last_access_str); created_at in - let* uuid = string_or_none "uuid" uuid in - let* user_agent = string_or_none "user-agent" user_agent in + let* uuid = Utils.Json.string_or_none "uuid" uuid in + let* user_agent = Utils.Json.string_or_none "user-agent" user_agent in Ok { name; diff --git a/utils.ml b/utils.ml index 548f69c2..ac963796 100644 --- a/utils.ml +++ b/utils.ml @@ -1,6 +1,14 @@ module Json = struct let get key assoc = Option.map snd (List.find_opt (fun (k, _) -> String.equal k key) assoc) + + let string_or_none field = function + | None | Some `Null -> Ok None + | Some (`String v) -> Ok (Some v) + | Some json -> + Error + (`Msg + ("invalid json for " ^ field ^ ": " ^ Yojson.Basic.to_string json)) end module TimeHelper = struct From ae7ef439e928d556c0968f4a7eccb9d9f4549328 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 31 Jan 2025 13:52:53 +0100 Subject: [PATCH 17/36] add a unikernel_info to json function and related sub functions --- albatross_json.ml | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/albatross_json.ml b/albatross_json.ml index 4cb1398d..ab387475 100644 --- a/albatross_json.ml +++ b/albatross_json.ml @@ -298,3 +298,63 @@ let config_of_json str = bridges; argv; } + +let fail_behaviour_to_json fb = + match fb with + | `Quit -> `String "quit" + | `Restart (Some codes) -> + `Assoc + [ + ("restart", `Bool true); + ( "exit_code", + `List + (List.map (fun code -> `Int code) (Vmm_core.IS.elements codes)) ); + ] + | `Restart None -> `Assoc [ ("restart", `Bool true) ] + +let bridge_to_json (name, host_device, mac) = + match (host_device, mac) with + | None, None -> `String name + | Some host, None -> + `Assoc [ ("name", `String name); ("host_device", `String host) ] + | None, Some mac -> + `Assoc + [ ("name", `String name); ("mac", `String (Macaddr.to_string mac)) ] + | Some host, Some mac -> + `Assoc + [ + ("name", `String name); + ("host_device", `String host); + ("mac", `String (Macaddr.to_string mac)); + ] + +let block_device_to_json (name, host_device, sector_size) = + match (host_device, sector_size) with + | None, None -> `String name + | Some host, None -> + `Assoc [ ("name", `String name); ("host_device", `String host) ] + | None, Some sector_size -> + `Assoc [ ("name", `String name); ("sector_size", `Int sector_size) ] + | Some host, Some sector_size -> + `Assoc + [ + ("name", `String name); + ("host_device", `String host); + ("sector_size", `Int sector_size); + ] + +let unikernel_info_to_json (unikernel : Vmm_core.Unikernel.info) = + `Assoc + ([ + ("fail_behaviour", fail_behaviour_to_json unikernel.fail_behaviour); + ("cpuid", `Int unikernel.cpuid); + ("memory", `Int unikernel.memory); + ("network_interfaces", `List (List.map bridge_to_json unikernel.bridges)); + ( "block_devices", + `List (List.map block_device_to_json unikernel.block_devices) ); + ] + @ + match unikernel.argv with + | Some args -> + [ ("arguments", `List (List.map (fun arg -> `String arg) args)) ] + | None -> []) From 375c3e85df4745130837d107f88d26d21d582ec6 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 31 Jan 2025 13:55:46 +0100 Subject: [PATCH 18/36] optionally display previous config of the currently running unikernel for users to update if needed --- unikernel_update.ml | 174 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 28 deletions(-) diff --git a/unikernel_update.ml b/unikernel_update.ml index c020c122..7bc89374 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -1,3 +1,139 @@ +let arg_modal ~unikernel_name (unikernel : Vmm_core.Unikernel.info) + (build : Builder_web.build) = + Tyxml_html.( + section + [ + div + ~a:[ a_class [ "my-4" ] ] + [ + div + ~a: + [ + a_class [ "my-6" ]; + Unsafe.string_attrib "x-data" "{ changeArgs: false}"; + ] + [ + label + ~a: + [ + a_label_for "arguments-toggle"; + a_class + [ "inline-flex cursor-pointer items-center gap-3" ]; + ] + [ + input + ~a: + [ + a_id "arguments-toggle"; + a_input_type `Checkbox; + a_class [ "peer sr-only" ]; + a_role [ "switch" ]; + Unsafe.string_attrib "x-on:click" + "changeArgs = !changeArgs"; + ] + (); + span + ~a: + [ + a_aria "hidden" [ "true" ]; + a_class + [ + "relative h-6 w-11 after:h-5 after:w-5 \ + peer-checked:after:translate-x-5 rounded-full \ + border border-gray-300 bg-gray-50 \ + after:absolute after:bottom-0 \ + after:left-[0.0625rem] after:top-0 \ + after:my-auto after:rounded-full \ + after:bg-gray-600 after:transition-all \ + after:content-[''] peer-checked:bg-primary-500 \ + peer-checked:after:bg-white peer-focus:outline \ + peer-focus:outline-2 \ + peer-focus:outline-offset-2 \ + peer-focus:outline-gray-800 \ + peer-focus:peer-checked:outline-primary-500 \ + peer-active:outline-offset-0 \ + peer-disabled:cursor-not-allowed \ + peer-disabled:opacity-70 dark:border-gray-700 \ + dark:bg-gray-900 dark:after:bg-gray-300 \ + dark:peer-checked:bg-primary-500 \ + dark:peer-checked:after:bg-white \ + dark:peer-focus:outline-gray-300 \ + dark:peer-focus:peer-checked:outline-primary-500"; + ]; + ] + []; + span + ~a: + [ + a_class + [ + "trancking-wide text-sm font-medium \ + text-gray-600 peer-checked:text-gray-900 \ + peer-disabled:cursor-not-allowed \n\ + \ \ + dark:peer-checked:text-white"; + ]; + ] + [ txt "Update the arguments for this build" ]; + ]; + div + ~a: + [ + Unsafe.string_attrib "x-show" "changeArgs"; + a_class [ "my-4" ]; + ] + [ + small + ~a:[ a_class [ "my-1" ] ] + [ + txt + "Use json syntax to provide arguments for the latest \ + build"; + ]; + textarea + ~a: + [ + a_rows 15; + a_required (); + a_name "arguments"; + a_id "unikernel-arguments"; + a_class + [ + "ring-primary-100 mt-1.5 transition \ + appearance-none block w-full px-3 py-3 \ + rounded-xl shadow-sm border \ + hover:border-primary-200\n\ + \ \ + focus:border-primary-300 bg-primary-50 \ + bg-opacity-0 hover:bg-opacity-50 \ + focus:bg-opacity-50 ring-primary-200 \ + focus:ring-primary-200\n\ + \ \ + focus:ring-[1px] focus:outline-none"; + ]; + ] + (txt + (Albatross_json.unikernel_info_to_json unikernel + |> Yojson.Basic.pretty_to_string)); + ]; + ]; + ]; + hr (); + div + ~a:[ a_class [ "my-4" ] ] + [ + Utils.button_component + ~attribs: + [ + a_id "update-unikernel-button"; + a_onclick + ("updateUnikernel('" ^ build.job ^ "','" ^ build.uuid + ^ "','" ^ unikernel_name ^ "')"); + ] + ~content:(txt "Proceed to update") ~btn_type:`Primary_full (); + ]; + ]) + let build_table (build : Builder_web.build) = Tyxml_html.( table @@ -313,21 +449,12 @@ let unikernel_update_layout ~unikernel_name unikernel current_time [ txt (Ohex.encode data.digest) ]; ]; (if build_comparison.right.main_binary then - div - [ - Utils.button_component - ~attribs: - [ - a_id "update-unikernel-button"; - a_onclick - ("updateUnikernel('" - ^ build_comparison.right.job ^ "','" - ^ build_comparison.right.uuid ^ "','" - ^ unikernel_name ^ "')"); - ] - ~content:(txt "Update to Latest") - ~btn_type:`Primary_full (); - ] + Modal_dialog.modal_dialog ~modal_title:"Check arguments" + ~button_content:(txt "Update to Latest") + ~content: + (arg_modal ~unikernel_name data + build_comparison.right) + () else p ~a:[ a_class [ "text-secondary-500 font-semibold" ] ] @@ -450,19 +577,10 @@ let unikernel_update_layout ~unikernel_name unikernel current_time ]; ]; (if build_comparison.right.main_binary then - div - [ - Utils.button_component - ~attribs: - [ - a_id "update-unikernel-button"; - a_onclick - ("updateUnikernel('" ^ build_comparison.right.job ^ "','" - ^ build_comparison.right.uuid ^ "','" ^ unikernel_name - ^ "')"); - ] - ~content:(txt "Update to Latest") ~btn_type:`Primary_full (); - ] + Modal_dialog.modal_dialog ~modal_title:"Check arguments" + ~button_content:(txt "Update to Latest") + ~content:(arg_modal ~unikernel_name data build_comparison.right) + () else p ~a:[ a_class [ "text-secondary-500 font-semibold" ] ] From dc60471919ccfb871f3dc8ae7494ab655f8ea9f3 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 31 Jan 2025 14:18:18 +0100 Subject: [PATCH 19/36] send unikernel config in post request --- assets/main.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/assets/main.js b/assets/main.js index 1466c3e9..27f7a03a 100644 --- a/assets/main.js +++ b/assets/main.js @@ -927,7 +927,14 @@ async function updateToken(value) { async function updateUnikernel(job, build, unikernel_name) { const updateButton = document.getElementById("update-unikernel-button"); + const unikernelArguments = document.getElementById("unikernel-arguments").value; + const argumentsToggle = document.getElementById("arguments-toggle").checked; const molly_csrf = document.getElementById("molly-csrf").value; + if (argumentsToggle && !unikernelArguments) { + postAlert("bg-secondary-300", "You must give arguments for this build else switch 'Update the arguments for this build' off"); + buttonLoading(updateButton, false, "Proceed to update") + return; + } try { buttonLoading(updateButton, true, "Updating...") const response = await fetch("/api/unikernel/update", { @@ -940,6 +947,7 @@ async function updateUnikernel(job, build, unikernel_name) { "job": job, "build": build, "unikernel_name": unikernel_name, + "unikernel_arguments": argumentsToggle ? unikernelArguments : null, "molly_csrf": molly_csrf }) }) @@ -947,14 +955,14 @@ async function updateUnikernel(job, build, unikernel_name) { if (data.status === 200) { postAlert("bg-primary-300", "Unikernel updated succesfully"); setTimeout(() => window.location.reload(), 1000); - buttonLoading(updateButton, false, "Update to Latest") + buttonLoading(updateButton, false, "Proceed to update") } else { postAlert("bg-secondary-300", data.data); - buttonLoading(updateButton, false, "Update to Latest") + buttonLoading(updateButton, false, "Proceed to update") } } catch (error) { postAlert("bg-secondary-300", error); - buttonLoading(updateButton, false, "Update to Latest") + buttonLoading(updateButton, false, "Proceed to update") } } From 8bde8cce52d49a25633e7e688ac9d16b1aef5f8d Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Fri, 31 Jan 2025 14:19:42 +0100 Subject: [PATCH 20/36] process update using new arguments or previous arguments --- unikernel.ml | 183 +++++++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 86 deletions(-) diff --git a/unikernel.ml b/unikernel.ml index d08dd4fa..87b3379d 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1241,106 +1241,117 @@ struct ~api_meth:false `Internal_server_error reqd ())))))) + let process_unikernel_update ~unikernel_name ~job ~build + (unikernel_cfg : Vmm_core.Unikernel.config) (user : User_model.user) + albatross http_client reqd = + Builder_web.send_request http_client + ("/job/" ^ job ^ "/build/" ^ build ^ "/main-binary") + >>= function + | Error (`Msg err) -> + Logs.err (fun m -> + m + "builds.robur.coop: Error while fetching the binary of %s with \ + error: %s" + unikernel_name err); + Middleware.http_response reqd ~title:"Error" + ~data: + (`String + ("An error occured while fetching the binary from \ + builds.robur.coop with error " ^ err)) + `Internal_server_error + | Ok image -> ( + match + Albatross.manifest_devices_match ~bridges:unikernel_cfg.bridges + ~block_devices:unikernel_cfg.block_devices image + with + | Error (`Msg err) -> + Middleware.http_response reqd ~title:"Error" + ~data:(`String ("Manifest mismatch: " ^ err)) + `Bad_request + | Ok () -> ( + let unikernel_config = { unikernel_cfg with image } in + Albatross.query albatross ~domain:user.name ~name:unikernel_name + (`Unikernel_cmd (`Unikernel_force_create unikernel_config)) + >>= function + | Error msg -> + Logs.err (fun m -> m "Error querying albatross: %s" msg); + Middleware.http_response reqd ~title:"Error" + ~data:(`String ("Error querying albatross: " ^ msg)) + `Internal_server_error + | Ok (_hdr, res) -> ( + match Albatross_json.res res with + | Error (`String err) -> + Middleware.http_response reqd ~title:"Error" + ~data:(`String (String.escaped err)) + `Internal_server_error + | Ok res -> + Logs.err (fun m -> + m "%s has been updated succesfully with result: %s" + unikernel_name + (Yojson.Basic.to_string res)); + Middleware.http_response reqd ~title:"Error" + ~data: + (`String + (unikernel_name + ^ " has been updated to the latest build.")) + `OK))) + let unikernel_update albatross reqd http_client ~json_dict (user : User_model.user) = match Utils.Json. ( get "job" json_dict, get "build" json_dict, - get "unikernel_name" json_dict ) + get "unikernel_name" json_dict, + get "unikernel_arguments" json_dict ) with - | Some (`String job), Some (`String build), Some (`String unikernel_name) - -> ( - user_unikernel albatross ~user_name:user.name ~unikernel_name - >>= fun unikernel_info -> - match unikernel_info with - | Error err -> - Middleware.redirect_to_error + | ( Some (`String job), + Some (`String build), + Some (`String unikernel_name), + arguments ) -> ( + match Utils.Json.string_or_none "unikernel_arguments" arguments with + | Error (`Msg err) -> + Middleware.http_response reqd + ~title:"Error with Unikernel Arguments Json" ~data: - (`String - ("An error occured while fetching " ^ unikernel_name - ^ " from albatross with error " ^ err)) - ~title:"Albatross Error" ~api_meth:false `Internal_server_error - reqd () - | Ok - ( _, - Vmm_core.Unikernel. - { - bridges; - block_devices; - argv; - cpuid; - memory; - fail_behaviour; - typ = `Solo5 as typ; - _; - } ) -> ( - Builder_web.send_request http_client - ("/job/" ^ job ^ "/build/" ^ build ^ "/main-binary") - >>= function - | Error (`Msg err) -> - Logs.err (fun m -> - m - "builds.robur.coop: Error while fetching the binary of \ - %s with error: %s" - unikernel_name err); - Middleware.http_response reqd ~title:"Error" + (`String ("Could not get the unikernel arguments json: " ^ err)) + `OK + | Ok None -> ( + user_unikernel albatross ~user_name:user.name ~unikernel_name + >>= fun unikernel_info -> + match unikernel_info with + | Error err -> + Middleware.redirect_to_error ~data: (`String - ("An error occured while fetching the binary from \ - builds.robur.coop with error " ^ err)) - `Internal_server_error - | Ok image -> ( - let config = - { - Vmm_core.Unikernel.typ; - compressed = true; - (*compressed here is assumed to be true.*) - image; - fail_behaviour; - cpuid; - memory; - block_devices; - bridges; - argv; - } - in + ("An error occured while fetching " ^ unikernel_name + ^ " from albatross with error " ^ err)) + ~title:"Albatross Error" ~api_meth:false + `Internal_server_error reqd () + | Ok (_, unikernel) -> ( match - Albatross.manifest_devices_match ~bridges ~block_devices image + Albatross_json.( + unikernel_info_to_json unikernel + |> Yojson.Basic.to_string |> config_of_json) with + | Ok cfg -> + process_unikernel_update ~unikernel_name ~job ~build cfg + user albatross http_client reqd | Error (`Msg err) -> + Logs.warn (fun m -> m "Couldn't decode data %s" err); Middleware.http_response reqd ~title:"Error" - ~data:(`String ("Manifest mismatch: " ^ err)) - `Bad_request - | Ok () -> ( - Albatross.query albatross ~domain:user.name - ~name:unikernel_name - (`Unikernel_cmd (`Unikernel_force_create config)) - >>= function - | Error msg -> - Logs.err (fun m -> m "Error querying albatross: %s" msg); - Middleware.http_response reqd ~title:"Error" - ~data:(`String ("Error querying albatross: " ^ msg)) - `Internal_server_error - | Ok (_hdr, res) -> ( - match Albatross_json.res res with - | Error (`String err) -> - Middleware.http_response reqd ~title:"Error" - ~data:(`String (String.escaped err)) - `Internal_server_error - | Ok res -> - Logs.err (fun m -> - m - "%s has been updated succesfully with \ - result: %s" - unikernel_name - (Yojson.Basic.to_string res)); - Middleware.http_response reqd ~title:"Error" - ~data: - (`String - (unikernel_name - ^ " has been updated to the latest build.")) - `OK))))) + ~data:(`String (String.escaped err)) + `Internal_server_error)) + | Ok (Some args) -> ( + match Albatross_json.config_of_json args with + | Ok cfg -> + process_unikernel_update ~unikernel_name ~job ~build cfg user + albatross http_client reqd + | Error (`Msg err) -> + Logs.warn (fun m -> m "Couldn't decode data %s" err); + Middleware.http_response reqd ~title:"Error" + ~data:(`String (String.escaped err)) + `Internal_server_error)) | _ -> Middleware.http_response reqd ~title:"Error" ~data:(`String "Couldn't find job or build in json. Received ") From 2f6a1612b59d95bb907dcdc1d3d21545759eac0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Fri, 31 Jan 2025 15:45:03 +0100 Subject: [PATCH 21/36] config.ml: pin owee and constrain solo5-elftool I (Reynir) intend to release a new version of solo5-elftool that doesn't depend on owee. In the meantime we pin our owee fork that doesn't depend on Unix. --- config.ml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.ml b/config.ml index b57dbd64..1de0a2f9 100644 --- a/config.ml +++ b/config.ml @@ -23,7 +23,9 @@ let mollymawk = package "duration"; package ~min:"0.2.0" "ohex"; package "http-mirage-client"; - package "solo5-elftool"; + package "solo5-elftool" ~max:"0.4.0"; + (* NOTE(reynir): Pin no-unix owee until solo5-elftool 0.4.0 is released *) + package "owee" ~pin:"git+https://github.com/robur-coop/owee#unix-free"; ] in main ~packages "Unikernel.Main" From 09acf8be445435a35582994d4ce298e60722e0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Fri, 31 Jan 2025 16:54:22 +0100 Subject: [PATCH 22/36] Fix type error in albatross.ml While this is very inefficient it is only temporary until changes to ocaml-solo5-elftool are merged and released. --- albatross.ml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/albatross.ml b/albatross.ml index 9050730d..a175e5f7 100644 --- a/albatross.ml +++ b/albatross.ml @@ -96,6 +96,11 @@ struct | Error (`Msg err) -> Error err let manifest_devices_match ~bridges ~block_devices binary = + let b = Bigarray.Array1.create Bigarray.Int8_unsigned Bigarray.c_layout (String.length b) in + for i = 0 to String.length b - 1 do + buf.{i} <- String.get_uint8 b i + done; + let binary = b in let* mft : Solo5_elftool.mft = Solo5_elftool.query_manifest binary in let req_bridges = List.map (fun (name, _, _) -> name) bridges |> String_set.of_list From 30ed5bb3c974eff3b1f61e9e0017b525f1ed5447 Mon Sep 17 00:00:00 2001 From: "Automated ocamlformat GitHub action, developed by robur.coop" Date: Fri, 31 Jan 2025 15:56:35 +0000 Subject: [PATCH 23/36] formatted code --- albatross.ml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/albatross.ml b/albatross.ml index a175e5f7..44c50009 100644 --- a/albatross.ml +++ b/albatross.ml @@ -96,7 +96,10 @@ struct | Error (`Msg err) -> Error err let manifest_devices_match ~bridges ~block_devices binary = - let b = Bigarray.Array1.create Bigarray.Int8_unsigned Bigarray.c_layout (String.length b) in + let b = + Bigarray.Array1.create Bigarray.Int8_unsigned Bigarray.c_layout + (String.length b) + in for i = 0 to String.length b - 1 do buf.{i} <- String.get_uint8 b i done; From a6e8d2deaacbba3a2274dc470783369af754d930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Fri, 31 Jan 2025 17:20:30 +0100 Subject: [PATCH 24/36] Fixup! Type error --- albatross.ml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/albatross.ml b/albatross.ml index 44c50009..8e28993f 100644 --- a/albatross.ml +++ b/albatross.ml @@ -98,10 +98,10 @@ struct let manifest_devices_match ~bridges ~block_devices binary = let b = Bigarray.Array1.create Bigarray.Int8_unsigned Bigarray.c_layout - (String.length b) + (String.length binary) in - for i = 0 to String.length b - 1 do - buf.{i} <- String.get_uint8 b i + for i = 0 to String.length binary - 1 do + b.{i} <- String.get_uint8 binary i done; let binary = b in let* mft : Solo5_elftool.mft = Solo5_elftool.query_manifest binary in From 4506d2ffc07aa0003455300e9035f33b3aef4d81 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Tue, 4 Feb 2025 16:24:01 +0100 Subject: [PATCH 25/36] refactor error messages --- albatross.ml | 54 ++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/albatross.ml b/albatross.ml index 8e28993f..dfaf4cb9 100644 --- a/albatross.ml +++ b/albatross.ml @@ -134,62 +134,58 @@ struct | req_only_bridges, [], [], [] -> Error (`Msg - ("Network devices missing from manifest: " - ^ String.concat "," req_only_bridges)) + (String.concat "," req_only_bridges + ^ " devices missing from manifest.")) | [], mft_only_bridges, [], [] -> Error (`Msg - ("Network devices only in manifest: " - ^ String.concat "," mft_only_bridges)) + (String.concat "," mft_only_bridges ^ " devices only in manifest.")) | [], [], req_only_blocks, [] -> Error (`Msg - ("Block devices missing from manifest: " - ^ String.concat "," req_only_blocks)) + (String.concat "," req_only_blocks + ^ " devices missing from manifest: ")) | [], [], [], mft_only_blocks -> Error (`Msg - ("Block devices only in manifest: " - ^ String.concat "," mft_only_blocks)) + (String.concat "," mft_only_blocks ^ " devices only in manifest.")) | req_only_bridges, req_only_blocks, [], [] -> Error (`Msg - ("Network devices missing from manifest: " - ^ String.concat "," req_only_bridges - ^ " and block devices missing from manifest: " - ^ String.concat "," req_only_blocks)) + (String.concat "," req_only_bridges + ^ String.concat "," req_only_blocks + ^ " devices missing from manifest.")) | [], req_only_blocks, mft_only_bridges, [] -> Error (`Msg - ("Network devices only in manifest: " - ^ String.concat "," mft_only_bridges - ^ " and block devices missing from manifest: " - ^ String.concat "," req_only_blocks)) + (String.concat "," mft_only_bridges + ^ " devices only in manifest, and " + ^ String.concat "," req_only_blocks + ^ " devices missing from manifest.")) | [], [], mft_only_blocks, req_only_bridges -> Error (`Msg - ("Network devices missing from manifest: " - ^ String.concat "," req_only_bridges - ^ " and block devices only in manifest: " - ^ String.concat "," mft_only_blocks)) + (String.concat "," req_only_bridges + ^ " devices missing from manifest and " + ^ String.concat "," mft_only_blocks + ^ " devices only in manifest.")) | req_only_bridges, [], mft_only_blocks, [] -> Error (`Msg - ("Network devices missing from manifest: " - ^ String.concat "," req_only_bridges - ^ " and block devices only in manifest: " + (String.concat "," req_only_bridges + ^ " devices missing from manifest and " + ^ String.concat "," mft_only_blocks + ^ " devices only in manifest." ^ String.concat "," mft_only_blocks)) | req_only_bridges, req_only_blocks, mft_only_bridges, mft_only_blocks -> Error (`Msg - ("Network devices missing from manifest: " - ^ String.concat "," req_only_bridges - ^ " and block devices missing from manifest: " + (String.concat "," req_only_bridges ^ String.concat "," req_only_blocks - ^ " and network devices only in manifest: " + ^ " devices missing from manifest and " ^ String.concat "," mft_only_bridges - ^ " and block devices only in manifest: " - ^ String.concat "," mft_only_blocks)) + ^ String.concat "," mft_only_blocks + ^ " devices only in manifest.")) let key_ids exts pub issuer = let open X509 in From e917cf1d802b63692107c67f97e80ab3c60e55cb Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Tue, 4 Feb 2025 16:26:02 +0100 Subject: [PATCH 26/36] response type --- unikernel.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unikernel.ml b/unikernel.ml index 87b3379d..ab0eb6e4 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1285,11 +1285,11 @@ struct ~data:(`String (String.escaped err)) `Internal_server_error | Ok res -> - Logs.err (fun m -> + Logs.info (fun m -> m "%s has been updated succesfully with result: %s" unikernel_name (Yojson.Basic.to_string res)); - Middleware.http_response reqd ~title:"Error" + Middleware.http_response reqd ~title:"Update Successful" ~data: (`String (unikernel_name From 33912d3973ebf71eb642b8bacf3a1e0cfc0e561e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Tue, 4 Feb 2025 19:35:47 +0100 Subject: [PATCH 27/36] Switch to ocaml-solo5-elftool 0.4.0 --- albatross.ml | 22 ++++++++++++++-------- config.ml | 5 ++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/albatross.ml b/albatross.ml index dfaf4cb9..dc59a0ce 100644 --- a/albatross.ml +++ b/albatross.ml @@ -96,15 +96,21 @@ struct | Error (`Msg err) -> Error err let manifest_devices_match ~bridges ~block_devices binary = - let b = - Bigarray.Array1.create Bigarray.Int8_unsigned Bigarray.c_layout - (String.length binary) + let cachet = + let map () ~pos len = + if pos >= String.length binary || len <= 0 then + (Cachet.Bstr.empty :> Cachet.bigstring) + else + let len = min len (max 0 (String.length binary - pos)) in + let b : Cachet.bigstring = Bigarray.Array1.create Bigarray.char Bigarray.c_layout len in + for i = 0 to len - 1 do + b.{i} <- (binary.[pos+i]) + done; + b + in + Cachet.make ~cachesize:8 ~map in - for i = 0 to String.length binary - 1 do - b.{i} <- String.get_uint8 binary i - done; - let binary = b in - let* mft : Solo5_elftool.mft = Solo5_elftool.query_manifest binary in + let* mft : Solo5_elftool.mft = Solo5_elftool.query_manifest cachet in let req_bridges = List.map (fun (name, _, _) -> name) bridges |> String_set.of_list and req_block_devices = diff --git a/config.ml b/config.ml index 1de0a2f9..78940f56 100644 --- a/config.ml +++ b/config.ml @@ -23,9 +23,8 @@ let mollymawk = package "duration"; package ~min:"0.2.0" "ohex"; package "http-mirage-client"; - package "solo5-elftool" ~max:"0.4.0"; - (* NOTE(reynir): Pin no-unix owee until solo5-elftool 0.4.0 is released *) - package "owee" ~pin:"git+https://github.com/robur-coop/owee#unix-free"; + (* XXX(reynir): remove the pin; it's only there for the CI until the release has been merged *) + package "solo5-elftool" ~min:"0.4.0" ~pin:"git+https://git.robur.coop/robur/ocaml-solo5-elftool.git"; ] in main ~packages "Unikernel.Main" From 6a70891588473a7b1f03351f2c7e851e1c382d66 Mon Sep 17 00:00:00 2001 From: "Automated ocamlformat GitHub action, developed by robur.coop" Date: Tue, 4 Feb 2025 18:38:12 +0000 Subject: [PATCH 28/36] formatted code --- albatross.ml | 6 ++++-- config.ml | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/albatross.ml b/albatross.ml index dc59a0ce..44130954 100644 --- a/albatross.ml +++ b/albatross.ml @@ -102,9 +102,11 @@ struct (Cachet.Bstr.empty :> Cachet.bigstring) else let len = min len (max 0 (String.length binary - pos)) in - let b : Cachet.bigstring = Bigarray.Array1.create Bigarray.char Bigarray.c_layout len in + let b : Cachet.bigstring = + Bigarray.Array1.create Bigarray.char Bigarray.c_layout len + in for i = 0 to len - 1 do - b.{i} <- (binary.[pos+i]) + b.{i} <- binary.[pos + i] done; b in diff --git a/config.ml b/config.ml index 78940f56..29d87944 100644 --- a/config.ml +++ b/config.ml @@ -24,7 +24,8 @@ let mollymawk = package ~min:"0.2.0" "ohex"; package "http-mirage-client"; (* XXX(reynir): remove the pin; it's only there for the CI until the release has been merged *) - package "solo5-elftool" ~min:"0.4.0" ~pin:"git+https://git.robur.coop/robur/ocaml-solo5-elftool.git"; + package "solo5-elftool" ~min:"0.4.0" + ~pin:"git+https://git.robur.coop/robur/ocaml-solo5-elftool.git"; ] in main ~packages "Unikernel.Main" From 0b0c79252ba8cbea87289e4f0dcd69c62feef792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Tue, 4 Feb 2025 19:55:59 +0100 Subject: [PATCH 29/36] Fixup! Switch to ocaml-solo5-elftool 0.4.0 --- albatross.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/albatross.ml b/albatross.ml index 44130954..4d8cdc83 100644 --- a/albatross.ml +++ b/albatross.ml @@ -110,7 +110,7 @@ struct done; b in - Cachet.make ~cachesize:8 ~map + Cachet.make ~cachesize:8 ~map () in let* mft : Solo5_elftool.mft = Solo5_elftool.query_manifest cachet in let req_bridges = From 52ceff5138a4faca5fa406bd511a504fe57b1df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Tue, 4 Feb 2025 20:24:27 +0100 Subject: [PATCH 30/36] Remove pin We succesfully tested in CI \o/ --- config.ml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/config.ml b/config.ml index 29d87944..77755d73 100644 --- a/config.ml +++ b/config.ml @@ -23,9 +23,7 @@ let mollymawk = package "duration"; package ~min:"0.2.0" "ohex"; package "http-mirage-client"; - (* XXX(reynir): remove the pin; it's only there for the CI until the release has been merged *) - package "solo5-elftool" ~min:"0.4.0" - ~pin:"git+https://git.robur.coop/robur/ocaml-solo5-elftool.git"; + package "solo5-elftool" ~min:"0.4.0"; ] in main ~packages "Unikernel.Main" From 7770c0012ff8544f5c3c7f0bf88df05f1c950fbc Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Thu, 6 Feb 2025 10:27:58 +0100 Subject: [PATCH 31/36] remove repeated parsing function --- albatross_json.ml | 60 --------------------------------------------- unikernel.ml | 4 +-- unikernel_update.ml | 10 +++++--- 3 files changed, 8 insertions(+), 66 deletions(-) diff --git a/albatross_json.ml b/albatross_json.ml index ab387475..4cb1398d 100644 --- a/albatross_json.ml +++ b/albatross_json.ml @@ -298,63 +298,3 @@ let config_of_json str = bridges; argv; } - -let fail_behaviour_to_json fb = - match fb with - | `Quit -> `String "quit" - | `Restart (Some codes) -> - `Assoc - [ - ("restart", `Bool true); - ( "exit_code", - `List - (List.map (fun code -> `Int code) (Vmm_core.IS.elements codes)) ); - ] - | `Restart None -> `Assoc [ ("restart", `Bool true) ] - -let bridge_to_json (name, host_device, mac) = - match (host_device, mac) with - | None, None -> `String name - | Some host, None -> - `Assoc [ ("name", `String name); ("host_device", `String host) ] - | None, Some mac -> - `Assoc - [ ("name", `String name); ("mac", `String (Macaddr.to_string mac)) ] - | Some host, Some mac -> - `Assoc - [ - ("name", `String name); - ("host_device", `String host); - ("mac", `String (Macaddr.to_string mac)); - ] - -let block_device_to_json (name, host_device, sector_size) = - match (host_device, sector_size) with - | None, None -> `String name - | Some host, None -> - `Assoc [ ("name", `String name); ("host_device", `String host) ] - | None, Some sector_size -> - `Assoc [ ("name", `String name); ("sector_size", `Int sector_size) ] - | Some host, Some sector_size -> - `Assoc - [ - ("name", `String name); - ("host_device", `String host); - ("sector_size", `Int sector_size); - ] - -let unikernel_info_to_json (unikernel : Vmm_core.Unikernel.info) = - `Assoc - ([ - ("fail_behaviour", fail_behaviour_to_json unikernel.fail_behaviour); - ("cpuid", `Int unikernel.cpuid); - ("memory", `Int unikernel.memory); - ("network_interfaces", `List (List.map bridge_to_json unikernel.bridges)); - ( "block_devices", - `List (List.map block_device_to_json unikernel.block_devices) ); - ] - @ - match unikernel.argv with - | Some args -> - [ ("arguments", `List (List.map (fun arg -> `String arg) args)) ] - | None -> []) diff --git a/unikernel.ml b/unikernel.ml index ab0eb6e4..998af111 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1328,10 +1328,10 @@ struct ^ " from albatross with error " ^ err)) ~title:"Albatross Error" ~api_meth:false `Internal_server_error reqd () - | Ok (_, unikernel) -> ( + | Ok (n, unikernel) -> ( match Albatross_json.( - unikernel_info_to_json unikernel + unikernel_info (n, unikernel) |> Yojson.Basic.to_string |> config_of_json) with | Ok cfg -> diff --git a/unikernel_update.ml b/unikernel_update.ml index 7bc89374..465d6d43 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -1,4 +1,5 @@ -let arg_modal ~unikernel_name (unikernel : Vmm_core.Unikernel.info) +let arg_modal ~unikernel_name + (unikernel : Vmm_core.Name.t * Vmm_core.Unikernel.info) (build : Builder_web.build) = Tyxml_html.( section @@ -113,7 +114,7 @@ let arg_modal ~unikernel_name (unikernel : Vmm_core.Unikernel.info) ]; ] (txt - (Albatross_json.unikernel_info_to_json unikernel + (Albatross_json.unikernel_info unikernel |> Yojson.Basic.pretty_to_string)); ]; ]; @@ -452,7 +453,7 @@ let unikernel_update_layout ~unikernel_name unikernel current_time Modal_dialog.modal_dialog ~modal_title:"Check arguments" ~button_content:(txt "Update to Latest") ~content: - (arg_modal ~unikernel_name data + (arg_modal ~unikernel_name unikernel build_comparison.right) () else @@ -579,7 +580,8 @@ let unikernel_update_layout ~unikernel_name unikernel current_time (if build_comparison.right.main_binary then Modal_dialog.modal_dialog ~modal_title:"Check arguments" ~button_content:(txt "Update to Latest") - ~content:(arg_modal ~unikernel_name data build_comparison.right) + ~content: + (arg_modal ~unikernel_name unikernel build_comparison.right) () else p From 3e299860c30b93414f4717234f75b655c36f055d Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Thu, 6 Feb 2025 10:58:17 +0100 Subject: [PATCH 32/36] don't parse typ, mac addresses or digest values in json emmitted to user --- albatross_json.ml | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/albatross_json.ml b/albatross_json.ml index 4cb1398d..b546f48f 100644 --- a/albatross_json.ml +++ b/albatross_json.ml @@ -1,8 +1,7 @@ open Utils.Json let unikernel_info (unikernel_name, info) = - let typ = function `Solo5 -> `String "solo5" - and fail_behaviour = function + let fail_behaviour = function | `Quit -> `String "quit" | `Restart ex -> let els = @@ -33,33 +32,25 @@ let unikernel_info (unikernel_name, info) = in `List (List.map block bs) and bridges bs = - let bridge (name, dev, mac) = + let bridge (name, dev, _) = let dev = Option.value ~default:name dev in - let mac = - Option.value ~default:(Vmm_core.Name.mac unikernel_name dev) mac - in - `Assoc - [ - ("name", `String name); - ("host_device", `String dev); - ("mac", `String (Macaddr.to_string mac)); - ] + `Assoc [ ("name", `String name); ("host_device", `String dev) ] in `List (List.map bridge bs) and argv args = `List (List.map (fun a -> `String a) (Option.value ~default:[] args)) - and digest d = `String (Ohex.encode d) in + in `Assoc [ - ("name", `String (Vmm_core.Name.to_string unikernel_name)); - ("typ", typ info.Vmm_core.Unikernel.typ); - ("fail_behaviour", fail_behaviour info.fail_behaviour); + ( "name", + `String (Option.value ~default:"" (Vmm_core.Name.name unikernel_name)) + ); + ("fail_behaviour", fail_behaviour info.Vmm_core.Unikernel.fail_behaviour); ("cpuid", cpuid info.cpuid); ("memory", memory info.memory); ("block_devices", block_devices info.block_devices); ("network_interfaces", bridges info.bridges); ("arguments", argv info.argv); - ("digest", digest info.digest); ] let unikernel_infos is = `List (List.map unikernel_info is) From 851a2c8daa9269a51ceba70483f09e5e9f2a5cec Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Thu, 6 Feb 2025 14:46:48 +0100 Subject: [PATCH 33/36] return and display better error messages --- albatross.ml | 90 ++++++++++++++++++++++++++++----------------- assets/main.js | 7 ++++ unikernel.ml | 13 +++++-- unikernel_update.ml | 5 ++- 4 files changed, 76 insertions(+), 39 deletions(-) diff --git a/albatross.ml b/albatross.ml index 4d8cdc83..d4946544 100644 --- a/albatross.ml +++ b/albatross.ml @@ -142,58 +142,82 @@ struct | req_only_bridges, [], [], [] -> Error (`Msg - (String.concat "," req_only_bridges - ^ " devices missing from manifest.")) + ("Extra network interfaces specified: " + ^ String.concat ", " req_only_bridges + ^ ". Please remove them from the 'network_interfaces' list of \ + your configuration.")) | [], mft_only_bridges, [], [] -> Error (`Msg - (String.concat "," mft_only_bridges ^ " devices only in manifest.")) + ("Missing required network interfaces: " + ^ String.concat ", " mft_only_bridges + ^ ". Please add them to the 'network_interfaces' list of your \ + configuration.")) | [], [], req_only_blocks, [] -> Error (`Msg - (String.concat "," req_only_blocks - ^ " devices missing from manifest: ")) + ("Extra block devices specified: " + ^ String.concat ", " req_only_blocks + ^ ". Please remove them from the 'block_devices' list of your \ + configuration.")) | [], [], [], mft_only_blocks -> Error (`Msg - (String.concat "," mft_only_blocks ^ " devices only in manifest.")) - | req_only_bridges, req_only_blocks, [], [] -> + ("Missing required block devices: " + ^ String.concat ", " mft_only_blocks + ^ ". Please add them to the 'block_devices' list of your \ + configuration.")) + | req_only_bridges, [], req_only_blocks, [] -> Error (`Msg - (String.concat "," req_only_bridges - ^ String.concat "," req_only_blocks - ^ " devices missing from manifest.")) - | [], req_only_blocks, mft_only_bridges, [] -> + ("Extra network interfaces: " + ^ String.concat ", " req_only_bridges + ^ " and extra block devices: " + ^ String.concat ", " req_only_blocks + ^ ". Please remove them from the 'network_interfaces' lists and \ + 'block_devices' list of your configuration.")) + | [], mft_only_bridges, [], mft_only_blocks -> Error (`Msg - (String.concat "," mft_only_bridges - ^ " devices only in manifest, and " - ^ String.concat "," req_only_blocks - ^ " devices missing from manifest.")) - | [], [], mft_only_blocks, req_only_bridges -> + ("Missing network interfaces: " + ^ String.concat ", " mft_only_bridges + ^ " and missing block devices: " + ^ String.concat ", " mft_only_blocks + ^ ". Please add them to the 'network_interfaces' lists and \ + 'block_devices' list of your configuration.")) + | req_only_bridges, [], [], mft_only_blocks -> Error (`Msg - (String.concat "," req_only_bridges - ^ " devices missing from manifest and " - ^ String.concat "," mft_only_blocks - ^ " devices only in manifest.")) - | req_only_bridges, [], mft_only_blocks, [] -> + ("Extra network interfaces: " + ^ String.concat ", " req_only_bridges + ^ " and missing block devices: " + ^ String.concat ", " mft_only_blocks + ^ ". Please remove the network interfaces from the \ + 'network_interfaces' list and add the block devices to the \ + 'block_devices' list of your configuration.")) + | [], mft_only_bridges, req_only_blocks, [] -> Error (`Msg - (String.concat "," req_only_bridges - ^ " devices missing from manifest and " - ^ String.concat "," mft_only_blocks - ^ " devices only in manifest." - ^ String.concat "," mft_only_blocks)) - | req_only_bridges, req_only_blocks, mft_only_bridges, mft_only_blocks -> + ("Missing network interfaces: " + ^ String.concat ", " mft_only_bridges + ^ " and extra block devices: " + ^ String.concat ", " req_only_blocks + ^ ". Please add the network interfaces to the \ + 'network_interfaces' list and remove the block devices from \ + the 'block_devices' list of your configuration.")) + | req_only_bridges, mft_only_bridges, req_only_blocks, mft_only_blocks -> Error (`Msg - (String.concat "," req_only_bridges - ^ String.concat "," req_only_blocks - ^ " devices missing from manifest and " - ^ String.concat "," mft_only_bridges - ^ String.concat "," mft_only_blocks - ^ " devices only in manifest.")) + ("Missing network interfaces: " + ^ String.concat ", " req_only_bridges + ^ " and missing block devices: " + ^ String.concat ", " req_only_blocks + ^ " while also having extra network interfaces: " + ^ String.concat ", " mft_only_bridges + ^ " and extra block devices: " + ^ String.concat ", " mft_only_blocks + ^ ". Please update 'network_interfaces' and 'block_devices' \ + accordingly.")) let key_ids exts pub issuer = let open X509 in diff --git a/assets/main.js b/assets/main.js index 27f7a03a..c3245716 100644 --- a/assets/main.js +++ b/assets/main.js @@ -930,6 +930,7 @@ async function updateUnikernel(job, build, unikernel_name) { const unikernelArguments = document.getElementById("unikernel-arguments").value; const argumentsToggle = document.getElementById("arguments-toggle").checked; const molly_csrf = document.getElementById("molly-csrf").value; + const formAlert = document.getElementById("unikernel-arguments-alert"); if (argumentsToggle && !unikernelArguments) { postAlert("bg-secondary-300", "You must give arguments for this build else switch 'Update the arguments for this build' off"); buttonLoading(updateButton, false, "Proceed to update") @@ -958,10 +959,16 @@ async function updateUnikernel(job, build, unikernel_name) { buttonLoading(updateButton, false, "Proceed to update") } else { postAlert("bg-secondary-300", data.data); + formAlert.classList.remove("hidden", "text-primary-500"); + formAlert.classList.add("text-secondary-500"); + formAlert.textContent = data.data buttonLoading(updateButton, false, "Proceed to update") } } catch (error) { postAlert("bg-secondary-300", error); + formAlert.classList.remove("hidden", "text-primary-500"); + formAlert.classList.add("text-secondary-500"); + formAlert.textContent = data.data buttonLoading(updateButton, false, "Proceed to update") } } diff --git a/unikernel.ml b/unikernel.ml index 998af111..7bc1543b 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1266,7 +1266,9 @@ struct with | Error (`Msg err) -> Middleware.http_response reqd ~title:"Error" - ~data:(`String ("Manifest mismatch: " ^ err)) + ~data: + (`String + ("An error occured with the unikernel configuration: " ^ err)) `Bad_request | Ok () -> ( let unikernel_config = { unikernel_cfg with image } in @@ -1274,9 +1276,11 @@ struct (`Unikernel_cmd (`Unikernel_force_create unikernel_config)) >>= function | Error msg -> - Logs.err (fun m -> m "Error querying albatross: %s" msg); + Logs.err (fun m -> + m "albatross-force-create: error querying albatross: %s" msg); Middleware.http_response reqd ~title:"Error" - ~data:(`String ("Error querying albatross: " ^ msg)) + ~data: + (`String ("Force create: Error querying albatross: " ^ msg)) `Internal_server_error | Ok (_hdr, res) -> ( match Albatross_json.res res with @@ -1293,7 +1297,8 @@ struct ~data: (`String (unikernel_name - ^ " has been updated to the latest build.")) + ^ " has been updated to the latest build with uuid " + ^ build)) `OK))) let unikernel_update albatross reqd http_client ~json_dict diff --git a/unikernel_update.ml b/unikernel_update.ml index 465d6d43..da8076b3 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -4,6 +4,7 @@ let arg_modal ~unikernel_name Tyxml_html.( section [ + p ~a:[ a_id "unikernel-arguments-alert"; a_class [ "my-4 hidden" ] ] []; div ~a:[ a_class [ "my-4" ] ] [ @@ -75,7 +76,7 @@ let arg_modal ~unikernel_name dark:peer-checked:text-white"; ]; ] - [ txt "Update the arguments for this build" ]; + [ txt "Update the configuration for this build" ]; ]; div ~a: @@ -578,7 +579,7 @@ let unikernel_update_layout ~unikernel_name unikernel current_time ]; ]; (if build_comparison.right.main_binary then - Modal_dialog.modal_dialog ~modal_title:"Check arguments" + Modal_dialog.modal_dialog ~modal_title:"Unikernel Configuration" ~button_content:(txt "Update to Latest") ~content: (arg_modal ~unikernel_name unikernel build_comparison.right) From b1fd02ebb60963817a8b2816ae078d01a9c2da54 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Thu, 6 Feb 2025 15:14:38 +0100 Subject: [PATCH 34/36] specify which albatross query this is from --- unikernel.ml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unikernel.ml b/unikernel.ml index 7bc1543b..b8ab55df 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1286,7 +1286,9 @@ struct match Albatross_json.res res with | Error (`String err) -> Middleware.http_response reqd ~title:"Error" - ~data:(`String (String.escaped err)) + ~data: + (`String + ("albatross force-create: " ^ String.escaped err)) `Internal_server_error | Ok res -> Logs.info (fun m -> From db8ef49afb5e961699db600c773a710cbd57e39d Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Thu, 6 Feb 2025 15:32:16 +0100 Subject: [PATCH 35/36] consistent naming --- unikernel_update.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unikernel_update.ml b/unikernel_update.ml index da8076b3..54a30544 100644 --- a/unikernel_update.ml +++ b/unikernel_update.ml @@ -451,7 +451,8 @@ let unikernel_update_layout ~unikernel_name unikernel current_time [ txt (Ohex.encode data.digest) ]; ]; (if build_comparison.right.main_binary then - Modal_dialog.modal_dialog ~modal_title:"Check arguments" + Modal_dialog.modal_dialog + ~modal_title:"Unikernel Configuration" ~button_content:(txt "Update to Latest") ~content: (arg_modal ~unikernel_name unikernel From 40a4b7432188d586b99ba4863068023cda52c715 Mon Sep 17 00:00:00 2001 From: Pixie Dust Date: Thu, 6 Feb 2025 16:59:26 +0100 Subject: [PATCH 36/36] pass json dict instead of string --- assets/main.js | 4 ++-- unikernel.ml | 29 +++++++++++++++++------------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/assets/main.js b/assets/main.js index c3245716..e9d67f29 100644 --- a/assets/main.js +++ b/assets/main.js @@ -932,7 +932,7 @@ async function updateUnikernel(job, build, unikernel_name) { const molly_csrf = document.getElementById("molly-csrf").value; const formAlert = document.getElementById("unikernel-arguments-alert"); if (argumentsToggle && !unikernelArguments) { - postAlert("bg-secondary-300", "You must give arguments for this build else switch 'Update the arguments for this build' off"); + postAlert("bg-secondary-300", "You must give arguments for this build else switch 'Update the configuration for this build' off"); buttonLoading(updateButton, false, "Proceed to update") return; } @@ -948,7 +948,7 @@ async function updateUnikernel(job, build, unikernel_name) { "job": job, "build": build, "unikernel_name": unikernel_name, - "unikernel_arguments": argumentsToggle ? unikernelArguments : null, + "unikernel_arguments": argumentsToggle ? JSON.parse(unikernelArguments) : null, "molly_csrf": molly_csrf }) }) diff --git a/unikernel.ml b/unikernel.ml index b8ab55df..b2a4c4fd 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -1305,6 +1305,18 @@ struct let unikernel_update albatross reqd http_client ~json_dict (user : User_model.user) = + let config_or_none field = function + | None | Some `Null -> Ok None + | Some json -> ( + match Albatross_json.config_of_json (Yojson.Basic.to_string json) with + | Ok cfg -> Ok (Some cfg) + | Error (`Msg err) -> + Error + (`Msg + ("invalid json for " ^ field ^ ": " + ^ Yojson.Basic.to_string json + ^ "failed with: " ^ err))) + in match Utils.Json. ( get "job" json_dict, @@ -1315,8 +1327,8 @@ struct | ( Some (`String job), Some (`String build), Some (`String unikernel_name), - arguments ) -> ( - match Utils.Json.string_or_none "unikernel_arguments" arguments with + configuration ) -> ( + match config_or_none "unikernel_arguments" configuration with | Error (`Msg err) -> Middleware.http_response reqd ~title:"Error with Unikernel Arguments Json" @@ -1349,16 +1361,9 @@ struct Middleware.http_response reqd ~title:"Error" ~data:(`String (String.escaped err)) `Internal_server_error)) - | Ok (Some args) -> ( - match Albatross_json.config_of_json args with - | Ok cfg -> - process_unikernel_update ~unikernel_name ~job ~build cfg user - albatross http_client reqd - | Error (`Msg err) -> - Logs.warn (fun m -> m "Couldn't decode data %s" err); - Middleware.http_response reqd ~title:"Error" - ~data:(`String (String.escaped err)) - `Internal_server_error)) + | Ok (Some cfg) -> + process_unikernel_update ~unikernel_name ~job ~build cfg user + albatross http_client reqd) | _ -> Middleware.http_response reqd ~title:"Error" ~data:(`String "Couldn't find job or build in json. Received ")