Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
impl.ml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351open Lwt.Infix let max_log_chunk_size = 102400L (* 100K at a time *) let read ~start path = let ch = open_in_bin (Fpath.to_string path) in Fun.protect ~finally:(fun () -> close_in ch) @@ fun () -> let len = LargeFile.in_channel_length ch in let (+) = Int64.add in let (-) = Int64.sub in let start = if start < 0L then len + start else start in let start = if start < 0L then 0L else if start > len then len else start in LargeFile.seek_in ch start; let len = min max_log_chunk_size (len - start) in really_input_string ch (Int64.to_int len), start + len (** Functor that takes both Current and Db modules. Used internally and for testing with mock databases. *) module Make_with_db (Current : S.CURRENT) (Db : S.DB) = struct open Capnp_rpc_lwt (* Helper functions for level conversion *) let level_to_capnp level = let s = Current.Level.to_string level in match s with | "harmless" -> Api.Builder.ConfirmLevel.Harmless | "mostly-harmless" -> Api.Builder.ConfirmLevel.MostlyHarmless | "average" -> Api.Builder.ConfirmLevel.Average | "above-average" -> Api.Builder.ConfirmLevel.AboveAverage | "dangerous" -> Api.Builder.ConfirmLevel.Dangerous | _ -> Api.Builder.ConfirmLevel.Harmless (* fallback *) let capnp_to_level = function | Api.Reader.ConfirmLevel.Harmless -> Current.Level.of_string "harmless" | Api.Reader.ConfirmLevel.MostlyHarmless -> Current.Level.of_string "mostly-harmless" | Api.Reader.ConfirmLevel.Average -> Current.Level.of_string "average" | Api.Reader.ConfirmLevel.AboveAverage -> Current.Level.of_string "above-average" | Api.Reader.ConfirmLevel.Dangerous -> Current.Level.of_string "dangerous" | Api.Reader.ConfirmLevel.Undefined _ -> Error (`Msg "undefined level") module Job = struct let job_cache = ref Current.Job.Map.empty let stream_log_data ~job_id ~start = match Current.Job.log_path job_id with | Error `Msg m -> Lwt_result.fail (`Capnp (`Exception (Capnp_rpc.Exception.v m))) | Ok path -> let rec aux () = match read ~start path with | ("", _) as x -> begin match Current.Job.lookup_running job_id with | None -> Lwt_result.return x | Some job -> Current.Job.wait_for_log_data job >>= aux end | x -> Lwt_result.return x in aux () let rec local engine job_id = let module Job = Api.Service.Job in match Current.Job.Map.find_opt job_id !job_cache with | Some job -> Capability.inc_ref job; job | None -> let cap = let lookup () = let state = Current.Engine.state engine in Current.Job.Map.find_opt job_id (state.jobs) in Job.local @@ object inherit Job.service method log_impl params release_param_caps = let open Job.Log in release_param_caps (); let start = Params.start_get params in Log.info (fun f -> f "log(%S, %Ld)" job_id start); Service.return_lwt @@ fun () -> stream_log_data ~job_id ~start >|= function | Error _ as e -> e | Ok (log, next) -> let response, results = Service.Response.create Results.init_pointer in Results.log_set results log; Results.next_set results next; Ok response method rebuild_impl _params release_param_caps = release_param_caps (); Log.info (fun f -> f "rebuild(%S)" job_id); match lookup () with | None -> Service.fail "Job is no longer active (cannot rebuild)" | Some job -> match job#rebuild with | None -> Service.fail "Job cannot be rebuilt at the moment" | Some rebuild -> let open Job.Rebuild in let response, results = Service.Response.create Results.init_pointer in let new_job = local engine (rebuild ()) in Results.job_set results (Some new_job); Capability.dec_ref new_job; Service.return_lwt @@ fun () -> (* Allow the engine to re-evaluate, so the job will appear active to the caller immediately. *) Lwt.pause () >|= fun () -> Ok response method cancel_impl _params release_param_caps = release_param_caps (); Log.info (fun f -> f "cancel(%S)" job_id); match Current.Job.lookup_running job_id with | None -> Service.fail "Job is no longer active (cannot cancel)" | Some job -> Current.Job.cancel job "Cancelled by user"; Service.return_empty () method status_impl _params release_param_caps = let open Job.Status in release_param_caps (); Log.info (fun f -> f "status(%S)" job_id); let response, results = Service.Response.create Results.init_pointer in Results.id_set results job_id; let can_cancel = match Current.Job.lookup_running job_id with | Some job -> Current.Job.cancelled_state job = Ok () | None -> false in begin match lookup () with | None -> Results.description_set results "Inactive job" | Some job -> Results.description_set results (Fmt.str "%t" job#pp); Results.can_cancel_set results can_cancel; Results.can_rebuild_set results (job#rebuild <> None); end; Service.return response method approve_early_start_impl _params release_param_caps = release_param_caps (); Log.info (fun f -> f "approveEarlyStart(%S)" job_id); match Current.Job.lookup_running job_id with | None -> Service.fail "Job is not running (cannot approve early start)" | Some job -> let response = Service.Response.create_empty () in Current.Job.approve_early_start job; Service.return response method! release = job_cache := Current.Job.Map.remove job_id !job_cache end in job_cache := Current.Job.Map.add job_id cap !job_cache; cap let local_opt engine job_id = match Current.Job.log_path job_id with | Error _ as e -> e | Ok _ -> Ok (local engine job_id) end let job ~engine id = Job.local engine id let engine engine = let module Engine = Api.Service.Engine in Engine.local @@ object inherit Engine.service (* Existing methods *) method active_jobs_impl _params release_param_caps = let open Engine.ActiveJobs in release_param_caps (); Log.info (fun f -> f "activeJobs"); let response, results = Service.Response.create Results.init_pointer in let state = Current.Engine.state engine in Current.Job.Map.bindings (state.jobs) |> List.map fst |> Results.ids_set_list results |> ignore; Service.return response method job_impl params release_param_caps = let open Engine.Job in let id = Params.id_get params in Log.info (fun f -> f "job(%S)" id); release_param_caps (); let response, results = Service.Response.create Results.init_pointer in match Job.local_opt engine id with | Error `Msg m -> Service.fail "%s" m | Ok job -> Results.job_set results (Some job); Capability.dec_ref job; Service.return response (* Database query methods - fully implemented with Db module *) method query_impl params release_param_caps = let open Engine.Query in release_param_caps (); Log.info (fun f -> f "query"); let p = Params.params_get params in let op = match Api.Reader.QueryParams.op_get p with "" -> None | s -> Some s in let ok = match Api.Reader.OptBool.get (Api.Reader.QueryParams.ok_get p) with | Api.Reader.OptBool.Unset -> None | Api.Reader.OptBool.Value v -> Some v | Api.Reader.OptBool.Undefined _ -> None in let rebuild = match Api.Reader.OptBool.get (Api.Reader.QueryParams.rebuild_get p) with | Api.Reader.OptBool.Unset -> None | Api.Reader.OptBool.Value v -> Some v | Api.Reader.OptBool.Undefined _ -> None in let job_prefix = match Api.Reader.QueryParams.job_prefix_get p with "" -> None | s -> Some s in let entries = Db.query ?op ?ok ?rebuild ?job_prefix () in let response, results = Service.Response.create Results.init_pointer in let arr = Results.entries_init results (List.length entries) in entries |> List.iteri (fun i (entry : Db.entry) -> let e = Capnp.Array.get arr i in Api.Builder.JobHistoryEntry.job_id_set e entry.job_id; Api.Builder.JobHistoryEntry.build_set e entry.build; Api.Builder.JobHistoryEntry.ready_set e entry.ready; Api.Builder.JobHistoryEntry.running_set e (Option.value entry.running ~default:0.0); Api.Builder.JobHistoryEntry.finished_set e entry.finished; Api.Builder.JobHistoryEntry.rebuild_set e entry.rebuild; let outcome = Api.Builder.JobHistoryEntry.outcome_init e in match entry.outcome with | Ok value -> Api.Builder.Outcome.success_set outcome value | Error (`Msg msg) -> Api.Builder.Outcome.failure_set outcome msg ); Service.return response method ops_impl _params release_param_caps = let open Engine.Ops in release_param_caps (); Log.info (fun f -> f "ops"); let response, results = Service.Response.create Results.init_pointer in Db.ops () |> Results.ops_set_list results |> ignore; Service.return response (* Pipeline overview methods *) method pipeline_stats_impl _params release_param_caps = let open Engine.PipelineStats in release_param_caps (); Log.info (fun f -> f "pipelineStats"); let pipeline = Current.Engine.pipeline engine in (* Analysis.stat returns a stats record which we convert to capnp *) let stats = Current.Analysis.stat pipeline in let response, results = Service.Response.create Results.init_pointer in let s = Results.stats_init results in Api.Builder.PipelineStats.ok_set_int_exn s stats.ok; Api.Builder.PipelineStats.waiting_for_confirmation_set_int_exn s stats.waiting_for_confirmation; Api.Builder.PipelineStats.ready_set_int_exn s stats.ready; Api.Builder.PipelineStats.running_set_int_exn s stats.running; Api.Builder.PipelineStats.failed_set_int_exn s stats.failed; Api.Builder.PipelineStats.blocked_set_int_exn s stats.blocked; Service.return response method pipeline_state_impl _params release_param_caps = let open Engine.PipelineState in release_param_caps (); Log.info (fun f -> f "pipelineState"); let state = Current.Engine.state engine in let response, results = Service.Response.create Results.init_pointer in let s = Results.state_init results in (match state.value with | Ok () -> Api.Builder.PipelineState.success_set s | Error (`Msg msg) -> Api.Builder.PipelineState.failed_set s msg | Error (`Active `Ready) -> Api.Builder.PipelineState.active_set s Api.Builder.ActiveState.Ready | Error (`Active `Running) -> Api.Builder.PipelineState.active_set s Api.Builder.ActiveState.Running | Error (`Active `Waiting_for_confirmation) -> Api.Builder.PipelineState.active_set s Api.Builder.ActiveState.WaitingForConfirmation ); Service.return response method pipeline_dot_impl _params release_param_caps = let open Engine.PipelineDot in release_param_caps (); Log.info (fun f -> f "pipelineDot"); let pipeline = Current.Engine.pipeline engine in let collapse_link ~k:_ ~v:_ = None in let job_info (meta : Current.Metadata.t) = let url = meta.job_id |> Option.map (fun id -> Printf.sprintf "/job/%s" id) in meta.update, url in let dot = Fmt.to_to_string (Current.Analysis.pp_dot ~env:[] ~collapse_link ~job_info) pipeline in let response, results = Service.Response.create Results.init_pointer in Results.dot_set results dot; Service.return response method get_confirm_level_impl _params release_param_caps = let open Engine.GetConfirmLevel in release_param_caps (); Log.info (fun f -> f "getConfirmLevel"); let config = Current.Engine.config engine in let response, results = Service.Response.create Results.init_pointer in (match Current.Config.get_confirm config with | None -> Results.is_set_set results false | Some level -> Results.is_set_set results true; Results.level_set results (level_to_capnp level) ); Service.return response method set_confirm_level_impl params release_param_caps = let open Engine.SetConfirmLevel in release_param_caps (); let unset = Params.unset_get params in Log.info (fun f -> f "setConfirmLevel(unset=%b)" unset); let config = Current.Engine.config engine in if unset then begin Current.Config.set_confirm config None; Service.return_empty () end else begin let level_capnp = Params.level_get params in match capnp_to_level level_capnp with | Error (`Msg msg) -> Service.fail "Invalid confirmation level: %s" msg | Ok level -> Current.Config.set_confirm config (Some level); Service.return_empty () end method rebuild_all_impl params release_param_caps = let open Engine.RebuildAll in release_param_caps (); let job_ids = Params.job_ids_get_list params in Log.info (fun f -> f "rebuildAll(%d jobs)" (List.length job_ids)); let state = Current.Engine.state engine in let jobs = state.jobs in let succeeded, failed = job_ids |> List.partition_map (fun job_id -> match Current.Job.Map.find_opt job_id jobs with | None -> Right job_id | Some actions -> match actions#rebuild with | None -> Right job_id | Some rebuild -> let _new_id : string = rebuild () in Left job_id ) in let response, results = Service.Response.create Results.init_pointer in succeeded |> Results.succeeded_set_list results |> ignore; failed |> Results.failed_set_list results |> ignore; Service.return response end end (** Main functor that uses Current_cache.Db for job history queries. *) module Make (Current : S.CURRENT) = Make_with_db(Current)(Current_cache.Db)