Skip to content

Commit f8031f5

Browse files
authored
system_status: enhance useability of the API call (#6690)
Now there's no failure when called without parameters, suggests a nice filename with the correct extension, advertises the content-type of the file served, and adds an option to list all the allowed entries supported by xen-bugtool in an xml format.
2 parents 0f0775b + 792f8a2 commit f8031f5

File tree

6 files changed

+158
-92
lines changed

6 files changed

+158
-92
lines changed

ocaml/libs/http-lib/http_svr.ml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ let response_redirect ?req s dest =
233233
in
234234
Unixext.really_write_string s (Http.Response.to_wire_string res)
235235

236-
let response_file ?mime_content_type ~hsts_time s file =
236+
let response_file ?mime_content_type ?download_name ~hsts_time s file =
237237
let size = (Unix.LargeFile.stat file).Unix.LargeFile.st_size in
238238
let keep_alive = [(Http.Hdr.connection, "keep-alive")] in
239239
let hsts_header =
@@ -248,9 +248,17 @@ let response_file ?mime_content_type ~hsts_time s file =
248248
~some:(fun ty -> [(Hdr.content_type, ty)])
249249
mime_content_type
250250
in
251+
let content_disposition =
252+
let hdr = Hdr.content_disposition in
253+
let typ = "attachment" in
254+
Option.fold ~none:[]
255+
~some:(fun name -> [(hdr, Printf.sprintf {|%s; filename="%s"|} typ name)])
256+
download_name
257+
in
251258
let res =
252259
Http.Response.make ~version:"1.1"
253-
~headers:(List.concat [keep_alive; hsts_header; mime_header])
260+
~headers:
261+
(List.concat [keep_alive; hsts_header; mime_header; content_disposition])
254262
~length:size "200" "OK"
255263
in
256264
Unixext.with_file file [Unix.O_RDONLY] 0 (fun f ->

ocaml/libs/http-lib/http_svr.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ val response_redirect : ?req:Http.Request.t -> Unix.file_descr -> string -> unit
109109

110110
val response_file :
111111
?mime_content_type:string
112+
-> ?download_name:string
112113
-> hsts_time:int
113114
-> Unix.file_descr
114115
-> string

ocaml/xapi/system_status.ml

Lines changed: 141 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -11,120 +11,173 @@
1111
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1212
* GNU Lesser General Public License for more details.
1313
*)
14-
open Http
15-
open Printf
16-
open Xapi_stdext_pervasives.Pervasiveext
17-
open Xapi_stdext_std.Xstringext
18-
open Forkhelpers
1914

20-
let content_type = "application/data"
15+
module Request = Http.Request
2116

22-
let xen_bugtool = "/usr/sbin/xen-bugtool"
17+
let finally = Xapi_stdext_pervasives.Pervasiveext.finally
2318

2419
let task_label = "Retrieving system status"
2520

26-
let module_key = "system_status"
21+
module L = Debug.Make (struct let name = __MODULE__ end)
2722

28-
module D = Debug.Make (struct let name = module_key end)
23+
module Output = struct
24+
(** The output formats of xen-bugtool *)
25+
type t = Tar | TarBz2 | Zip
2926

30-
open D
27+
let of_string = function
28+
| "tar" ->
29+
Some Tar
30+
| "tar.bz2" ->
31+
Some TarBz2
32+
| "zip" ->
33+
Some Zip
34+
| _ ->
35+
None
3136

32-
let get_capabilities () =
33-
let cmd = sprintf "%s --capabilities" xen_bugtool in
34-
Helpers.get_process_output cmd
37+
let to_extension = function
38+
| Tar ->
39+
"tar"
40+
| TarBz2 ->
41+
"tar.bz2"
42+
| Zip ->
43+
"zip"
3544

36-
(* This fn outputs xen-bugtool straight to the socket, only
37-
for tar output. It should work on embedded edition *)
38-
let send_via_fd __context s entries output =
39-
let s_uuid = Uuidx.to_string (Uuidx.make ()) in
40-
let params =
45+
let to_mime = function
46+
| Tar ->
47+
"appliation/x-tar"
48+
| TarBz2 ->
49+
"application/x-bzip2"
50+
| Zip ->
51+
"application/zip"
52+
end
53+
54+
module Bugtool = struct
55+
let path = "/usr/sbin/xen-bugtool"
56+
57+
let params_cp ~entries ~extension =
4158
[
42-
sprintf "--entries=%s" entries
59+
Printf.sprintf "--entries=%s" entries
4360
; "--silent"
4461
; "--yestoall"
45-
; sprintf "--output=%s" output
46-
; "--outfd=" ^ s_uuid
62+
; Printf.sprintf "--output=%s" extension
4763
]
48-
in
49-
let cmd = sprintf "%s %s" xen_bugtool (String.concat " " params) in
50-
debug "running %s" cmd ;
51-
try
52-
let headers =
53-
Http.http_200_ok ~keep_alive:false ~version:"1.0" ()
54-
@ [
55-
"Server: " ^ Xapi_version.xapi_user_agent
56-
; Http.Hdr.content_type ^ ": " ^ content_type
57-
; "Content-Disposition: attachment; filename=\"system_status.tgz\""
58-
]
59-
in
60-
Http_svr.headers s headers ;
61-
let result =
62-
with_logfile_fd "get-system-status" (fun log_fd ->
63-
let pid =
64-
safe_close_and_exec None (Some log_fd) (Some log_fd)
65-
[(s_uuid, s)]
66-
xen_bugtool params
67-
in
68-
waitpid_fail_if_bad_exit pid
69-
)
64+
65+
let cmd_capabilities = Printf.sprintf "%s --capabilities" path
66+
67+
let params_fd ~entries ~extension ~uuid =
68+
let params =
69+
params_cp ~entries ~extension @ [Printf.sprintf "--outfd=%s" uuid]
7070
in
71-
match result with
72-
| Success _ ->
73-
debug "xen-bugtool exited successfully"
74-
| Failure (log, exn) ->
75-
debug "xen-bugtool failed with output: %s" log ;
76-
raise exn
77-
with e ->
78-
let msg = "xen-bugtool failed: " ^ Printexc.to_string e in
79-
error "%s" msg ;
80-
raise
81-
(Api_errors.Server_error (Api_errors.system_status_retrieval_failed, [msg])
82-
)
71+
let cmd = String.concat " " (path :: params) in
72+
L.debug "%s: running %s" __FUNCTION__ cmd ;
73+
params
74+
75+
let cmd_cp ~entries ~extension =
76+
let params = params_cp ~entries ~extension in
77+
let cmd = String.concat " " (path :: params) in
78+
L.debug "%s: running %s" __FUNCTION__ cmd ;
79+
cmd
80+
81+
let filename __context extension =
82+
let timestamp = Ptime_clock.now () |> Ptime.to_rfc3339 ~tz_offset_s:0 in
83+
let self = Helpers.get_localhost ~__context in
84+
let hostname = Db.Host.get_hostname ~__context ~self in
85+
Printf.sprintf "system_status-%s-%s.%s" timestamp hostname extension
86+
end
87+
88+
let get_capabilities () = Helpers.get_process_output Bugtool.cmd_capabilities
89+
90+
(* This fn outputs xen-bugtool straight to the socket, only
91+
for tar output. It should work on embedded edition *)
92+
let send_via_fd __context s entries output =
93+
let uuid = Uuidx.to_string (Uuidx.make ()) in
94+
let extension = Output.to_extension output in
95+
let content_type = Output.to_mime output in
96+
let filename = Bugtool.filename __context extension in
97+
let params = Bugtool.params_fd ~entries ~extension ~uuid in
98+
let headers =
99+
Http.http_200_ok ~keep_alive:false ~version:"1.0" ()
100+
@ [
101+
Printf.sprintf "Server: %s" Xapi_version.xapi_user_agent
102+
; Printf.sprintf "%s: %s" Http.Hdr.content_type content_type
103+
; Printf.sprintf {|%s: attachment; filename="%s"|}
104+
Http.Hdr.content_disposition filename
105+
]
106+
in
107+
Http_svr.headers s headers ;
108+
let result =
109+
Forkhelpers.with_logfile_fd "get-system-status" (fun log_fd ->
110+
let pid =
111+
Forkhelpers.safe_close_and_exec None (Some log_fd) (Some log_fd)
112+
[(uuid, s)]
113+
Bugtool.path params
114+
in
115+
Forkhelpers.waitpid_fail_if_bad_exit pid
116+
)
117+
in
118+
match result with Success _ -> Ok () | Failure (log, exn) -> Error (log, exn)
83119

84120
(* This fn outputs xen-bugtool into a file and then write the
85121
file out to the socket, to deal with zipped bugtool outputs
86122
It will not work on embedded edition *)
87123
let send_via_cp __context s entries output =
88-
let cmd =
89-
sprintf "%s --entries=%s --silent --yestoall --output=%s" xen_bugtool
90-
entries output
91-
in
92-
let () = debug "running %s" cmd in
124+
let extension = Output.to_extension output in
125+
let content_type = Output.to_mime output in
126+
let cmd = Bugtool.cmd_cp ~entries ~extension in
93127
try
94-
let filename = String.rtrim (Helpers.get_process_output cmd) in
128+
let filepath = String.trim (Helpers.get_process_output cmd) in
129+
let filename = Bugtool.filename __context extension in
95130
let hsts_time = !Xapi_globs.hsts_max_age in
96131
finally
97132
(fun () ->
98-
debug "bugball path: %s" filename ;
99-
Http_svr.response_file ~mime_content_type:content_type ~hsts_time s
100-
filename
133+
Http_svr.response_file ~mime_content_type:content_type ~hsts_time
134+
~download_name:filename s filepath
101135
)
102136
(fun () ->
103137
Helpers.log_exn_continue "deleting xen-bugtool output" Unix.unlink
104-
filename
105-
)
106-
with e ->
107-
let msg = "xen-bugtool failed: " ^ ExnHelper.string_of_exn e in
108-
error "%s" msg ;
109-
raise
110-
(Api_errors.Server_error (Api_errors.system_status_retrieval_failed, [msg])
111-
)
138+
filepath
139+
) ;
140+
Ok ()
141+
with e -> Error ("(Not captured)", e)
142+
143+
let with_api_errors f ctx s entries output =
144+
match f ctx s entries output with
145+
| Ok () ->
146+
()
147+
| Error (log, exn) ->
148+
L.debug "xen-bugtool failed with output: %s" log ;
149+
let msg = "xen-bugtool failed: " ^ Printexc.to_string exn in
150+
raise Api_errors.(Server_error (system_status_retrieval_failed, [msg]))
151+
152+
let send_capabilities req s =
153+
let content = get_capabilities () in
154+
let xml_type = "application/xml" in
155+
let hdrs =
156+
[
157+
("Server", Xapi_version.xapi_user_agent); (Http.Hdr.content_type, xml_type)
158+
]
159+
in
160+
Http_svr.response_str req ~hdrs s content
112161

