@@ -108,6 +108,101 @@ let pvs_version = "3.0"
108108let supported_api_versions = [pvs_version; " 5.0" ]
109109let api_max = List. fold_left ~f: max supported_api_versions ~init: " "
110110
111+ let id = fun x -> x
112+
113+ type compat_in = R .t -> R .t
114+ (* * A function that changes the input to make it compatible with an older
115+ script *)
116+
117+ type compat_out = R .t -> R .t
118+ (* * A function that changes the output of an older script to make it
119+ compatible with the new interface and ensure it is unmarshalled without
120+ error. *)
121+
122+ module Compat (V : sig val version : string option ref end ) : sig
123+ (* * Module for making the inputs and outputs compatible with the old PVS
124+ version of the storage scripts. *)
125+
126+ type device_config = (Core.String .t , string ) Core.List.Assoc .t
127+
128+ val compat_out_volume : compat_out
129+ (* * Add the missing [sharable] field to the Dict in [rpc], to ensure the
130+ volume in the output match the new volume record type and is successfully
131+ parsed by rpclib. *)
132+
133+ val compat_out_volumes : compat_out
134+ (* * Add the missing [sharable] field to the Dicts in [rpc], to ensure the
135+ volumes in the output match the new volume record type and are
136+ successfully parsed by rpclib. *)
137+
138+ val compat_uri : device_config -> (compat_in , Storage_interface.Exception .exnty ) Deferred.Result .t
139+ (* * For the old PVS version, adds the uri parameter to the call from
140+ device_config, for newer versions, removes the uri key from device_config *)
141+
142+ val sr_create : device_config -> (device_config * compat_in * compat_out , Storage_interface.Exception .exnty ) Deferred.Result .t
143+ (* * Compatiblity for the old PVS version of SR.create, which had signature
144+ [uri -> name -> desc -> config -> unit] *)
145+ end = struct
146+
147+ type device_config = (Core.String .t , string ) Core.List.Assoc .t
148+ type compat_in = R .t -> R .t
149+ type compat_out = R .t -> R .t
150+
151+ let remove field rpc =
152+ match ! V. version, rpc with
153+ | Some v , R. Dict d when v = pvs_version ->
154+ R. Dict (List. filter ~f: (fun (k ,_ ) -> k <> field) d)
155+ | _ -> rpc
156+
157+ let with_pvs_version f rpc = if ! V. version = Some pvs_version then f rpc else rpc
158+
159+ let add_param_to_input params =
160+ with_pvs_version (function
161+ (* Currently all parameters must be named. In this case, rpclib
162+ currently puts them into a Dict. *)
163+ | R. Dict d -> R. Dict (List. rev_append params d)
164+ | rpc -> rpc)
165+
166+ let add_fields_to_dict fields =
167+ function R. Dict d -> R. Dict (List. rev_append fields d) | rpc -> rpc
168+
169+ let add_fields_to_record_output fields =
170+ with_pvs_version (function
171+ | R. Dict _ as d -> add_fields_to_dict fields d
172+ | rpc -> rpc)
173+
174+ let add_fields_to_record_list_output fields =
175+ with_pvs_version (function
176+ | R. Enum l -> R. Enum (List. map ~f: (add_fields_to_dict fields) l)
177+ | rpc -> rpc)
178+
179+ let compat_out_volume =
180+ add_fields_to_record_output [" sharable" , R. Bool false ]
181+
182+ let compat_out_volumes =
183+ add_fields_to_record_list_output [" sharable" , R. Bool false ]
184+
185+ let compat_uri device_config =
186+ if ! V. version = Some pvs_version then
187+ match List.Assoc. find ~equal: String. equal device_config " uri" with
188+ | None ->
189+ return (Error (missing_uri () ))
190+ | Some uri ->
191+ return (Ok (add_param_to_input [" uri" , R. String uri]))
192+ else
193+ return (Ok id)
194+
195+ let sr_create device_config =
196+ compat_uri device_config >>> = fun compat_in ->
197+ let compat_out rpc =
198+ (* The PVS version will return nothing *)
199+ if rpc = R. Null then
200+ Rpcmarshal. marshal Xapi_storage.Control. typ_of_configuration device_config
201+ else rpc
202+ in
203+ return (Ok (device_config, compat_in, compat_out))
204+ end
205+
111206let check_plugin_version_compatible query_result =
112207 let Xapi_storage.Plugin. { name; required_api_version; _ } = query_result in
113208 if required_api_version <> api_max then
@@ -197,8 +292,6 @@ module Script = struct
197292 find ()
198293end
199294
200- let id = fun x -> x
201-
202295(* * Call the script named after the RPC method in the [script_dir]
203296 directory. The arguments (not the whole JSON-RPC call) are passed as JSON
204297 to its stdin, and stdout is returned. In case of a non-zero exit code,
@@ -212,101 +305,102 @@ let id = fun x -> x
212305 output.
213306 This function either returns a successful RPC response, or raises
214307 Fork_exec_error with a suitable SMAPIv2 error if the call failed. *)
215- let fork_exec_rpc ~script_dir ?missing ?(compat_in =id) ?(compat_out =id) =
216- let invoke_script call script_name =
217- Process. create ~prog: script_name ~args: [" --json" ] ()
218- >> = function
219- | Error e ->
220- error " %s failed: %s" script_name (Error. to_string_hum e);
221- return (Error (backend_error " SCRIPT_FAILED" [ script_name; Error. to_string_hum e ]))
222- | Ok p ->
223- (* Send the request as json on stdin *)
224- let w = Process. stdin p in
225- (* We pass just the args, not the complete JSON-RPC call.
226- Currently the Python code generated by rpclib requires all params to
227- be named - they will be converted into a name->value Python dict.
228- Rpclib currently puts all named params into a dict, so we expect
229- params to be a single Dict, if all the params are named. *)
230- (match call.R. params with
231- | [R. Dict _ as d] ->
232- return (Ok d)
233- | _ -> return (Error (backend_error " INCORRECT_PARAMETERS" [ script_name; " All the call parameters should be named and should be in a RPC Dict" ]))
234- ) >>> = fun args ->
235- let args = compat_in args in
236- Writer. write w (Jsonrpc. to_string args);
237- Writer. close w
238- >> = fun () ->
239- Process. collect_output_and_wait p
240- >> = fun output ->
241- begin match output.Process.Output. exit_status with
242- | Error (`Exit_non_zero code ) ->
243- (* Expect an exception and backtrace on stdout *)
244- begin match Or_error. try_with (fun () -> Jsonrpc. of_string output.Process.Output. stdout) with
245- | Error _ ->
246- error " %s failed and printed bad error json: %s" script_name output.Process.Output. stdout;
247- error " %s failed, stderr: %s" script_name output.Process.Output. stderr;
248- return (Error (backend_error " SCRIPT_FAILED" [ script_name; " non-zero exit and bad json on stdout" ; string_of_int code; output.Process.Output. stdout; output.Process.Output. stdout ]))
249- | Ok response ->
250- begin match Or_error. try_with (fun () -> error_of_rpc response) with
251- | Error _ ->
252- error " %s failed and printed bad error json: %s" script_name output.Process.Output. stdout;
253- error " %s failed, stderr: %s" script_name output.Process.Output. stderr;
254- return (Error (backend_error " SCRIPT_FAILED" [ script_name; " non-zero exit and bad json on stdout" ; string_of_int code; output.Process.Output. stdout; output.Process.Output. stdout ]))
255- | Ok x -> return (Error (backend_backtrace_error x.code x.params x.backtrace))
256- end
257- end
258- | Error (`Signal signal ) ->
259- error " %s caught a signal and failed" script_name;
260- return (Error (backend_error " SCRIPT_FAILED" [ script_name; " signalled" ; Signal. to_string signal; output.Process.Output. stdout; output.Process.Output. stdout ]))
261- | Ok () ->
262-
263- (* Parse the json on stdout. We get back a JSON-RPC
264- value from the scripts, not a complete JSON-RPC response *)
265- begin match Or_error. try_with (fun () -> Jsonrpc. of_string output.Process.Output. stdout) with
266- | Error _ ->
267- error " %s succeeded but printed bad json: %s" script_name output.Process.Output. stdout;
268- return (Error (backend_error " SCRIPT_FAILED" [ script_name; " bad json on stdout" ; output.Process.Output. stdout ]))
269- | Ok response ->
270- info " %s succeeded: %s" script_name output.Process.Output. stdout;
271- let response = compat_out response in
272- let response = R. success response in
273- return (Ok response)
274- end
275- end
276- in
308+ let fork_exec_rpc : script_dir:string -> ?missing:R.t -> ?compat_in:compat_in -> ?compat_out:compat_out -> R.call -> R.response Deferred.t =
309+ fun ~script_dir ?missing ?(compat_in =id) ?(compat_out =id) ->
310+ let invoke_script call script_name : (R.response, Storage_interface.Exception.exnty) Deferred.Result.t =
311+ Process. create ~prog: script_name ~args: [" --json" ] ()
312+ >> = function
313+ | Error e ->
314+ error " %s failed: %s" script_name (Error. to_string_hum e);
315+ return (Error (backend_error " SCRIPT_FAILED" [ script_name; Error. to_string_hum e ]))
316+ | Ok p ->
317+ (* Send the request as json on stdin *)
318+ let w = Process. stdin p in
319+ (* We pass just the args, not the complete JSON-RPC call.
320+ Currently the Python code generated by rpclib requires all params to
321+ be named - they will be converted into a name->value Python dict.
322+ Rpclib currently puts all named params into a dict, so we expect
323+ params to be a single Dict, if all the params are named. *)
324+ (match call.R. params with
325+ | [R. Dict _ as d] ->
326+ return (Ok d)
327+ | _ -> return (Error (backend_error " INCORRECT_PARAMETERS" [ script_name; " All the call parameters should be named and should be in a RPC Dict" ]))
328+ ) >>> = fun args ->
329+ let args = compat_in args in
330+ Writer. write w (Jsonrpc. to_string args);
331+ Writer. close w
332+ >> = fun () ->
333+ Process. collect_output_and_wait p
334+ >> = fun output ->
335+ begin match output.Process.Output. exit_status with
336+ | Error (`Exit_non_zero code ) ->
337+ (* Expect an exception and backtrace on stdout *)
338+ begin match Or_error. try_with (fun () -> Jsonrpc. of_string output.Process.Output. stdout) with
339+ | Error _ ->
340+ error " %s failed and printed bad error json: %s" script_name output.Process.Output. stdout;
341+ error " %s failed, stderr: %s" script_name output.Process.Output. stderr;
342+ return (Error (backend_error " SCRIPT_FAILED" [ script_name; " non-zero exit and bad json on stdout" ; string_of_int code; output.Process.Output. stdout; output.Process.Output. stdout ]))
343+ | Ok response ->
344+ begin match Or_error. try_with (fun () -> error_of_rpc response) with
345+ | Error _ ->
346+ error " %s failed and printed bad error json: %s" script_name output.Process.Output. stdout;
347+ error " %s failed, stderr: %s" script_name output.Process.Output. stderr;
348+ return (Error (backend_error " SCRIPT_FAILED" [ script_name; " non-zero exit and bad json on stdout" ; string_of_int code; output.Process.Output. stdout; output.Process.Output. stdout ]))
349+ | Ok x -> return (Error (backend_backtrace_error x.code x.params x.backtrace))
350+ end
351+ end
352+ | Error (`Signal signal ) ->
353+ error " %s caught a signal and failed" script_name;
354+ return (Error (backend_error " SCRIPT_FAILED" [ script_name; " signalled" ; Signal. to_string signal; output.Process.Output. stdout; output.Process.Output. stdout ]))
355+ | Ok () ->
356+
357+ (* Parse the json on stdout. We get back a JSON-RPC
358+ value from the scripts, not a complete JSON-RPC response *)
359+ begin match Or_error. try_with (fun () -> Jsonrpc. of_string output.Process.Output. stdout) with
360+ | Error _ ->
361+ error " %s succeeded but printed bad json: %s" script_name output.Process.Output. stdout;
362+ return (Error (backend_error " SCRIPT_FAILED" [ script_name; " bad json on stdout" ; output.Process.Output. stdout ]))
363+ | Ok response ->
364+ info " %s succeeded: %s" script_name output.Process.Output. stdout;
365+ let response = compat_out response in
366+ let response = R. success response in
367+ return (Ok response)
368+ end
369+ end
370+ in
277371
278- let script_rpc call =
279- info " %s" (Jsonrpc. string_of_call call);
280- Script. path ~script_dir ~script_name: call.R. name >> = function
281- | Error (`missing path ) ->
282- error " %s is not a file" path;
283- (match missing with
284- | None ->
285- return (Error (backend_error " SCRIPT_MISSING" [ path; " Check whether the file exists and has correct permissions" ]))
286- | Some m ->
287- warn " Deprecated: script '%s' is missing, treating as no-op. Update your plugin!" path;
288- return (Ok (R. success m)))
289- | Error (`not_executable (path , exn )) ->
290- error " %s is not executable" path;
291- return (Error (backend_error " SCRIPT_NOT_EXECUTABLE" [ path; Exn. to_string exn ]))
292- | Ok path -> invoke_script call path
293- in
372+ let script_rpc call : (R.response, Storage_interface.Exception.exnty) Deferred.Result.t =
373+ info " %s" (Jsonrpc. string_of_call call);
374+ Script. path ~script_dir ~script_name: call.R. name >> = function
375+ | Error (`missing path ) ->
376+ error " %s is not a file" path;
377+ (match missing with
378+ | None ->
379+ return (Error (backend_error " SCRIPT_MISSING" [ path; " Check whether the file exists and has correct permissions" ]))
380+ | Some m ->
381+ warn " Deprecated: script '%s' is missing, treating as no-op. Update your plugin!" path;
382+ return (Ok (R. success m)))
383+ | Error (`not_executable (path , exn )) ->
384+ error " %s is not executable" path;
385+ return (Error (backend_error " SCRIPT_NOT_EXECUTABLE" [ path; Exn. to_string exn ]))
386+ | Ok path -> invoke_script call path
387+ in
294388
295- (* The Errors we return from this function and the special error format
296- returned by the scripts are not included in the error types of the various
297- SMAPIv3 interfaces, therefore we have to propagate them as exceptions
298- instead of returning an RPC call with an error, because rpclib would fail
299- to unmarshal that error.
300- Therefore we either return a successful RPC response, or raise
301- Fork_exec_error with a suitable SMAPIv2 error if the call failed. *)
302- let rpc call =
303- script_rpc call >> = fun result ->
304- Result. map_error ~f: (fun e -> Fork_exec_error e) result
305- |> Result. ok_exn
306- |> return
307- in
389+ (* The Errors we return from this function and the special error format
390+ returned by the scripts are not included in the error types of the various
391+ SMAPIv3 interfaces, therefore we have to propagate them as exceptions
392+ instead of returning an RPC call with an error, because rpclib would fail
393+ to unmarshal that error.
394+ Therefore we either return a successful RPC response, or raise
395+ Fork_exec_error with a suitable SMAPIv2 error if the call failed. *)
396+ let rpc : R. call -> R.response Deferred.t = fun call ->
397+ script_rpc call >> = fun result ->
398+ Result. map_error ~f: (fun e -> Fork_exec_error e) result
399+ |> Result. ok_exn
400+ |> return
401+ in
308402
309- rpc
403+ rpc
310404
311405module Attached_SRs = struct
312406 type state = {
@@ -421,75 +515,6 @@ let vdi_of_volume x =
421515 persistent = true ;
422516}
423517
424- module Compat (V : sig val version : string option ref end ) = struct
425- (* * Module for making the inputs and outputs compatible with the old PVS
426- version of the storage scripts. *)
427-
428- let remove field rpc =
429- match ! V. version, rpc with
430- | Some v , R. Dict d when v = pvs_version ->
431- R. Dict (List. filter ~f: (fun (k ,_ ) -> k <> field) d)
432- | _ -> rpc
433-
434- let with_pvs_version f rpc = if ! V. version = Some pvs_version then f rpc else rpc
435-
436- let add_param_to_input params =
437- with_pvs_version (function
438- (* Currently all parameters must be named. In this case, rpclib
439- currently puts them into a Dict. *)
440- | R. Dict d -> R. Dict (List. rev_append params d)
441- | rpc -> rpc)
442-
443- let add_fields_to_dict fields =
444- function R. Dict d -> R. Dict (List. rev_append fields d) | rpc -> rpc
445-
446- let add_fields_to_record_output fields =
447- with_pvs_version (function
448- | R. Dict _ as d -> add_fields_to_dict fields d
449- | rpc -> rpc)
450-
451- let add_fields_to_record_list_output fields =
452- with_pvs_version (function
453- | R. Enum l -> R. Enum (List. map ~f: (add_fields_to_dict fields) l)
454- | rpc -> rpc)
455-
456- (* * Add the missing [sharable] field to the Dict in [rpc], to ensure the
457- volume in the output match the new volume record type and is successfully
458- parsed by rpclib. *)
459- let compat_out_volume =
460- add_fields_to_record_output [" sharable" , R. Bool false ]
461-
462- (* * Add the missing [sharable] field to the Dicts in [rpc], to ensure the
463- volumes in the output match the new volume record type and are
464- successfully parsed by rpclib. *)
465- let compat_out_volumes =
466- add_fields_to_record_list_output [" sharable" , R. Bool false ]
467-
468- (* * For the old PVS version, adds the uri parameter to the call from
469- device_config, for newer versions, removes the uri key from device_config *)
470- let compat_uri device_config =
471- if ! V. version = Some pvs_version then
472- match List.Assoc. find ~equal: String. equal device_config " uri" with
473- | None ->
474- return (Error (missing_uri () ))
475- | Some uri ->
476- return (Ok (add_param_to_input [" uri" , R. String uri]))
477- else
478- return (Ok id)
479-
480- (* * Compatiblity for the old PVS version of SR.create, which had signature
481- [uri -> name -> desc -> config -> unit] *)
482- let sr_create device_config =
483- compat_uri device_config >>> = fun compat_in ->
484- let compat_out rpc =
485- (* The PVS version will return nothing *)
486- if rpc = R. Null then
487- Rpcmarshal. marshal Xapi_storage.Control. typ_of_configuration device_config
488- else rpc
489- in
490- return (Ok (device_config, compat_in, compat_out))
491- end
492-
493518let choose_datapath ?(persistent = true ) response =
494519 (* We can only use a URI with a valid scheme, since we use the scheme
495520 to name the datapath plugin. *)
0 commit comments