diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 4c2762b5222..d8259ca9cd8 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -29,6 +29,7 @@ b020cf35a1f2c274f95a4118d4596043cba6113f
ff39018fd6d91985f9c893a56928771dfe9fa48d
cbb9edb17dfd122c591beb14d1275acc39492335
d6ab15362548b8fe270bd14d5153b8d94e1b15c0
+b12cf444edea15da6274975e1b2ca6a7fce2a090
# ocp-indent
d018d26d6acd4707a23288b327b49e44f732725e
diff --git a/.github/workflows/generate-and-build-sdks.yml b/.github/workflows/generate-and-build-sdks.yml
index 80b32b5c8d9..db28438062f 100644
--- a/.github/workflows/generate-and-build-sdks.yml
+++ b/.github/workflows/generate-and-build-sdks.yml
@@ -24,6 +24,12 @@ jobs:
shell: bash
run: opam exec -- make sdk
+ - name: Store C SDK source
+ uses: actions/upload-artifact@v4
+ with:
+ name: SDK_Source_C
+ path: _build/install/default/xapi/sdk/c/*
+
- name: Store C# SDK source
uses: actions/upload-artifact@v4
with:
@@ -39,6 +45,30 @@ jobs:
- name: Cleanup XenAPI environment
uses: ./.github/workflows/cleanup-xapi-environment
+ build-c-sdk:
+ name: Build C SDK
+ runs-on: ubuntu-latest
+ needs: generate-sdk-sources
+ steps:
+ - name: Install dependencies
+ run: sudo apt-get install libxml2-dev
+
+ - name: Retrieve C SDK source
+ uses: actions/download-artifact@v4
+ with:
+ name: SDK_Source_C
+ path: source/
+
+ - name: Build C SDK
+ shell: bash
+ run: make -C source
+
+ - name: Store C SDK
+ uses: actions/upload-artifact@v4
+ with:
+ name: SDK_Artifacts_C
+ path: source/*
+
build-csharp-sdk:
name: Build C# SDK
runs-on: windows-2022
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 87d5cc8721f..c4d133d2fa1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -52,6 +52,12 @@ jobs:
name: XenAPI
path: dist/
+ - name: Retrieve C SDK distribution binaries
+ uses: actions/download-artifact@v4
+ with:
+ name: SDK_Artifacts_C
+ path: libxenserver/usr/local/
+
- name: Retrieve C# SDK distribution artifacts
uses: actions/download-artifact@v4
with:
@@ -70,10 +76,19 @@ jobs:
name: SDK_Binaries_XenServerPowerShell_NET6
path: sdk_powershell_7x/
+ - name: Package C SDK artifacts for deployment
+ shell: bash
+ run: |
+ mkdir -p libxenserver/usr/local/lib
+ mv libxenserver/usr/local/libxenserver.* libxenserver/usr/local/lib/
+ tar -zcvf libxenserver-prerelease.tar.gz -C ./libxenserver usr/local/lib/ usr/local/include/xen/api
+ rm -rf libxenserver/usr/local/lib/
+ tar -zcvf libxenserver-prerelease.src.tar.gz -C ./libxenserver/usr/local .
+
- name: Zip PowerShell 5.x SDK artifacts for deployment
shell: bash
run: zip PowerShell-SDK-5.x-prerelease-unsigned.zip ./sdk_powershell_5x -r
-
+
- name: Zip PowerShell 7.x SDK artifacts for deployment
shell: bash
run: zip PowerShell-SDK-7.x-prerelease-unsigned.zip ./sdk_powershell_7x -r
@@ -83,7 +98,8 @@ jobs:
run: |
gh release create ${{ github.ref_name }} --repo ${{ github.repository }} --generate-notes dist/* \
PowerShell-SDK-5.x-prerelease-unsigned.zip \
- PowerShell-SDK-7.x-prerelease-unsigned.zip
+ PowerShell-SDK-7.x-prerelease-unsigned.zip \
+ libxenserver-prerelease.tar.gz libxenserver-prerelease.src.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/Makefile b/Makefile
index 45a0b3e6b09..d4a2b01bacf 100644
--- a/Makefile
+++ b/Makefile
@@ -122,10 +122,10 @@ sdk:
sh ocaml/sdk-gen/windows-line-endings.sh $(XAPISDK)/csharp
sh ocaml/sdk-gen/windows-line-endings.sh $(XAPISDK)/powershell
-.PHONY: sdk-build-c sdk
+.PHONY: sdk-build-c
sdk-build-c: sdk
- cd _build/install/default/xapi/sdk/c && make -j $(JOBS)
+ cd _build/install/default/xapi/sdk/c && make clean && make -j $(JOBS)
.PHONY: sdk-build-java
@@ -207,6 +207,7 @@ install: build doc sdk doc-json
install -D -m 755 _build/install/default/bin/xcp-rrdd-iostat $(DESTDIR)$(LIBEXECDIR)/xcp-rrdd-plugins/xcp-rrdd-iostat
install -D -m 755 _build/install/default/bin/xcp-rrdd-squeezed $(DESTDIR)$(LIBEXECDIR)/xcp-rrdd-plugins/xcp-rrdd-squeezed
install -D -m 755 _build/install/default/bin/xcp-rrdd-xenpm $(DESTDIR)$(LIBEXECDIR)/xcp-rrdd-plugins/xcp-rrdd-xenpm
+ install -D -m 755 _build/install/default/bin/xcp-rrdd-dcmi $(DESTDIR)$(LIBEXECDIR)/xcp-rrdd-plugins/xcp-rrdd-dcmi
install -D -m 644 ocaml/xcp-rrdd/bugtool-plugin/rrdd-plugins.xml $(DESTDIR)$(ETCXENDIR)/bugtool/xcp-rrdd-plugins.xml
install -D -m 644 ocaml/xcp-rrdd/bugtool-plugin/rrdd-plugins/stuff.xml $(DESTDIR)$(ETCXENDIR)/bugtool/xcp-rrdd-plugins/stuff.xml
install -D -m 755 ocaml/xcp-rrdd/bin/rrdp-scripts/sysconfig-rrdd-plugins $(DESTDIR)/etc/sysconfig/xcp-rrdd-plugins
diff --git a/forkexec.opam b/forkexec.opam
index c458ac94713..a3296ea9771 100644
--- a/forkexec.opam
+++ b/forkexec.opam
@@ -21,6 +21,7 @@ depends: [
"xapi-log"
"xapi-stdext-pervasives"
"xapi-stdext-unix"
+ "xapi-tracing"
]
synopsis: "Sub-process control service for xapi"
description:
diff --git a/forkexec.opam.template b/forkexec.opam.template
index 6e9cd4df453..cf537533421 100644
--- a/forkexec.opam.template
+++ b/forkexec.opam.template
@@ -19,6 +19,7 @@ depends: [
"xapi-log"
"xapi-stdext-pervasives"
"xapi-stdext-unix"
+ "xapi-tracing"
]
synopsis: "Sub-process control service for xapi"
description:
diff --git a/message-switch-core.opam b/message-switch-core.opam
index 960934bea54..44e2983cc5b 100644
--- a/message-switch-core.opam
+++ b/message-switch-core.opam
@@ -22,6 +22,7 @@ depends: [
"ppx_sexp_conv"
"rpclib"
"sexplib"
+ "xapi-log"
]
synopsis: "A simple store-and-forward message switch"
description: """
diff --git a/message-switch-core.opam.template b/message-switch-core.opam.template
index 7ec11e91dc3..7f65fa07598 100644
--- a/message-switch-core.opam.template
+++ b/message-switch-core.opam.template
@@ -20,6 +20,7 @@ depends: [
"ppx_sexp_conv"
"rpclib"
"sexplib"
+ "xapi-log"
]
synopsis: "A simple store-and-forward message switch"
description: """
diff --git a/ocaml/forkexecd/lib/dune b/ocaml/forkexecd/lib/dune
index 2830cd13937..160f444dd34 100644
--- a/ocaml/forkexecd/lib/dune
+++ b/ocaml/forkexecd/lib/dune
@@ -7,12 +7,13 @@
fd-send-recv
rpclib.core
rpclib.json
+ rpclib.xml
uuid
xapi-backtrace
xapi-log
xapi-stdext-pervasives
xapi-stdext-unix
- rpclib.xml
+ xapi-tracing
)
(preprocess
(pps ppx_deriving_rpc)))
diff --git a/ocaml/forkexecd/lib/fe.ml b/ocaml/forkexecd/lib/fe.ml
index 1a176a62baa..c928cd3fc10 100644
--- a/ocaml/forkexecd/lib/fe.ml
+++ b/ocaml/forkexecd/lib/fe.ml
@@ -1,13 +1,13 @@
(* Disable "Warning 39: unused rec flag." caused by rpc *)
[@@@warning "-39"]
-type syslog_stdout_t = {enabled: bool; key: string option} [@@deriving rpc]
+type syslog_stdout = {enabled: bool; key: string option} [@@deriving rpc]
type setup_cmd = {
cmdargs: string list
; env: string list
; id_to_fd_map: (string * int option) list
- ; syslog_stdout: syslog_stdout_t
+ ; syslog_stdout: syslog_stdout
; redirect_stderr_to_stdout: bool
}
[@@deriving rpc]
diff --git a/ocaml/forkexecd/lib/forkhelpers.ml b/ocaml/forkexecd/lib/forkhelpers.ml
index d55901c3c68..2c8041b9535 100644
--- a/ocaml/forkexecd/lib/forkhelpers.ml
+++ b/ocaml/forkexecd/lib/forkhelpers.ml
@@ -21,6 +21,8 @@
(* XXX: this is a work in progress *)
+module D = Debug.Make (struct let name = __MODULE__ end)
+
let default_path = ["/sbin"; "/usr/sbin"; "/bin"; "/usr/bin"]
let default_path_env_pair = [|"PATH=" ^ String.concat ":" default_path|]
@@ -34,6 +36,10 @@ let test_path =
let runtime_path = Option.value ~default:"/var" test_path
+let _with_tracing ?tracing ~name f =
+ let name = Printf.sprintf "forkhelpers.%s" name in
+ Tracing.with_tracing ?parent:tracing ~name f
+
let finally = Xapi_stdext_pervasives.Pervasiveext.finally
type pidty = Unix.file_descr * int
@@ -72,14 +78,47 @@ let waitpid (sock, pid) =
in
failwith msg
-let waitpid_nohang ((sock, _) as x) =
+(* [waitpid_nohang] reports the status of a socket to a process. The
+ intention is to make this non-blocking. If the process is finished,
+ the socket is closed and not otherwise. *)
+let waitpid_nohang (sock, pid) =
+ let verbose = false in
+ if verbose then D.debug "%s pid=%d" __FUNCTION__ pid ;
+ let fail fmt = Printf.kprintf failwith fmt in
Unix.set_nonblock sock ;
- let r =
- try waitpid x
- with Unix.(Unix_error ((EAGAIN | EWOULDBLOCK), _, _)) ->
- (0, Unix.WEXITED 0)
- in
- Unix.clear_nonblock sock ; r
+ match Fecomms.read_raw_rpc sock with
+ | Ok Fe.(Finished (WEXITED n)) ->
+ if verbose then D.debug "%s pid=%d WEXITED" __FUNCTION__ pid ;
+ Unix.close sock ;
+ (pid, Unix.WEXITED n)
+ | Ok Fe.(Finished (WSIGNALED n)) ->
+ if verbose then D.debug "%s pid=%d WSIGNALED" __FUNCTION__ pid ;
+ Unix.close sock ;
+ (pid, Unix.WSIGNALED n)
+ | Ok Fe.(Finished (WSTOPPED n)) ->
+ if verbose then D.debug "%s pid=%d WSTOPPED" __FUNCTION__ pid ;
+ Unix.close sock ;
+ (pid, Unix.WSTOPPED n)
+ | Ok status ->
+ Unix.clear_nonblock sock ;
+ fail "%s: unexpected status received (%s)" __FUNCTION__
+ (Fe.ferpc_to_string status)
+ | Error msg ->
+ D.debug "%s pid=%d %s" __FUNCTION__ pid msg ;
+ Unix.clear_nonblock sock ;
+ fail "%s: error happened when trying to read the status. %s" __FUNCTION__
+ msg
+ (* it's a bit crazy that we have Result.t and exceptions from
+ read_raw_rpc *)
+ | exception Unix.(Unix_error ((EAGAIN | EWOULDBLOCK), _, _)) ->
+ if verbose then D.debug "%s pid=%d EAGAIN EWOULDBLOCK" __FUNCTION__ pid ;
+ Unix.clear_nonblock sock ;
+ (0, Unix.WEXITED 0) (* this a convention, see MLI *)
+ | exception exn ->
+ D.debug "%s pid=%d %s" __FUNCTION__ pid (Printexc.to_string exn) ;
+ Unix.clear_nonblock sock ;
+ fail "%s: error happened when trying to read the status. %s" __FUNCTION__
+ (Printexc.to_string exn)
let dontwaitpid (sock, _pid) =
( try
@@ -136,7 +175,7 @@ let with_logfile_fd ?(delete = true) prefix f =
exception Spawn_internal_error of string * string * Unix.process_status
-type syslog_stdout_t =
+type syslog_stdout =
| NoSyslogging
| Syslog_DefaultKey
| Syslog_WithKey of string
diff --git a/ocaml/forkexecd/lib/forkhelpers.mli b/ocaml/forkexecd/lib/forkhelpers.mli
index 6252f0e75ca..186cbe51872 100644
--- a/ocaml/forkexecd/lib/forkhelpers.mli
+++ b/ocaml/forkexecd/lib/forkhelpers.mli
@@ -34,7 +34,7 @@
(** {2 High-level interface } *)
-type syslog_stdout_t =
+type syslog_stdout =
| NoSyslogging
| Syslog_DefaultKey
| Syslog_WithKey of string
@@ -45,7 +45,7 @@ val default_path_env_pair : string array
val execute_command_get_output :
?env:string array
- -> ?syslog_stdout:syslog_stdout_t
+ -> ?syslog_stdout:syslog_stdout
-> ?redirect_stderr_to_stdout:bool
-> ?timeout:float
-> string
@@ -57,7 +57,7 @@ val execute_command_get_output :
val execute_command_get_output_send_stdin :
?env:string array
- -> ?syslog_stdout:syslog_stdout_t
+ -> ?syslog_stdout:syslog_stdout
-> ?redirect_stderr_to_stdout:bool
-> ?timeout:float
-> string
@@ -97,7 +97,7 @@ val safe_close_and_exec :
-> Unix.file_descr option
-> Unix.file_descr option
-> (string * Unix.file_descr) list
- -> ?syslog_stdout:syslog_stdout_t
+ -> ?syslog_stdout:syslog_stdout
-> ?redirect_stderr_to_stdout:bool
-> string
-> string list
@@ -111,8 +111,10 @@ val waitpid : pidty -> int * Unix.process_status
(** [waitpid p] returns the (pid, Unix.process_status) *)
val waitpid_nohang : pidty -> int * Unix.process_status
-(** [waitpid_nohang p] returns the (pid, Unix.process_status) if the process has already
- quit or (0, Unix.WEXITTED 0) if the process is still running. *)
+(** [waitpid_nohang p] returns the (pid, Unix.process_status) if the
+ process has already quit or (0, Unix.WEXITTED 0) if the process is
+ still running. If the process is finished, the socket is closed
+ and not otherwise. *)
val dontwaitpid : pidty -> unit
(** [dontwaitpid p]: signals the caller's desire to never call waitpid. Note that the final
diff --git a/ocaml/forkexecd/src/child.ml b/ocaml/forkexecd/src/child.ml
index 197f3b91f65..0bdb5fc1dc1 100644
--- a/ocaml/forkexecd/src/child.ml
+++ b/ocaml/forkexecd/src/child.ml
@@ -3,13 +3,13 @@ let debug (fmt : ('a, unit, string, unit) format4) =
exception Cancelled
-type syslog_stdout_t = {enabled: bool; key: string option}
+type syslog_stdout = {enabled: bool; key: string option}
type state_t = {
cmdargs: string list
; env: string list
; id_to_fd_map: (string * int option) list
- ; syslog_stdout: syslog_stdout_t
+ ; syslog_stdout: syslog_stdout
; redirect_stderr_to_stdout: bool
; ids_received: (string * Unix.file_descr) list
; fd_sock2: Unix.file_descr option
diff --git a/ocaml/forkexecd/test/fe_test.ml b/ocaml/forkexecd/test/fe_test.ml
index bb740d94df8..42991d5f16b 100644
--- a/ocaml/forkexecd/test/fe_test.ml
+++ b/ocaml/forkexecd/test/fe_test.ml
@@ -109,14 +109,23 @@ let test_delay () =
let start = Unix.gettimeofday () in
let exe = Printf.sprintf "/proc/%d/exe" (Unix.getpid ()) in
let args = ["sleep"] in
+ (* Need to have fractional part because some internal usage split integer
+ and fractional and do computation.
+ Better to have a high fractional part (> 0.5) to more probably exceed
+ the unit.
+ *)
+ let timeout = 1.7 in
try
- Forkhelpers.execute_command_get_output ~timeout:4.0 exe args |> ignore ;
+ Forkhelpers.execute_command_get_output ~timeout exe args |> ignore ;
failwith "Failed to timeout"
with
| Forkhelpers.Subprocess_timeout ->
- Printf.printf "Caught timeout exception after %f seconds\n%!"
- (Unix.gettimeofday () -. start) ;
- ()
+ let elapsed = Unix.gettimeofday () -. start in
+ Printf.printf "Caught timeout exception after %f seconds\n%!" elapsed ;
+ if elapsed < timeout then
+ failwith "Process exited too soon" ;
+ if elapsed > timeout +. 0.2 then
+ failwith "Excessive time elapsed"
| e ->
failwith
(Printf.sprintf "Failed with unexpected exception: %s"
@@ -140,6 +149,10 @@ let fail x =
Printf.fprintf stderr "%s\n" x ;
assert false
+let expect expected s =
+ if s <> expected ^ "\n" then
+ fail (Printf.sprintf "output %s expected %s" s expected)
+
let test_exitcode () =
let run_expect cmd expected =
try Forkhelpers.execute_command_get_output cmd [] |> ignore
@@ -150,15 +163,39 @@ let test_exitcode () =
in
run_expect "/bin/false" 1 ;
run_expect "/bin/xe-fe-test-no-command" 127 ;
+ run_expect "/bin/xe-fe-no-path/xe-fe-test-no-command" 127 ;
run_expect "/etc/hosts" 126 ;
Printf.printf "\nCompleted exitcode tests\n"
+let test_output () =
+ let exe = Printf.sprintf "/proc/%d/exe" (Unix.getpid ()) in
+ let expected_out = "output string" in
+ let expected_err = "error string" in
+ let args = ["echo"; expected_out; expected_err] in
+ let out, err = Forkhelpers.execute_command_get_output exe args in
+ expect expected_out out ;
+ expect expected_err err ;
+ print_endline "Completed output tests"
+
+let test_input () =
+ let exe = Printf.sprintf "/proc/%d/exe" (Unix.getpid ()) in
+ let input = "input string" in
+ let args = ["replay"] in
+ let out, _ =
+ Forkhelpers.execute_command_get_output_send_stdin exe args input
+ in
+ expect input out ;
+ print_endline "Completed input tests"
+
let master fds =
Printf.printf "\nPerforming timeout tests\n%!" ;
test_delay () ;
test_notimeout () ;
Printf.printf "\nCompleted timeout test\n%!" ;
test_exitcode () ;
+ Printf.printf "\nPerforming input/output tests\n%!" ;
+ test_output () ;
+ test_input () ;
let combinations = shuffle (all_combinations fds) in
Printf.printf "Starting %d tests\n%!" (List.length combinations) ;
let i = ref 0 in
@@ -233,7 +270,15 @@ let slave = function
pid (List.length filtered) ls
)
-let sleep () = Unix.sleep 5 ; Printf.printf "Ok\n"
+let sleep () = Unix.sleep 3 ; Printf.printf "Ok\n"
+
+let echo out err =
+ if out <> "" then print_endline out ;
+ if err <> "" then prerr_endline err
+
+let replay () =
+ let line = read_line () in
+ print_endline line
let usage () =
Printf.printf "Usage:\n" ;
@@ -253,6 +298,10 @@ let _ =
sleep ()
| _ :: "slave" :: rest ->
slave rest
+ | _ :: "echo" :: out :: err :: _ ->
+ echo out err
+ | _ :: "replay" :: _ ->
+ replay ()
| [_] ->
master max_fds
| [_; fds] -> (
diff --git a/ocaml/forkexecd/test/fe_test.sh b/ocaml/forkexecd/test/fe_test.sh
index fa5ffc514cd..aa0b9899ee7 100755
--- a/ocaml/forkexecd/test/fe_test.sh
+++ b/ocaml/forkexecd/test/fe_test.sh
@@ -6,13 +6,14 @@ export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-$TMPDIR}
export FE_TEST=1
SOCKET=${XDG_RUNTIME_DIR}/xapi/forker/main
+rm -f "$SOCKET"
../src/fe_main.exe &
MAIN=$!
cleanup () {
kill $MAIN
}
-trap cleanup EXIT
+trap cleanup EXIT INT
for _ in $(seq 1 10); do
test -S ${SOCKET} || sleep 1
done
diff --git a/ocaml/idl/datamodel.ml b/ocaml/idl/datamodel.ml
index c8fa2614150..4d4edd972ac 100644
--- a/ocaml/idl/datamodel.ml
+++ b/ocaml/idl/datamodel.ml
@@ -6070,7 +6070,7 @@ module Event = struct
~doc:
"Blocking call which returns a (possibly empty) batch of events. This \
method is only recommended for legacy use. New development should use \
- event.from which supercedes this method."
+ event.from which supersedes this method."
~custom_marshaller:true ~flags:[`Session]
~result:(Set (Record _event), "A set of events")
~errs:[Api_errors.session_not_registered; Api_errors.events_lost]
@@ -6520,14 +6520,55 @@ module Network_sriov = struct
end
(** PCI devices *)
+let pci_dom0_access =
+ Enum
+ ( "pci_dom0_access"
+ , [
+ ("enabled", "dom0 can access this device as normal")
+ ; ( "disable_on_reboot"
+ , "On host reboot dom0 will be blocked from accessing this device"
+ )
+ ; ("disabled", "dom0 cannot access this device")
+ ; ( "enable_on_reboot"
+ , "On host reboot dom0 will be allowed to access this device"
+ )
+ ]
+ )
module PCI = struct
+ let disable_dom0_access =
+ call ~name:"disable_dom0_access" ~lifecycle:[]
+ ~doc:
+ "Hide a PCI device from the dom0 kernel. (Takes affect after next \
+ boot.)"
+ ~params:[(Ref _pci, "self", "The PCI to hide")]
+ ~result:(pci_dom0_access, "The accessibility of this PCI from dom0")
+ ~allowed_roles:_R_POOL_OP ()
+
+ let enable_dom0_access =
+ call ~name:"enable_dom0_access" ~lifecycle:[]
+ ~doc:
+ "Unhide a PCI device from the dom0 kernel. (Takes affect after next \
+ boot.)"
+ ~params:[(Ref _pci, "self", "The PCI to unhide")]
+ ~result:(pci_dom0_access, "The accessibility of this PCI from dom0")
+ ~allowed_roles:_R_POOL_OP ()
+
+ let get_dom0_access_status =
+ call ~name:"get_dom0_access_status" ~lifecycle:[]
+ ~doc:"Return a PCI device dom0 access status."
+ ~params:[(Ref _pci, "self", "The PCI")]
+ ~result:(pci_dom0_access, "The accessibility of this PCI from dom0")
+ ~allowed_roles:_R_POOL_OP ()
+
let t =
create_obj ~name:_pci ~descr:"A PCI device" ~doccomments:[]
~gen_constructor_destructor:false ~gen_events:true ~in_db:true
~lifecycle:[(Published, rel_boston, "")]
- ~messages:[] ~messages_default_allowed_roles:_R_POOL_OP
- ~persist:PersistEverything ~in_oss_since:None ~db_logging:Log_destroy
+ ~messages:
+ [disable_dom0_access; enable_dom0_access; get_dom0_access_status]
+ ~messages_default_allowed_roles:_R_POOL_OP ~persist:PersistEverything
+ ~in_oss_since:None ~db_logging:Log_destroy
~contents:
[
uid _pci ~lifecycle:[(Published, rel_boston, "")]
@@ -6621,21 +6662,6 @@ end
(** Physical GPUs (pGPU) *)
module PGPU = struct
- let dom0_access =
- Enum
- ( "pgpu_dom0_access"
- , [
- ("enabled", "dom0 can access this device as normal")
- ; ( "disable_on_reboot"
- , "On host reboot dom0 will be blocked from accessing this device"
- )
- ; ("disabled", "dom0 cannot access this device")
- ; ( "enable_on_reboot"
- , "On host reboot dom0 will be allowed to access this device"
- )
- ]
- )
-
let add_enabled_VGPU_types =
call ~name:"add_enabled_VGPU_types"
~lifecycle:[(Published, rel_vgpu_tech_preview, "")]
@@ -6756,7 +6782,11 @@ module PGPU = struct
let enable_dom0_access =
call ~name:"enable_dom0_access"
- ~lifecycle:[(Published, rel_cream, "")]
+ ~lifecycle:
+ [
+ (Published, rel_cream, "")
+ ; (Deprecated, "24.14.0", "Use PCI.enable_dom0_access instead.")
+ ]
~versioned_params:
[
{
@@ -6767,12 +6797,16 @@ module PGPU = struct
; param_default= None
}
]
- ~result:(dom0_access, "The accessibility of this PGPU from dom0")
+ ~result:(pci_dom0_access, "The accessibility of this PGPU from dom0")
~allowed_roles:_R_POOL_OP ()
let disable_dom0_access =
call ~name:"disable_dom0_access"
- ~lifecycle:[(Published, rel_cream, "")]
+ ~lifecycle:
+ [
+ (Published, rel_cream, "")
+ ; (Deprecated, "24.14.0", "Use PCI.disable_dom0_access instead.")
+ ]
~versioned_params:
[
{
@@ -6783,7 +6817,7 @@ module PGPU = struct
; param_default= None
}
]
- ~result:(dom0_access, "The accessibility of this PGPU from dom0")
+ ~result:(pci_dom0_access, "The accessibility of this PGPU from dom0")
~allowed_roles:_R_POOL_OP ()
let t =
@@ -6844,8 +6878,15 @@ module PGPU = struct
"A map relating each VGPU type supported on this GPU to the \
maximum number of VGPUs of that type which can run simultaneously \
on this GPU"
- ; field ~qualifier:DynamicRO ~ty:dom0_access
- ~lifecycle:[(Published, rel_cream, "")]
+ ; field ~qualifier:DynamicRO ~ty:pci_dom0_access
+ ~lifecycle:
+ [
+ (Published, rel_cream, "")
+ ; ( Deprecated
+ , "24.14.0"
+ , "Use PCI.get_dom0_access_status instead."
+ )
+ ]
~default_value:(Some (VEnum "enabled")) "dom0_access"
"The accessibility of this device from dom0"
; field ~qualifier:DynamicRO ~ty:Bool
@@ -8177,6 +8218,7 @@ let http_actions =
; Bool_query_arg "include_dom0"
; Bool_query_arg "include_vhd_parents"
; Bool_query_arg "export_snapshots"
+ ; String_query_arg "excluded_device_types"
]
, _R_VM_ADMIN
, []
diff --git a/ocaml/idl/datamodel_common.ml b/ocaml/idl/datamodel_common.ml
index 709cb5eb059..fc13fb0a7b1 100644
--- a/ocaml/idl/datamodel_common.ml
+++ b/ocaml/idl/datamodel_common.ml
@@ -10,7 +10,7 @@ open Datamodel_roles
to leave a gap for potential hotfixes needing to increment the schema version.*)
let schema_major_vsn = 5
-let schema_minor_vsn = 775
+let schema_minor_vsn = 776
(* Historical schema versions just in case this is useful later *)
let rio_schema_major_vsn = 5
diff --git a/ocaml/idl/datamodel_observer.ml b/ocaml/idl/datamodel_observer.ml
index bbda9021898..1d80d030a62 100644
--- a/ocaml/idl/datamodel_observer.ml
+++ b/ocaml/idl/datamodel_observer.ml
@@ -95,7 +95,7 @@ let set_components =
call ~name:"set_components" ~in_oss_since:None ~lifecycle:[]
~doc:
"Set the components on which the observer will broadcast to. i.e. xapi, \
- xenopsd, networkd, etc"
+ xenopsd, networkd, etc."
~params:
[
(Ref _observer, "self", "The observer")
@@ -106,7 +106,7 @@ let set_components =
let t =
create_obj ~name:_observer
~descr:
- "Describes a observer which will control observability activity in the \
+ "Describes an observer which will control observability activity in the \
Toolstack"
~doccomments:[] ~gen_constructor_destructor:true ~gen_events:true
~in_db:true ~lifecycle:[] ~persist:PersistEverything ~in_oss_since:None
diff --git a/ocaml/idl/datamodel_vm.ml b/ocaml/idl/datamodel_vm.ml
index c1a6b9a7d9c..aa45d93de5b 100644
--- a/ocaml/idl/datamodel_vm.ml
+++ b/ocaml/idl/datamodel_vm.ml
@@ -1899,7 +1899,7 @@ let t =
; field ~qualifier:DynamicRO ~ty:(Set (Ref _vbd)) "VBDs"
"virtual block devices"
; field ~qualifier:DynamicRO ~ty:(Set (Ref _vusb)) "VUSBs"
- "vitual usb devices"
+ "virtual usb devices"
; field ~writer_roles:_R_POOL_ADMIN ~qualifier:DynamicRO
~ty:(Set (Ref _crashdump)) "crash_dumps"
"crash dumps associated with this VM"
diff --git a/ocaml/idl/ocaml_backend/gen_api.ml b/ocaml/idl/ocaml_backend/gen_api.ml
index 0bceed12255..2b63e09b332 100644
--- a/ocaml/idl/ocaml_backend/gen_api.ml
+++ b/ocaml/idl/ocaml_backend/gen_api.ml
@@ -72,6 +72,23 @@ let overrides =
)
]
+(** Generate enum__all and enum_to_string bindings for all enums *)
+let gen_enum_helpers tys =
+ let gen_string_and_all = function
+ | DT.Set (DT.Enum (_, elist) as e) ->
+ let nlist = List.map fst elist in
+ [
+ Printf.sprintf "let %s__all = %s" (OU.alias_of_ty e)
+ (OU.ocaml_list_of_enum nlist)
+ ; (Printf.sprintf "let %s_to_string = %s")
+ (OU.alias_of_ty e)
+ (OU.ocaml_to_string_of_enum nlist)
+ ]
+ | _ ->
+ []
+ in
+ List.concat_map gen_string_and_all tys
+
(** Generate a single type declaration for simple types (eg not containing references to record objects) *)
let gen_non_record_type tys =
let rec aux accu = function
@@ -395,6 +412,7 @@ let gen_client_types highapi =
; gen_non_record_type all_types
; gen_record_type ~with_module:true highapi
(toposort_types highapi all_types)
+ ; gen_enum_helpers all_types
; O.Signature.strings_of (Gen_client.gen_signature highapi)
]
)
diff --git a/ocaml/idl/ocaml_backend/ocaml_utils.ml b/ocaml/idl/ocaml_backend/ocaml_utils.ml
index e3ab8ac19dd..a01ae955586 100644
--- a/ocaml/idl/ocaml_backend/ocaml_utils.ml
+++ b/ocaml/idl/ocaml_backend/ocaml_utils.ml
@@ -58,9 +58,15 @@ let ocaml_of_record_field = function
let ocaml_of_module_name x = String.capitalize_ascii x
+let ocaml_map_enum_ sep f list = String.concat sep (List.map f list)
+
(** Convert an IDL enum into a polymorhic variant. *)
let ocaml_of_enum list =
- "[ " ^ String.concat " | " (List.map constructor_of list) ^ " ]"
+ Printf.sprintf "[%s]" (ocaml_map_enum_ " | " constructor_of list)
+
+(* Create a to_string function for a polymorphic variant. *)
+let ocaml_list_of_enum list =
+ Printf.sprintf "[%s]" (ocaml_map_enum_ "; " constructor_of list)
(** Convert an IDL type to a function name; we need to generate functions to
marshal/unmarshal from XML for each unique IDL type *)
@@ -90,6 +96,11 @@ let rec alias_of_ty = function
| Option x ->
sprintf "%s_option" (alias_of_ty x)
+(** Create the body of a to_string function for an enum *)
+let ocaml_to_string_of_enum list =
+ let single name = Printf.sprintf {|%s -> "%s"|} (constructor_of name) name in
+ Printf.sprintf "function %s" (ocaml_map_enum_ " | " single list)
+
(** Convert an IDL type into a string containing OCaml code representing the
type. *)
let rec ocaml_of_ty = function
diff --git a/ocaml/idl/schematest.ml b/ocaml/idl/schematest.ml
index c8e5972c9a6..d25bb9e4219 100644
--- a/ocaml/idl/schematest.ml
+++ b/ocaml/idl/schematest.ml
@@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex
(* BEWARE: if this changes, check that schema has been bumped accordingly in
ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *)
-let last_known_schema_hash = "186131ad48f40dff30246e8e0c0dbf0a"
+let last_known_schema_hash = "7db36ba4b150b06a5098ff9bed87b191"
let current_schema_hash : string =
let open Datamodel_types in
diff --git a/ocaml/libs/sexpr/sExpr.ml b/ocaml/libs/sexpr/sExpr.ml
index 2976f121f68..ec354e373b1 100644
--- a/ocaml/libs/sexpr/sExpr.ml
+++ b/ocaml/libs/sexpr/sExpr.ml
@@ -27,6 +27,8 @@ let unescape_buf buf s =
if Astring.String.fold_left aux false s then
Buffer.add_char buf '\\'
+let is_escape_char = function '\\' | '"' | '\'' -> true | _ -> false
+
(* XXX: This escapes "'c'" and "\'c\'" to "\\'c\\'".
* They are both unescaped as "'c'". They have been ported
* to make sure that this corner case is left unchanged.
@@ -36,28 +38,31 @@ let unescape_buf buf s =
* that have guaranteed invariants and optimised performances *)
let escape s =
let open Astring in
- let escaped = Buffer.create (String.length s + 10) in
- String.iter
- (fun c ->
- let c' =
+ if String.exists is_escape_char s then (
+ let escaped = Buffer.create (String.length s + 10) in
+ String.iter
+ (fun c ->
match c with
| '\\' ->
- "\\\\"
+ Buffer.add_string escaped "\\\\"
| '"' ->
- "\\\""
+ Buffer.add_string escaped "\\\""
| '\'' ->
- "\\\'"
+ Buffer.add_string escaped "\\\'"
| _ ->
- Astring.String.of_char c
- in
- Buffer.add_string escaped c'
- )
- s ;
- Buffer.contents escaped
+ Buffer.add_char escaped c
+ )
+ s ;
+ Buffer.contents escaped
+ ) else
+ s
let unescape s =
- let buf = Buffer.create (String.length s) in
- unescape_buf buf s ; Buffer.contents buf
+ if String.contains s '\\' then (
+ let buf = Buffer.create (String.length s) in
+ unescape_buf buf s ; Buffer.contents buf
+ ) else
+ s
let mkstring x = String (unescape x)
diff --git a/ocaml/libs/tracing/dune b/ocaml/libs/tracing/dune
index bd2d8fd768b..434239cc24e 100644
--- a/ocaml/libs/tracing/dune
+++ b/ocaml/libs/tracing/dune
@@ -1,23 +1,29 @@
(library
- (name tracing)
- (public_name xapi-tracing)
- (libraries
- cohttp
- cohttp-posix
- ptime
- ptime.clock.os
- re
- rpclib.core
- rpclib.json
- result
- rresult
- uri
- threads.posix
- xapi-log
- xapi-open-uri
- xapi-stdext-threads
- xapi-stdext-unix
- zstd
- )
- (preprocess (pps ppx_deriving_rpc))
-)
+ (name tracing)
+ (modules tracing)
+ (libraries re uri xapi-log xapi-stdext-threads threads.posix)
+ (public_name xapi-tracing))
+
+(library
+ (name tracing_export)
+ (modules tracing_export)
+ (public_name xapi-tracing.export)
+ (libraries
+ cohttp
+ cohttp-posix
+ ptime
+ ptime.clock.os
+ rpclib.core
+ rpclib.json
+ result
+ rresult
+ tracing
+ threads.posix
+ uri
+ xapi-log
+ xapi-open-uri
+ xapi-stdext-threads
+ xapi-stdext-unix
+ zstd)
+ (preprocess
+ (pps ppx_deriving_rpc)))
diff --git a/ocaml/libs/tracing/tracing.ml b/ocaml/libs/tracing/tracing.ml
index 87326fb65cf..8327ad3a299 100644
--- a/ocaml/libs/tracing/tracing.ml
+++ b/ocaml/libs/tracing/tracing.ml
@@ -88,8 +88,6 @@ let observe = ref true
let set_observe mode = observe := mode
-let ( let@ ) f x = f x
-
module SpanKind = struct
type t = Server | Consumer | Client | Producer | Internal [@@deriving rpcty]
@@ -154,6 +152,8 @@ module SpanContext = struct
None
let trace_id_of_span_context t = t.trace_id
+
+ let span_id_of_span_context t = t.span_id
end
module SpanLink = struct
@@ -216,6 +216,21 @@ module Span = struct
let get_tag t tag = Attributes.find tag t.attributes
+ let get_name span = span.name
+
+ let get_parent span = span.parent
+
+ let get_span_kind span = span.span_kind
+
+ let get_begin_time span = span.begin_time
+
+ let get_end_time span = span.end_time
+
+ let get_events span = span.events
+
+ let get_attributes span =
+ Attributes.fold (fun k v tags -> (k, v) :: tags) span.attributes []
+
let finish ?(attributes = Attributes.empty) ~span () =
let attributes =
Attributes.union (fun _k a _b -> Some a) attributes span.attributes
@@ -281,6 +296,8 @@ module Spans = struct
let spans = Hashtbl.create 100
+ let span_count () = Hashtbl.length spans
+
let max_spans = ref 1000
let set_max_spans x = max_spans := x
@@ -617,315 +634,3 @@ let with_tracing ?(attributes = []) ?(parent = None) ~name f =
| Error e ->
warn "Failed to start tracing: %s" (Printexc.to_string e) ;
f None
-
-module Export = struct
- let export_interval = ref 30.
-
- let set_export_interval t = export_interval := t
-
- let host_id = ref "localhost"
-
- let set_host_id id = host_id := id
-
- let service_name = ref None
-
- let set_service_name name = service_name := Some name
-
- let get_service_name () =
- match !service_name with
- | None ->
- warn "service name not yet set!" ;
- "unknown"
- | Some name ->
- name
-
- module Content = struct
- module Json = struct
- module Zipkinv2 = struct
- module ZipkinSpan = struct
- type zipkinEndpoint = {serviceName: string} [@@deriving rpcty]
-
- type annotation = {timestamp: int; value: string} [@@deriving rpcty]
-
- type t = {
- id: string
- ; traceId: string
- ; parentId: string option
- ; name: string
- ; timestamp: int
- ; duration: int
- ; kind: string option
- ; localEndpoint: zipkinEndpoint
- ; annotations: annotation list
- ; tags: (string * string) list
- }
- [@@deriving rpcty]
-
- type t_list = t list [@@deriving rpcty]
-
- let kind_to_zipkin_kind = function
- | SpanKind.Internal ->
- None
- | k ->
- Some k
-
- let json_of_t_list s =
- Rpcmarshal.marshal t_list.Rpc.Types.ty s |> Jsonrpc.to_string
- end
-
- let zipkin_span_of_span (s : Span.t) : ZipkinSpan.t =
- let serviceName = get_service_name () in
- let annotations =
- List.map
- (fun event : ZipkinSpan.annotation ->
- let timestamp =
- int_of_float (event.SpanEvent.time *. 1000000.)
- in
- let value = event.SpanEvent.name in
- {timestamp; value}
- )
- s.events
- in
- {
- id= s.context.span_id
- ; traceId= s.context.trace_id
- ; parentId= Option.map (fun x -> x.Span.context.span_id) s.parent
- ; name= s.name
- ; timestamp= int_of_float (s.begin_time *. 1000000.)
- ; duration=
- Option.value s.end_time ~default:(Unix.gettimeofday () *. 1000000.)
- -. s.begin_time
- |> ( *. ) 1000000.
- |> int_of_float
- ; kind=
- Option.map SpanKind.to_string
- (ZipkinSpan.kind_to_zipkin_kind s.span_kind)
- ; localEndpoint= {serviceName}
- ; annotations
- ; tags=
- Attributes.fold (fun k v tags -> (k, v) :: tags) s.attributes []
- }
-
- let content_of (spans : Span.t list) =
- List.map zipkin_span_of_span spans |> ZipkinSpan.json_of_t_list
- end
- end
- end
-
- module Destination = struct
- module File = struct
- let trace_log_dir = ref "/var/log/dt/zipkinv2/json"
-
- let max_file_size = ref (1 lsl 20)
-
- let compress_tracing_files = ref true
-
- let set_trace_log_dir dir = trace_log_dir := dir
-
- let get_trace_log_dir () = !trace_log_dir
-
- let set_max_file_size size = max_file_size := size
-
- let set_compress_tracing_files enabled = compress_tracing_files := enabled
-
- let file_name = ref None
-
- let lock = Mutex.create ()
-
- let new_file_name () =
- let date = Ptime_clock.now () |> Ptime.to_rfc3339 ~frac_s:6 in
- let ( // ) = Filename.concat in
- let name =
- !trace_log_dir
- // String.concat "-" [get_service_name (); !host_id; date]
- ^ ".ndjson"
- in
- file_name := Some name ;
- name
-
- let with_fd file_name =
- Xapi_stdext_unix.Unixext.with_file file_name
- [O_WRONLY; O_CREAT; O_APPEND]
- 0o700
-
- let write fd str =
- let content = str ^ "\n" in
- ignore @@ Unix.write_substring fd content 0 (String.length content)
-
- let export json =
- try
- let file_name =
- match !file_name with None -> new_file_name () | Some x -> x
- in
- Xapi_stdext_unix.Unixext.mkdir_rec (Filename.dirname file_name) 0o700 ;
- let@ fd = file_name |> with_fd in
- write fd json ;
- if (Unix.fstat fd).st_size >= !max_file_size then (
- debug "Tracing: Rotating file %s > %d" file_name !max_file_size ;
- if !compress_tracing_files then
- Zstd.Fast.compress_file Zstd.Fast.compress ~file_path:file_name
- ~file_ext:"zst" ;
- ignore @@ new_file_name ()
- ) ;
- Ok ()
- with e -> Error e
-
- let with_stream f =
- Xapi_stdext_threads.Threadext.Mutex.execute lock (fun () -> f export)
- end
-
- module Http = struct
- module Request = Cohttp.Request.Make (Cohttp_posix_io.Buffered_IO)
- module Response = Cohttp.Response.Make (Cohttp_posix_io.Buffered_IO)
-
- let export ~url json =
- try
- let body = json in
- let headers =
- Cohttp.Header.of_list
- ([
- ("Content-Type", "application/json")
- ; ("Content-Length", string_of_int (String.length body))
- ]
- @
- match Uri.host url with
- | None ->
- []
- | Some h ->
- let port =
- match Uri.port url with
- | Some p ->
- ":" ^ string_of_int p
- | None ->
- ""
- in
- [("Host", h ^ port)]
- )
- in
- Open_uri.with_open_uri url (fun fd ->
- let request =
- Cohttp.Request.make ~meth:`POST ~version:`HTTP_1_1 ~headers url
- in
- let ic = Unix.in_channel_of_descr fd in
- let oc = Unix.out_channel_of_descr fd in
- Request.write
- (fun writer -> Request.write_body writer body)
- request oc ;
- (* We flush instead of closing the sending stream as nginx responds to a TCP
- half-shutdown with a full shutdown of both directions of the HTTP request *)
- flush oc ;
- match try Response.read ic with _ -> `Eof with
- | `Eof ->
- Ok ()
- | `Invalid x ->
- Error (Failure ("invalid read: " ^ x))
- | `Ok response
- when Cohttp.Code.(response.status |> code_of_status |> is_error)
- ->
- Error (Failure (Cohttp.Code.string_of_status response.status))
- | `Ok response ->
- let body = Buffer.create 128 in
- let reader = Response.make_body_reader response ic in
- let rec loop () =
- match Response.read_body_chunk reader with
- | Cohttp.Transfer.Chunk x ->
- Buffer.add_string body x ; loop ()
- | Cohttp.Transfer.Final_chunk x ->
- Buffer.add_string body x
- | Cohttp.Transfer.Done ->
- ()
- in
- loop () ; Ok ()
- )
- with e -> Error e
- end
-
- let export_to_endpoint parent traces endpoint =
- debug "Tracing: About to export" ;
- try
- File.with_stream (fun file_export ->
- let export, name =
- match endpoint with
- | Url url ->
- (Http.export ~url, "Tracing.Http.export")
- | Bugtool ->
- (file_export, "Tracing.File.export")
- in
- let all_spans =
- Hashtbl.fold (fun _ spans acc -> spans @ acc) traces []
- in
- let attributes =
- [
- ("export.span.count", List.length all_spans |> string_of_int)
- ; ("export.endpoint", endpoint_to_string endpoint)
- ; ( "xs.tracing.spans_table.count"
- , Hashtbl.length Spans.spans |> string_of_int
- )
- ; ( "xs.tracing.finished_spans_table.count"
- , Hashtbl.length traces |> string_of_int
- )
- ]
- in
- let@ _ = with_tracing ~parent ~attributes ~name in
- Content.Json.Zipkinv2.content_of all_spans
- |> export
- |> Result.iter_error raise
- )
- with exn ->
- debug "Tracing: unable to export span : %s" (Printexc.to_string exn)
-
- let flush_spans () =
- let span_list = Spans.since () in
- let attributes =
- [("export.traces.count", Hashtbl.length span_list |> string_of_int)]
- in
- let@ parent =
- with_tracing ~parent:None ~attributes ~name:"Tracing.flush_spans"
- in
- get_tracer_providers ()
- |> List.filter (fun x -> x.TracerProvider.enabled)
- |> List.concat_map (fun x -> TracerProvider.get_endpoints x)
- |> List.iter (export_to_endpoint parent span_list)
-
- let delay = Delay.make ()
-
- (* Note this signal will flush the spans and terminate the exporter thread *)
- let signal () = Delay.signal delay
-
- let create_exporter () =
- enable_span_garbage_collector () ;
- Thread.create
- (fun () ->
- let signaled = ref false in
- while not !signaled do
- debug "Tracing: Waiting %d seconds before exporting spans"
- (int_of_float !export_interval) ;
- if not (Delay.wait delay !export_interval) then (
- debug "Tracing: we are signaled, export spans now and exit" ;
- signaled := true
- ) ;
- flush_spans ()
- done
- )
- ()
-
- let exporter = ref None
-
- let lock = Mutex.create ()
-
- let main () =
- Xapi_stdext_threads.Threadext.Mutex.execute lock (fun () ->
- match !exporter with
- | None ->
- let tid = create_exporter () in
- exporter := Some tid ;
- tid
- | Some tid ->
- tid
- )
- end
-end
-
-let flush_and_exit = Export.Destination.signal
-
-let main = Export.Destination.main
diff --git a/ocaml/libs/tracing/tracing.mli b/ocaml/libs/tracing/tracing.mli
index ee30b29f041..0440339c6b9 100644
--- a/ocaml/libs/tracing/tracing.mli
+++ b/ocaml/libs/tracing/tracing.mli
@@ -34,12 +34,24 @@ module SpanKind : sig
val to_string : t -> string
end
+module Attributes : sig
+ include Map.S with type key := String.t
+
+ val of_list : (string * 'a) list -> 'a t
+
+ val to_assoc_list : 'a t -> (string * 'a) list
+end
+
module Status : sig
type status_code
type t
end
+module SpanEvent : sig
+ type t = {name: string; time: float; attributes: string Attributes.t}
+end
+
module SpanContext : sig
type t
@@ -48,6 +60,8 @@ module SpanContext : sig
val of_traceparent : string -> t option
val trace_id_of_span_context : t -> string
+
+ val span_id_of_span_context : t -> string
end
module Span : sig
@@ -61,9 +75,23 @@ module Span : sig
val add_event : t -> string -> (string * string) list -> t
+ val get_events : t -> SpanEvent.t list
+
val set_span_kind : t -> SpanKind.t -> t
+ val get_span_kind : t -> SpanKind.t
+
val get_tag : t -> string -> string
+
+ val get_name : t -> string
+
+ val get_parent : t -> t option
+
+ val get_begin_time : t -> float
+
+ val get_end_time : t -> float option
+
+ val get_attributes : t -> (string * string) list
end
module Spans : sig
@@ -71,6 +99,10 @@ module Spans : sig
val set_max_traces : int -> unit
+ val span_count : unit -> int
+
+ val since : unit -> (string, Span.t list) Hashtbl.t
+
val dump :
unit -> (string, Span.t list) Hashtbl.t * (string, Span.t list) Hashtbl.t
end
@@ -133,6 +165,8 @@ val get_tracer_providers : unit -> TracerProvider.t list
val get_tracer : name:string -> Tracer.t
+val enable_span_garbage_collector : ?timeout:float -> unit -> unit
+
val with_tracing :
?attributes:(string * string) list
-> ?parent:Span.t option
@@ -140,36 +174,6 @@ val with_tracing :
-> (Span.t option -> 'a)
-> 'a
-module Export : sig
- val set_export_interval : float -> unit
-
- val set_host_id : string -> unit
-
- val set_service_name : string -> unit
-
- module Destination : sig
- module File : sig
- val set_max_file_size : int -> unit
-
- val set_trace_log_dir : string -> unit
-
- val get_trace_log_dir : unit -> string
-
- val set_compress_tracing_files : bool -> unit
- end
-
- val flush_spans : unit -> unit
-
- module Http : sig
- val export : url:Uri.t -> string -> (unit, exn) result
- end
- end
-end
-
val set_observe : bool -> unit
val validate_attribute : string * string -> bool
-
-val flush_and_exit : unit -> unit
-
-val main : unit -> Thread.t
diff --git a/ocaml/libs/tracing/tracing_export.ml b/ocaml/libs/tracing/tracing_export.ml
new file mode 100644
index 00000000000..a769b2403bc
--- /dev/null
+++ b/ocaml/libs/tracing/tracing_export.ml
@@ -0,0 +1,328 @@
+(*
+ * Copyright (C) 2024 Cloud Software Group
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; version 2.1 only. with the special
+ * exception on linking described in file LICENSE.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *)
+
+module D = Debug.Make (struct let name = "tracing_export" end)
+
+module Delay = Xapi_stdext_threads.Threadext.Delay
+open D
+open Tracing
+
+let ( let@ ) f x = f x
+
+let export_interval = ref 30.
+
+let set_export_interval t = export_interval := t
+
+let host_id = ref "localhost"
+
+let set_host_id id = host_id := id
+
+let service_name = ref "unknown"
+
+let set_service_name name = service_name := name
+
+let get_service_name () = !service_name
+
+module Content = struct
+ module Json = struct
+ module ZipkinV2 = struct
+ (* Module that helps export spans under Zipkin protocol, version 2. *)
+ module ZipkinSpan = struct
+ type zipkinEndpoint = {serviceName: string} [@@deriving rpcty]
+
+ type annotation = {timestamp: int; value: string} [@@deriving rpcty]
+
+ type t = {
+ id: string
+ ; traceId: string
+ ; parentId: string option
+ ; name: string
+ ; timestamp: int
+ ; duration: int
+ ; kind: string option
+ ; localEndpoint: zipkinEndpoint
+ ; annotations: annotation list
+ ; tags: (string * string) list
+ }
+ [@@deriving rpcty]
+
+ type t_list = t list [@@deriving rpcty]
+
+ let kind_to_zipkin_kind = function
+ | SpanKind.Internal ->
+ None
+ | k ->
+ Some k
+
+ let json_of_t_list s =
+ Rpcmarshal.marshal t_list.Rpc.Types.ty s |> Jsonrpc.to_string
+ end
+
+ let zipkin_span_of_span (s : Span.t) : ZipkinSpan.t =
+ let serviceName = get_service_name () in
+ let annotations =
+ s
+ |> Span.get_events
+ |> List.map (fun event : ZipkinSpan.annotation ->
+ let timestamp =
+ int_of_float (event.SpanEvent.time *. 1000000.)
+ in
+ let value = event.SpanEvent.name in
+ {timestamp; value}
+ )
+ in
+ {
+ id= s |> Span.get_context |> SpanContext.span_id_of_span_context
+ ; traceId= s |> Span.get_context |> SpanContext.trace_id_of_span_context
+ ; parentId=
+ s
+ |> Span.get_parent
+ |> Option.map (fun x ->
+ x |> Span.get_context |> SpanContext.span_id_of_span_context
+ )
+ ; name= s |> Span.get_name
+ ; timestamp= int_of_float (Span.get_begin_time s *. 1000000.)
+ ; duration=
+ Option.value (Span.get_end_time s)
+ ~default:(Unix.gettimeofday () *. 1000000.)
+ -. Span.get_begin_time s
+ |> ( *. ) 1000000.
+ |> int_of_float
+ ; kind=
+ s
+ |> Span.get_span_kind
+ |> ZipkinSpan.kind_to_zipkin_kind
+ |> Option.map SpanKind.to_string
+ ; localEndpoint= {serviceName}
+ ; annotations
+ ; tags= Span.get_attributes s
+ }
+
+ let content_of (spans : Span.t list) =
+ List.map zipkin_span_of_span spans |> ZipkinSpan.json_of_t_list
+ end
+ end
+end
+
+module Destination = struct
+ module File = struct
+ let trace_log_dir = ref "/var/log/dt/zipkinv2/json"
+
+ let max_file_size = ref (1 lsl 20)
+
+ let compress_tracing_files = ref true
+
+ let set_trace_log_dir dir = trace_log_dir := dir
+
+ let get_trace_log_dir () = !trace_log_dir
+
+ let set_max_file_size size = max_file_size := size
+
+ let set_compress_tracing_files enabled = compress_tracing_files := enabled
+
+ let file_name = ref None
+
+ let lock = Mutex.create ()
+
+ let make_file_name () =
+ let date = Ptime_clock.now () |> Ptime.to_rfc3339 ~frac_s:6 in
+ let ( // ) = Filename.concat in
+ let name =
+ !trace_log_dir
+ // String.concat "-" [get_service_name (); !host_id; date]
+ ^ ".ndjson"
+ in
+ file_name := Some name ;
+ name
+
+ let with_fd file_name =
+ Xapi_stdext_unix.Unixext.with_file file_name
+ [O_WRONLY; O_CREAT; O_APPEND]
+ 0o700
+
+ let write fd str =
+ let content = str ^ "\n" in
+ ignore @@ Unix.write_substring fd content 0 (String.length content)
+
+ let export json =
+ try
+ let file_name =
+ match !file_name with None -> make_file_name () | Some x -> x
+ in
+ Xapi_stdext_unix.Unixext.mkdir_rec (Filename.dirname file_name) 0o700 ;
+ let@ fd = file_name |> with_fd in
+ write fd json ;
+ if (Unix.fstat fd).st_size >= !max_file_size then (
+ debug "Tracing: Rotating file %s > %d" file_name !max_file_size ;
+ if !compress_tracing_files then
+ Zstd.Fast.compress_file Zstd.Fast.compress ~file_path:file_name
+ ~file_ext:"zst" ;
+ ignore @@ make_file_name ()
+ ) ;
+ Ok ()
+ with e -> Error e
+
+ let with_stream f =
+ Xapi_stdext_threads.Threadext.Mutex.execute lock (fun () -> f export)
+ end
+
+ module Http = struct
+ module Request = Cohttp.Request.Make (Cohttp_posix_io.Buffered_IO)
+ module Response = Cohttp.Response.Make (Cohttp_posix_io.Buffered_IO)
+
+ let export ~url json =
+ try
+ let body = json in
+ let content_headers =
+ [
+ ("Content-Type", "application/json")
+ ; ("Content-Length", string_of_int (String.length body))
+ ]
+ in
+ let host =
+ match (Uri.host url, Uri.port url) with
+ | None, _ ->
+ None
+ | Some host, None ->
+ Some host
+ | Some host, Some port ->
+ Some (Printf.sprintf "%s:%d" host port)
+ in
+ let host_headers =
+ Option.fold ~none:[] ~some:(fun h -> [("Host", h)]) host
+ in
+ let headers =
+ List.concat [content_headers; host_headers] |> Cohttp.Header.of_list
+ in
+
+ Open_uri.with_open_uri url (fun fd ->
+ let request =
+ Cohttp.Request.make ~meth:`POST ~version:`HTTP_1_1 ~headers url
+ in
+ (* `with_open_uri` already closes the `fd`. And therefore
+ according to the documentation of `in_channel_of_descr` and
+ `out_channel_of_descr` we should not close the channels on top of
+ `fd`. *)
+ let ic = Unix.in_channel_of_descr fd in
+ let oc = Unix.out_channel_of_descr fd in
+ Request.write
+ (fun writer -> Request.write_body writer body)
+ request oc ;
+ (* We flush instead of closing the sending stream as nginx responds to a TCP
+ half-shutdown with a full shutdown of both directions of the HTTP request *)
+ flush oc ;
+ match try Response.read ic with _ -> `Eof with
+ | `Eof ->
+ Ok ()
+ | `Invalid x ->
+ Error (Failure ("invalid read: " ^ x))
+ | `Ok response
+ when Cohttp.Code.(response.status |> code_of_status |> is_error)
+ ->
+ Error (Failure (Cohttp.Code.string_of_status response.status))
+ | `Ok _ ->
+ Ok ()
+ )
+ with e -> Error e
+ end
+
+ let export_to_endpoint parent traces endpoint =
+ debug "Tracing: About to export" ;
+ try
+ File.with_stream (fun file_export ->
+ let export, name =
+ match endpoint with
+ | Url url ->
+ (Http.export ~url, "Tracing.Http.export")
+ | Bugtool ->
+ (file_export, "Tracing.File.export")
+ in
+ let all_spans =
+ Hashtbl.fold (fun _ spans acc -> spans @ acc) traces []
+ in
+ let attributes =
+ [
+ ("export.span.count", all_spans |> List.length |> string_of_int)
+ ; ("export.endpoint", endpoint_to_string endpoint)
+ ; ( "xs.tracing.spans_table.count"
+ , Spans.span_count () |> string_of_int
+ )
+ ; ( "xs.tracing.finished_spans_table.count"
+ , traces |> Hashtbl.length |> string_of_int
+ )
+ ]
+ in
+ let@ _ = with_tracing ~parent ~attributes ~name in
+ all_spans
+ |> Content.Json.ZipkinV2.content_of
+ |> export
+ |> Result.iter_error raise
+ )
+ with exn ->
+ debug "Tracing: unable to export span : %s" (Printexc.to_string exn)
+
+ let flush_spans () =
+ let span_list = Spans.since () in
+ let attributes =
+ [("export.traces.count", Hashtbl.length span_list |> string_of_int)]
+ in
+ let@ parent =
+ with_tracing ~parent:None ~attributes ~name:"Tracing.flush_spans"
+ in
+ get_tracer_providers ()
+ |> List.filter TracerProvider.get_enabled
+ |> List.concat_map TracerProvider.get_endpoints
+ |> List.iter (export_to_endpoint parent span_list)
+
+ let delay = Delay.make ()
+
+ (* Note this signal will flush the spans and terminate the exporter thread *)
+ let signal () = Delay.signal delay
+
+ let create_exporter () =
+ enable_span_garbage_collector () ;
+ Thread.create
+ (fun () ->
+ let signaled = ref false in
+ while not !signaled do
+ debug "Tracing: Waiting %d seconds before exporting spans"
+ (int_of_float !export_interval) ;
+ if not (Delay.wait delay !export_interval) then (
+ debug "Tracing: we are signaled, export spans now and exit" ;
+ signaled := true
+ ) ;
+ flush_spans ()
+ done
+ )
+ ()
+
+ let exporter = ref None
+
+ let lock = Mutex.create ()
+
+ let main () =
+ Xapi_stdext_threads.Threadext.Mutex.execute lock (fun () ->
+ match !exporter with
+ | None ->
+ let tid = create_exporter () in
+ exporter := Some tid ;
+ tid
+ | Some tid ->
+ tid
+ )
+end
+
+let flush_and_exit = Destination.signal
+
+let main = Destination.main
diff --git a/ocaml/libs/tracing/tracing_export.mli b/ocaml/libs/tracing/tracing_export.mli
new file mode 100644
index 00000000000..3f8ca750026
--- /dev/null
+++ b/ocaml/libs/tracing/tracing_export.mli
@@ -0,0 +1,95 @@
+(*
+* Copyright (C) 2024 Cloud Software Group
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published
+* by the Free Software Foundation; version 2.1 only. with the special
+* exception on linking described in file LICENSE.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*)
+
+(** [Tracing_export] is a module dedicated for the creation and management of
+ threads that export the tracing data.
+ *)
+
+val set_export_interval : float -> unit
+(** [set_export_interval seconds] sets the time interval between consecutive
+ exports of the finished spans to [seconds].
+
+ Default is every [30.] seconds.
+ *)
+
+val set_host_id : string -> unit
+(** [set_host_id id] sets the id of the host to [id].
+
+ Default is ["localhost"].
+ *)
+
+val set_service_name : string -> unit
+(** [set_service_name name] sets the name of the service to [name].
+ All spans will be exported under this service's name.
+
+ Default name is ["unknown"].
+ *)
+
+(** [Destination] is a module for managing the export of tracing data to
+ different types of endpoints, whether is exporting it to a [File] or an
+ [Http] endpoint.
+ *)
+module Destination : sig
+ (** [File] is a module for managing the files in which the tracing data is
+ exported.
+ *)
+ module File : sig
+ val set_max_file_size : int -> unit
+ (** [set_max_file_size n] sets the maximum file size to [n]. If a file is
+ is already created at the time of export and the file exceeds the
+ maximum size, a new tracing file is created.
+ *)
+
+ val set_trace_log_dir : string -> unit
+ (** [set_trace_log_dir log_dir] sets the location to which traces will be
+ exported.
+
+ Default is ["/var/log/dt/zipkinv2/json"]
+ *)
+
+ val get_trace_log_dir : unit -> string
+ (** [get_trace_log_dir ()] returns the cuurent location to which traces are
+ exported.
+ *)
+
+ val set_compress_tracing_files : bool -> unit
+ (** [set_compress_tracing_files flag] sets wheater or not the tracing files
+ are compressed or not.
+ *)
+ end
+
+ val flush_spans : unit -> unit
+ (** [flush_spans ()] forcefully flushes the spans to the current enabled
+ endpoints.
+ *)
+
+ (** [Http] is a module for managing exporting tracing data to an http
+ endpoint.
+ *)
+ module Http : sig
+ val export : url:Uri.t -> string -> (unit, exn) result
+ (** [export ~url json] forcefully flushes json formatted spans [json] to the
+ given [url] .
+ *)
+ end
+end
+
+val flush_and_exit : unit -> unit
+(** [flush_and_exit ()] sends a signal to flush the finish spans and terminate
+ the exporter thread.
+ *)
+
+val main : unit -> Thread.t
+(** [main ()] starts the exporter thread.
+ *)
diff --git a/ocaml/message-switch/async/dune b/ocaml/message-switch/async/dune
index 28ee31ecfa5..89f2c3a5ff4 100644
--- a/ocaml/message-switch/async/dune
+++ b/ocaml/message-switch/async/dune
@@ -2,12 +2,12 @@
(name message_switch_async)
(public_name message-switch-async)
(libraries
- async
- async_unix
+ (re_export async)
+ (re_export async_unix)
async_kernel
base
cohttp-async
- core
+ (re_export core)
core_unix
core_kernel
core_unix.time_unix
diff --git a/ocaml/message-switch/async/protocol_async.ml b/ocaml/message-switch/async/protocol_async.ml
index 9ec9cc42b74..5898d22f77f 100644
--- a/ocaml/message-switch/async/protocol_async.ml
+++ b/ocaml/message-switch/async/protocol_async.ml
@@ -30,9 +30,16 @@ module M = struct
let iter f t = Deferred.List.iter t ~f
+ let iter_dontwait f t =
+ Deferred.don't_wait_for @@ Deferred.List.iter ~how:`Parallel t ~f
+
let any = Deferred.any
+ let all = Deferred.all
+
let is_determined = Deferred.is_determined
+
+ let return_unit = Deferred.unit
end
let connect path =
@@ -95,6 +102,20 @@ module M = struct
)
end
+ module Condition = struct
+ open Async_kernel
+
+ type 'a t = 'a Condition.t
+
+ let create = Condition.create
+
+ let wait = Condition.wait
+
+ let broadcast = Condition.broadcast
+
+ let signal = Condition.signal
+ end
+
module Clock = struct
type timer = {cancel: unit Ivar.t}
@@ -117,3 +138,4 @@ end
module Client = Message_switch_core.Make.Client (M)
module Server = Message_switch_core.Make.Server (M)
+module Mtest = Message_switch_core.Mtest.Make (M)
diff --git a/ocaml/message-switch/async/protocol_async.mli b/ocaml/message-switch/async/protocol_async.mli
index f691c24c989..d18b37b742c 100644
--- a/ocaml/message-switch/async/protocol_async.mli
+++ b/ocaml/message-switch/async/protocol_async.mli
@@ -19,3 +19,5 @@ open Message_switch_core
module Client : S.CLIENT with type 'a io = 'a Deferred.t
module Server : S.SERVER with type 'a io = 'a Deferred.t
+
+module Mtest : Mtest.MTEST with type 'a io = 'a Deferred.t
diff --git a/ocaml/message-switch/core/dune b/ocaml/message-switch/core/dune
index 676fa3f20ee..41cbf9e9f2d 100644
--- a/ocaml/message-switch/core/dune
+++ b/ocaml/message-switch/core/dune
@@ -8,7 +8,10 @@
rpclib.json
sexplib
sexplib0
+ threads.posix
uri
+ xapi-log
+ xapi-stdext-threads
)
(preprocess (pps ppx_deriving_rpc ppx_sexp_conv))
)
diff --git a/ocaml/message-switch/core/make.ml b/ocaml/message-switch/core/make.ml
index 54e8904e1a9..224012909ac 100644
--- a/ocaml/message-switch/core/make.ml
+++ b/ocaml/message-switch/core/make.ml
@@ -16,6 +16,10 @@
open Sexplib.Std
open Protocol
+module D = Debug.Make (struct let name = "Message_switch.make" end)
+
+open D
+
module Connection =
functor
(IO : Cohttp.S.IO)
@@ -406,4 +410,92 @@ functor
in
let _ = loop c None in
return (Ok t)
+
+ let listen_p ~process ~switch:port ~queue:name () =
+ let token = Printf.sprintf "%d" (Unix.getpid ()) in
+ let protect_connect path f =
+ M.connect path >>= fun conn ->
+ f conn >>= function
+ | Ok _ as ok ->
+ return ok
+ | Error _ as err ->
+ M.disconnect conn >>= fun () -> return err
+ in
+ let reconnect () =
+ protect_connect port @@ fun request_conn ->
+ Connection.rpc request_conn (In.Login token) >>|= fun (_ : string) ->
+ protect_connect port @@ fun reply_conn ->
+ Connection.rpc reply_conn (In.Login token) >>|= fun (_ : string) ->
+ return (Ok (request_conn, reply_conn))
+ in
+ reconnect () >>|= fun ((request_conn, reply_conn) as c) ->
+ let request_shutdown = M.Ivar.create () in
+ let on_shutdown = M.Ivar.create () in
+ let mutex = M.Mutex.create () in
+ Connection.rpc request_conn (In.CreatePersistent name) >>|= fun _ ->
+ let t = {request_shutdown; on_shutdown} in
+ let reconnect () =
+ M.disconnect request_conn >>= fun () ->
+ M.disconnect reply_conn >>= reconnect
+ in
+ let rec loop c from =
+ let transfer = {In.from; timeout; queues= [name]} in
+ let frame = In.Transfer transfer in
+ let message = Connection.rpc request_conn frame in
+ any [map (fun _ -> ()) message; M.Ivar.read request_shutdown]
+ >>= fun () ->
+ if is_determined (M.Ivar.read request_shutdown) then (
+ M.Ivar.fill on_shutdown () ; return (Ok ())
+ ) else
+ message >>= function
+ | Error _e ->
+ M.Mutex.with_lock mutex reconnect >>|= fun c -> loop c from
+ | Ok raw -> (
+ let transfer = Out.transfer_of_rpc (Jsonrpc.of_string raw) in
+ let print_error = function
+ | Ok (_ : string) ->
+ return ()
+ | Error _ as err ->
+ error "message switch reply received error" ;
+ ignore @@ error_to_msg err ;
+ return ()
+ in
+ match transfer.Out.messages with
+ | [] ->
+ loop c from
+ | _ :: _ ->
+ iter_dontwait
+ (fun (i, m) ->
+ process m.Message.payload >>= fun response ->
+ ( match m.Message.kind with
+ | Message.Response _ ->
+ return () (* configuration error *)
+ | Message.Request reply_to ->
+ let request =
+ In.Send
+ ( reply_to
+ , {
+ Message.kind= Message.Response i
+ ; payload= response
+ }
+ )
+ in
+ M.Mutex.with_lock mutex (fun () ->
+ Connection.rpc reply_conn request
+ )
+ >>= print_error
+ )
+ >>= fun () ->
+ let request = In.Ack i in
+ M.Mutex.with_lock mutex (fun () ->
+ Connection.rpc reply_conn request
+ )
+ >>= print_error
+ )
+ transfer.Out.messages ;
+ loop c (Some transfer.Out.next)
+ )
+ in
+ let _ = loop c None in
+ return (Ok t)
end
diff --git a/ocaml/message-switch/core/mtest.ml b/ocaml/message-switch/core/mtest.ml
new file mode 100644
index 00000000000..3b8da9803fe
--- /dev/null
+++ b/ocaml/message-switch/core/mtest.ml
@@ -0,0 +1,42 @@
+module type MTEST = sig
+ type +'a io
+
+ val mutex_provides_mutal_exclusion : unit -> unit io
+end
+
+module Make =
+functor
+ (M : S.BACKEND)
+ ->
+ struct
+ open M.IO
+
+ type 'a io = 'a M.IO.t
+
+ let ocaml_lock = Mutex.create ()
+
+ let mu = M.Mutex.create ()
+
+ let cond = M.Condition.create ()
+
+ let broadcast () = M.Condition.broadcast cond ()
+
+ let mutex_provides_mutal_exclusion () : unit io =
+ let promises =
+ List.init 100 (fun _ ->
+ M.Condition.wait cond >>= fun () ->
+ M.Mutex.with_lock mu (fun () ->
+ M.IO.return_unit >>= fun () ->
+ (* the with_lock implementation should ensure that only one
+ monad can try to acquire this lock *)
+ assert (Mutex.try_lock ocaml_lock) ;
+ M.IO.return_unit >>= fun () ->
+ Mutex.unlock ocaml_lock ; M.IO.return_unit
+ )
+ )
+ in
+ broadcast () ;
+ ignore @@ all promises ;
+ Printf.printf "%s test.\n" (M.whoami ()) ;
+ M.IO.return_unit
+ end
diff --git a/ocaml/message-switch/core/s.ml b/ocaml/message-switch/core/s.ml
index f99e0582687..423304d1b24 100644
--- a/ocaml/message-switch/core/s.ml
+++ b/ocaml/message-switch/core/s.ml
@@ -29,9 +29,15 @@ module type BACKEND = sig
val iter : ('a -> unit t) -> 'a list -> unit t
+ val iter_dontwait : ('a -> unit t) -> 'a list -> unit
+
val any : 'a t list -> 'a t
+ val all : 'a t list -> 'a list t
+
val is_determined : 'a t -> bool
+
+ val return_unit : unit t
end
val connect : string -> (IO.ic * IO.oc) IO.t
@@ -56,6 +62,18 @@ module type BACKEND = sig
val with_lock : t -> (unit -> 'a IO.t) -> 'a IO.t
end
+ module Condition : sig
+ type 'a t
+
+ val create : unit -> 'a t
+
+ val wait : 'a t -> 'a IO.t
+
+ val broadcast : 'a t -> 'a -> unit
+
+ val signal : 'a t -> 'a -> unit
+ end
+
module Clock : sig
type timer
@@ -89,6 +107,14 @@ module type SERVER = sig
(** Connect to [switch] and start processing messages on [queue] via function
[process] *)
+ val listen_p :
+ process:(string -> string io)
+ -> switch:string
+ -> queue:string
+ -> unit
+ -> t result io
+ (** same as above, but processes requests concurrently *)
+
val shutdown : t:t -> unit -> unit io
(** [shutdown t] shutdown a server *)
end
diff --git a/ocaml/message-switch/core_test/async/server_async_main.ml b/ocaml/message-switch/core_test/async/server_async_main.ml
index 2372cb34c98..cd7984bec27 100644
--- a/ocaml/message-switch/core_test/async/server_async_main.ml
+++ b/ocaml/message-switch/core_test/async/server_async_main.ml
@@ -23,6 +23,8 @@ let path = ref "/var/run/message-switch/sock"
let name = ref "server"
+let concurrent = ref false
+
let shutdown = Ivar.create ()
let process = function
@@ -33,7 +35,10 @@ let process = function
let main () =
let (_ : 'a Deferred.t) =
- Server.listen ~process ~switch:!path ~queue:!name ()
+ if !concurrent then
+ Server.listen_p ~process ~switch:!path ~queue:!name ()
+ else
+ Server.listen ~process ~switch:!path ~queue:!name ()
in
Ivar.read shutdown >>= fun () ->
Clock.after (Time.Span.of_sec 1.) >>= fun () -> exit 0
@@ -49,6 +54,11 @@ let _ =
, Arg.Set_string name
, Printf.sprintf "name to send message to (default %s)" !name
)
+ ; ( "-concurrent"
+ , Arg.Set concurrent
+ , Printf.sprintf "set concurrent processing of messages (default %b)"
+ !concurrent
+ )
]
(fun x -> P.fprintf stderr "Ignoring unexpected argument: %s" x)
"Respond to RPCs on a name" ;
diff --git a/ocaml/message-switch/core_test/basic-rpc-test.sh b/ocaml/message-switch/core_test/basic-rpc-test.sh
index e73c3a873d1..877790370a2 100755
--- a/ocaml/message-switch/core_test/basic-rpc-test.sh
+++ b/ocaml/message-switch/core_test/basic-rpc-test.sh
@@ -1,12 +1,14 @@
#!/bin/bash
set -e
-SPATH=${TMPDIR:-/tmp}/sock
-SWITCHPATH=${TMPDIR:-/tmp}/switch
+SPATH=${TMPDIR:-/tmp}/sock_s
+SWITCHPATH=${TMPDIR:-/tmp}/switch_s
rm -rf ${SWITCHPATH} && mkdir -p ${SWITCHPATH}
+echo Test message switch serial processing
+
echo Checking the switch can start late
./server_unix_main.exe -path $SPATH &
sleep 1
diff --git a/ocaml/message-switch/core_test/concur-rpc-test.sh b/ocaml/message-switch/core_test/concur-rpc-test.sh
new file mode 100755
index 00000000000..a91768972fe
--- /dev/null
+++ b/ocaml/message-switch/core_test/concur-rpc-test.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+set -e
+
+SPATH="${TMPDIR:-/tmp}/sock_p-$$"
+SWITCHPATH="${TMPDIR:-/tmp}/switch_p-$$"
+
+trap "cleanup" TERM INT
+
+function cleanup {
+ rm -rf "${SWITCHPATH}"
+}
+
+rm -rf "${SWITCHPATH}" && mkdir -p "${SWITCHPATH}"
+
+echo Test message switch concurrent processing
+
+echo Checking the switch can start late
+test -x ./server_unix_main.exe || exit 1
+./server_unix_main.exe -path "$SPATH" &
+sleep 1
+test -x ../switch/switch_main.exe && test -x ./client_unix_main.exe || exit 1
+../switch/switch_main.exe --path "$SPATH" --statedir "${SWITCHPATH}" &
+./client_unix_main.exe -path "$SPATH" -secs 5
+sleep 2
+
+echo Performance test of Lwt to Lwt
+test -x lwt/server_main.exe && test -x lwt/client_main.exe || exit 1
+lwt/server_main.exe -path "$SPATH" -concurrent &
+lwt/client_main.exe -path "$SPATH" -secs 5
+sleep 2
+
+echo Performance test of Async to Lwt
+test -x lwt/server_main.exe && test -x async/client_async_main.exe || exit 1
+lwt/server_main.exe -path "$SPATH" -concurrent &
+async/client_async_main.exe -path "$SPATH" -secs 5
+sleep 2
+
+echo Performance test of Async to Async
+test -x async/server_async_main.exe && test -x async/client_async_main.exe || exit 1
+async/server_async_main.exe -path "$SPATH" -concurrent &
+async/client_async_main.exe -path "$SPATH" -secs 5
+sleep 2
+
+../cli/main.exe shutdown --path "$SPATH"
+sleep 2
diff --git a/ocaml/message-switch/core_test/dune b/ocaml/message-switch/core_test/dune
index d500c101354..449f2fae5c5 100644
--- a/ocaml/message-switch/core_test/dune
+++ b/ocaml/message-switch/core_test/dune
@@ -3,13 +3,43 @@
(names
client_unix_main
server_unix_main
+ lock_test_async
+ lock_test_lwt
+ )
+ (modules
+ client_unix_main
+ server_unix_main
+ lock_test_async
+ lock_test_lwt
)
(libraries
message-switch-unix
+ message-switch-core
+ message-switch-async
+ message-switch-lwt
threads.posix
)
)
+(rule
+ (alias runtest)
+ (deps
+ lock_test_async.exe
+ )
+ (action (run ./lock_test_async.exe))
+ (package message-switch)
+)
+
+(rule
+ (alias runtest)
+ (deps
+ lock_test_lwt.exe
+ )
+ (action (run ./lock_test_lwt.exe))
+ (package message-switch)
+)
+
+
(rule
(alias runtest)
(deps
@@ -27,3 +57,20 @@
(package message-switch)
)
+(rule
+ (alias runtest)
+ (deps
+ client_unix_main.exe
+ server_unix_main.exe
+ async/client_async_main.exe
+ async/server_async_main.exe
+ lwt/client_main.exe
+ lwt/server_main.exe
+ lwt/link_test_main.exe
+ ../switch/switch_main.exe
+ ../cli/main.exe
+ )
+ (action (run ./concur-rpc-test.sh))
+ (package message-switch)
+)
+
diff --git a/ocaml/message-switch/core_test/lock_test_async.ml b/ocaml/message-switch/core_test/lock_test_async.ml
new file mode 100644
index 00000000000..85cde8eaecb
--- /dev/null
+++ b/ocaml/message-switch/core_test/lock_test_async.ml
@@ -0,0 +1,13 @@
+open Core
+open Async
+open Message_switch_async
+
+let ( >>= ) = Deferred.( >>= )
+
+let test_async_lock () = Protocol_async.Mtest.mutex_provides_mutal_exclusion ()
+
+let () =
+ don't_wait_for
+ (test_async_lock () >>= fun () -> shutdown 0 ; Deferred.return ())
+
+let () = never_returns (Scheduler.go ())
diff --git a/ocaml/message-switch/core_test/lock_test_lwt.ml b/ocaml/message-switch/core_test/lock_test_lwt.ml
new file mode 100644
index 00000000000..784599dafa4
--- /dev/null
+++ b/ocaml/message-switch/core_test/lock_test_lwt.ml
@@ -0,0 +1,5 @@
+open Message_switch_lwt
+
+let test_lwt_lock = Protocol_lwt.Mtest.mutex_provides_mutal_exclusion ()
+
+let () = Lwt_main.run test_lwt_lock
diff --git a/ocaml/message-switch/core_test/lwt/server_main.ml b/ocaml/message-switch/core_test/lwt/server_main.ml
index c30021ff35d..ece423dcb74 100644
--- a/ocaml/message-switch/core_test/lwt/server_main.ml
+++ b/ocaml/message-switch/core_test/lwt/server_main.ml
@@ -20,6 +20,8 @@ let path = ref "/var/run/message-switch/sock"
let name = ref "server"
+let concurrent = ref false
+
let t, u = Lwt.task ()
let process = function
@@ -29,8 +31,13 @@ let process = function
return x
let main () =
- Message_switch_lwt.Protocol_lwt.Server.listen ~process ~switch:!path
- ~queue:!name ()
+ ( if !concurrent then
+ Message_switch_lwt.Protocol_lwt.Server.listen_p ~process ~switch:!path
+ ~queue:!name ()
+ else
+ Message_switch_lwt.Protocol_lwt.Server.listen ~process ~switch:!path
+ ~queue:!name ()
+ )
>>= fun _ ->
t >>= fun () -> Lwt_unix.sleep 1.
@@ -45,6 +52,11 @@ let _ =
, Arg.Set_string name
, Printf.sprintf "name to send message to (default %s)" !name
)
+ ; ( "-concurrent"
+ , Arg.Set concurrent
+ , Printf.sprintf "set concurrent processing of messages (default %b)"
+ !concurrent
+ )
]
(fun x -> Printf.fprintf stderr "Ignoring unexpected argument: %s" x)
"Respond to RPCs on a name" ;
diff --git a/ocaml/message-switch/lwt/dune b/ocaml/message-switch/lwt/dune
index 2aaf432952b..12f03301298 100644
--- a/ocaml/message-switch/lwt/dune
+++ b/ocaml/message-switch/lwt/dune
@@ -4,8 +4,8 @@
(libraries
cohttp-lwt-unix
message-switch-core
- lwt
- lwt.unix
+ (re_export lwt)
+ (re_export lwt.unix)
)
)
diff --git a/ocaml/message-switch/lwt/protocol_lwt.ml b/ocaml/message-switch/lwt/protocol_lwt.ml
index 6da59eb3212..26c9c874d55 100644
--- a/ocaml/message-switch/lwt/protocol_lwt.ml
+++ b/ocaml/message-switch/lwt/protocol_lwt.ml
@@ -27,9 +27,15 @@ module M = struct
let iter = Lwt_list.iter_s
+ let iter_dontwait f lst = Lwt.async (fun () -> Lwt_list.iter_p f lst)
+
let any = Lwt.choose
+ let all = Lwt.all
+
let is_determined t = Lwt.state t <> Lwt.Sleep
+
+ let return_unit = Lwt.return_unit
end
let connect path =
@@ -75,6 +81,18 @@ module M = struct
let with_lock = Lwt_mutex.with_lock
end
+ module Condition = struct
+ type 'a t = 'a Lwt_condition.t
+
+ let create = Lwt_condition.create
+
+ let signal = Lwt_condition.signal
+
+ let wait c = Lwt_condition.wait c
+
+ let broadcast = Lwt_condition.broadcast
+ end
+
module Clock = struct
type timer = unit Lwt.t
@@ -90,3 +108,4 @@ end
module Client = Message_switch_core.Make.Client (M)
module Server = Message_switch_core.Make.Server (M)
+module Mtest = Message_switch_core.Mtest.Make (M)
diff --git a/ocaml/message-switch/lwt/protocol_lwt.mli b/ocaml/message-switch/lwt/protocol_lwt.mli
index c9bd220155d..64ca15c0e8e 100644
--- a/ocaml/message-switch/lwt/protocol_lwt.mli
+++ b/ocaml/message-switch/lwt/protocol_lwt.mli
@@ -19,3 +19,5 @@ open Message_switch_core
module Client : S.CLIENT with type 'a io = 'a Lwt.t
module Server : S.SERVER with type 'a io = 'a Lwt.t
+
+module Mtest : Mtest.MTEST with type 'a io = 'a Lwt.t
diff --git a/ocaml/message-switch/switch/switch_main.ml b/ocaml/message-switch/switch/switch_main.ml
index 9bf78973a85..583baf6e594 100644
--- a/ocaml/message-switch/switch/switch_main.ml
+++ b/ocaml/message-switch/switch/switch_main.ml
@@ -75,6 +75,13 @@ module Lwt_result = struct
let ( >>= ) m f = m >>= fun x -> f (Stdlib.Result.get_ok x)
end
+let exn_hook e =
+ let bt = Printexc.get_raw_backtrace () in
+ error "Caught exception in Lwt.async: %s" (Printexc.to_string e) ;
+ error "backtrace: %s" (Printexc.raw_backtrace_to_string bt)
+
+let () = Lwt.async_exception_hook := exn_hook
+
let make_server config trace_config =
let open Config in
info "Started server on %s" config.path ;
diff --git a/ocaml/message-switch/unix/protocol_unix.ml b/ocaml/message-switch/unix/protocol_unix.ml
index 678b302ab5a..485964a40ec 100644
--- a/ocaml/message-switch/unix/protocol_unix.ml
+++ b/ocaml/message-switch/unix/protocol_unix.ml
@@ -546,5 +546,7 @@ module Server = struct
let (_ : Thread.t) = thread_forever (loop connections) None in
Ok ()
+ let listen_p = listen
+
let shutdown ~t:_ () = failwith "Shutdown is unimplemented"
end
diff --git a/ocaml/sdk-gen/README.md b/ocaml/sdk-gen/README.md
index 7473d141f83..fb4d71650bf 100644
--- a/ocaml/sdk-gen/README.md
+++ b/ocaml/sdk-gen/README.md
@@ -12,9 +12,7 @@ The Python module is not auto-generated, it can be found at
[XenAPI.py](../../scripts/examples/python/XenAPI/XenAPI.py).
To compile the generated source code, follow the instructions in the corresponding
-README files. The (patched) third party libraries required for the compilation
-of the C# and PowerShell source code can be obtained from
-[xenserver/dotnet-packages](https://github.com/xenserver/dotnet-packages)
+`README` files.
The repository [xenserver/xenserver-samples](https://github.com/xenserver/xenserver-samples)
contains a number of examples for each of the five programming languages to help
diff --git a/ocaml/sdk-gen/c/README.dist b/ocaml/sdk-gen/c/README.dist
index dfe92390216..e5fb8622069 100644
--- a/ocaml/sdk-gen/c/README.dist
+++ b/ocaml/sdk-gen/c/README.dist
@@ -58,4 +58,3 @@ Compiling from source
---------------------
To build, simply type "make" in the libxenserver/src directory.
-To build on Windows with cygwin type "make CYGWIN=1".
diff --git a/ocaml/sdk-gen/c/gen_c_binding.ml b/ocaml/sdk-gen/c/gen_c_binding.ml
index 2302fc6cf99..757046ac336 100644
--- a/ocaml/sdk-gen/c/gen_c_binding.ml
+++ b/ocaml/sdk-gen/c/gen_c_binding.ml
@@ -2,7 +2,7 @@
* Copyright (c) Cloud Software Group, Inc.
*)
-(* Generator of C bindings from the datamodel *)
+(* Generator of the C SDK from the datamodel *)
open Printf
open Datamodel_types
@@ -55,31 +55,23 @@ let enum_maps = ref TypeSet.empty
let all_headers = ref []
-let joined sep f l =
- let r = List.map f l in
- String.concat sep (List.filter (fun x -> String.compare x "" != 0) r)
+let rec is_last x list =
+ match list with
+ | [] ->
+ false
+ | hd :: [] ->
+ if hd = x then true else false
+ | hd :: tl ->
+ if hd = x then false else is_last x tl
let rec main () =
- let include_dir = Filename.concat destdir "include" in
- let src_dir = Filename.concat destdir "src" in
-
- gen_failure_h () ;
- gen_failure_c () ;
-
let filtered_classes =
List.filter
(fun x -> not (List.mem x.name ["session"; "debug"; "data_source"]))
classes
in
- List.iter
- (fun x ->
- ( gen_class write_predecl predecl_filename x include_dir ;
- gen_class write_decl decl_filename x include_dir ;
- gen_class write_impl impl_filename x
- )
- src_dir
- )
- filtered_classes ;
+ List.iter gen_decl filtered_classes ;
+ List.iter gen_impl filtered_classes ;
all_headers := List.map (fun x -> x.name) filtered_classes ;
@@ -89,11 +81,12 @@ let rec main () =
maps := TypeSet.add (Map (Int, Int)) !maps ;
maps := TypeSet.add (Map (String, Set String)) !maps ;
maps := TypeSet.add (Map (String, Map (String, String))) !maps ;
- TypeSet.iter (gen_map write_map_decl decl_filename include_dir) !maps ;
- TypeSet.iter (gen_map write_map_impl impl_filename src_dir) !maps ;
+
+ TypeSet.iter (function Map (l, r) -> render_map_decl l r | _ -> ()) !maps ;
+ TypeSet.iter (function Map (l, r) -> render_map_impl l r | _ -> ()) !maps ;
TypeSet.iter
- (gen_map write_enum_map_internal_decl internal_decl_filename include_dir)
+ (function Map (l, r) -> render_enum_map l r | _ -> ())
!enum_maps ;
let class_records =
@@ -118,7 +111,10 @@ let rec main () =
json1 templates_dir destdir ;
let sorted_headers =
- List.sort String.compare (List.map decl_filename !all_headers)
+ !all_headers
+ |> List.filter (fun x -> not (Astring.String.is_suffix ~affix:"internal" x))
+ |> List.map String.lowercase_ascii
+ |> List.sort String.compare
in
let json2 =
`O
@@ -132,295 +128,305 @@ let rec main () =
("xen_all.h.mustache", "include/xen/api/xen_all.h")
json2 templates_dir destdir
-and gen_class f g clas targetdir =
- let out_chan = open_out (Filename.concat targetdir (g clas.name)) in
- Fun.protect (fun () -> f clas out_chan) ~finally:(fun () -> close_out out_chan)
-
-and gen_map f g targetdir = function
- | Map (l, r) ->
- let name = mapname l r in
- if not (List.mem name !all_headers) then
- all_headers := name :: !all_headers ;
- let out_chan = open_out (Filename.concat targetdir (g name)) in
- Fun.protect
- (fun () -> f name l r out_chan)
- ~finally:(fun () -> close_out out_chan)
- | _ ->
- assert false
-
-and write_predecl {name= classname; _} out_chan =
- let print format = fprintf out_chan format in
- let protect = protector (classname ^ "_decl") in
- let tn = typename classname in
- let record_tn = record_typename classname in
- let record_opt_tn = record_opt_typename classname in
-
- print_h_header out_chan protect ;
-
- if classname <> "event" then (
- print "typedef void *%s;\n\n" tn ;
- print "%s\n" (predecl_set tn)
- ) ;
- print "%s\n" (predecl record_tn) ;
- print "%s\n" (predecl_set record_tn) ;
- if classname <> "event" then (
- print "%s\n" (predecl record_opt_tn) ;
- print "%s\n" (predecl_set record_opt_tn)
- ) ;
- print_h_footer out_chan
+and gen_decl cls =
+ let headers = ref (StringSet.add (cls.name ^ "_decl") StringSet.empty) in
+ let rec get_needed = function
+ | Field fr ->
+ find_needed headers fr.ty
+ | Namespace (_, cs) ->
+ List.iter get_needed cs
+ in
+ List.iter get_needed cls.contents ;
-and write_decl {name= classname; contents; description; messages; _} out_chan =
- let print format = fprintf out_chan format in
- let protect = protector classname in
- let tn = typename classname in
- let record_tn = record_typename classname in
- let record_opt_tn = record_opt_typename classname in
- let class_has_refs = true (* !!! *) in
- let needed = ref (StringSet.add (classname ^ "_decl") StringSet.empty) in
- let record = decl_record needed tn record_tn contents in
- let record_opt = decl_record_opt tn record_tn record_opt_tn in
- let message_decls =
- decl_messages needed classname
- (List.filter
- (fun x -> not (classname = "event" && x.msg_name = "from"))
- messages
- )
+ let asyncParams x =
+ if x.msg_async then
+ {
+ param_type= Ref "task"
+ ; param_name= "*result"
+ ; param_doc= ""
+ ; param_release= x.msg_release
+ ; param_default= None
+ }
+ :: x.msg_params
+ else
+ x.msg_params
in
- let full_stop =
- if Astring.String.is_suffix ~affix:"." description then "" else "."
+ let syncParams x =
+ match x.msg_result with
+ | Some res ->
+ {
+ param_type= fst res
+ ; param_name= "*result"
+ ; param_doc= ""
+ ; param_release= x.msg_release
+ ; param_default= None
+ }
+ :: x.msg_params
+ | None ->
+ x.msg_params
+ in
+ let paramJson x =
+ `O
+ [
+ ("param_name", `String (paramname x.param_name))
+ ; ("param_type", `String (c_type_of_ty headers false x.param_type))
+ ]
in
+ let json =
+ `O
+ [
+ ("class_upper", `String (String.uppercase_ascii cls.name))
+ ; ("class_lower", `String (String.lowercase_ascii cls.name))
+ ; ("class_doc", `String (Helper.comment false (full_class_doc cls)))
+ ; ("is_event", `Bool (cls.name = "event"))
+ ; ( "headers"
+ , `A
+ (List.map
+ (fun x -> `O [("header", `String x)])
+ ("common" :: StringSet.elements !headers
+ |> List.map String.lowercase_ascii
+ |> List.sort String.compare
+ |> List.filter (fun x ->
+ not (Astring.String.is_suffix ~affix:"internal" x)
+ )
+ )
+ )
+ )
+ ; ( "fields"
+ , `A
+ (cls
+ |> Datamodel_utils.fields_of_obj
+ |> List.map (fun field ->
+ `O
+ [
+ ( "field_name_lower"
+ , `String (fieldname (String.concat "_" field.full_name))
+ )
+ ; ( "field_type"
+ , `String (c_type_of_ty headers true field.ty)
+ )
+ ]
+ )
+ )
+ )
+ ; ( "messages"
+ , `A
+ (cls.messages
+ |> List.filter (fun x ->
+ not (cls.name = "event" && x.msg_name = "from")
+ )
+ |> List.map (fun x ->
+ `O
+ [
+ ( "msg_name_lower"
+ , `String (String.lowercase_ascii x.msg_name)
+ )
+ ; ( "msg_doc"
+ , `String (Helper.comment true (full_msg_doc x))
+ )
+ ; ("is_async", `Bool x.msg_async)
+ ; ("sync_params", `A (List.map paramJson (syncParams x)))
+ ; ("async_params", `A (List.map paramJson (asyncParams x)))
+ ]
+ )
+ )
+ )
+ ]
+ in
+ render_file
+ ( "class_decl.h.mustache"
+ , sprintf "include/xen/api/xen_%s_decl.h" (String.lowercase_ascii cls.name)
+ )
+ json templates_dir destdir ;
+ render_file
+ ( "class.h.mustache"
+ , sprintf "include/xen/api/xen_%s.h" (String.lowercase_ascii cls.name)
+ )
+ json templates_dir destdir
- let rec get_needed x =
- match x with
+and gen_impl cls =
+ let headers = ref StringSet.empty in
+ let rec get_needed = function
| Field fr ->
- find_needed'' needed fr.ty
+ find_needed headers fr.ty
| Namespace (_, cs) ->
List.iter get_needed cs
in
- List.iter get_needed contents ;
-
- print_h_header out_chan protect ;
- print "%s\n" (hash_includes !needed) ;
-
- print "\n\n%s\n\n\n"
- (Helper.comment false
- (sprintf "The %s class.\n\n%s%s" classname description full_stop)
- ) ;
+ List.iter get_needed cls.contents ;
- if classname <> "event" then (
- print "%s\n\n"
- (decl_free tn (String.lowercase_ascii classname) false "handle") ;
- print "%s\n" (decl_set tn false)
- ) ;
- print "%s\n" record ;
- if classname <> "event" then
- print "%s\n" record_opt ;
- print "%s\n\n" (decl_set record_tn class_has_refs) ;
- if classname <> "event" then
- print "%s\n\n" (decl_set record_opt_tn true) ;
- print "%s\n" message_decls ;
- print_h_footer out_chan
-
-and predecl_set tn = predecl (tn ^ "_set")
-
-and predecl tn = sprintf "struct %s;" tn
-
-and decl_set tn referenced =
- let alloc_com =
- Helper.comment true (sprintf "Allocate a %s_set of the given size." tn)
- in
-
- sprintf
- "\n\
- typedef struct %s_set\n\
- {\n\
- \ size_t size;\n\
- \ %s *contents[];\n\
- } %s_set;\n\n\
- %s\n\
- extern %s_set *\n\
- %s_set_alloc(size_t size);\n\n\
- %s\n"
- tn tn tn alloc_com tn tn
- (decl_free (sprintf "%s_set" tn) "*set" referenced "set")
+ List.iter
+ (fun x ->
+ List.iter (fun p -> find_needed headers p.param_type) x.msg_params ;
+ match x.msg_result with
+ | Some res ->
+ find_needed headers (fst res)
+ | None ->
+ ()
+ )
+ cls.messages ;
-and decl_free tn cn referenced thing =
- let com =
- Helper.comment true
- (sprintf
- "Free the given %s%s. The given %s must have been allocated by this \
- library."
- tn
- (if referenced then ", and all referenced values" else "")
- thing
- )
+ let allFields = cls |> Datamodel_utils.fields_of_obj in
+ let result_type message =
+ match message.msg_result with
+ | Some res ->
+ abstract_type false (fst res)
+ | None ->
+ ""
in
-
- sprintf "%s\nextern void\n%s_free(%s %s);" com tn tn cn
-
-and decl_record needed tn record_tn contents =
- sprintf
- "\n\
- typedef struct %s\n\
- {\n\
- %s %s\n\
- } %s;\n\n\
- %s\n\
- extern %s *\n\
- %s_alloc(void);\n\n\
- %s\n"
- record_tn
- (if tn <> "xen_event" then sprintf " %s handle;\n" tn else "")
- (record_fields contents needed)
- record_tn
- (Helper.comment true (sprintf "Allocate a %s." record_tn))
- record_tn record_tn
- (decl_free record_tn "*record" true "record")
-
-and decl_record_opt tn record_tn record_opt_tn =
- sprintf
- "\n\
- typedef struct %s\n\
- {\n\
- \ bool is_record;\n\
- \ union\n\
- \ {\n\
- \ %s handle;\n\
- \ %s *record;\n\
- \ } u;\n\
- } %s;\n\n\
- %s\n\
- extern %s *\n\
- %s_alloc(void);\n\n\
- %s\n"
- record_opt_tn tn record_tn record_opt_tn
- (Helper.comment true (sprintf "Allocate a %s." record_opt_tn))
- record_opt_tn record_opt_tn
- (decl_free record_opt_tn "*record_opt" true "record_opt")
-
-and record_fields contents needed =
- joined "\n " (record_field needed "") contents
-
-and record_field needed prefix content =
- match content with
- | Field fr ->
- sprintf "%s%s%s;"
- (c_type_of_ty needed true fr.ty)
- prefix (fieldname fr.field_name)
- | Namespace (p, c) ->
- joined "\n " (record_field needed (prefix ^ fieldname p ^ "_")) c
-
-and decl_messages needed classname messages =
- joined "\n\n" (decl_message needed classname) messages
-
-and decl_message needed classname message =
- let message_sig = message_signature needed classname message in
- let messageAsyncVersion = decl_message_async needed classname message in
- sprintf "%s\n%sextern %s;\n%s"
- (get_message_comment message)
- (get_deprecated_message message)
- message_sig messageAsyncVersion
-
-and decl_message_async needed classname message =
- if message.msg_async then (
- let messageSigAsync = message_signature_async needed classname message in
- needed := StringSet.add "task_decl" !needed ;
- sprintf "\n%s\n%sextern %s;\n"
- (get_message_comment message)
- (get_deprecated_message message)
- messageSigAsync
- ) else
- ""
-
-and get_message_comment message =
- let full_stop =
- if Astring.String.is_suffix ~affix:"." message.msg_doc then "" else "."
+ let init_result message =
+ match message.msg_result with
+ | Some res -> (
+ match fst res with
+ | SecretString | String | Ref _ | Set _ | Map _ | Record _ ->
+ true
+ | _ ->
+ false
+ )
+ | None ->
+ false
in
- let minimum_allowed_role = get_minimum_allowed_role message in
- let content =
- sprintf "%s%s\nMinimum allowed role: %s." message.msg_doc full_stop
- minimum_allowed_role
+ let is_result_record message =
+ match message.msg_result with
+ | Some res -> (
+ match fst res with Record _ -> true | _ -> false
+ )
+ | None ->
+ false
in
- Helper.comment true content
-
-and impl_messages needed classname messages =
- joined "\n\n" (impl_message needed classname) messages
-
-and impl_message needed classname message =
- let message_sig = message_signature needed classname message in
- let param_count = List.length message.msg_params in
-
- let param_decl, param_call =
- if param_count = 0 then
- ("", "NULL")
+ let asyncParams x =
+ if x.msg_async then
+ {
+ param_type= Ref "task"
+ ; param_name= "*result"
+ ; param_doc= ""
+ ; param_release= x.msg_release
+ ; param_default= None
+ }
+ :: x.msg_params
else
- let param_pieces = abstract_params message.msg_params in
-
- ( sprintf
- " abstract_value param_values[] =\n\
- \ {\n\
- \ %s\n\
- \ };\n"
- param_pieces
- , "param_values"
- )
+ x.msg_params
in
-
- let result_bits =
- match message.msg_result with
+ let syncParams x =
+ match x.msg_result with
| Some res ->
- abstract_result_handling classname message.msg_name param_count res
+ {
+ param_type= fst res
+ ; param_name= "*result"
+ ; param_doc= ""
+ ; param_release= x.msg_release
+ ; param_default= None
+ }
+ :: x.msg_params
| None ->
- sprintf
- " xen_call_(session, \"%s.%s\", %s, %d, NULL, NULL);\n\
- \ return session->ok;\n"
- classname message.msg_name
- (if param_count = 0 then "NULL" else param_call)
- param_count
+ x.msg_params
in
-
- let messageAsyncImpl = impl_message_async needed classname message in
- sprintf "%s%s\n{\n%s\n%s}\n%s"
- (get_deprecated_message message)
- message_sig param_decl result_bits messageAsyncImpl
-
-and impl_message_async needed classname message =
- if message.msg_async then
- let messageSigAsync = message_signature_async needed classname message in
- let param_count = List.length message.msg_params in
-
- let param_decl, _ =
- if param_count = 0 then
- ("", "NULL")
- else
- let param_pieces = abstract_params message.msg_params in
-
- ( sprintf
- " abstract_value param_values[] =\n\
- \ {\n\
- \ %s\n\
- \ };\n"
- param_pieces
- , "param_values"
- )
+ let messageJson msg =
+ let paramJson p =
+ `O
+ [
+ ("param_name", `String (paramname p.param_name))
+ ; ("param_type", `String (c_type_of_ty headers false p.param_type))
+ ; ("abstract_param_type", `String (abstract_type false p.param_type))
+ ; ("abstract_member", `String (abstract_member p.param_type))
+ ; ( "abstract_member_conv"
+ , `String (abstract_param_conv p.param_name p.param_type)
+ )
+ ; ("is_last", `Bool (is_last p msg.msg_params))
+ ]
in
+ `O
+ [
+ ("msg_name_lower", `String (String.lowercase_ascii msg.msg_name))
+ ; ("msg_name", `String msg.msg_name)
+ ; ("msg_doc", `String (Helper.comment true (full_msg_doc msg)))
+ ; ("is_async", `Bool msg.msg_async)
+ ; ("sync_params", `A (List.map paramJson (syncParams msg)))
+ ; ("async_params", `A (List.map paramJson (asyncParams msg)))
+ ; ("msg_params", `A (List.map paramJson msg.msg_params))
+ ; ("abstract_result_type", `String (result_type msg))
+ ; ("has_params", `Bool (List.length msg.msg_params <> 0))
+ ; ("param_count", `String (string_of_int (List.length msg.msg_params)))
+ ; ("has_result", `Bool (String.compare (result_type msg) "" <> 0))
+ ; ("init_result", `Bool (init_result msg))
+ ; ("is_result_record", `Bool (is_result_record msg))
+ ]
+ in
+ let fieldJson field =
+ let fullName = String.concat "_" field.full_name in
+ let freeing = free_impl ("record->" ^ fieldname fullName) true field.ty in
+ `O
+ [
+ ("field_name_lower", `String (fieldname fullName))
+ ; ("field_name", `String fullName)
+ ; ("abstract_field_type", `String (abstract_type true field.ty))
+ ; ("can_free", `Bool (freeing <> ""))
+ ; ("free_record_field", `String freeing)
+ ; ("is_last", `Bool (is_last field allFields))
+ ]
+ in
+ let json =
+ `O
+ [
+ ("class_name", `String cls.name)
+ ; ("class_lower", `String (String.lowercase_ascii cls.name))
+ ; ("is_event", `Bool (cls.name = "event"))
+ ; ( "has_all_records"
+ , `Bool
+ (List.exists (fun x -> x.msg_name = "get_all_records") cls.messages)
+ )
+ ; ( "headers"
+ , `A
+ (List.map
+ (fun x -> `O [("header", `String x)])
+ (["common"; String.lowercase_ascii cls.name]
+ |> List.sort String.compare
+ )
+ )
+ )
+ ; ( "internal_headers"
+ , `A
+ (List.map
+ (fun x -> `O [("header", `String x)])
+ ("internal" :: StringSet.elements !headers
+ |> List.map String.lowercase_ascii
+ |> List.sort String.compare
+ |> List.filter (fun x ->
+ Astring.String.is_suffix ~affix:"internal" x
+ )
+ )
+ )
+ )
+ ; ("fields", `A (allFields |> List.map fieldJson))
+ ; ( "messages"
+ , `A
+ (cls.messages
+ |> List.filter (fun x ->
+ not (cls.name = "event" && x.msg_name = "from")
+ )
+ |> List.map messageJson
+ )
+ )
+ ]
+ in
+ render_file
+ ( "class.c.mustache"
+ , sprintf "src/xen_%s.c" (String.lowercase_ascii cls.name)
+ )
+ json templates_dir destdir
- let result_bits =
- abstract_result_handling_async classname message.msg_name param_count
- in
- sprintf "\n%s%s\n{\n%s\n%s}"
- (get_deprecated_message message)
- messageSigAsync param_decl result_bits
- else
- ""
+and full_stop x = if Astring.String.is_suffix ~affix:"." x then "" else "."
-and abstract_params params = joined ",\n " abstract_param params
+and full_class_doc cls =
+ let intro = sprintf "The %s class.\n\n" cls.name in
+ intro ^ cls.description ^ full_stop cls.description
-and abstract_param p =
- let ab_typ = abstract_type false p.param_type in
- sprintf "{ .type = &%s,\n .u.%s_val = %s }" ab_typ
- (abstract_member p.param_type)
- (abstract_param_conv p.param_name p.param_type)
+and full_msg_doc message =
+ let role =
+ sprintf "\nMinimum allowed role: %s." (get_minimum_allowed_role message)
+ in
+ let deprecated = get_deprecated_info_message message in
+ let deprecated = if deprecated = "" then "" else "\n" ^ deprecated in
+ message.msg_doc ^ full_stop message.msg_doc ^ role ^ deprecated
and abstract_param_conv name = function
| Set _ | Map _ ->
@@ -443,9 +449,7 @@ and abstract_member = function
"bool"
| DateTime ->
"datetime"
- | Set _ ->
- "set"
- | Map _ ->
+ | Set _ | Map _ ->
"set"
| Record _ ->
"struct"
@@ -453,81 +457,6 @@ and abstract_member = function
eprintf "%s" (Types.to_string x) ;
assert false
-and abstract_result_handling classname msg_name param_count = function
- | typ, _ -> (
- let call =
- if param_count = 0 then
- sprintf
- "xen_call_(session, \"%s.%s\", NULL, 0, &result_type, result);"
- classname msg_name
- else
- sprintf "XEN_CALL_(\"%s.%s\");" classname msg_name
- in
-
- match typ with
- | String | Ref _ | Int | Float | Bool | DateTime | Set _ | Map _ ->
- sprintf "%s\n\n%s %s\n return session->ok;\n"
- (abstract_result_type typ) (initialiser_of_ty typ) call
- | Record n ->
- let record_tn = record_typename n in
- sprintf
- " abstract_type result_type = %s_abstract_type_;\n\n\
- %s %s\n\n\
- \ if (session->ok)\n\
- \ {\n\
- \ (*result)->handle = xen_strdup_((*result)->uuid);\n\
- \ }\n\n\
- \ return session->ok;\n"
- record_tn
- (initialiser_of_ty (Record n))
- call
- | Enum (_, _) ->
- sprintf "%s\n %s\n return session->ok;\n"
- (abstract_result_type typ) call
- | x ->
- eprintf "%s" (Types.to_string x) ;
- assert false
- )
-
-and abstract_result_handling_async classname msg_name param_count =
- let call =
- if param_count = 0 then
- sprintf
- "xen_call_(session, \"Async.%s.%s\", NULL, 0, &result_type, result);"
- classname msg_name
- else
- sprintf "XEN_CALL_(\"Async.%s.%s\");" classname msg_name
- in
- sprintf
- " abstract_type result_type = abstract_type_string;\n\n\
- \ *result = NULL;\n\
- \ %s\n\
- \ return session->ok;\n"
- call
-
-and abstract_record_field classname prefix prefix_caps content =
- match content with
- | Field fr ->
- let fn = fieldname fr.field_name in
- sprintf
- "{ .key = \"%s%s\",\n\
- \ .type = &%s,\n\
- \ .offset = offsetof(%s, %s%s) }" prefix_caps fr.field_name
- (abstract_type true fr.ty)
- (record_typename classname)
- prefix fn
- | Namespace (p, c) ->
- joined ",\n "
- (abstract_record_field classname
- (prefix ^ fieldname p ^ "_")
- (prefix_caps ^ p ^ "_")
- )
- c
-
-and abstract_result_type typ =
- let ab_typ = abstract_type false typ in
- sprintf " abstract_type result_type = %s;" ab_typ
-
and abstract_type record = function
| SecretString | String ->
"abstract_type_string"
@@ -575,87 +504,6 @@ and abstract_type record = function
| Option n ->
abstract_type record n
-and get_deprecated_message message =
- let deprecatedMessage = get_deprecated_info_message message in
- if deprecatedMessage = "" then
- sprintf ""
- else
- sprintf "/* " ^ deprecatedMessage ^ " */\n"
-
-and message_signature needed classname message =
- let front =
- {
- param_type= Ref "session"
- ; param_name= "session"
- ; param_doc= ""
- ; param_release= message.msg_release
- ; param_default= None
- }
- ::
- ( match message.msg_result with
- | Some res ->
- [
- {
- param_type= fst res
- ; param_name= "*result"
- ; param_doc= ""
- ; param_release= message.msg_release
- ; param_default= None
- }
- ]
- | None ->
- []
- )
- in
- let params = joined ", " (param needed) (front @ message.msg_params) in
- sprintf "bool\n%s(%s)" (messagename classname message.msg_name) params
-
-and message_signature_async needed classname message =
- let sessionParam =
- {
- param_type= Ref "session"
- ; param_name= "session"
- ; param_doc= ""
- ; param_release= message.msg_release
- ; param_default= None
- }
- in
- let taskParam =
- {
- param_type= Ref "task"
- ; param_name= "*result"
- ; param_doc= ""
- ; param_release= message.msg_release
- ; param_default= None
- }
- in
- let params =
- joined ", " (param needed) (sessionParam :: taskParam :: message.msg_params)
- in
- sprintf "bool\n%s(%s)" (messagename_async classname message.msg_name) params
-
-and param needed p =
- let t = p.param_type in
- let n = p.param_name in
- sprintf "%s%s" (c_type_of_ty needed false t) (paramname n)
-
-and hash_includes needed =
- String.concat "\n"
- (List.sort String.compare
- (List.filter
- (function s -> s <> "")
- (List.map hash_include ("common" :: StringSet.elements needed))
- )
- )
-
-and hash_include n =
- if Astring.String.is_suffix ~affix:"internal" n then
- sprintf "#include \"%s\"" (decl_filename n)
- else if n = "session" then
- ""
- else
- sprintf "#include <%s>" (decl_filename n)
-
and replace_dashes x =
Astring.String.map (fun y -> match y with '-' -> '_' | _ -> y) x
@@ -677,7 +525,9 @@ and render_enum x =
`O
[
("enum_value", `String n)
- ; ("enum_value_doc", `String c)
+ ; ( "enum_value_doc"
+ , `String (Helper.comment true ~indent:4 c)
+ )
; ( "enum_value_upper"
, `String (replace_dashes (String.uppercase_ascii n))
)
@@ -702,371 +552,159 @@ and render_enum x =
| _ ->
()
-and write_map_decl name l r out_chan =
- let print format = fprintf out_chan format in
- let tn = typename name in
- let protect = protector name in
- let needed = ref StringSet.empty in
- let alloc_com =
- Helper.comment true (sprintf "Allocate a %s of the given size." tn)
- in
-
- print_h_header out_chan protect ;
- print
- "\n\
- %s%s%s\n\n\n\
- typedef struct %s_contents\n\
- {\n\
- \ %skey;\n\
- \ %sval;\n\
- } %s_contents;\n\n\n\
- typedef struct %s\n\
- {\n\
- \ size_t size;\n\
- \ %s_contents contents[];\n\
- } %s;\n\n\
- %s\n\
- extern %s *\n\
- %s_alloc(size_t size);\n\n\
- %s\n\n"
- (hash_include "common") (hash_include_enum l) (hash_include_enum r) tn
- (c_type_of_ty needed false l)
- (c_type_of_ty needed true r)
- tn tn tn tn alloc_com tn tn
- (decl_free tn "*map" true "map") ;
- print_h_footer out_chan
-
-and write_map_impl name l r out_chan =
- let print format = fprintf out_chan format in
- let tn = typename name in
- let l_free_impl = free_impl "map->contents[i].key" false l in
- let r_free_impl = free_impl "map->contents[i].val" true r in
- let needed = ref StringSet.empty in
- find_needed'' needed l ;
- find_needed'' needed r ;
- needed := StringSet.add "internal" !needed ;
- needed := StringSet.add name !needed ;
- ( match r with
- | Set String ->
- needed := StringSet.add "string_set" !needed
- | _ ->
- ()
- ) ;
-
- print
- "%s\n\n\n\
- %s\n\n\n\
- %s *\n\
- %s_alloc(size_t size)\n\
- {\n\
- \ %s *result = calloc(1, sizeof(%s) +\n\
- \ %s size * sizeof(struct %s_contents));\n\
- \ result->size = size;\n\
- \ return result;\n\
- }\n\n\n\
- void\n\
- %s_free(%s *map)\n\
- {\n"
- Licence.bsd_two_clause (hash_includes !needed) tn tn tn tn
- (String.make (String.length tn) ' ')
- tn tn tn ;
-
- if String.compare l_free_impl "" != 0 || String.compare r_free_impl "" != 0
- then
- print
- " if (map == NULL)\n\
- \ {\n\
- \ return;\n\
- \ }\n\n\
- \ size_t n = map->size;\n\
- \ for (size_t i = 0; i < n; i++)\n\
- \ {\n\
- \ %s\n\
- \ %s\n\
- \ }\n\n"
- l_free_impl r_free_impl ;
-
- print " free(map);\n}\n" ;
-
- match (l, r) with
- | Enum (_, _), _ ->
- gen_enum_map_abstract_type print l r
- | _, Enum (_, _) ->
- gen_enum_map_abstract_type print l r
- | _ ->
- ()
-
-and gen_enum_map_abstract_type print l r =
- let tn = mapname l r in
- print
- "\n\n\
- static const struct_member %s_struct_members[] =\n\
- \ {\n\
- \ { .type = &%s,\n\
- \ .offset = offsetof(xen_%s_contents, key) },\n\
- \ { .type = &%s,\n\
- \ .offset = offsetof(xen_%s_contents, val) },\n\
- \ };\n\n\
- const abstract_type %s_abstract_type_ =\n\
- \ {\n\
- \ .XEN_API_TYPE = MAP,\n\
- \ .struct_size = sizeof(%s_struct_members),\n\
- \ .member_count =\n\
- \ sizeof(%s_struct_members) / sizeof(struct_member),\n\
- \ .members = %s_struct_members\n\
- \ };\n"
- tn (abstract_type false l) tn (abstract_type false r) tn tn tn tn tn
-
-and write_enum_map_internal_decl name l r out_chan =
- let print format = fprintf out_chan format in
- let protect = protector (sprintf "%s_internal" name) in
-
- print_h_header out_chan protect ;
- print "\nextern const abstract_type %s_abstract_type_;\n\n" (mapname l r) ;
- print_h_footer out_chan
-
-and hash_include_enum = function
- | Enum (x, _) ->
- "\n" ^ hash_include x
- | _ ->
- ""
-
-and gen_failure_h () =
- let protect = protector "api_failure" in
- let out_chan =
- open_out (Filename.concat destdir "include/xen/api/xen_api_failure.h")
+and render_enum_map l r =
+ let x = mapname l r in
+ let json =
+ `O
+ [
+ ("map_upper", `String (String.uppercase_ascii x))
+ ; ("map_lower", `String (String.lowercase_ascii x))
+ ]
in
- Fun.protect
- (fun () ->
- print_h_header out_chan protect ;
- gen_failure_enum out_chan ;
- gen_failure_funcs out_chan ;
- print_h_footer out_chan
+ render_file
+ ( "xen_enum_map_internal.h.mustache"
+ , sprintf "include/xen_%s_internal.h" (String.lowercase_ascii x)
)
- ~finally:(fun () -> close_out out_chan)
-
-and gen_failure_enum out_chan =
- let print format = fprintf out_chan format in
- print "\nenum xen_api_failure\n{\n%s\n};\n\n\n"
- (String.concat ",\n\n" (failure_enum_entries ()))
-
-and failure_enum_entries () =
- let r = Hashtbl.fold failure_enum_entry Datamodel.errors [] in
- let r = List.sort (fun (x, _) (y, _) -> String.compare y x) r in
- let r =
- failure_enum_entry "UNDEFINED"
- {
- err_doc= "Unknown to this version of the bindings."
- ; err_params= []
- ; err_name= "UNDEFINED"
- }
- r
+ json templates_dir destdir
+
+and render_map_decl l r =
+ let headers = ref StringSet.empty in
+ let add_enum_header = function
+ | Enum (x, _) ->
+ headers := StringSet.add x !headers
+ | _ ->
+ ()
+ in
+ add_enum_header l ;
+ add_enum_header r ;
+ let x = mapname l r in
+ let json =
+ `O
+ [
+ ("key_type_lower", `String (c_type_of_ty headers false l))
+ ; ("val_type_lower", `String (c_type_of_ty headers true r))
+ ; ("map_upper", `String (String.uppercase_ascii x))
+ ; ("map_lower", `String (String.lowercase_ascii x))
+ ; ( "headers"
+ , `A
+ (List.map
+ (fun x -> `O [("header", `String x)])
+ ("common" :: StringSet.elements !headers
+ |> List.map String.lowercase_ascii
+ |> List.sort String.compare
+ |> List.filter (fun x ->
+ not (Astring.String.is_suffix ~affix:"internal" x)
+ )
+ )
+ )
+ )
+ ]
in
- List.map (fun (_, y) -> y) (List.rev r)
-
-and failure_enum_entry name err acc =
- ( name
- , sprintf "%s\n %s"
- (Helper.comment true ~indent:4 err.Datamodel_types.err_doc)
- (failure_enum name)
- )
- :: acc
-
-and gen_failure_funcs out_chan =
- let print format = fprintf out_chan format in
- print
- "%s\n\
- extern const char *\n\
- xen_api_failure_to_string(enum xen_api_failure val);\n\n\n\
- %s\n\
- extern enum xen_api_failure\n\
- xen_api_failure_from_string(const char *str);\n\n"
- (Helper.comment true
- "Return the name corresponding to the given code. This string must not \
- be modified or freed."
- )
- (Helper.comment true
- "Return the correct code for the given string, or UNDEFINED if the \
- given string does not match a known code."
- )
-
-and gen_failure_c () =
- let out_chan = open_out (Filename.concat destdir "src/xen_api_failure.c") in
- let print format = fprintf out_chan format in
- Fun.protect
- (fun () ->
- print
- "%s\n\n\
- #include \"xen_internal.h\"\n\
- #include