113162
let handler (req : Request.t) s _ =
114-
debug "In system status http handler..." ;
115163
req.Request.close <- true ;
116-
let get_param s = try List.assoc s req.Request.query with _ -> "" in
117-
let entries = get_param "entries" in
118-
let output = get_param "output" in
119-
let () = debug "session_id: %s" (get_param "session_id") in
120-
Xapi_http.with_context task_label req s (fun __context ->
121-
if Helpers.on_oem ~__context && output <> "tar" then
122-
raise
123-
(Api_errors.Server_error
124-
(Api_errors.system_status_must_use_tar_on_oem, [])
125-
)
126-
else if output = "tar" then
127-
send_via_fd __context s entries output
128-
else
129-
send_via_cp __context s entries output
130-
)
164+
let get_param s = List.assoc_opt s req.Request.query in
165+
let list_capabilies = Option.is_some (get_param "list") in
166+
let entries = Option.value ~default:"" (get_param "entries") in
167+
let output = Option.bind (get_param "output") Output.of_string in
168+
169+
let send_list () = send_capabilities req s in
170+
let send_file () =
171+
Xapi_http.with_context task_label req s @@ fun __context ->
172+
match
173+
(Helpers.on_oem ~__context, Option.value ~default:Output.Tar output)
174+
with
175+
| _, (Output.Tar as output) ->
176+
with_api_errors send_via_fd __context s entries output
177+
| false, output ->
178+
with_api_errors send_via_cp __context s entries output
179+
| true, _ ->
180+
raise Api_errors.(Server_error (system_status_must_use_tar_on_oem, []))
181+
in
182+
183+
if list_capabilies then send_list () else send_file ()

ocaml/xapi/system_status.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
val get_capabilities : unit -> string
2+
3+
val handler : Http.Request.t -> Unix.file_descr -> 'a -> unit

ocaml/xapi/xapi_vncsnapshot.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ let vncsnapshot_handler (req : Request.t) s _ =
3030
Rbac_static.permission_http_get_vncsnapshot_host_console
3131
.Db_actions.role_name_label ;
3232
let tmp = Filename.temp_file "snapshot" "jpg" in
33+
let filename = Filename.basename tmp in
3334
Xapi_stdext_pervasives.Pervasiveext.finally
3435
(fun () ->
3536
let vnc_port =
@@ -48,7 +49,7 @@ let vncsnapshot_handler (req : Request.t) s _ =
4849
in
4950
let hsts_time = !Xapi_globs.hsts_max_age in
5051
waitpid_fail_if_bad_exit pid ;
51-
Http_svr.response_file ~hsts_time s tmp
52+
Http_svr.response_file ~hsts_time s tmp ~download_name:filename
5253
)
5354
(fun () -> try Unix.unlink tmp with _ -> ())
5455
with e ->

quality-gate.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ verify-cert () {
2525
}
2626

2727
mli-files () {
28-
N=463
28+
N=462
2929
X="ocaml/tests"
3030
X+="|ocaml/quicktest"
3131
X+="|ocaml/message-switch/core_test"

0 commit comments

Comments
 (0)