From b1bc5bef4e0ad4dace20b636667578d810cd9ef3 Mon Sep 17 00:00:00 2001 From: Konstantina Chremmou Date: Mon, 13 Jan 2025 14:11:33 +0000 Subject: [PATCH 01/53] CA-403634: Corrected error text (this error can be issued for other types of devices, not only disks). Signed-off-by: Konstantina Chremmou --- ocaml/sdk-gen/csharp/FriendlyErrorNames.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx b/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx index 7562889272e..7cfbf626a23 100644 --- a/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx +++ b/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx @@ -61,7 +61,7 @@ The device {0} is not currently attached - There is already a disk at position {0} on the selected VM + There is already a device at position {0} on the selected VM A timeout happened while attempting to attach a device {1} of type {0} to a VM From 976b59214611d60116c096e2452ebf04c19c733c Mon Sep 17 00:00:00 2001 From: Konstantina Chremmou Date: Mon, 13 Jan 2025 14:37:26 +0000 Subject: [PATCH 02/53] Removed errors that are not issued by the API. Signed-off-by: Konstantina Chremmou --- ocaml/sdk-gen/csharp/FriendlyErrorNames.resx | 24 -------------------- ocaml/sdk-gen/csharp/autogen/src/Failure.cs | 17 +++----------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx b/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx index 7cfbf626a23..69b5ae29fb9 100644 --- a/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx +++ b/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx @@ -114,18 +114,12 @@ Server cannot attach network (in the case of NIC bonding, this may be because attaching the network on this server would require other networks [that are currently active] to be taken down). - - Cannot attach network - This server cannot destroy itself. The VM could not start because the CD drive is empty. - - Disabled - This server cannot be forgotten because there are some user VMs still running @@ -147,15 +141,9 @@ Not enough server memory is available to perform this operation - - Not enough free memory - The restore could not be performed because the server is not in recovery mode - - Unreachable - Server could not be contacted @@ -796,9 +784,6 @@ Action: {0} The VM's Virtual Hardware Platform version is incompatible with this host. - - HVM not supported - The host does not have some of the CPU features that the VM is currently using @@ -814,21 +799,12 @@ Action: {0} You attempted to run a VM on a host which does not have a GPU available in the GPU group needed by the VM. - - GPU not available - This VM needs a network that cannot be seen from that server - - Cannot see required network - This VM needs storage that cannot be seen from that server - - Cannot see required storage - The VM cannot start because the virtual GPU required by it cannot be allocated on any GPU in the GPU group needed by the VM. diff --git a/ocaml/sdk-gen/csharp/autogen/src/Failure.cs b/ocaml/sdk-gen/csharp/autogen/src/Failure.cs index 923c5488d4e..62cd536afd0 100644 --- a/ocaml/sdk-gen/csharp/autogen/src/Failure.cs +++ b/ocaml/sdk-gen/csharp/autogen/src/Failure.cs @@ -49,16 +49,16 @@ public partial class Failure : Exception private readonly List errorDescription = new List(); private string errorText; - private string shortError; public List ErrorDescription { get { return errorDescription; } } + [Obsolete("Use property Message instead.")] public string ShortMessage { - get { return shortError; } + get { return errorText; } } public override string Message @@ -93,7 +93,6 @@ protected Failure(SerializationInfo info, StreamingContext context) { errorDescription = (List)info.GetValue("errorDescription", typeof(List)); errorText = info.GetString("errorText"); - shortError = info.GetString("shortError"); } #endregion @@ -141,15 +140,6 @@ where trimmed.Length > 0 //call these before setting the shortError because they modify the errorText ParseSmapiV3Failures(); - - try - { - shortError = errorDescriptions.GetString(ErrorDescription[0] + "_SHORT") ?? errorText; - } - catch (Exception) - { - shortError = errorText; - } } /// @@ -192,9 +182,8 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont info.AddValue("errorDescription", errorDescription, typeof(List)); info.AddValue("errorText", errorText); - info.AddValue("shortError", shortError); base.GetObjectData(info, context); } } -} \ No newline at end of file +} From a32076477cef5b07b26ea4d08dbc594fe96f5ac6 Mon Sep 17 00:00:00 2001 From: Frediano Ziglio Date: Sat, 27 Jul 2024 11:26:55 +0000 Subject: [PATCH 03/53] fe_test: add test for syslog feature The syslog feature allows to run a program and redirect its output/error stream to system log. It's triggered passing "syslog_stdout" argument to "Forkhelpers" functions like "execute_command_get_output". To test this feature use a small preload library to redirect syslog writing. This allows to test program without changing it. The log is redirected to /tmp/xyz instead of /dev/log. The name was chosen to allow for future static build redirection. The C program is used only for the test, so the code style take into account this (specifically it does not try to handle all redirect situation and all error paths). Signed-off-by: Frediano Ziglio --- ocaml/forkexecd/test/dune | 11 ++- ocaml/forkexecd/test/fe_test.ml | 75 ++++++++++++++++++++ ocaml/forkexecd/test/fe_test.sh | 4 +- ocaml/forkexecd/test/syslog.c | 121 ++++++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 ocaml/forkexecd/test/syslog.c diff --git a/ocaml/forkexecd/test/dune b/ocaml/forkexecd/test/dune index 91c90e64188..689c972ca5a 100644 --- a/ocaml/forkexecd/test/dune +++ b/ocaml/forkexecd/test/dune @@ -1,11 +1,18 @@ (executable (modes exe) (name fe_test) - (libraries fmt forkexec mtime clock mtime.clock.os uuid xapi-stdext-unix fd-send-recv)) + (libraries fmt forkexec mtime clock mtime.clock.os uuid xapi-stdext-unix fd-send-recv xapi-log)) + +; preload library to redirect "/dev/log" +(rule + (targets syslog.so) + (deps syslog.c) + (action + (run %{cc} -O2 -Wall -DPIC -fPIC -s --shared -o %{targets} %{deps} -ldl))) (rule (alias runtest) (package xapi-forkexecd) - (deps fe_test.sh fe_test.exe ../src/fe_main.exe) + (deps fe_test.sh fe_test.exe ../src/fe_main.exe syslog.so) (action (run ./fe_test.sh))) diff --git a/ocaml/forkexecd/test/fe_test.ml b/ocaml/forkexecd/test/fe_test.ml index 57455ed5dc4..a209bece087 100644 --- a/ocaml/forkexecd/test/fe_test.ml +++ b/ocaml/forkexecd/test/fe_test.ml @@ -238,6 +238,77 @@ let test_internal_failure_error () = Printexc.print_backtrace stderr ; fail "Failed with unexpected exception: %s" (Printexc.to_string e) +(* Emulate syslog and output lines to returned channel *) +let syslog_lines sockname = + let clean () = try Unix.unlink sockname with _ -> () in + clean () ; + let sock = Unix.socket ~cloexec:true Unix.PF_UNIX Unix.SOCK_DGRAM 0 in + let rd_pipe, wr_pipe = Unix.pipe ~cloexec:true () in + Unix.bind sock (Unix.ADDR_UNIX sockname) ; + match Unix.fork () with + | 0 -> + (* child, read from socket and output to pipe *) + let term_handler = Sys.Signal_handle (fun _ -> clean () ; exit 0) in + Sys.set_signal Sys.sigint term_handler ; + Sys.set_signal Sys.sigterm term_handler ; + Unix.close rd_pipe ; + Unix.dup2 wr_pipe Unix.stdout ; + Unix.close wr_pipe ; + let buf = Bytes.create 1024 in + let rec fwd () = + let l = Unix.recv sock buf 0 (Bytes.length buf) [] in + if l > 0 then ( + print_bytes (Bytes.sub buf 0 l) ; + print_newline () ; + (fwd [@tailcall]) () + ) + in + fwd () ; exit 0 + | pid -> + Unix.close sock ; + Unix.close wr_pipe ; + (pid, Unix.in_channel_of_descr rd_pipe) + +let test_syslog with_stderr = + let rec syslog_line ic = + let line = input_line ic in + (* ignore log lines from daemon *) + if String.ends_with ~suffix:"\\x0A" line then + syslog_line ic + else + let re = Str.regexp ": " in + match Str.bounded_split re line 3 with + | _ :: _ :: final :: _ -> + final ^ "\n" + | _ -> + raise Not_found + in + let expected_out = "output string" in + let expected_err = "error string" in + let args = ["echo"; expected_out; expected_err] in + let child, ic = syslog_lines "/tmp/xyz" in + let out, err = + Forkhelpers.execute_command_get_output ~syslog_stdout:Syslog_DefaultKey + ~redirect_stderr_to_stdout:with_stderr exe args + in + expect "" (out ^ "\n") ; + if with_stderr then + expect "" (err ^ "\n") + else + expect expected_err err ; + Unix.sleepf 0.05 ; + Syslog.log Syslog.Daemon Syslog.Err "exe: XXX\n" ; + Syslog.log Syslog.Daemon Syslog.Err "exe: YYY\n" ; + let out = syslog_line ic in + expect expected_out out ; + let err = syslog_line ic in + let expected = if with_stderr then expected_err else "XXX" in + expect expected err ; + Unix.kill child Sys.sigint ; + Unix.waitpid [] child |> ignore ; + close_in ic ; + print_endline "Completed syslog test" + let master fds = Printf.printf "\nPerforming timeout tests\n%!" ; test_delay () ; @@ -249,6 +320,10 @@ let master fds = test_input () ; Printf.printf "\nPerforming internal failure test\n%!" ; test_internal_failure_error () ; + Printf.printf "\nPerforming syslog tests\n%!" ; + test_syslog true ; + test_syslog false ; + let combinations = shuffle (all_combinations fds) in Printf.printf "Starting %d tests\n%!" (List.length combinations) ; let i = ref 0 in diff --git a/ocaml/forkexecd/test/fe_test.sh b/ocaml/forkexecd/test/fe_test.sh index aa0b9899ee7..fe454e89802 100755 --- a/ocaml/forkexecd/test/fe_test.sh +++ b/ocaml/forkexecd/test/fe_test.sh @@ -8,7 +8,7 @@ export FE_TEST=1 SOCKET=${XDG_RUNTIME_DIR}/xapi/forker/main rm -f "$SOCKET" -../src/fe_main.exe & +LD_PRELOAD="$PWD/syslog.so" ../src/fe_main.exe & MAIN=$! cleanup () { kill $MAIN @@ -17,4 +17,4 @@ trap cleanup EXIT INT for _ in $(seq 1 10); do test -S ${SOCKET} || sleep 1 done -echo "" | ./fe_test.exe 16 +echo "" | LD_PRELOAD="$PWD/syslog.so" ./fe_test.exe 16 diff --git a/ocaml/forkexecd/test/syslog.c b/ocaml/forkexecd/test/syslog.c new file mode 100644 index 00000000000..2316e84a25e --- /dev/null +++ b/ocaml/forkexecd/test/syslog.c @@ -0,0 +1,121 @@ +#define _GNU_SOURCE +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define START(name) \ + static typeof(name) *old_func = NULL; \ + if (!old_func) \ + old_func = (typeof(name) *) dlsym(RTLD_NEXT, #name); + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + static const char dev_log[] = "/dev/log"; + START(connect); + + struct sockaddr_un *un = (struct sockaddr_un *) addr; + if (!addr || addr->sa_family != AF_UNIX + || memcmp(un->sun_path, dev_log, sizeof(dev_log)) != 0) + return old_func(sockfd, addr, addrlen); + + struct sockaddr_un new_addr; + new_addr.sun_family = AF_UNIX; + strcpy(new_addr.sun_path, "/tmp/xyz"); + return old_func(sockfd, (struct sockaddr *) &new_addr, sizeof(new_addr)); +} + +static const char *month_name(int month) +{ + static const char names[12][4] = { + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }; + if (month >= 0 && month < 12) + return names[month]; + + return "Xxx"; +} + +static void vsyslog_internal(int priority, const char *format, va_list ap) +{ + // format is "<13>Jul 9 07:19:01 hostname: message" + time_t now = time(NULL); + struct tm tm, *p; + p = gmtime_r(&now, &tm); + + if (LOG_FAC(priority) == 0) + priority |= LOG_USER; + + char buffer[1024]; + char *buf = buffer; + const int prefix_len = sprintf(buffer, "<%d> %s % 2d %02d:%02d:%02d %s: ", priority, month_name(p->tm_mon), + p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, "dummy"); + + int left = (int) sizeof(buffer) - prefix_len; + int l = vsnprintf(buffer + prefix_len, left, format, ap); + if (l >= left) { + buf = malloc(prefix_len + l + 1); + if (!buf) + return; + memcpy(buf, buffer, prefix_len); + l = vsnprintf(buf + prefix_len, l + 1, format, ap); + } + + int sock = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sock >= 0) { + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, "/tmp/xyz"); + sendto(sock, buf, prefix_len + l, MSG_NOSIGNAL, &addr, sizeof(addr)); + + close(sock); + } + if (buf != buffer) + free(buf); +} + +void syslog(int priority, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + vsyslog_internal(priority, format, ap); + va_end(ap); +} + +void vsyslog(int priority, const char *format, va_list ap) +{ + vsyslog_internal(priority, format, ap); +} + +void __syslog_chk(int priority, int flags, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + vsyslog_internal(priority, format, ap); + va_end(ap); +} + +void __vsyslog_chk(int priority, int flags, const char *format, va_list ap) +{ + vsyslog_internal(priority, format, ap); +} From f801eef46f764823ac4ac286c7d01548d49d602a Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 04/53] MVD CP-52334 multi-version driver API/CLI Provide an API/CLI for multi-version drivers. Two new classes represent them: Host_driver and Driver_variant. See * doc/content/toolstack/features/MVD/index.md for technical background and feature overview. The current implementation mocks the missing drivertool that manipulates the file system and observes driver state. We expect to integrate this at a later point. Consequently, the current implementation is incomplete and exists to faciliate development of API and XE clients. On Xapi start and restart driver information is purged from the database and re-built from scrach. This could be improved in the future to be incremental. Signed-off-by: Christian Lindig --- doc/content/toolstack/features/MVD/index.md | 347 +++++++++++ ocaml/idl/datamodel.ml | 5 + ocaml/idl/datamodel_common.ml | 4 + ocaml/idl/datamodel_driver_variant.ml | 56 ++ ocaml/idl/datamodel_host.ml | 7 + ocaml/idl/datamodel_host_driver.ml | 86 +++ ocaml/idl/datamodel_lifecycle.ml | 48 ++ ocaml/idl/dune | 5 +- ocaml/idl/schematest.ml | 2 +- ocaml/libs/uuid/uuidx.ml | 2 + ocaml/libs/uuid/uuidx.mli | 2 + ocaml/tests/dune | 51 +- ocaml/tests/test_data/buildid.s | 9 + ocaml/tests/test_data/gen_notes.sh | 22 + ocaml/tests/test_data/linux.s | 9 + ocaml/tests/test_data/xenserver.s | 9 + ocaml/tests/test_data/xenserver_two_notes.s | 20 + ocaml/tests/test_host_driver_helpers.ml | 89 +++ ocaml/tests/test_host_driver_helpers.mli | 0 ocaml/util/dune | 6 + ocaml/util/xapi_host_driver_helpers.ml | 137 +++++ ocaml/util/xapi_host_driver_helpers.mli | 28 + ocaml/xapi-cli-server/cli_frontend.ml | 48 ++ ocaml/xapi-cli-server/cli_operations.ml | 79 +++ ocaml/xapi-cli-server/records.ml | 211 +++++++ ocaml/xapi/api_server_common.ml | 2 + ocaml/xapi/db_gc_util.ml | 40 +- ocaml/xapi/dbsync_slave.ml | 4 + ocaml/xapi/dune | 1 + ocaml/xapi/message_forwarding.ml | 46 ++ ocaml/xapi/xapi_globs.ml | 18 + ocaml/xapi/xapi_host.ml | 2 + ocaml/xapi/xapi_host.mli | 2 + ocaml/xapi/xapi_host_driver.ml | 160 +++++ ocaml/xapi/xapi_host_driver.mli | 61 ++ ocaml/xapi/xapi_host_driver_tool.ml | 648 ++++++++++++++++++++ ocaml/xapi/xapi_host_driver_tool.mli | 43 ++ ocaml/xe-cli/bash-completion | 11 + quality-gate.sh | 18 +- scripts/bugtool-plugin/xapi/stuff.xml | 2 + 40 files changed, 2302 insertions(+), 38 deletions(-) create mode 100644 doc/content/toolstack/features/MVD/index.md create mode 100644 ocaml/idl/datamodel_driver_variant.ml create mode 100644 ocaml/idl/datamodel_host_driver.ml create mode 100644 ocaml/tests/test_data/buildid.s create mode 100755 ocaml/tests/test_data/gen_notes.sh create mode 100644 ocaml/tests/test_data/linux.s create mode 100644 ocaml/tests/test_data/xenserver.s create mode 100644 ocaml/tests/test_data/xenserver_two_notes.s create mode 100644 ocaml/tests/test_host_driver_helpers.ml create mode 100644 ocaml/tests/test_host_driver_helpers.mli create mode 100644 ocaml/util/xapi_host_driver_helpers.ml create mode 100644 ocaml/util/xapi_host_driver_helpers.mli create mode 100644 ocaml/xapi/xapi_host_driver.ml create mode 100644 ocaml/xapi/xapi_host_driver.mli create mode 100644 ocaml/xapi/xapi_host_driver_tool.ml create mode 100644 ocaml/xapi/xapi_host_driver_tool.mli diff --git a/doc/content/toolstack/features/MVD/index.md b/doc/content/toolstack/features/MVD/index.md new file mode 100644 index 00000000000..c8a696c191b --- /dev/null +++ b/doc/content/toolstack/features/MVD/index.md @@ -0,0 +1,347 @@ +'++ +title = "Multi-version drivers" ++++ + +Linux loads device drivers on boot and every device driver exists in one +version. XAPI extends this scheme such that device drivers may exist in +multiple variants plus a mechanism to select the variant being loaded on +boot. Such a driver is called a multi-version driver and we expect only +a small subset of drivers, built and distributed by XenServer, to have +this property. The following covers the background, API, and CLI for +multi-version drivers in XAPI. + +## Variant vs. Version + +A driver comes in several variants, each of which has a version. A +variant may be updated to a later version while retaining its identity. +This makes variants and versions somewhat synonymous and is admittedly +confusing. + +## Device Drivers in Linux and XAPI + +Drivers that are not compiled into the kernel are loaded dynamically +from the file system. They are loaded from the hierarchy + +* `/lib/modules//` + +and we are particularly interested in the hierarchy + +* `/lib/modules//updates/` + +where vendor-supplied ("driver disk") drivers are located and where we +want to support multiple versions. A driver has typically file extension +`.ko` (kernel object). + +A presence in the file system does not mean that a driver is loaded as +this happens only on demand. The actually loaded drivers +(or modules, in Linux parlance) can be observed from + +* `/proc/modules` + +``` +netlink_diag 16384 0 - Live 0x0000000000000000 +udp_diag 16384 0 - Live 0x0000000000000000 +tcp_diag 16384 0 - Live 0x0000000000000000 +``` + +which includes dependencies between modules (the `-` means no dependencies). + +## Driver Properties + +* A driver name is unique and a driver can be loaded only once. The fact + that kernel object files are located in a file system hierarchy means + that a driver may exist multiple times and in different version in the + file system. From the kernel's perspective a driver has a unique name + and is loaded at most once. We thus can talk about a driver using its + name and acknowledge it may exist in different versions in the file + system. + +* A driver that is loaded by the kernel we call *active*. + +* A driver file (`name.ko`) that is in a hierarchy searched by the + kernel is called *selected*. If the kernel needs the driver of that + name, it would load this object file. + +For a driver (`name.ko`) selection and activation are independent +properties: + +* *inactive*, *deselected*: not loaded now and won't be loaded on next + boot. +* *active*, *deselected*: currently loaded but won't be loaded on next + boot. +* *inactive*, *selected*: not loaded now but will be loaded on demand. +* *active*, *selected*: currently loaded and will be loaded on demand + after a reboot. + +For a driver to be selected it needs to be in the hierarchy searched by +the kernel. By removing a driver from the hierarchy it can be +de-selected. This is possible even for drivers that are already loaded. +Hence, activation and selection are independent. + +## Multi-Version Drivers + +To support multi-version drivers, XenServer introduces a new +hierarchy in Dom0. This is mostly technical background because a +lower-level tool deals with this and not XAPI directly. + +* `/lib/modules//updates/` is searched by the kernel for + drivers. +* The hierarchy is expected to contain symbolic links to the file + actually containing the driver: + `/lib/modules//xenserver///.ko` + +The `xenserver` hierarchy provides drivers in several versions. To +select a particular version, we expect a symbolic link from +`updates/.ko` to `//.ko`. At the next boot, +the kernel will search the `updates/` entries and load the linked +driver, which will become active. + +Example filesystem hierarchy: +``` +/lib/ +└── modules + └── 4.19.0+1 -> + ├── updates + │ ├── aacraid.ko + │ ├── bnx2fc.ko -> ../xenserver/bnx2fc/2.12.13/bnx2fc.ko + │ ├── bnx2i.ko + │ ├── cxgb4i.ko + │ ├── cxgb4.ko + │ ├── dell_laptop.ko -> ../xenserver/dell_laptop/1.2.3/dell_laptop.ko + │ ├── e1000e.ko + │ ├── i40e.ko + │ ├── ice.ko -> ../xenserver/intel-ice/1.11.17.1/ice.ko + │ ├── igb.ko + │ ├── smartpqi.ko + │ └── tcm_qla2xxx.ko + └── xenserver + ├── bnx2fc + │ ├── 2.12.13 + │ │ └── bnx2fc.ko + │ └── 2.12.20-dell + │ └── bnx2fc.ko + ├── dell_laptop + │ └── 1.2.3 + │ └── dell_laptop.ko + └── intel-ice + ├── 1.11.17.1 + │ └── ice.ko + └── 1.6.4 + └── ice.ko + +``` + +Selection of a driver is synonymous with creating a symbolic link to the +desired version. + +## Versions + +The version of a driver is encoded in the path to its object file but +not in the name itself: for `xenserver/intel-ice/1.11.17.1/ice.ko` the +driver name is `ice` and only its location hints at the version. + +The kernel does not reveal the location from where it loaded an active +driver. Hence the name is not sufficient to observe the currently active +version. For this, we use [ELF notes]. + +The driver file (`name.ko`) is in ELF linker format and may contain +custom [ELF notes]. These are binary annotations that can be compiled +into the file. The kernel reveals these details for loaded drivers +(i.e., modules) in: + +* `/sys/module//notes/` + +The directory contains files like + +* `/sys/module/xfs/notes/.note.gnu.build-id` + +with a specific name (`.note.xenserver`) for our purpose. Such a file contains +in binary encoding a sequence of records, each containing: + +* A null-terminated name (string) +* A type (integer) +* A desc (see below) + +The format of the description is vendor specific and is used for +a null-terminated string holding the version. The name is fixed to +"XenServer". The exact format is described in [ELF notes]. + +A note with the name "XenServer" and a particular type then has the version +as a null-terminated string the `desc` field. Additional "XenServer" notes +of a different type may be present. + +[ELF notes]: https://www.netbsd.org/docs/kernel/elf-notes.html + +## API + +XAPI has capabilities to inspect and select multi-version drivers. + +The API uses the terminology introduced above: + +* A driver is specific to a host. +* A driver has a unique name; however, for API purposes a driver is + identified by a UUID (on the CLI) and reference (programmatically). +* A driver has multiple variants; each variant has a version. + Programatically, variants are represented as objects (referenced by + UUID and a reference) but this is mostly hidden in the CLI for + convenience. +* A driver variant is active if it is currently used by the kernel + (loaded). +* A driver variant is selected if it will be considered by the kernel + (on next boot or when loading on demand). +* Only one variant can be active, and only one variants can be selected. + +Inspection and selection of drivers is facilitated by a tool +("drivertool") that is called by xapi. Hence, XAPI does not by itself +manipulate the file system that implements driver selection. + +An example interaction with the API through xe: + +``` +[root@lcy2-dt110 log]# xe hostdriver-list uuid=c0fe459d-5f8a-3fb1-3fe5-3c602fafecc0 params=all +uuid ( RO) : c0fe459d-5f8a-3fb1-3fe5-3c602fafecc0 + name ( RO): cisco-fnic + type ( RO): network + description ( RO): cisco-fnic + info ( RO): cisco-fnic + host-uuid ( RO): 6de288e7-0f82-4563-b071-bcdc083b0ffd + active-variant ( RO): + selected-variant ( RO): + variants ( RO): generic/1.2 + variants-dev-status ( RO): generic=beta + variants-uuid ( RO): generic=abf5997b-f2ad-c0ef-b27f-3f8a37bf58a6 + variants-hw-present ( RO): +``` + +Selection of a variant by name (which is unique per driver); this +variant would become active after reboot. + +``` +[root@lcy2-dt110 log]# xe hostdriver-select variant-name=generic uuid=c0fe459d-5f8a-3fb1-3fe5-3c602fafecc0 +[root@lcy2-dt110 log]# xe hostdriver-list uuid=c0fe459d-5f8a-3fb1-3fe5-3c602fafecc0 params=all +uuid ( RO) : c0fe459d-5f8a-3fb1-3fe5-3c602fafecc0 + name ( RO): cisco-fnic + type ( RO): network + description ( RO): cisco-fnic + info ( RO): cisco-fnic + host-uuid ( RO): 6de288e7-0f82-4563-b071-bcdc083b0ffd + active-variant ( RO): + selected-variant ( RO): generic + variants ( RO): generic/1.2 + variants-dev-status ( RO): generic=beta + variants-uuid ( RO): generic=abf5997b-f2ad-c0ef-b27f-3f8a37bf58a6 + variants-hw-present ( RO): +``` + +The variant can be inspected, too, using it's UUID. + +``` +[root@lcy2-dt110 log]# xe hostdriver-variant-list uuid=abf5997b-f2ad-c0ef-b27f-3f8a37bf58a6 +uuid ( RO) : abf5997b-f2ad-c0ef-b27f-3f8a37bf58a6 + name ( RO): generic + version ( RO): 1.2 + status ( RO): beta + active ( RO): false + selected ( RO): true + driver-uuid ( RO): c0fe459d-5f8a-3fb1-3fe5-3c602fafecc0 + driver-name ( RO): cisco-fnic + host-uuid ( RO): 6de288e7-0f82-4563-b071-bcdc083b0ffd + hw-present ( RO): false +``` + +## Class Host_driver + +Class `Host_driver` represents an instance of a multi-version driver on +a host. It references `Driver_variant` objects for the details of the +available and active variants. A variant has a version. + +### Fields + +All fields are read-only and can't be set directly. Be aware that names +in the CLI and the API may differ. + +* `host`: reference to the host where the driver is installed. +* `name`: string; name of the driver without ".ko" extension. +* `variants`: string set; set of variants available on the host for this + driver. The name of each variant of a driver is unique and used in + the CLI for selecting it. +* `selected_varinat`: variant, possibly empty. Variant that is selected, + i.e. the variant of the driver that will be considered by the kernel + when loading the driver the next time. May be null when none is + selected. +* `active_variant`: variant, possibly empty. Variant that is currently + loaded by the kernel. +* `type`, `info`, `description`: strings providing background + information. + +The CLI uses `hostdriver` and a dash instead of an underscore. The CLI +also offers convenience fields. Whenever selected and +active variant are not the same, a reboot is required to activate the +selected driver/variant combination. + +(We are not using `host-driver` in the CLI to avoid the impression that +this is part of a host object.) + +### Methods + +* All method invocations require `Pool_Operator` rights. "The Pool + Operator role manages host- and pool-wide resources, including setting + up storage, creating resource pools and managing patches, high + availability (HA) and workload balancing (WLB)" + +* `select (self, version)`; select `version` of driver `self`. Selecting + the version (a string) of an existing driver. + +* `deselect(self)`: this driver can't be loaded next time the kernel is + looking for a driver. This is a potentially dangerous operation, so it's + protected in the CLI with a `--force` flag. + +* `rescan (host)`: scan the host and update its driver information. + Called on toolstack restart and may be invoked from the CLI for + development. + +## Class `Driver_variant` + +An object of this class represents a variant of a driver on a host, +i.e., it is specific to both. + +* `name`: unique name +* `driver`: what host driver this belongs to +* `version`: string; a driver variant has a version +* `status`: string: development status, like "beta" +* `hardware_present`: boolean, true if the host has the hardware + installed supported by this driver + +The only method available is `select(self)` to select a variant. It has +the same effect as the `select` method on the `Host_driver` class. + +The CLI comes with corresponding `xe hostdriver-variant-*` commands to +list and select a variant. + +``` +[root@lcy2-dt110 log]# xe hostdriver-variant-list uuid=abf5997b-f2ad-c0ef-b27f-3f8a37bf58a6 +uuid ( RO) : abf5997b-f2ad-c0ef-b27f-3f8a37bf58a6 + name ( RO): generic + version ( RO): 1.2 + status ( RO): beta + active ( RO): false + selected ( RO): true + driver-uuid ( RO): c0fe459d-5f8a-3fb1-3fe5-3c602fafecc0 + driver-name ( RO): cisco-fnic + host-uuid ( RO): 6de288e7-0f82-4563-b071-bcdc083b0ffd + hw-present ( RO): false +``` + +### Database + +Each `Host_driver` and `Driver_variant` object is represented in the +database and data is persisted over reboots. This means this data will +be part of data collected in a `xen-bugtool` invocation. + +### Scan and Rescan + +On XAPI start-up, XAPI updates the `Host_driver` objects belonging to the +host to reflect the actual situation. This can be initiated from the +CLI, too, mostly for development. + + diff --git a/ocaml/idl/datamodel.ml b/ocaml/idl/datamodel.ml index 33eb339dfa1..27068cf4d28 100644 --- a/ocaml/idl/datamodel.ml +++ b/ocaml/idl/datamodel.ml @@ -10441,6 +10441,8 @@ let all_system = ; Datamodel_repository.t ; Datamodel_observer.t ; Datamodel_vm_group.t + ; Datamodel_host_driver.t + ; Datamodel_driver_variant.t ] (* If the relation is one-to-many, the "many" nodes (one edge each) must come before the "one" node (many edges) *) @@ -10533,6 +10535,7 @@ let all_relations = ; ((_network_sriov, "logical_PIF"), (_pif, "sriov_logical_PIF_of")) ; ((_certificate, "host"), (_host, "certificates")) ; ((_vm, "groups"), (_vm_group, "VMs")) + ; ((_driver_variant, "driver"), (_host_driver, "variants")) ] let update_lifecycles = @@ -10688,6 +10691,8 @@ let expose_get_all_messages_for = ; _repository ; _vtpm ; _observer + ; _host_driver + ; _driver_variant ] let no_task_id_for = [_task; (* _alert; *) _event] diff --git a/ocaml/idl/datamodel_common.ml b/ocaml/idl/datamodel_common.ml index 80c5076fef7..0ccbc5a12ac 100644 --- a/ocaml/idl/datamodel_common.ml +++ b/ocaml/idl/datamodel_common.ml @@ -311,6 +311,10 @@ let _repository = "Repository" let _observer = "Observer" +let _host_driver = "Host_driver" + +let _driver_variant = "Driver_variant" + let update_guidances = Enum ( "update_guidances" diff --git a/ocaml/idl/datamodel_driver_variant.ml b/ocaml/idl/datamodel_driver_variant.ml new file mode 100644 index 00000000000..607ac670105 --- /dev/null +++ b/ocaml/idl/datamodel_driver_variant.ml @@ -0,0 +1,56 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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. + *) + +open Datamodel_types +open Datamodel_common +open Datamodel_roles + +(** This is pure data with no methods *) + +let select = + call ~name:"select" ~in_oss_since:None ~lifecycle:[] + ~doc: + "UNSUPPORTED Select this variant of a driver to become active after \ + reboot or immediately if currently no version is active" + ~params: + [ + ( Ref _driver_variant + , "self" + , "Driver variant to become active (after reboot)." + ) + ] + ~allowed_roles:_R_POOL_ADMIN () + +let t = + create_obj ~in_db:true ~in_oss_since:None ~persist:PersistEverything + ~lifecycle:[] ~name:_driver_variant ~gen_constructor_destructor:false + ~descr:"UNSUPPORTED. Variant of a host driver" ~gen_events:true + ~doccomments:[] ~messages_default_allowed_roles:_R_POOL_ADMIN + ~contents: + [ + uid _driver_variant ~lifecycle:[] + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "name" + "Name identifying the driver variant within the driver" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:(Ref _host_driver) "driver" + "Driver this variant is a part of" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "version" + "Unique versions of this driver variant" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:Bool "hardware_present" + "True if the hardware for this variant is present on the host" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:Float "priority" + "Priority; this needs an explanation how this is ordered" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "status" + "Development and release status of this variant, like 'alpha'" + ] + ~messages:[select] () diff --git a/ocaml/idl/datamodel_host.ml b/ocaml/idl/datamodel_host.ml index 78b68a35722..0505807dd67 100644 --- a/ocaml/idl/datamodel_host.ml +++ b/ocaml/idl/datamodel_host.ml @@ -2287,6 +2287,12 @@ let apply_updates = ~allowed_roles:(_R_POOL_OP ++ _R_CLIENT_CERT) () +let rescan_drivers = + call ~name:"rescan_drivers" ~in_oss_since:None ~lifecycle:[] + ~doc:"Scan the host and update its driver information." + ~params:[(Ref _host, "host", "The host to be rescanned")] + ~allowed_roles:_R_POOL_ADMIN () + let copy_primary_host_certs = call ~name:"copy_primary_host_certs" ~in_oss_since:None ~lifecycle:[(Published, "1.307.0", "")] @@ -2490,6 +2496,7 @@ let t = ; emergency_reenable_tls_verification ; cert_distrib_atom ; apply_updates + ; rescan_drivers ; copy_primary_host_certs ; set_https_only ; apply_recommended_guidances diff --git a/ocaml/idl/datamodel_host_driver.ml b/ocaml/idl/datamodel_host_driver.ml new file mode 100644 index 00000000000..e61ca372337 --- /dev/null +++ b/ocaml/idl/datamodel_host_driver.ml @@ -0,0 +1,86 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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. + *) + +open Datamodel_types +open Datamodel_common +open Datamodel_roles + +(** A Host_driver instance represents a driver on a host that is + installed in multiple versions. At most one version is active; a + different version can be selected to become active after reboot. If + no version is active, selecting a version makes it the active + version immediately. *) + +let select = + call ~name:"select" ~in_oss_since:None ~lifecycle:[] + ~doc: + "UNSUPPORTED. Select a variant of the driver to become active after \ + reboot or immediately if currently no version is active" + ~params: + [ + (Ref _host_driver, "self", "Driver to become active (after reboot).") + ; ( Ref _driver_variant + , "variant" + , "Driver version to become active (after reboot)." + ) + ] + ~allowed_roles:_R_POOL_ADMIN () + +let deselect = + call ~name:"deselect" ~in_oss_since:None ~lifecycle:[] + ~doc: + "UNSUPPORTED. Deselect the currently active variant of this driver after \ + reboot. No action will be taken if no variant is currently active." + ~params: + [(Ref _host_driver, "self", "Driver to become inactive (after reboot).")] + ~allowed_roles:_R_POOL_ADMIN () + +let rescan = + call ~name:"rescan" ~in_oss_since:None ~lifecycle:[] + ~doc: + "UNSUPPORTED. Re-scan a host's drivers and update information about \ + them. This is mostly for trouble shooting." + ~params:[(Ref _host, "host", "Update driver information of this host.")] + ~allowed_roles:_R_POOL_ADMIN () + +let t = + create_obj ~in_db:true ~in_oss_since:None ~persist:PersistEverything + ~lifecycle:[] ~name:_host_driver ~gen_constructor_destructor:false + ~descr:"UNSUPPORTED. A multi-version driver on a host" ~gen_events:true + ~doccomments:[] ~messages_default_allowed_roles:_R_POOL_ADMIN + ~contents: + [ + uid _host_driver ~lifecycle:[] + ; field ~lifecycle:[] ~qualifier:StaticRO ~ty:(Ref _host) "host" + "Host where this driver is installed" ~default_value:(Some (VRef "")) + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "name" + "Name identifying the driver uniquely" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "friendly_name" + "Descriptive name, not used for identification" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:(Set (Ref _driver_variant)) + "variants" "Variants of this driver available for selection" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:(Ref _driver_variant) + "active_variant" + "Currently active variant of this driver, if any, or Null." + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:(Ref _driver_variant) + "selected_variant" + "Variant (if any) selected to become active after reboot. Or Null" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "type" + "Devive type this driver supports, like network or storage" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "description" + "Description of the driver" + ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "info" + "Information about the driver" + ] + ~messages:[select; deselect; rescan] () diff --git a/ocaml/idl/datamodel_lifecycle.ml b/ocaml/idl/datamodel_lifecycle.ml index 9e3007f4744..cc252d2cf55 100644 --- a/ocaml/idl/datamodel_lifecycle.ml +++ b/ocaml/idl/datamodel_lifecycle.ml @@ -1,4 +1,8 @@ let prototyped_of_class = function + | "Driver_variant" -> + Some "24.36.0" + | "Host_driver" -> + Some "24.35.0" | "VM_group" -> Some "24.19.1" | "Observer" -> @@ -9,6 +13,40 @@ let prototyped_of_class = function None let prototyped_of_field = function + | "Driver_variant", "status" -> + Some "24.36.0" + | "Driver_variant", "priority" -> + Some "24.36.0" + | "Driver_variant", "hardware_present" -> + Some "24.36.0" + | "Driver_variant", "version" -> + Some "24.36.0" + | "Driver_variant", "driver" -> + Some "24.36.0" + | "Driver_variant", "name" -> + Some "24.36.0" + | "Driver_variant", "uuid" -> + Some "24.36.0" + | "Host_driver", "info" -> + Some "24.39.0" + | "Host_driver", "description" -> + Some "24.39.0" + | "Host_driver", "type" -> + Some "24.39.0" + | "Host_driver", "selected_variant" -> + Some "24.36.0" + | "Host_driver", "active_variant" -> + Some "24.36.0" + | "Host_driver", "variants" -> + Some "24.36.0" + | "Host_driver", "friendly_name" -> + Some "24.39.0" + | "Host_driver", "name" -> + Some "24.35.0" + | "Host_driver", "host" -> + Some "24.35.0" + | "Host_driver", "uuid" -> + Some "24.35.0" | "VM_group", "VMs" -> Some "24.19.1" | "VM_group", "placement" -> @@ -117,6 +155,14 @@ let prototyped_of_field = function None let prototyped_of_message = function + | "Driver_variant", "select" -> + Some "24.39.0" + | "Host_driver", "rescan" -> + Some "24.39.0-next" + | "Host_driver", "deselect" -> + Some "24.35.0" + | "Host_driver", "select" -> + Some "24.35.0" | "Observer", "set_components" -> Some "23.14.0" | "Observer", "set_endpoints" -> @@ -159,6 +205,8 @@ let prototyped_of_message = function Some "23.18.0" | "host", "set_https_only" -> Some "22.27.0" + | "host", "rescan_drivers" -> + Some "24.35.0" | "host", "set_numa_affinity_policy" -> Some "24.0.0" | "VM", "get_secureboot_readiness" -> diff --git a/ocaml/idl/dune b/ocaml/idl/dune index 84ad1c35a93..9530ec3f6e9 100644 --- a/ocaml/idl/dune +++ b/ocaml/idl/dune @@ -6,7 +6,8 @@ datamodel_pool datamodel_cluster datamodel_cluster_host dm_api escaping datamodel_values datamodel_schema datamodel_certificate datamodel_diagnostics datamodel_repository datamodel_lifecycle - datamodel_vtpm datamodel_observer datamodel_vm_group api_version) + datamodel_vtpm datamodel_observer datamodel_vm_group api_version + datamodel_host_driver datamodel_driver_variant) (libraries rpclib.core sexplib0 @@ -80,7 +81,7 @@ (public_name gen_lifecycle) (package xapi-datamodel) (modules gen_lifecycle) - (libraries + (libraries xapi-datamodel xapi-consts.xapi_version ) diff --git a/ocaml/idl/schematest.ml b/ocaml/idl/schematest.ml index 2c4a87453ba..a1d89e5cbfd 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 = "18df8c33434e3df1982e11ec55d1f3f8" +let last_known_schema_hash = "863c257bad0d20800297cf968c294e4e" let current_schema_hash : string = let open Datamodel_types in diff --git a/ocaml/libs/uuid/uuidx.ml b/ocaml/libs/uuid/uuidx.ml index 7bcb74aae04..b22c22ebd14 100644 --- a/ocaml/libs/uuid/uuidx.ml +++ b/ocaml/libs/uuid/uuidx.ml @@ -30,6 +30,8 @@ type without_secret = | `Generic | `GPU_group | `host + | `Host_driver + | `Driver_variant | `host_cpu | `host_crashdump | `host_metrics diff --git a/ocaml/libs/uuid/uuidx.mli b/ocaml/libs/uuid/uuidx.mli index 8561a975cc1..bd0865cf628 100644 --- a/ocaml/libs/uuid/uuidx.mli +++ b/ocaml/libs/uuid/uuidx.mli @@ -41,6 +41,8 @@ type without_secret = | `Generic | `GPU_group | `host + | `Host_driver + | `Driver_variant | `host_cpu | `host_crashdump | `host_metrics diff --git a/ocaml/tests/dune b/ocaml/tests/dune index b51bbca8b80..fbdc42b66fc 100644 --- a/ocaml/tests/dune +++ b/ocaml/tests/dune @@ -7,7 +7,7 @@ test_cluster_host test_cluster test_pusb test_network_sriov test_client test_valid_ref_list suite_alcotest_server test_vm_placement test_vm_helpers test_repository test_repository_helpers - test_ref test_xapi_helpers test_vm_group + test_ref test_xapi_helpers test_vm_group test_host_driver_helpers test_livepatch test_rpm test_updateinfo test_storage_smapiv1_wrapper test_storage_quicktest test_observer test_pool_periodic_update_sync test_pkg_mgr test_tar_ext test_pool_repository)) (libraries @@ -15,7 +15,7 @@ angstrom astring cstruct - + fmt http_lib httpsvr @@ -32,25 +32,26 @@ threads.posix uuid xapi-backtrace - xapi_cli_server xapi-consts - xapi_database xapi-datamodel xapi-idl xapi-idl.storage.interface xapi-idl.xen.interface xapi-idl.xen.interface.types - xapi_internal xapi-log xapi-stdext-date + xapi-stdext-pervasives xapi-stdext-std xapi-stdext-threads xapi-stdext-unix xapi-test-utils xapi-tracing xapi-types - xapi-stdext-pervasives xapi_xenopsd + xapi_cli_server + xapi_database + xapi_host_driver_helpers + xapi_internal xml-light2 ) (deps @@ -81,14 +82,14 @@ (names test_vm_helpers test_vm_placement test_network_sriov test_vdi_cbt test_bounded_psq test_auth_cache test_clustering test_pusb test_daemon_manager test_repository test_repository_helpers test_livepatch test_rpm test_updateinfo test_pool_periodic_update_sync test_pkg_mgr - test_xapi_helpers test_tar_ext test_pool_repository) + test_xapi_helpers test_tar_ext test_pool_repository test_host_driver_helpers) (package xapi) (modes exe) (modules test_vm_helpers test_vm_placement test_network_sriov test_vdi_cbt test_bounded_psq test_auth_cache test_event test_clustering test_cluster_host test_cluster test_pusb test_daemon_manager test_repository test_repository_helpers test_livepatch test_rpm test_updateinfo test_pool_periodic_update_sync test_pkg_mgr - test_xapi_helpers test_tar_ext test_pool_repository) + test_xapi_helpers test_tar_ext test_pool_repository test_host_driver_helpers) (libraries alcotest bos @@ -104,21 +105,22 @@ threads.posix uuid xapi-client - xapi_cli_server xapi-consts - xapi_database xapi-idl xapi-idl.cluster xapi-idl.storage xapi-idl.storage.interface xapi-idl.xen - xapi_internal - xapi-test-utils - xapi-tracing - xapi-types xapi-stdext-date xapi-stdext-threads xapi-stdext-unix + xapi-test-utils + xapi-tracing + xapi-types + xapi_cli_server + xapi_database + xapi_host_driver_helpers + xapi_internal xml-light2 yojson ) @@ -167,6 +169,27 @@ (action (run ./check-no-xenctrl %{x})) ) +(rule + (alias runtest) + (package xapi) + (targets + .note.XenServer + .note.Linux + .note.gnu.build-id + .note.XenServerTwo + ) + (deps + (:asm + test_data/xenserver.s + test_data/xenserver_two_notes.s + test_data/linux.s + test_data/buildid.s + ) + (:script test_data/gen_notes.sh) + ) + (action (bash "%{script} %{asm}")) +) + (env (_ (env-vars (XAPI_TEST 1)))) ; disassemble, but without sources diff --git a/ocaml/tests/test_data/buildid.s b/ocaml/tests/test_data/buildid.s new file mode 100644 index 00000000000..75f77766980 --- /dev/null +++ b/ocaml/tests/test_data/buildid.s @@ -0,0 +1,9 @@ +.section ".note.gnu.build-id", "a" + .p2align 2 + .long 1f - 0f # name size (not including padding) + .long 3f - 2f # desc size (not including padding) + .long 0x1 # type +0: .asciz "gnu.build-id" # name +1: .p2align 2 +2: .long 0x000000 # desc +3: .p2align 2 diff --git a/ocaml/tests/test_data/gen_notes.sh b/ocaml/tests/test_data/gen_notes.sh new file mode 100755 index 00000000000..9b173bd31da --- /dev/null +++ b/ocaml/tests/test_data/gen_notes.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Copyright (c) Cloud Software Group, Inc. +# +# 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. + +elf_file=test_data/xenserver_elf_file +as "$@" -o $elf_file + +sections=$(readelf -n $elf_file | grep -Po "(?<=Displaying notes found in: ).*") +for dep in $sections; do + objcopy "$elf_file" "$dep" --only-section="$dep" -O binary +done + diff --git a/ocaml/tests/test_data/linux.s b/ocaml/tests/test_data/linux.s new file mode 100644 index 00000000000..ca106e94af7 --- /dev/null +++ b/ocaml/tests/test_data/linux.s @@ -0,0 +1,9 @@ +.section ".note.Linux", "a" + .p2align 2 + .long 1f - 0f # name size (not including padding) + .long 3f - 2f # desc size (not including padding) + .long 0x257 # type +0: .asciz "Linux" # name +1: .p2align 2 +2: .asciz "4.19.0+1" # desc +3: .p2align 2 diff --git a/ocaml/tests/test_data/xenserver.s b/ocaml/tests/test_data/xenserver.s new file mode 100644 index 00000000000..f44575ce5eb --- /dev/null +++ b/ocaml/tests/test_data/xenserver.s @@ -0,0 +1,9 @@ +.section ".note.XenServer", "a" + .p2align 2 + .long 1f - 0f # name size (not including padding) + .long 3f - 2f # desc size (not including padding) + .long 0x1 # type +0: .asciz "XenServer" # name +1: .p2align 2 +2: .asciz "v2.1.3+0.1fix" # desc +3: .p2align 2 diff --git a/ocaml/tests/test_data/xenserver_two_notes.s b/ocaml/tests/test_data/xenserver_two_notes.s new file mode 100644 index 00000000000..cbde4916dd5 --- /dev/null +++ b/ocaml/tests/test_data/xenserver_two_notes.s @@ -0,0 +1,20 @@ +.section ".note.XenServerTwo", "a" + .p2align 2 + .long 1f - 0f # name size (not including padding) + .long 3f - 2f # desc size (not including padding) + .long 0x2 # type +0: .asciz "XenServer" # name +1: .p2align 2 +2: .asciz "Built on December 25th" # desc +3: .p2align 2 + +.section ".note.XenServerTwo", "a" + .p2align 2 + .long 1f - 0f # name size (not including padding) + .long 3f - 2f # desc size (not including padding) + .long 0x1 # type +0: .asciz "XenServer" # name +1: .p2align 2 +2: .asciz "2.0.0-rc.2" # desc +3: .p2align 2 + diff --git a/ocaml/tests/test_host_driver_helpers.ml b/ocaml/tests/test_host_driver_helpers.ml new file mode 100644 index 00000000000..bb1a49050b1 --- /dev/null +++ b/ocaml/tests/test_host_driver_helpers.ml @@ -0,0 +1,89 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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. + *) + +open Xapi_host_driver_helpers + +let note = + Alcotest.testable + (Fmt.of_to_string (fun n -> + Printf.sprintf "{typ=%d; name=%s; desc=%s}" (Int32.to_int n.typ) n.name + n.desc + ) + ) + ( = ) + +let versions = + [ + (".note.XenServer", Some "v2.1.3+0.1fix") + ; (".note.XenServerTwo", Some "2.0.0-rc.2") + ; (".note.Linux", None) + ; (".note.gnu.build-id", None) + ] + +let get_version_test = + List.map + (fun (filename, expected) -> + let test_version () = + let parsed_ver = Result.to_option (get_version filename) in + Printf.printf "%s\n" filename ; + Alcotest.(check (option string)) + "ELF notes should be parsed properly" expected parsed_ver + in + ( Printf.sprintf {|Validation of ELF note parsing: "%s"|} filename + , `Quick + , test_version + ) + ) + versions + +let notes = + [ + (".note.XenServer", [{typ= 1l; name= "XenServer"; desc= "v2.1.3+0.1fix"}]) + ; ( ".note.XenServerTwo" + , [ + {typ= 2l; name= "XenServer"; desc= "Built on December 25th"} + ; {typ= 1l; name= "XenServer"; desc= "2.0.0-rc.2"} + ] + ) + ; (".note.Linux", [{typ= 599l; name= "Linux"; desc= "4.19.0+1"}]) + ; ( ".note.gnu.build-id" + , [{typ= 1l; name= "gnu.build-id"; desc= "\x00\x00\x00"}] + ) + ] + +let note_parsing_test = + List.map + (fun (filename, expected) -> + let test_note () = + let parsed = + match get_notes filename with Ok res -> res | Error e -> failwith e + in + Printf.printf "%s\n" filename ; + Alcotest.(check (list note)) + "ELF notes should be parsed properly" expected parsed + in + ( Printf.sprintf {|Validation of ELF note parsing: "%s"|} filename + , `Quick + , test_note + ) + ) + notes + +let () = + Suite_init.harness_init () ; + Alcotest.run "Test Host Driver Helpers suite" + [ + ("Test_host_driver_helpers.get_note", note_parsing_test) + ; ("Test_host_driver_helpers.get_version", get_version_test) + ] diff --git a/ocaml/tests/test_host_driver_helpers.mli b/ocaml/tests/test_host_driver_helpers.mli new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ocaml/util/dune b/ocaml/util/dune index 7a21f9bb24b..d9380eecbc9 100644 --- a/ocaml/util/dune +++ b/ocaml/util/dune @@ -17,3 +17,9 @@ (wrapped false) ) +(library + (name xapi_host_driver_helpers) + (modules xapi_host_driver_helpers) + (libraries yojson angstrom) + (wrapped false) +) diff --git a/ocaml/util/xapi_host_driver_helpers.ml b/ocaml/util/xapi_host_driver_helpers.ml new file mode 100644 index 00000000000..61ce6b8e537 --- /dev/null +++ b/ocaml/util/xapi_host_driver_helpers.ml @@ -0,0 +1,137 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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 J = Yojson +open Angstrom + +let defer finally = Fun.protect ~finally + +let int n = Int32.to_int n + +let ( // ) = Filename.concat + +(** Read a (small) file into a string *) +let read path = + let ic = open_in path in + defer (fun () -> close_in ic) @@ fun () -> + let size = in_channel_length ic in + really_input_string ic size + +type note = {typ: int32; name: string; desc: string} + +module JSON = struct + let note l = + let l = + List.map + (fun d -> + `Assoc + [ + ("type", `Int (int d.typ)) + ; ("name", `String d.name) + ; ("desc", `String d.desc) + ] + ) + l + in + `List l + + let emit json = J.pretty_to_channel stdout json +end + +(** return the smallest k >= n such that k is divisible by 4 *) +let align4 n = + let ( & ) = Int.logand in + n + (-n & 3) + +(** advance the cursor to position n *) +let advance_to n = + let* pos in + advance (max 0 (n - pos)) + +(** align the cursor to a multiple of 4 *) +let align = + let* pos in + advance_to (align4 pos) + +(** parse an ELF note entry; it assumes that name and desc are null + terminated strings. This should be always true for name but desc + depends on the entry. We don't capture the terminating zero for + strings. *) +let note = + let* name_length = LE.any_int32 in + let* desc_length = LE.any_int32 in + let* typ = LE.any_int32 in + let* name = take (int name_length - 1) in + (* skip over terminating null and re-align cursor *) + let* _ = char '\000' in + let* () = align in + let* desc = take (int desc_length - 1) in + (* skip over terminating null and re-align cursor *) + let* _ = char '\000' in + let* () = align in + return {typ; name; desc} + +(** parser for a sequence of note entries *) +let notes = many note + +(** parse a sequence of note entries from a string *) +let parse str = + let consume = Consume.Prefix in + parse_string ~consume notes str + +let get_version path = + let version = + read path + |> parse + |> Result.map + @@ List.filter_map (fun note -> + match (note.typ, note.name) with + | 1l, "XenServer" -> + Some note.desc + | _ -> + None + ) + in + match version with + | Ok (v :: _) -> + Ok v + | _ -> + Error + (Format.sprintf + "Failed to parse %s, didn't find a XenServer driver version notes \ + section" + path + ) + +let get_notes path = + let version = read path |> parse in + match version with + | Ok (_ :: _) as v -> + v + | _ -> + Error + (Format.sprintf "Failed to parse %s, didn't find a notes section" path) + +let dump_notes prefix = + let notes_dir = prefix // "notes" in + try + let lst = + Sys.readdir notes_dir + |> Array.to_list + |> List.map (fun n -> read (notes_dir // n)) + |> List.filter_map (fun note_str -> Result.to_option (parse note_str)) + |> List.map (fun note -> (prefix, JSON.note note)) + in + JSON.emit (`Assoc lst) + with _ -> () diff --git a/ocaml/util/xapi_host_driver_helpers.mli b/ocaml/util/xapi_host_driver_helpers.mli new file mode 100644 index 00000000000..6528d6bec94 --- /dev/null +++ b/ocaml/util/xapi_host_driver_helpers.mli @@ -0,0 +1,28 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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. + *) + +type note = {typ: int32; name: string; desc: string} + +(* Parse an ELF notes section, returning the specially-encoded driver version. + + The kernel does not reveal the location from where it loaded an active + driver. Hence the name is not sufficient to observe the currently active + version. For this, XS uses ELF notes, with the kernel presenting a particular + note section in `/sys/module//notes/.note.XenServer` *) +val get_version : string -> (string, string) result + +val get_notes : string -> (note list, string) result + +(* Dumps JSON-formatted parsed ELF notes of a driver *) +val dump_notes : string -> unit diff --git a/ocaml/xapi-cli-server/cli_frontend.ml b/ocaml/xapi-cli-server/cli_frontend.ml index 3de231f3cad..05c9890ff14 100644 --- a/ocaml/xapi-cli-server/cli_frontend.ml +++ b/ocaml/xapi-cli-server/cli_frontend.ml @@ -3705,6 +3705,54 @@ let rec cmdtable_data : (string * cmd_spec) list = ; flags= [] } ) + ; ( "hostdriver-select" + , { + reqd= ["uuid"; "variant-name"] + ; optn= [] + ; help= + "Select a variant of the specified driver. If no variant of the \ + driver is loaded, this variant will become active. If some other \ + variant is already active, a reboot will be required to activate \ + the new variant." + ; implementation= No_fd Cli_operations.Host_driver.select + ; flags= [] + } + ) + ; ( "hostdriver-deselect" + , { + reqd= ["uuid"] + ; optn= ["force"] + ; help= + "Deselect the currently active variant of the specified driver after \ + reboot. No action will be taken if no version is currently active. \ + Potentially dangerous operation, needs the '--force' flag \ + specified." + ; implementation= No_fd Cli_operations.Host_driver.deselect + ; flags= [] + } + ) + ; ( "hostdriver-variant-select" + , { + reqd= ["uuid"] + ; optn= [] + ; help= + "Select a variant of a driver. If no variant of the driver is \ + loaded, this variant will become active. If some other variant is \ + already active, a reboot will be required to activate the new \ + variant." + ; implementation= No_fd Cli_operations.Driver_variant.select + ; flags= [] + } + ) + ; ( "hostdriver-rescan" + , { + reqd= [] + ; optn= [] + ; help= "Scan a host and update its driver information." + ; implementation= No_fd Cli_operations.Host_driver.rescan + ; flags= [Host_selectors] + } + ) ; ( "vtpm-create" , { reqd= ["vm-uuid"] diff --git a/ocaml/xapi-cli-server/cli_operations.ml b/ocaml/xapi-cli-server/cli_operations.ml index 1e8ba0f3b37..b725890014d 100644 --- a/ocaml/xapi-cli-server/cli_operations.ml +++ b/ocaml/xapi-cli-server/cli_operations.ml @@ -29,6 +29,8 @@ open Records let failwith str = raise (Cli_util.Cli_failure str) +let failwith' fmt = Printf.ksprintf failwith fmt + exception ExitWithError of int let bool_of_string param string = @@ -1322,6 +1324,37 @@ let gen_cmds rpc session_id = ] rpc session_id ) + ; Client.Driver_variant.( + mk get_all_records_where get_by_uuid driver_variant_record + "hostdriver-variant" [] + [ + "uuid" + ; "driver-name" + ; "name" + ; "version" + ; "priotory" + ; "host-uuid" + ; "driver-uuid" + ; "active" + ; "selected" + ; "status" + ; "hw-present" + ] + rpc session_id + ) + ; Client.Host_driver.( + mk get_all_records_where get_by_uuid host_driver_record "hostdriver" [] + [ + "uuid" + ; "name" + ; "host-uuid" + ; "active-variant" + ; "selected-variant" + ; "variants" + ; "variants-uuid" + ] + rpc session_id + ) ; Client.VTPM.( mk get_all_records_where get_by_uuid vtpm_record "vtpm" [] ["uuid"; "vm-uuid"; "profile"] @@ -7954,6 +7987,52 @@ module Repository = struct ~value:gpgkey_path end +module Driver_variant = struct + let select _ rpc session_id params = + let uuid = List.assoc "uuid" params in + let self = Client.Driver_variant.get_by_uuid ~rpc ~session_id ~uuid in + Client.Driver_variant.select ~rpc ~session_id ~self +end + +module Host_driver = struct + let select _ rpc session_id params = + let driver_uuid = List.assoc "uuid" params in + let name = List.assoc "variant-name" params in + let driver = + Client.Host_driver.get_by_uuid ~rpc ~session_id ~uuid:driver_uuid + in + let by_name (_, variant) = variant.API.driver_variant_name = name in + let variants = + List.map + (fun self -> + (self, Client.Driver_variant.get_record ~rpc ~session_id ~self) + ) + (Client.Host_driver.get_variants ~rpc ~session_id ~self:driver) + in + + match List.find_opt by_name variants with + | None -> + failwith' "%s does not identify a variant of this driver" name + | Some (variant, _) -> + Client.Host_driver.select ~rpc ~session_id ~self:driver ~variant + + let deselect _ rpc session_id params = + fail_without_force params ; + let uuid = List.assoc "uuid" params in + let self = Client.Host_driver.get_by_uuid ~rpc ~session_id ~uuid in + Client.Host_driver.deselect ~rpc ~session_id ~self + + let rescan _printer rpc session_id params = + ignore + (do_host_op rpc session_id ~multiple:false + (fun _ host -> + let host = host.getref () in + Client.Host_driver.rescan ~rpc ~session_id ~host + ) + params [] + ) +end + module VTPM = struct let create printer rpc session_id params = let vm_uuid = List.assoc "vm-uuid" params in diff --git a/ocaml/xapi-cli-server/records.ml b/ocaml/xapi-cli-server/records.ml index cd7e2f5ae80..b2ca1d1d5fd 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -5290,6 +5290,217 @@ let repository_record rpc session_id repository = ] } +let driver_variant_record rpc session_id variant = + let _ref = ref variant in + let empty = + ToGet + (fun () -> Client.Driver_variant.get_record ~rpc ~session_id ~self:!_ref) + in + let record = ref empty in + let x () = lzy_get record in + + let empty_driver = + ToGet + (fun () -> + Client.Host_driver.get_record ~rpc ~session_id + ~self:(x ()).API.driver_variant_driver + ) + in + + let driver = ref empty_driver in + let xd () = lzy_get driver in + + { + setref= + (fun r -> + _ref := r ; + record := empty + ) + ; setrefrec= + (fun (a, b) -> + _ref := a ; + record := Got b + ) + ; record= x + ; getref= (fun () -> !_ref) + ; fields= + [ + make_field ~name:"uuid" + ~get:(fun () -> (x ()).API.driver_variant_uuid) + () + ; make_field ~name:"name" + ~get:(fun () -> (x ()).API.driver_variant_name) + () + ; make_field ~name:"version" + ~get:(fun () -> (x ()).API.driver_variant_version) + () + ; make_field ~name:"status" + ~get:(fun () -> (x ()).API.driver_variant_status) + () + ; make_field ~name:"priority" + ~get:(fun () -> + Printf.sprintf "%5.1f" (x ()).API.driver_variant_priority + ) + () + ; make_field ~name:"active" + ~get:(fun () -> + string_of_bool ((xd ()).API.host_driver_active_variant = !_ref) + ) + () + ; make_field ~name:"selected" + ~get:(fun () -> + string_of_bool ((xd ()).API.host_driver_selected_variant = !_ref) + ) + () + ; make_field ~name:"driver-uuid" + ~get:(fun () -> get_uuid_from_ref (x ()).API.driver_variant_driver) + () + ; make_field ~name:"driver-name" + ~get:(fun () -> (xd ()).API.host_driver_name) + () + ; make_field ~name:"host-uuid" + ~get:(fun () -> + try get_uuid_from_ref (xd ()).API.host_driver_host with _ -> nid + ) + () + ; make_field ~name:"hw-present" + ~get:(fun () -> + string_of_bool (x ()).API.driver_variant_hardware_present + ) + () + ] + } + +let host_driver_record rpc session_id host_driver = + let _ref = ref host_driver in + let none = "" in + let empty = + ToGet (fun () -> Client.Host_driver.get_record ~rpc ~session_id ~self:!_ref) + in + let record = ref empty in + let x () = lzy_get record in + + (* variants of this driver in priority order; should be a short list *) + let variants = + ToGet + (fun () -> + List.map + (fun self -> + (self, Client.Driver_variant.get_record ~rpc ~session_id ~self) + ) + (Client.Host_driver.get_variants ~rpc ~session_id ~self:host_driver) + |> List.stable_sort (fun (_, x) (_, y) -> + Float.compare x.API.driver_variant_priority + y.API.driver_variant_priority + |> Int.neg + ) + ) + in + + let variants = ref variants in + let xv () = lzy_get variants in + + { + setref= + (fun r -> + _ref := r ; + record := empty + ) + ; setrefrec= + (fun (a, b) -> + _ref := a ; + record := Got b + ) + ; record= x + ; getref= (fun () -> !_ref) + ; fields= + [ + make_field ~name:"uuid" ~get:(fun () -> (x ()).API.host_driver_uuid) () + ; make_field ~name:"name" ~get:(fun () -> (x ()).API.host_driver_name) () + ; make_field ~name:"type" ~get:(fun () -> (x ()).API.host_driver_type) () + ; make_field ~name:"description" + ~get:(fun () -> (x ()).API.host_driver_description) + () + ; make_field ~name:"info" ~get:(fun () -> (x ()).API.host_driver_info) () + ; make_field ~name:"host-uuid" + ~get:(fun () -> + try get_uuid_from_ref (x ()).API.host_driver_host with _ -> nid + ) + () + ; make_field ~name:"active-variant" + ~get:(fun () -> + let r = (x ()).API.host_driver_active_variant in + match List.find_opt (fun (r', _) -> r = r') (xv ()) with + | None -> + none + | Some (_, v) -> + v.API.driver_variant_name + ) + () + ; make_field ~name:"selected-variant" + ~get:(fun () -> + let r = (x ()).API.host_driver_selected_variant in + match List.find_opt (fun (r', _) -> r = r') (xv ()) with + | None -> + none + | Some (_, v) -> + v.API.driver_variant_name + ) + () + ; make_field ~name:"variants" + ~get:(fun () -> + xv () + |> List.map (fun (_, v) -> + (v.API.driver_variant_name, v.API.driver_variant_version) + ) + |> List.map (fun (name, version) -> + Printf.sprintf "%s/%s" name version + ) + |> String.concat "; " + ) + () + ; make_field ~name:"variants-dev-status" + ~get:(fun () -> + xv () + |> List.map (fun (_, v) -> + ( v.API.driver_variant_name + , v.API.driver_variant_version + , v.API.driver_variant_status + ) + ) + |> List.map (fun (name, _version, status) -> + Printf.sprintf "%s=%s" name status + ) + |> String.concat "; " + ) + () + ; make_field ~name:"variants-uuid" + ~get:(fun () -> + xv () + |> List.map (fun (_, v) -> + (v.API.driver_variant_name, v.API.driver_variant_uuid) + ) + |> List.map (fun (name, uuid) -> Printf.sprintf "%s=%s" name uuid) + |> String.concat "; " + ) + () + ; make_field ~name:"variants-hw-present" + ~get:(fun () -> + xv () + |> List.map (fun (_, v) -> + ( v.API.driver_variant_name + , v.API.driver_variant_version + , v.API.driver_variant_hardware_present + ) + ) + |> List.filter (fun (_, _, status) -> status = true) + |> List.map (fun (name, _, _) -> name) + |> String.concat "; " + ) + () + ] + } + let vtpm_record rpc session_id vtpm = let _ref = ref vtpm in let empty_record = diff --git a/ocaml/xapi/api_server_common.ml b/ocaml/xapi/api_server_common.ml index 1cd1758a078..f4167c1f36a 100644 --- a/ocaml/xapi/api_server_common.ml +++ b/ocaml/xapi/api_server_common.ml @@ -130,6 +130,8 @@ module Actions = struct module Diagnostics = Xapi_diagnostics module Repository = Repository module Observer = Xapi_observer + module Host_driver = Xapi_host_driver + module Driver_variant = Xapi_host_driver.Variant end (** Use the server functor to make an XML-RPC dispatcher. *) diff --git a/ocaml/xapi/db_gc_util.ml b/ocaml/xapi/db_gc_util.ml index 7972aa28ed9..202b51cc5eb 100644 --- a/ocaml/xapi/db_gc_util.ml +++ b/ocaml/xapi/db_gc_util.ml @@ -71,6 +71,27 @@ let gc_VDIs ~__context = ) (Db.VDI.get_all ~__context) +let gc_Host_drivers ~__context = + let all_host_drivers = Db.Host_driver.get_all ~__context in + List.iter + (fun self -> + if not (valid_ref __context (Db.Host_driver.get_host ~__context ~self)) + then + Db.Host_driver.destroy ~__context ~self + ) + all_host_drivers + +let gc_Host_driver_variants ~__context = + let variants = Db.Driver_variant.get_all ~__context in + List.iter + (fun self -> + if + not (valid_ref __context (Db.Driver_variant.get_driver ~__context ~self)) + then + Db.Driver_variant.destroy ~__context ~self + ) + variants + let gc_PIFs ~__context = gc_connector ~__context Db.PIF.get_all Db.PIF.get_record (fun x -> valid_ref __context x.pIF_host) @@ -581,17 +602,6 @@ let gc_PVS_cache_storage ~__context = (fun x -> valid_ref __context x.pVS_cache_storage_host) Db.PVS_cache_storage.destroy -(* -let timeout_alerts ~__context = - let all_alerts = Db.Alert.get_all ~__context in - let now = Unix.gettimeofday() in - List.iter (fun alert -> - let alert_time = Date.to_unix_time (Db.Alert.get_timestamp ~__context ~self:alert) in - if now -. alert_time > Xapi_globs.alert_timeout then - Db.Alert.destroy ~__context ~self:alert - ) all_alerts -*) - let gc_updates_requiring_reboot ~__context = List.iter (fun host -> @@ -632,9 +642,9 @@ let gc_subtask_list = ; ("PVS servers", gc_PVS_servers) ; ("PVS cache storage", gc_PVS_cache_storage) ; ("Certificates", gc_certificates) - ; ("VTPMs", gc_vtpms) - ; (* timeout_alerts; *) - (* CA-29253: wake up all blocked clients *) - ("Heartbeat", Xapi_event.heartbeat) + ; ("VTPMs", gc_vtpms) (* CA-29253: wake up all blocked clients *) + ; ("Heartbeat", Xapi_event.heartbeat) ; ("Updates requiring reboot", gc_updates_requiring_reboot) + ; ("Host drivers", gc_Host_drivers) + ; ("Host driver variants", gc_Host_driver_variants) ] diff --git a/ocaml/xapi/dbsync_slave.ml b/ocaml/xapi/dbsync_slave.ml index 3ff89881de3..1c1b5f1f241 100644 --- a/ocaml/xapi/dbsync_slave.ml +++ b/ocaml/xapi/dbsync_slave.ml @@ -353,6 +353,10 @@ let update_env __context sync_keys = ~value:current_bios_strings ) ) ; + switched_sync Xapi_globs.sync_host_driver (fun () -> + debug "%s" __FUNCTION__ ; + ignore (Xapi_host.rescan_drivers ~__context ~host:localhost) + ) ; (* CA-35549: In a pool rolling upgrade, the master will detect the end of upgrade when the software versions of all the hosts are the same. It will then assume that (for example) per-host patch records have diff --git a/ocaml/xapi/dune b/ocaml/xapi/dune index d2e2fb17de8..22b5376b8d6 100644 --- a/ocaml/xapi/dune +++ b/ocaml/xapi/dune @@ -209,6 +209,7 @@ xxhash yojson zstd + xapi_host_driver_helpers ) (preprocess (per_module ((pps ppx_sexp_conv) Cert_distrib) diff --git a/ocaml/xapi/message_forwarding.ml b/ocaml/xapi/message_forwarding.ml index 63b27076a1a..6eaa0515571 100644 --- a/ocaml/xapi/message_forwarding.ml +++ b/ocaml/xapi/message_forwarding.ml @@ -4143,6 +4143,14 @@ functor info "Host.apply_updates: host = '%s'; hash = '%s'" uuid hash ; Local.Host.apply_updates ~__context ~self ~hash + let rescan_drivers ~__context ~host = + let uuid = host_uuid ~__context host in + info "Host.rescan_drivers: host = '%s'" uuid ; + let local_fn = Local.Host.rescan_drivers ~host in + do_op_on ~local_fn ~__context ~host (fun session_id rpc -> + Client.Host.rescan_drivers ~rpc ~session_id ~host + ) + let set_https_only ~__context ~self ~value = let uuid = host_uuid ~__context self in info "Host.set_https_only: self = %s ; value = %b" uuid value ; @@ -6599,6 +6607,44 @@ functor module Certificate = struct end + module Host_driver = struct + (** select needs to be executed on the host of the driver *) + let select ~__context ~self ~variant = + info "%s" __FUNCTION__ ; + let host = Db.Host_driver.get_host ~__context ~self in + let local_fn = Local.Host_driver.select ~self ~variant in + do_op_on ~__context ~local_fn ~host (fun session_id rpc -> + Client.Host_driver.select ~rpc ~session_id ~self ~variant + ) + + (** deselect needs to be executed on the host of the driver *) + let deselect ~__context ~self = + info "%s" __FUNCTION__ ; + let host = Db.Host_driver.get_host ~__context ~self in + let local_fn = Local.Host_driver.deselect ~self in + do_op_on ~__context ~local_fn ~host (fun session_id rpc -> + Client.Host_driver.deselect ~rpc ~session_id ~self + ) + + let rescan ~__context ~host = + info "%s" __FUNCTION__ ; + let local_fn = Local.Host_driver.rescan ~host in + do_op_on ~__context ~local_fn ~host (fun session_id rpc -> + Client.Host_driver.rescan ~rpc ~session_id ~host + ) + end + + module Driver_variant = struct + let select ~__context ~self = + info "%s" __FUNCTION__ ; + let drv = Db.Driver_variant.get_driver ~__context ~self in + let host = Db.Host_driver.get_host ~__context ~self:drv in + let local_fn = Local.Driver_variant.select ~self in + do_op_on ~__context ~local_fn ~host (fun session_id rpc -> + Client.Driver_variant.select ~rpc ~session_id ~self + ) + end + module Repository = struct let introduce ~__context ~name_label ~name_description ~binary_url ~source_url ~update ~gpgkey_path = diff --git a/ocaml/xapi/xapi_globs.ml b/ocaml/xapi/xapi_globs.ml index 384ec35aed5..c768b1f577f 100644 --- a/ocaml/xapi/xapi_globs.ml +++ b/ocaml/xapi/xapi_globs.ml @@ -372,6 +372,8 @@ let sync_pci_devices = "sync_pci_devices" let sync_gpus = "sync_gpus" +let sync_host_driver = "sync_host_driver" + (* Allow dbsync actions to be disabled via the redo log, since the database isn't of much use if xapi won't start. *) let disable_dbsync_for = ref [] @@ -929,6 +931,14 @@ let xen_livepatch_cmd = ref "/usr/sbin/xen-livepatch" let xl_cmd = ref "/usr/sbin/xl" +let depmod = ref "/usr/sbin/depmod" + +let driver_tool = ref "/opt/xensource/debug/drivertool.sh" + +let dracut = ref "/usr/sbin/dracut" + +let udevadm = ref "/usr/sbin/udevadm" + let yum_repos_config_dir = ref "/etc/yum.repos.d" let remote_repository_prefix = ref "remote" @@ -1616,6 +1626,11 @@ let other_options = , (fun () -> string_of_bool !disable_webserver) , "Disable the host webserver" ) + ; ( "drivertool" + , Arg.Set_string driver_tool + , (fun () -> !driver_tool) + , "Path to drivertool for selecting host driver variants" + ) ] (* The options can be set with the variable xapiflags in /etc/sysconfig/xapi. @@ -1713,6 +1728,9 @@ module Resources = struct ; ("createrepo-cmd", createrepo_cmd, "Path to createrepo command") ; ("modifyrepo-cmd", modifyrepo_cmd, "Path to modifyrepo command") ; ("rpm-cmd", rpm_cmd, "Path to rpm command") + ; ("depmod", depmod, "Path to depmod command") + ; ("dracut", dracut, "Path to dracut command") + ; ("udevadm", udevadm, "Path to udevadm command") ] let nonessential_executables = diff --git a/ocaml/xapi/xapi_host.ml b/ocaml/xapi/xapi_host.ml index cd6ae3a7d35..5b37873219e 100644 --- a/ocaml/xapi/xapi_host.ml +++ b/ocaml/xapi/xapi_host.ml @@ -3089,6 +3089,8 @@ let apply_updates ~__context ~self ~hash = Db.Host.set_last_update_hash ~__context ~self ~value:hash ; warnings +let rescan_drivers ~__context ~host = Xapi_host_driver.scan ~__context ~host + let cc_prep () = let cc = "CC_PREPARATIONS" in Xapi_inventory.lookup ~default:"false" cc |> String.lowercase_ascii diff --git a/ocaml/xapi/xapi_host.mli b/ocaml/xapi/xapi_host.mli index f8fe73f8379..74131a59974 100644 --- a/ocaml/xapi/xapi_host.mli +++ b/ocaml/xapi/xapi_host.mli @@ -555,6 +555,8 @@ val get_host_updates_handler : Http.Request.t -> Unix.file_descr -> 'a -> unit val apply_updates : __context:Context.t -> self:API.ref_host -> hash:string -> string list list +val rescan_drivers : __context:Context.t -> host:API.ref_host -> unit + val copy_primary_host_certs : __context:Context.t -> host:API.ref_host -> unit val set_https_only : diff --git a/ocaml/xapi/xapi_host_driver.ml b/ocaml/xapi/xapi_host_driver.ml new file mode 100644 index 00000000000..443593d5a25 --- /dev/null +++ b/ocaml/xapi/xapi_host_driver.ml @@ -0,0 +1,160 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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 = __MODULE__ end) + +open D +module Unixext = Xapi_stdext_unix.Unixext + +(* +module DriverMap = Map.Make (String) +module DriverSet = Set.Make (String) +*) +module T = Xapi_host_driver_tool + +let invalid_value field value = + raise Api_errors.(Server_error (invalid_value, [field; value])) + +let internal_error fmt = + Printf.ksprintf + (fun msg -> + error "%s" msg ; + raise Api_errors.(Server_error (internal_error, [msg])) + ) + fmt + +let drivertool args = + let path = !Xapi_globs.driver_tool in + try + let stdout, _stderr = Forkhelpers.execute_command_get_output path args in + debug "%s: executed %s %s" __FUNCTION__ path (String.concat " " args) ; + stdout + with e -> + internal_error "%s: failed to run %s %s: %s" __FUNCTION__ path + (String.concat " " args) (Printexc.to_string e) + +module Variant = struct + let create ~__context ~name ~version ~driver ~hw_present ~priority ~dev_status + = + info "%s: %s" __FUNCTION__ name ; + let ref = Ref.make () in + let uuid = Uuidx.to_string (Uuidx.make ()) in + Db.Driver_variant.create ~__context ~ref ~driver ~uuid ~name ~version + ~hardware_present:hw_present ~priority ~status:dev_status ; + ref + + let destroy ~__context ~self = + debug "Destroying driver variant %s" (Ref.string_of self) ; + Db.Driver_variant.destroy ~__context ~self + + let select ~__context ~self = + debug "%s: %s" __FUNCTION__ (Ref.string_of self) ; + let drv = Db.Driver_variant.get_driver ~__context ~self in + let d = Db.Host_driver.get_record ~__context ~self:drv in + let v = Db.Driver_variant.get_record ~__context ~self in + let stdout = + drivertool ["select"; d.API.host_driver_name; v.API.driver_variant_name] + in + info "%s: %s" __FUNCTION__ stdout ; + Db.Host_driver.set_selected_variant ~__context ~self:drv ~value:self +end + +let create ~__context ~host ~name ~friendly_name ~_type ~description ~info:inf + ~active_variant ~selected_variant = + D.info "%s: %s" __FUNCTION__ name ; + let ref = Ref.make () in + let uuid = Uuidx.to_string (Uuidx.make ()) in + Db.Host_driver.create ~__context ~ref ~uuid ~host ~name ~friendly_name ~_type + ~description ~info:inf ~active_variant ~selected_variant ; + ref + +(** destroy driver and recursively its variants, too *) +let destroy ~__context ~self = + D.debug "Destroying driver %s" (Ref.string_of self) ; + let variants = Db.Host_driver.get_variants ~__context ~self in + variants |> List.iter (fun self -> Variant.destroy ~__context ~self) ; + Db.Host_driver.destroy ~__context ~self + +(** Runs on the host where the driver is installed *) +let select ~__context ~self ~variant = + let drv = Ref.string_of self in + let var = Ref.string_of variant in + let variants = Db.Host_driver.get_variants ~__context ~self in + match List.mem variant variants with + | true -> + D.debug "%s selecting driver %s variant %s" __FUNCTION__ drv var ; + let d = Db.Host_driver.get_record ~__context ~self in + let v = Db.Driver_variant.get_record ~__context ~self:variant in + let stdout = + drivertool ["select"; d.API.host_driver_name; v.API.driver_variant_name] + in + info "%s: %s" __FUNCTION__ stdout ; + Db.Host_driver.set_selected_variant ~__context ~self ~value:variant + | false -> + error "%s variant %s does not belong to driver %s" __FUNCTION__ var drv ; + invalid_value "variant" var + +(** Runs on the host where the driver is installed *) +let deselect ~__context ~self = + D.debug "%s driver %s" __FUNCTION__ (Ref.string_of self) ; + let d = Db.Host_driver.get_record ~__context ~self in + let stdout = drivertool ["deselect"; d.API.host_driver_name] in + info "%s: %s" __FUNCTION__ stdout ; + Db.Host_driver.set_active_variant ~__context ~self ~value:Ref.null ; + Db.Host_driver.set_selected_variant ~__context ~self ~value:Ref.null + +(** remove all host driver entries for this host *) +let reset ~__context ~host = + D.debug "%s" __FUNCTION__ ; + let open Xapi_database.Db_filter_types in + let expr = Eq (Field "host", Literal (Ref.string_of host)) in + let drivers = Db.Host_driver.get_refs_where ~__context ~expr in + drivers |> List.iter (fun self -> destroy ~__context ~self) + +(** Runs on [host] *) +let scan ~__context ~host = + T.Mock.install () ; + let null = Ref.null in + reset ~__context ~host ; + drivertool ["list"] + |> T.parse + |> List.iter @@ fun (_name, driver) -> + let driver_ref = + create ~__context ~host ~name:driver.T.name ~friendly_name:driver.T.name + ~info:driver.T.info ~active_variant:null ~selected_variant:null + ~description:driver.T.descr ~_type:driver.T.ty + in + driver.T.variants + |> List.iter @@ fun (name, v) -> + let var_ref = + Variant.create ~__context ~name ~version:v.T.version + ~driver:driver_ref ~hw_present:v.T.hw_present ~priority:v.T.priority + ~dev_status:v.T.dev_status + in + ( match driver.T.selected with + | Some v when v = name -> + Db.Host_driver.set_selected_variant ~__context ~self:driver_ref + ~value:var_ref + | _ -> + () + ) ; + match driver.T.active with + | Some v when v = name -> + Db.Host_driver.set_active_variant ~__context ~self:driver_ref + ~value:var_ref + | _ -> + () + +(** Runs on [host] *) +let rescan ~__context ~host = debug "%s" __FUNCTION__ ; scan ~__context ~host diff --git a/ocaml/xapi/xapi_host_driver.mli b/ocaml/xapi/xapi_host_driver.mli new file mode 100644 index 00000000000..41076ae45f5 --- /dev/null +++ b/ocaml/xapi/xapi_host_driver.mli @@ -0,0 +1,61 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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. + *) + +(** A host driver variant, referred to by a host driver *) +module Variant : sig + val create : + __context:Context.t + -> name:string + -> version:string + -> driver:[`Host_driver] API.Ref.t + -> hw_present:bool + -> priority:float + -> dev_status:string + -> [`Driver_variant] Ref.t + + val destroy : __context:Context.t -> self:[`Driver_variant] API.Ref.t -> unit + + val select : __context:Context.t -> self:[`Driver_variant] API.Ref.t -> unit +end + +val create : + __context:Context.t + -> host:[`host] API.Ref.t + -> name:string + -> friendly_name:string + -> _type:string + -> description:string + -> info:string + -> active_variant:[`Driver_variant] API.Ref.t + -> selected_variant:[`Driver_variant] API.Ref.t + -> [`Host_driver] Ref.t +(** A host driver *) + +val destroy : __context:Context.t -> self:[`Host_driver] API.Ref.t -> unit + +val select : + __context:Context.t + -> self:[`Host_driver] API.Ref.t + -> variant:[`Driver_variant] API.Ref.t + -> unit + +val deselect : __context:Context.t -> self:[`Host_driver] API.Ref.t -> unit +(** This is just for completeness; don't see a use case right now *) + +val scan : __context:Context.t -> host:[`host] API.Ref.t -> unit +(** scan and re-scan scan the [host] for drivers and update the xapi + database accordingly. Previous entries are purged (this may change + in the future *) + +val rescan : __context:Context.t -> host:[`host] API.Ref.t -> unit diff --git a/ocaml/xapi/xapi_host_driver_tool.ml b/ocaml/xapi/xapi_host_driver_tool.ml new file mode 100644 index 00000000000..a35608b84f1 --- /dev/null +++ b/ocaml/xapi/xapi_host_driver_tool.ml @@ -0,0 +1,648 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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. + *) + +(* This module provides functions to communicate with with the + host-driver-tool that manages multi-version drivers and reports + the state of them in JSON *) + +module D = Debug.Make (struct let name = __MODULE__ end) + +open D + +let internal_error fmt = + Printf.ksprintf + (fun str -> + error "%s" str ; + raise Api_errors.(Server_error (internal_error, [str])) + ) + fmt + +(** types to represent the JSON output of the script that reports + drivers *) +type variant = { + version: string + ; hw_present: bool + ; priority: float + ; dev_status: string +} + +type driver = { + ty: string + ; name: string + ; descr: string + ; info: string + ; selected: string option + ; active: string option + ; variants: (string * variant) list +} + +type t = {protocol: string; operation: string; drivers: (string * driver) list} + +(** names for drivers and variants must only have certain characters *) +let is_legal_char = function + | 'a' .. 'z' -> + true + | 'A' .. 'Z' -> + true + | '0' .. '9' -> + true + | '_' -> + true + | '.' -> + true + | '@' -> + true + | _ -> + false + +module R = Result + +(** create an error result from printf format string *) +let error fmt = Printf.ksprintf (fun str -> R.error str) fmt + +(** currently unused but needs to be used for names of drivers *) +let _is_legal_name name = + match String.for_all is_legal_char name with + | true -> + R.ok name + | false -> + error "'%s' contains illegal characters" name + +(* Wrap the combinators to parse JSON such that they don't raise + exceptions but return Result.t values. These can be used for + monadic error handling *) +module J = struct + module U = Yojson.Basic.Util + module B = Yojson.Basic + + let wrap f x = + try f x |> R.ok with + | U.Type_error (msg, json) -> + error "%s in %s" msg (Yojson.Basic.to_string json) + | e -> + R.error (Printexc.to_string e) + + let wrap2 f x y = + try f x y |> R.ok with + | U.Type_error (msg, json) -> + error "%s in %s" msg (Yojson.Basic.to_string json) + | e -> + R.error (Printexc.to_string e) + + let _keys = wrap U.keys + + let _values = wrap U.values + + let _combine = wrap2 U.combine + + let member key json = + match U.member key json with + | x -> + R.ok x + | exception U.Type_error (msg, json) -> + error "%s in %s" msg (Yojson.Basic.to_string json) + | exception e -> + R.error (Printexc.to_string e) + + let _path = wrap2 U.path + + let _index = wrap2 U.index + + let _map = wrap2 U.map + + let to_assoc = wrap U.to_assoc + + let _to_option f = wrap (U.to_option f) + + let to_bool = wrap U.to_bool + + let _to_bool_option = wrap U.to_bool_option + + let to_number = wrap U.to_number + + let _to_number_option = wrap U.to_number_option + + let _to_float = wrap U.to_float + + let _to_float_option = wrap U.to_float_option + + let _to_int = wrap U.to_int + + let _to_int_option = wrap U.to_int_option + + let _to_list = wrap U.to_list + + let to_string = wrap U.to_string + + let to_string_option = wrap U.to_string_option + + let _convert_each f = wrap (U.convert_each f) +end + +(** combinators for results and accessing members in JSON objects *) +let ( let* ) = Result.bind + +let ( ||> ) = Result.bind + +let ( ||. ) json key = json ||> J.member key + +let ( |. ) json key = json |> J.member key + +(** combine a list of ok list of oks into an ok list: + ('a, 'b) result list -> ('a list, 'b) result. *) +let _combine results = + let rec loop acc = function + | [] -> + R.Ok (List.rev acc) + | R.Ok x :: xs -> + loop (x :: acc) xs + | (R.Error _ as err) :: _ -> + err + in + loop [] results + +(** combine an association list where the value is ok/error into an ok + association list *) +let combine_assoc results = + let rec loop acc = function + | [] -> + R.Ok (List.rev acc) + | (key, R.Ok x) :: xs -> + loop ((key, x) :: acc) xs + | (_, (R.Error _ as err)) :: _ -> + err + in + loop [] results + +let _protocol json = json |. "protocol" ||. "version" ||> J.to_string + +let _operation json = json |. "operation" ||. "reboot" ||> J.to_bool + +let variant json = + let* version = json |. "version" ||> J.to_string in + let* hw_present = json |. "hardware_present" ||> J.to_bool in + let* priority = json |. "priority" ||> J.to_number in + let* dev_status = json |. "status" ||> J.to_string in + R.ok {version; hw_present; priority; dev_status} + +let variants json = + let* assoc = J.to_assoc json in + assoc |> List.map (fun (name, json) -> (name, variant json)) |> combine_assoc + +let driver json = + let* ty = json |. "type" ||> J.to_string in + let* name = json |. "friendly_name" ||> J.to_string in + let* descr = json |. "description" ||> J.to_string in + let* info = json |. "info" ||> J.to_string in + let* selected = json |. "selected" ||> J.to_string_option in + let* active = json |. "active" ||> J.to_string_option in + let* variants = json |. "variants" ||> variants in + R.ok {ty; name; descr; info; selected; active; variants} + +let drivers json = + let* assoc = J.to_assoc json in + assoc |> List.map (fun (name, json) -> (name, driver json)) |> combine_assoc + +let t json = json |. "drivers" ||> drivers + +let parse str = + match J.B.from_string str |> t with + | R.Ok x -> + x + | R.Error msg -> + internal_error "%s parsing failed: %s" __FUNCTION__ msg + | exception e -> + raise e + +let read path = + match J.B.from_file path |> t with + | R.Ok x -> + x + | R.Error msg -> + internal_error "%s parsing %s failed: %s" __FUNCTION__ path msg + | exception e -> + raise e + +module Mock = struct + let drivertool_sh = + {|#!/usr/bin/env bash + +set -o errexit +set -o pipefail +if [[ -n "$TRACE" ]]; then set -o xtrace; fi +set -o nounset + +if [[ "${1-}" =~ ^-*h(elp)?$ ]]; then + cat <&1 + exit 1 + ;; +esac +|} + + let install () = + let path = !Xapi_globs.driver_tool in + try + Xapi_stdext_unix.Unixext.write_string_to_file path drivertool_sh ; + Unix.chmod path 0o755 + with e -> + internal_error "%s: can't install %s: %s" __FUNCTION__ path + (Printexc.to_string e) +end diff --git a/ocaml/xapi/xapi_host_driver_tool.mli b/ocaml/xapi/xapi_host_driver_tool.mli new file mode 100644 index 00000000000..6957aa0a866 --- /dev/null +++ b/ocaml/xapi/xapi_host_driver_tool.mli @@ -0,0 +1,43 @@ +(* + Copyright (c) Cloud Software Group, Inc. + + 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. + *) + +type variant = { + version: string (** just a string, not interpreted right now *) + ; hw_present: bool (** hardware present *) + ; priority: float (** higher = higher priority *) + ; dev_status: string (** development status, like alpha, beta *) +} + +type driver = { + ty: string (** category, like "network" *) + ; name: string (** unique *) + ; descr: string + ; info: string + ; selected: string option (** refers to named variant below *) + ; active: string option (** refers to named variant below *) + ; variants: (string * variant) list (** named variants, name is unique *) +} + +type t = {protocol: string; operation: string; drivers: (string * driver) list} + +val parse : string -> (string * driver) list +(** parse from a string *) + +val read : string -> (string * driver) list +(** read from a file whose path is provided *) + +(** install a mock drivertool.sh *) +module Mock : sig + val install : unit -> unit +end diff --git a/ocaml/xe-cli/bash-completion b/ocaml/xe-cli/bash-completion index aae832f4d67..8120df874f3 100644 --- a/ocaml/xe-cli/bash-completion +++ b/ocaml/xe-cli/bash-completion @@ -492,6 +492,17 @@ _xe() return 0 ;; + version) # for hostdriver-select + if [[ "$COMP_CWORD" == "3" ]]; then + __xe_debug "triggering autocompletion for hostdriver's version" + IFS=$'\n,' + local cmd="$xe hostdriver-list ${OLDSTYLE_WORDS[2]} --minimal params=versions 2>/dev/null" + __xe_debug "full list cmd is '$cmd'" + local vals=$(eval "$cmd") + set_completions "${vals//; /,}" "$value" + fi + ;; + data-source) # for host-data-source-* __xe_debug "param is 'data-source', list command is 'host-data-source-list'" IFS=$',' diff --git a/quality-gate.sh b/quality-gate.sh index cfef6614e00..9743046fb0c 100755 --- a/quality-gate.sh +++ b/quality-gate.sh @@ -25,15 +25,21 @@ verify-cert () { } mli-files () { +<<<<<<< HEAD N=496 + N=499 + X="ocaml/tests" + X+="|ocaml/quicktest" + X+="|ocaml/message-switch/core_test" # do not count ml files from the tests in ocaml/{tests/quicktest} - MLIS=$(git ls-files -- '**/*.mli' | grep -vE "ocaml/tests|ocaml/quicktest|ocaml/message-switch/core_test" | xargs -I {} sh -c "echo {} | cut -f 1 -d '.'" \;) - MLS=$(git ls-files -- '**/*.ml' | grep -vE "ocaml/tests|ocaml/quicktest|ocaml/message-switch/core_test" | xargs -I {} sh -c "echo {} | cut -f 1 -d '.'" \;) - num_mls_without_mlis=$(comm -23 <(sort <<<"$MLS") <(sort <<<"$MLIS") | wc -l) - if [ "$num_mls_without_mlis" -eq "$N" ]; then - echo "OK counted $num_mls_without_mlis .ml files without an .mli" + M=$(comm -23 <(git ls-files -- '**/*.ml' | sed 's/.ml$//' | sort) \ + <(git ls-files -- '**/*.mli' | sed 's/.mli$//' | sort) |\ + grep -cvE "$X") + + if [ "$M" -eq "$N" ]; then + echo "OK counted $M .ml files without an .mli" else - echo "ERROR expected $N .ml files without .mlis, got $num_mls_without_mlis."\ + echo "ERROR expected $N .ml files without .mlis, got $M."\ "If you created some .ml files, they are probably missing corresponding .mli's" 1>&2 exit 1 fi diff --git a/scripts/bugtool-plugin/xapi/stuff.xml b/scripts/bugtool-plugin/xapi/stuff.xml index cc4cf61dc32..b7d8ad95162 100644 --- a/scripts/bugtool-plugin/xapi/stuff.xml +++ b/scripts/bugtool-plugin/xapi/stuff.xml @@ -14,4 +14,6 @@ cat @ETCXENDIR@/xapi-pool-tls.pem | @BINDIR@/openssl x509 -text rrd-cli save_rrds @OPTDIR@/bin/xe task-list params=all +ls -lR /lib/modules/$(uname -r)/updates +ls -lR /lib/modules/$(uname -r)/xenserver From 57596343ecadc843724c901acbe33dbc35b550b8 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 05/53] MVD CP-52334 multi-version driver API/CLI - incremental update When scanning a host for drivers, don't remove and re-create them but instead update an entry if it already exists. Signed-off-by: Christian Lindig --- ocaml/xapi/xapi_host_driver.ml | 64 ++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/ocaml/xapi/xapi_host_driver.ml b/ocaml/xapi/xapi_host_driver.ml index 443593d5a25..8d0f02e48d9 100644 --- a/ocaml/xapi/xapi_host_driver.ml +++ b/ocaml/xapi/xapi_host_driver.ml @@ -58,6 +58,34 @@ module Variant = struct debug "Destroying driver variant %s" (Ref.string_of self) ; Db.Driver_variant.destroy ~__context ~self + (** create' is like create but updates an exisiting entry if it + exists. This avoids entries becoming stale *) + let create' ~__context ~name ~version ~driver ~hw_present ~priority + ~dev_status = + let open Xapi_database.Db_filter_types in + (* driver and name identify a variant uniquely *) + let driver' = Eq (Field "driver", Literal (Ref.string_of driver)) in + let name' = Eq (Field "name", Literal name) in + let expr = And (driver', name') in + match Db.Driver_variant.get_refs_where ~__context ~expr with + | [] -> + create ~__context ~name ~version ~driver ~hw_present ~priority + ~dev_status + | [self] -> + debug "%s: updating existing entry for variant %s" __FUNCTION__ name ; + Db.Driver_variant.set_version ~__context ~self ~value:version ; + Db.Driver_variant.set_priority ~__context ~self ~value:priority ; + Db.Driver_variant.set_status ~__context ~self ~value:dev_status ; + Db.Driver_variant.set_hardware_present ~__context ~self + ~value:hw_present ; + self + | variants -> + warn "%s: multiple entries for %s found; recreating one" __FUNCTION__ + name ; + variants |> List.iter (fun self -> destroy ~__context ~self) ; + create ~__context ~name ~version ~driver ~hw_present ~priority + ~dev_status + let select ~__context ~self = debug "%s: %s" __FUNCTION__ (Ref.string_of self) ; let drv = Db.Driver_variant.get_driver ~__context ~self in @@ -86,6 +114,37 @@ let destroy ~__context ~self = variants |> List.iter (fun self -> Variant.destroy ~__context ~self) ; Db.Host_driver.destroy ~__context ~self +(** create' is like create except it checks if an entry exists and + modifies it. This avoids ref/UUIDs becoming stale *) +let create' ~__context ~host ~name ~friendly_name ~_type ~description ~info:inf + ~active_variant ~selected_variant = + let null = Ref.null in + let open Xapi_database.Db_filter_types in + let host' = Eq (Field "host", Literal (Ref.string_of host)) in + let name' = Eq (Field "name", Literal name) in + let expr = And (host', name') in + match Db.Host_driver.get_refs_where ~__context ~expr with + | [] -> + (* no such entry exists - create it *) + create ~__context ~host ~name ~friendly_name ~info:inf + ~active_variant:null ~selected_variant:null ~description ~_type + | [self] -> + (* one existing entry - update it *) + info "%s: updating host driver %s" __FUNCTION__ name ; + Db.Host_driver.set_friendly_name ~__context ~self ~value:name ; + Db.Host_driver.set_info ~__context ~self ~value:inf ; + Db.Host_driver.set_active_variant ~__context ~self ~value:null ; + Db.Host_driver.set_selected_variant ~__context ~self ~value:null ; + Db.Host_driver.set_description ~__context ~self ~value:description ; + Db.Host_driver.set_type ~__context ~self ~value:_type ; + self + | drivers -> + warn "%s: more than one entry for driver %s; destroying them" __FUNCTION__ + name ; + drivers |> List.iter (fun self -> destroy ~__context ~self) ; + create ~__context ~host ~name ~friendly_name ~info:inf + ~active_variant:null ~selected_variant:null ~description ~_type + (** Runs on the host where the driver is installed *) let select ~__context ~self ~variant = let drv = Ref.string_of self in @@ -126,19 +185,18 @@ let reset ~__context ~host = let scan ~__context ~host = T.Mock.install () ; let null = Ref.null in - reset ~__context ~host ; drivertool ["list"] |> T.parse |> List.iter @@ fun (_name, driver) -> let driver_ref = - create ~__context ~host ~name:driver.T.name ~friendly_name:driver.T.name + create' ~__context ~host ~name:driver.T.name ~friendly_name:driver.T.name ~info:driver.T.info ~active_variant:null ~selected_variant:null ~description:driver.T.descr ~_type:driver.T.ty in driver.T.variants |> List.iter @@ fun (name, v) -> let var_ref = - Variant.create ~__context ~name ~version:v.T.version + Variant.create' ~__context ~name ~version:v.T.version ~driver:driver_ref ~hw_present:v.T.hw_present ~priority:v.T.priority ~dev_status:v.T.dev_status in From f38cb666b93ad0093c668e49730fea432a15ac9a Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 06/53] MVD CP-52334 multi-version driver API/CLI - incremental update When scanning a host for drivers, don't remove and re-create them but instead update an entry if it already exists. Signed-off-by: Christian Lindig --- doc/content/toolstack/features/MVD/index.md | 2 +- ocaml/idl/datamodel_driver_variant.ml | 4 +- ocaml/idl/datamodel_lifecycle.ml | 2 +- ocaml/idl/schematest.ml | 2 +- ocaml/xapi-cli-server/cli_operations.ml | 4 +- ocaml/xapi/message_forwarding.ml | 9 +- ocaml/xapi/xapi_host_driver.ml | 119 +++++++++----------- ocaml/xapi/xapi_host_driver_tool.ml | 14 ++- ocaml/xapi/xapi_host_driver_tool.mli | 3 + quality-gate.sh | 4 +- 10 files changed, 79 insertions(+), 84 deletions(-) diff --git a/doc/content/toolstack/features/MVD/index.md b/doc/content/toolstack/features/MVD/index.md index c8a696c191b..7d8589cdcfe 100644 --- a/doc/content/toolstack/features/MVD/index.md +++ b/doc/content/toolstack/features/MVD/index.md @@ -1,4 +1,4 @@ -'++ ++++ title = "Multi-version drivers" +++ diff --git a/ocaml/idl/datamodel_driver_variant.ml b/ocaml/idl/datamodel_driver_variant.ml index 607ac670105..c520aa47899 100644 --- a/ocaml/idl/datamodel_driver_variant.ml +++ b/ocaml/idl/datamodel_driver_variant.ml @@ -16,8 +16,6 @@ open Datamodel_types open Datamodel_common open Datamodel_roles -(** This is pure data with no methods *) - let select = call ~name:"select" ~in_oss_since:None ~lifecycle:[] ~doc: @@ -45,7 +43,7 @@ let t = ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:(Ref _host_driver) "driver" "Driver this variant is a part of" ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "version" - "Unique versions of this driver variant" + "Unique version of this driver variant" ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:Bool "hardware_present" "True if the hardware for this variant is present on the host" ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:Float "priority" diff --git a/ocaml/idl/datamodel_lifecycle.ml b/ocaml/idl/datamodel_lifecycle.ml index cc252d2cf55..cb3c3e3abb0 100644 --- a/ocaml/idl/datamodel_lifecycle.ml +++ b/ocaml/idl/datamodel_lifecycle.ml @@ -158,7 +158,7 @@ let prototyped_of_message = function | "Driver_variant", "select" -> Some "24.39.0" | "Host_driver", "rescan" -> - Some "24.39.0-next" + Some "24.40.0" | "Host_driver", "deselect" -> Some "24.35.0" | "Host_driver", "select" -> diff --git a/ocaml/idl/schematest.ml b/ocaml/idl/schematest.ml index a1d89e5cbfd..5e0416156c1 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 = "863c257bad0d20800297cf968c294e4e" +let last_known_schema_hash = "36cb241822399344b119cce654162d3f" let current_schema_hash : string = let open Datamodel_types in diff --git a/ocaml/xapi-cli-server/cli_operations.ml b/ocaml/xapi-cli-server/cli_operations.ml index b725890014d..d15054207a7 100644 --- a/ocaml/xapi-cli-server/cli_operations.ml +++ b/ocaml/xapi-cli-server/cli_operations.ml @@ -29,7 +29,7 @@ open Records let failwith str = raise (Cli_util.Cli_failure str) -let failwith' fmt = Printf.ksprintf failwith fmt +let failwithfmt fmt = Printf.ksprintf failwith fmt exception ExitWithError of int @@ -8012,7 +8012,7 @@ module Host_driver = struct match List.find_opt by_name variants with | None -> - failwith' "%s does not identify a variant of this driver" name + failwithfmt "%s does not identify a variant of this driver" name | Some (variant, _) -> Client.Host_driver.select ~rpc ~session_id ~self:driver ~variant diff --git a/ocaml/xapi/message_forwarding.ml b/ocaml/xapi/message_forwarding.ml index 6eaa0515571..704b56acd10 100644 --- a/ocaml/xapi/message_forwarding.ml +++ b/ocaml/xapi/message_forwarding.ml @@ -6610,7 +6610,8 @@ functor module Host_driver = struct (** select needs to be executed on the host of the driver *) let select ~__context ~self ~variant = - info "%s" __FUNCTION__ ; + info "Host_driver.select %s %s" (Ref.string_of self) + (Ref.string_of variant) ; let host = Db.Host_driver.get_host ~__context ~self in let local_fn = Local.Host_driver.select ~self ~variant in do_op_on ~__context ~local_fn ~host (fun session_id rpc -> @@ -6619,7 +6620,7 @@ functor (** deselect needs to be executed on the host of the driver *) let deselect ~__context ~self = - info "%s" __FUNCTION__ ; + info "Host_driver.deselect %s" (Ref.string_of self) ; let host = Db.Host_driver.get_host ~__context ~self in let local_fn = Local.Host_driver.deselect ~self in do_op_on ~__context ~local_fn ~host (fun session_id rpc -> @@ -6627,7 +6628,7 @@ functor ) let rescan ~__context ~host = - info "%s" __FUNCTION__ ; + info "Host_driver.rescan %s" (Ref.string_of host) ; let local_fn = Local.Host_driver.rescan ~host in do_op_on ~__context ~local_fn ~host (fun session_id rpc -> Client.Host_driver.rescan ~rpc ~session_id ~host @@ -6636,7 +6637,7 @@ functor module Driver_variant = struct let select ~__context ~self = - info "%s" __FUNCTION__ ; + info "Driver_variant.select %s" (Ref.string_of self) ; let drv = Db.Driver_variant.get_driver ~__context ~self in let host = Db.Host_driver.get_host ~__context ~self:drv in let local_fn = Local.Driver_variant.select ~self in diff --git a/ocaml/xapi/xapi_host_driver.ml b/ocaml/xapi/xapi_host_driver.ml index 8d0f02e48d9..be66e3093f0 100644 --- a/ocaml/xapi/xapi_host_driver.ml +++ b/ocaml/xapi/xapi_host_driver.ml @@ -16,34 +16,11 @@ module D = Debug.Make (struct let name = __MODULE__ end) open D module Unixext = Xapi_stdext_unix.Unixext - -(* -module DriverMap = Map.Make (String) -module DriverSet = Set.Make (String) -*) -module T = Xapi_host_driver_tool +module Tool = Xapi_host_driver_tool let invalid_value field value = raise Api_errors.(Server_error (invalid_value, [field; value])) -let internal_error fmt = - Printf.ksprintf - (fun msg -> - error "%s" msg ; - raise Api_errors.(Server_error (internal_error, [msg])) - ) - fmt - -let drivertool args = - let path = !Xapi_globs.driver_tool in - try - let stdout, _stderr = Forkhelpers.execute_command_get_output path args in - debug "%s: executed %s %s" __FUNCTION__ path (String.concat " " args) ; - stdout - with e -> - internal_error "%s: failed to run %s %s: %s" __FUNCTION__ path - (String.concat " " args) (Printexc.to_string e) - module Variant = struct let create ~__context ~name ~version ~driver ~hw_present ~priority ~dev_status = @@ -55,7 +32,7 @@ module Variant = struct ref let destroy ~__context ~self = - debug "Destroying driver variant %s" (Ref.string_of self) ; + debug "%s: destroying driver variant %s" __FUNCTION__ (Ref.string_of self) ; Db.Driver_variant.destroy ~__context ~self (** create' is like create but updates an exisiting entry if it @@ -92,7 +69,7 @@ module Variant = struct let d = Db.Host_driver.get_record ~__context ~self:drv in let v = Db.Driver_variant.get_record ~__context ~self in let stdout = - drivertool ["select"; d.API.host_driver_name; v.API.driver_variant_name] + Tool.call ["select"; d.API.host_driver_name; v.API.driver_variant_name] in info "%s: %s" __FUNCTION__ stdout ; Db.Host_driver.set_selected_variant ~__context ~self:drv ~value:self @@ -126,15 +103,16 @@ let create' ~__context ~host ~name ~friendly_name ~_type ~description ~info:inf match Db.Host_driver.get_refs_where ~__context ~expr with | [] -> (* no such entry exists - create it *) - create ~__context ~host ~name ~friendly_name ~info:inf - ~active_variant:null ~selected_variant:null ~description ~_type + create ~__context ~host ~name ~friendly_name ~info:inf ~active_variant + ~selected_variant ~description ~_type | [self] -> (* one existing entry - update it *) info "%s: updating host driver %s" __FUNCTION__ name ; Db.Host_driver.set_friendly_name ~__context ~self ~value:name ; Db.Host_driver.set_info ~__context ~self ~value:inf ; - Db.Host_driver.set_active_variant ~__context ~self ~value:null ; - Db.Host_driver.set_selected_variant ~__context ~self ~value:null ; + Db.Host_driver.set_active_variant ~__context ~self ~value:active_variant ; + Db.Host_driver.set_selected_variant ~__context ~self + ~value:selected_variant ; Db.Host_driver.set_description ~__context ~self ~value:description ; Db.Host_driver.set_type ~__context ~self ~value:_type ; self @@ -156,7 +134,7 @@ let select ~__context ~self ~variant = let d = Db.Host_driver.get_record ~__context ~self in let v = Db.Driver_variant.get_record ~__context ~self:variant in let stdout = - drivertool ["select"; d.API.host_driver_name; v.API.driver_variant_name] + Tool.call ["select"; d.API.host_driver_name; v.API.driver_variant_name] in info "%s: %s" __FUNCTION__ stdout ; Db.Host_driver.set_selected_variant ~__context ~self ~value:variant @@ -168,51 +146,60 @@ let select ~__context ~self ~variant = let deselect ~__context ~self = D.debug "%s driver %s" __FUNCTION__ (Ref.string_of self) ; let d = Db.Host_driver.get_record ~__context ~self in - let stdout = drivertool ["deselect"; d.API.host_driver_name] in + let stdout = Tool.call ["deselect"; d.API.host_driver_name] in info "%s: %s" __FUNCTION__ stdout ; Db.Host_driver.set_active_variant ~__context ~self ~value:Ref.null ; Db.Host_driver.set_selected_variant ~__context ~self ~value:Ref.null -(** remove all host driver entries for this host *) -let reset ~__context ~host = +(** remove all host driver entries that are not in [except]. We exepect + any list to be short *) +let remove ~__context ~host ~except = D.debug "%s" __FUNCTION__ ; let open Xapi_database.Db_filter_types in let expr = Eq (Field "host", Literal (Ref.string_of host)) in - let drivers = Db.Host_driver.get_refs_where ~__context ~expr in - drivers |> List.iter (fun self -> destroy ~__context ~self) + Db.Host_driver.get_refs_where ~__context ~expr + |> List.filter (fun driver -> not @@ List.mem driver except) + |> List.iter (fun self -> destroy ~__context ~self) -(** Runs on [host] *) +(** Runs on [host]. We update or create an entry for each driver + reported by drivertool and remove any extra driver that is in xapi. *) let scan ~__context ~host = - T.Mock.install () ; + Tool.Mock.install () ; let null = Ref.null in - drivertool ["list"] - |> T.parse - |> List.iter @@ fun (_name, driver) -> - let driver_ref = - create' ~__context ~host ~name:driver.T.name ~friendly_name:driver.T.name - ~info:driver.T.info ~active_variant:null ~selected_variant:null - ~description:driver.T.descr ~_type:driver.T.ty - in - driver.T.variants - |> List.iter @@ fun (name, v) -> - let var_ref = - Variant.create' ~__context ~name ~version:v.T.version - ~driver:driver_ref ~hw_present:v.T.hw_present ~priority:v.T.priority - ~dev_status:v.T.dev_status - in - ( match driver.T.selected with - | Some v when v = name -> - Db.Host_driver.set_selected_variant ~__context ~self:driver_ref - ~value:var_ref - | _ -> - () - ) ; - match driver.T.active with - | Some v when v = name -> - Db.Host_driver.set_active_variant ~__context ~self:driver_ref - ~value:var_ref - | _ -> - () + let drivers (* on this host *) = + Tool.call ["list"] + |> Tool.parse + |> List.map @@ fun (_name, driver) -> + let driver_ref = + create' ~__context ~host ~name:driver.Tool.name + ~friendly_name:driver.Tool.name ~info:driver.Tool.info + ~active_variant:null ~selected_variant:null + ~description:driver.Tool.descr ~_type:driver.Tool.ty + in + (driver.Tool.variants + |> List.iter @@ fun (name, v) -> + let var_ref = + Variant.create' ~__context ~name ~version:v.Tool.version + ~driver:driver_ref ~hw_present:v.Tool.hw_present + ~priority:v.Tool.priority ~dev_status:v.Tool.dev_status + in + ( match driver.Tool.selected with + | Some v when v = name -> + Db.Host_driver.set_selected_variant ~__context ~self:driver_ref + ~value:var_ref + | _ -> + () + ) ; + match driver.Tool.active with + | Some v when v = name -> + Db.Host_driver.set_active_variant ~__context ~self:driver_ref + ~value:var_ref + | _ -> + () + ) ; + driver_ref + in + remove ~__context ~host ~except:drivers (** Runs on [host] *) let rescan ~__context ~host = debug "%s" __FUNCTION__ ; scan ~__context ~host diff --git a/ocaml/xapi/xapi_host_driver_tool.ml b/ocaml/xapi/xapi_host_driver_tool.ml index a35608b84f1..f55d03ad3c0 100644 --- a/ocaml/xapi/xapi_host_driver_tool.ml +++ b/ocaml/xapi/xapi_host_driver_tool.ml @@ -16,9 +16,7 @@ host-driver-tool that manages multi-version drivers and reports the state of them in JSON *) -module D = Debug.Make (struct let name = __MODULE__ end) - -open D +open Debug.Make (struct let name = __MODULE__ end) let internal_error fmt = Printf.ksprintf @@ -234,6 +232,16 @@ let read path = | exception e -> raise e +let call args = + let path = !Xapi_globs.driver_tool in + try + let stdout, _stderr = Forkhelpers.execute_command_get_output path args in + debug "%s: executed %s %s" __FUNCTION__ path (String.concat " " args) ; + stdout + with e -> + internal_error "%s: failed to run %s %s: %s" __FUNCTION__ path + (String.concat " " args) (Printexc.to_string e) + module Mock = struct let drivertool_sh = {|#!/usr/bin/env bash diff --git a/ocaml/xapi/xapi_host_driver_tool.mli b/ocaml/xapi/xapi_host_driver_tool.mli index 6957aa0a866..5eb40825f09 100644 --- a/ocaml/xapi/xapi_host_driver_tool.mli +++ b/ocaml/xapi/xapi_host_driver_tool.mli @@ -37,6 +37,9 @@ val parse : string -> (string * driver) list val read : string -> (string * driver) list (** read from a file whose path is provided *) +val call : string list -> string +(** invoke drivertool with argumtns and return stdout *) + (** install a mock drivertool.sh *) module Mock : sig val install : unit -> unit diff --git a/quality-gate.sh b/quality-gate.sh index 9743046fb0c..ca50cac6ab2 100755 --- a/quality-gate.sh +++ b/quality-gate.sh @@ -25,9 +25,7 @@ verify-cert () { } mli-files () { -<<<<<<< HEAD - N=496 - N=499 + N=498 X="ocaml/tests" X+="|ocaml/quicktest" X+="|ocaml/message-switch/core_test" From f34ddfece9b6ca690878f77120239c59e315e877 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 07/53] MVD CP-52334 multi-version driver API/CLI - small fixes Address review comments; fix typos and parameter names. Signed-off-by: Christian Lindig --- ocaml/idl/datamodel_host.ml | 2 +- ocaml/idl/datamodel_host_driver.ml | 2 +- ocaml/util/dune | 2 +- ocaml/util/xapi_host_driver_helpers.ml | 6 +----- ocaml/xapi/dbsync_slave.ml | 2 +- ocaml/xapi/message_forwarding.ml | 10 +++++----- ocaml/xapi/xapi_host.ml | 3 ++- ocaml/xapi/xapi_host.mli | 2 +- 8 files changed, 13 insertions(+), 16 deletions(-) diff --git a/ocaml/idl/datamodel_host.ml b/ocaml/idl/datamodel_host.ml index 0505807dd67..10607e7e382 100644 --- a/ocaml/idl/datamodel_host.ml +++ b/ocaml/idl/datamodel_host.ml @@ -2290,7 +2290,7 @@ let apply_updates = let rescan_drivers = call ~name:"rescan_drivers" ~in_oss_since:None ~lifecycle:[] ~doc:"Scan the host and update its driver information." - ~params:[(Ref _host, "host", "The host to be rescanned")] + ~params:[(Ref _host, "self", "The host to be rescanned")] ~allowed_roles:_R_POOL_ADMIN () let copy_primary_host_certs = diff --git a/ocaml/idl/datamodel_host_driver.ml b/ocaml/idl/datamodel_host_driver.ml index e61ca372337..0d1becae339 100644 --- a/ocaml/idl/datamodel_host_driver.ml +++ b/ocaml/idl/datamodel_host_driver.ml @@ -77,7 +77,7 @@ let t = "selected_variant" "Variant (if any) selected to become active after reboot. Or Null" ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "type" - "Devive type this driver supports, like network or storage" + "Device type this driver supports, like network or storage" ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "description" "Description of the driver" ; field ~lifecycle:[] ~qualifier:DynamicRO ~ty:String "info" diff --git a/ocaml/util/dune b/ocaml/util/dune index d9380eecbc9..488cf4f444f 100644 --- a/ocaml/util/dune +++ b/ocaml/util/dune @@ -20,6 +20,6 @@ (library (name xapi_host_driver_helpers) (modules xapi_host_driver_helpers) - (libraries yojson angstrom) + (libraries yojson angstrom xapi-stdext-unix) (wrapped false) ) diff --git a/ocaml/util/xapi_host_driver_helpers.ml b/ocaml/util/xapi_host_driver_helpers.ml index 61ce6b8e537..2f855b0222a 100644 --- a/ocaml/util/xapi_host_driver_helpers.ml +++ b/ocaml/util/xapi_host_driver_helpers.ml @@ -22,11 +22,7 @@ let int n = Int32.to_int n let ( // ) = Filename.concat (** Read a (small) file into a string *) -let read path = - let ic = open_in path in - defer (fun () -> close_in ic) @@ fun () -> - let size = in_channel_length ic in - really_input_string ic size +let read path = Xapi_stdext_unix.Unixext.string_of_file path type note = {typ: int32; name: string; desc: string} diff --git a/ocaml/xapi/dbsync_slave.ml b/ocaml/xapi/dbsync_slave.ml index 1c1b5f1f241..597cd7b5704 100644 --- a/ocaml/xapi/dbsync_slave.ml +++ b/ocaml/xapi/dbsync_slave.ml @@ -355,7 +355,7 @@ let update_env __context sync_keys = ) ; switched_sync Xapi_globs.sync_host_driver (fun () -> debug "%s" __FUNCTION__ ; - ignore (Xapi_host.rescan_drivers ~__context ~host:localhost) + ignore (Xapi_host.rescan_drivers ~__context ~self:localhost) ) ; (* CA-35549: In a pool rolling upgrade, the master will detect the end of upgrade when the software versions diff --git a/ocaml/xapi/message_forwarding.ml b/ocaml/xapi/message_forwarding.ml index 704b56acd10..c843b563918 100644 --- a/ocaml/xapi/message_forwarding.ml +++ b/ocaml/xapi/message_forwarding.ml @@ -4143,12 +4143,12 @@ functor info "Host.apply_updates: host = '%s'; hash = '%s'" uuid hash ; Local.Host.apply_updates ~__context ~self ~hash - let rescan_drivers ~__context ~host = - let uuid = host_uuid ~__context host in + let rescan_drivers ~__context ~self = + let uuid = host_uuid ~__context self in info "Host.rescan_drivers: host = '%s'" uuid ; - let local_fn = Local.Host.rescan_drivers ~host in - do_op_on ~local_fn ~__context ~host (fun session_id rpc -> - Client.Host.rescan_drivers ~rpc ~session_id ~host + let local_fn = Local.Host.rescan_drivers ~self in + do_op_on ~local_fn ~__context ~host:self (fun session_id rpc -> + Client.Host.rescan_drivers ~rpc ~session_id ~self ) let set_https_only ~__context ~self ~value = diff --git a/ocaml/xapi/xapi_host.ml b/ocaml/xapi/xapi_host.ml index 5b37873219e..99b1c4db0ae 100644 --- a/ocaml/xapi/xapi_host.ml +++ b/ocaml/xapi/xapi_host.ml @@ -3089,7 +3089,8 @@ let apply_updates ~__context ~self ~hash = Db.Host.set_last_update_hash ~__context ~self ~value:hash ; warnings -let rescan_drivers ~__context ~host = Xapi_host_driver.scan ~__context ~host +let rescan_drivers ~__context ~self = + Xapi_host_driver.scan ~__context ~host:self let cc_prep () = let cc = "CC_PREPARATIONS" in diff --git a/ocaml/xapi/xapi_host.mli b/ocaml/xapi/xapi_host.mli index 74131a59974..c81b4e7b219 100644 --- a/ocaml/xapi/xapi_host.mli +++ b/ocaml/xapi/xapi_host.mli @@ -555,7 +555,7 @@ val get_host_updates_handler : Http.Request.t -> Unix.file_descr -> 'a -> unit val apply_updates : __context:Context.t -> self:API.ref_host -> hash:string -> string list list -val rescan_drivers : __context:Context.t -> host:API.ref_host -> unit +val rescan_drivers : __context:Context.t -> self:API.ref_host -> unit val copy_primary_host_certs : __context:Context.t -> host:API.ref_host -> unit From 2bca3911e7772aa104f5360fdc07014d8e832de2 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 08/53] MVD CP-52334 multi-version driver API/CLI - update schema hash Signed-off-by: Christian Lindig --- ocaml/idl/schematest.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocaml/idl/schematest.ml b/ocaml/idl/schematest.ml index 5e0416156c1..283164de4c6 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 = "36cb241822399344b119cce654162d3f" +let last_known_schema_hash = "458f20f5270a5615c7ee92be8a383172" let current_schema_hash : string = let open Datamodel_types in From dc076cb16e94959faa9057c337b5629d95ee00a4 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 09/53] MVD CP-52334 multi-version driver API/CLI - remove dead code Signed-off-by: Christian Lindig --- ocaml/util/xapi_host_driver_helpers.ml | 2 -- 1 file changed, 2 deletions(-) diff --git a/ocaml/util/xapi_host_driver_helpers.ml b/ocaml/util/xapi_host_driver_helpers.ml index 2f855b0222a..4910ed8d11f 100644 --- a/ocaml/util/xapi_host_driver_helpers.ml +++ b/ocaml/util/xapi_host_driver_helpers.ml @@ -15,8 +15,6 @@ module J = Yojson open Angstrom -let defer finally = Fun.protect ~finally - let int n = Int32.to_int n let ( // ) = Filename.concat From c6c775363939cbc8be704634181cb60b9256f1f4 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 10/53] MVD CP-52334 multi-version driver API/CLI - fix typo This fixes a typo in list of fields we show in the CLI. Signed-off-by: Christian Lindig --- ocaml/xapi-cli-server/cli_operations.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocaml/xapi-cli-server/cli_operations.ml b/ocaml/xapi-cli-server/cli_operations.ml index d15054207a7..9a6381892b1 100644 --- a/ocaml/xapi-cli-server/cli_operations.ml +++ b/ocaml/xapi-cli-server/cli_operations.ml @@ -1332,7 +1332,7 @@ let gen_cmds rpc session_id = ; "driver-name" ; "name" ; "version" - ; "priotory" + ; "priority" ; "host-uuid" ; "driver-uuid" ; "active" From 05c1fdf0c02ac7d6bf8034942626581d4f09a400 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 11/53] MVD CP-52334 multi-version driver API/CLI - regen datamodel_lifecycle Re-generate datamodel_lifecycle.ml to reflect correctly the current version of the API. Signed-off-by: Christian Lindig --- ocaml/idl/datamodel_lifecycle.ml | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/ocaml/idl/datamodel_lifecycle.ml b/ocaml/idl/datamodel_lifecycle.ml index cb3c3e3abb0..b47fb49ab61 100644 --- a/ocaml/idl/datamodel_lifecycle.ml +++ b/ocaml/idl/datamodel_lifecycle.ml @@ -1,8 +1,8 @@ let prototyped_of_class = function | "Driver_variant" -> - Some "24.36.0" + Some "25.1.0-next" | "Host_driver" -> - Some "24.35.0" + Some "25.1.0-next" | "VM_group" -> Some "24.19.1" | "Observer" -> @@ -14,39 +14,39 @@ let prototyped_of_class = function let prototyped_of_field = function | "Driver_variant", "status" -> - Some "24.36.0" + Some "25.1.0-next" | "Driver_variant", "priority" -> - Some "24.36.0" + Some "25.1.0-next" | "Driver_variant", "hardware_present" -> - Some "24.36.0" + Some "25.1.0-next" | "Driver_variant", "version" -> - Some "24.36.0" + Some "25.1.0-next" | "Driver_variant", "driver" -> - Some "24.36.0" + Some "25.1.0-next" | "Driver_variant", "name" -> - Some "24.36.0" + Some "25.1.0-next" | "Driver_variant", "uuid" -> - Some "24.36.0" + Some "25.1.0-next" | "Host_driver", "info" -> - Some "24.39.0" + Some "25.1.0-next" | "Host_driver", "description" -> - Some "24.39.0" + Some "25.1.0-next" | "Host_driver", "type" -> - Some "24.39.0" + Some "25.1.0-next" | "Host_driver", "selected_variant" -> - Some "24.36.0" + Some "25.1.0-next" | "Host_driver", "active_variant" -> - Some "24.36.0" + Some "25.1.0-next" | "Host_driver", "variants" -> - Some "24.36.0" + Some "25.1.0-next" | "Host_driver", "friendly_name" -> - Some "24.39.0" + Some "25.1.0-next" | "Host_driver", "name" -> - Some "24.35.0" + Some "25.1.0-next" | "Host_driver", "host" -> - Some "24.35.0" + Some "25.1.0-next" | "Host_driver", "uuid" -> - Some "24.35.0" + Some "25.1.0-next" | "VM_group", "VMs" -> Some "24.19.1" | "VM_group", "placement" -> @@ -156,13 +156,13 @@ let prototyped_of_field = function let prototyped_of_message = function | "Driver_variant", "select" -> - Some "24.39.0" + Some "25.1.0-next" | "Host_driver", "rescan" -> - Some "24.40.0" + Some "25.1.0-next" | "Host_driver", "deselect" -> - Some "24.35.0" + Some "25.1.0-next" | "Host_driver", "select" -> - Some "24.35.0" + Some "25.1.0-next" | "Observer", "set_components" -> Some "23.14.0" | "Observer", "set_endpoints" -> @@ -206,7 +206,7 @@ let prototyped_of_message = function | "host", "set_https_only" -> Some "22.27.0" | "host", "rescan_drivers" -> - Some "24.35.0" + Some "25.1.0-next" | "host", "set_numa_affinity_policy" -> Some "24.0.0" | "VM", "get_secureboot_readiness" -> From b576ba0f5e20207115ff7d6c9abbd517dc4b0e1b Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Thu, 16 Jan 2025 15:23:24 +0000 Subject: [PATCH 12/53] Update ocaml/xapi-cli-server/records.ml Co-authored-by: Pau Ruiz Safont Signed-off-by: Christian Lindig --- ocaml/xapi-cli-server/records.ml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ocaml/xapi-cli-server/records.ml b/ocaml/xapi-cli-server/records.ml index b2ca1d1d5fd..940d202c654 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -5449,14 +5449,9 @@ let host_driver_record rpc session_id host_driver = () ; make_field ~name:"variants" ~get:(fun () -> - xv () - |> List.map (fun (_, v) -> - (v.API.driver_variant_name, v.API.driver_variant_version) - ) - |> List.map (fun (name, version) -> - Printf.sprintf "%s/%s" name version - ) - |> String.concat "; " + map_and_concat + (fun _, v -> Printf.sprintf "%s/%s" v.API.driver_variant_name v.API.driver_variant_version) + (xv ()) ) () ; make_field ~name:"variants-dev-status" From 31adafc3ce796e58fa1c996cdc70ff58489cc88b Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Thu, 16 Jan 2025 15:23:43 +0000 Subject: [PATCH 13/53] Update ocaml/xapi-cli-server/records.ml Co-authored-by: Pau Ruiz Safont Signed-off-by: Christian Lindig --- ocaml/xapi-cli-server/records.ml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ocaml/xapi-cli-server/records.ml b/ocaml/xapi-cli-server/records.ml index 940d202c654..71906d25459 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -5471,13 +5471,9 @@ let host_driver_record rpc session_id host_driver = () ; make_field ~name:"variants-uuid" ~get:(fun () -> - xv () - |> List.map (fun (_, v) -> - (v.API.driver_variant_name, v.API.driver_variant_uuid) - ) - |> List.map (fun (name, uuid) -> Printf.sprintf "%s=%s" name uuid) - |> String.concat "; " - ) + map_and_concat + (fun _, v -> Printf.sprintf "%s/%s" v.API.driver_variant_name v.API.driver_variant_uuid) + (xv ()) () ; make_field ~name:"variants-hw-present" ~get:(fun () -> From 56f7b74dd8a430812a4f83c509688f135603ff79 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Thu, 16 Jan 2025 15:23:57 +0000 Subject: [PATCH 14/53] Update ocaml/xapi-cli-server/records.ml Co-authored-by: Pau Ruiz Safont Signed-off-by: Christian Lindig --- ocaml/xapi-cli-server/records.ml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/ocaml/xapi-cli-server/records.ml b/ocaml/xapi-cli-server/records.ml index 71906d25459..d47b0c0fb65 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -5477,16 +5477,13 @@ let host_driver_record rpc session_id host_driver = () ; make_field ~name:"variants-hw-present" ~get:(fun () -> - xv () - |> List.map (fun (_, v) -> - ( v.API.driver_variant_name - , v.API.driver_variant_version - , v.API.driver_variant_hardware_present - ) - ) - |> List.filter (fun (_, _, status) -> status = true) - |> List.map (fun (name, _, _) -> name) - |> String.concat "; " + map_filter_and_concat (fun _, v -> + if v.API.driver_variant_hardware_present then + Some v.API.driver_variant_name + else + None + ) + (xv ()) ) () ] From eef7fe2e1dc6845fa75fc52644670c798c93039b Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 15/53] MVD CP-52334 multi-version driver API/CLI - fix documentation Fix documentation for select() method. Signed-off-by: Christian Lindig --- doc/content/toolstack/features/MVD/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/content/toolstack/features/MVD/index.md b/doc/content/toolstack/features/MVD/index.md index 7d8589cdcfe..ed31722295f 100644 --- a/doc/content/toolstack/features/MVD/index.md +++ b/doc/content/toolstack/features/MVD/index.md @@ -289,8 +289,8 @@ this is part of a host object.) up storage, creating resource pools and managing patches, high availability (HA) and workload balancing (WLB)" -* `select (self, version)`; select `version` of driver `self`. Selecting - the version (a string) of an existing driver. +* `select (self, variant)`; select `variant` of driver `self`. Selecting + the variant (a reference) of an existing driver. * `deselect(self)`: this driver can't be loaded next time the kernel is looking for a driver. This is a potentially dangerous operation, so it's From ab4d4fa038ac9d55df49f0bdecacdca84153b120 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 16/53] MVD CP-52334 multi-version driver API/CLI - improve records Use more existing functions to format records. Signed-off-by: Christian Lindig --- ocaml/xapi-cli-server/records.ml | 35 ++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/ocaml/xapi-cli-server/records.ml b/ocaml/xapi-cli-server/records.ml index d47b0c0fb65..178bc45c6bc 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -5449,9 +5449,12 @@ let host_driver_record rpc session_id host_driver = () ; make_field ~name:"variants" ~get:(fun () -> - map_and_concat - (fun _, v -> Printf.sprintf "%s/%s" v.API.driver_variant_name v.API.driver_variant_version) - (xv ()) + map_and_concat + (fun (_, v) -> + Printf.sprintf "%s/%s" v.API.driver_variant_name + v.API.driver_variant_version + ) + (xv ()) ) () ; make_field ~name:"variants-dev-status" @@ -5471,19 +5474,25 @@ let host_driver_record rpc session_id host_driver = () ; make_field ~name:"variants-uuid" ~get:(fun () -> - map_and_concat - (fun _, v -> Printf.sprintf "%s/%s" v.API.driver_variant_name v.API.driver_variant_uuid) - (xv ()) + map_and_concat + (fun (_, v) -> + Printf.sprintf "%s/%s" v.API.driver_variant_name + v.API.driver_variant_uuid + ) + (xv ()) + ) () ; make_field ~name:"variants-hw-present" ~get:(fun () -> - map_filter_and_concat (fun _, v -> - if v.API.driver_variant_hardware_present then - Some v.API.driver_variant_name - else - None - ) - (xv ()) + xv () + |> List.map (fun (_, v) -> + ( v.API.driver_variant_name + , v.API.driver_variant_version + , v.API.driver_variant_hardware_present + ) + ) + |> List.filter (fun (_, _, status) -> status = true) + |> map_and_concat (fun (name, _, _) -> name) ) () ] From cf34f2eac892abb19cab1b491d875ef0b2f6ce5a Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Wed, 4 Dec 2024 15:29:50 +0000 Subject: [PATCH 17/53] MVD CP-52334 multi-version driver API/CLI - update datamodel_lifecycle.ml Signed-off-by: Christian Lindig --- ocaml/idl/datamodel_lifecycle.ml | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/ocaml/idl/datamodel_lifecycle.ml b/ocaml/idl/datamodel_lifecycle.ml index b47fb49ab61..738a5ef397f 100644 --- a/ocaml/idl/datamodel_lifecycle.ml +++ b/ocaml/idl/datamodel_lifecycle.ml @@ -1,8 +1,8 @@ let prototyped_of_class = function | "Driver_variant" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver" -> - Some "25.1.0-next" + Some "25.2.0" | "VM_group" -> Some "24.19.1" | "Observer" -> @@ -14,39 +14,39 @@ let prototyped_of_class = function let prototyped_of_field = function | "Driver_variant", "status" -> - Some "25.1.0-next" + Some "25.2.0" | "Driver_variant", "priority" -> - Some "25.1.0-next" + Some "25.2.0" | "Driver_variant", "hardware_present" -> - Some "25.1.0-next" + Some "25.2.0" | "Driver_variant", "version" -> - Some "25.1.0-next" + Some "25.2.0" | "Driver_variant", "driver" -> - Some "25.1.0-next" + Some "25.2.0" | "Driver_variant", "name" -> - Some "25.1.0-next" + Some "25.2.0" | "Driver_variant", "uuid" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "info" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "description" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "type" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "selected_variant" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "active_variant" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "variants" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "friendly_name" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "name" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "host" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "uuid" -> - Some "25.1.0-next" + Some "25.2.0" | "VM_group", "VMs" -> Some "24.19.1" | "VM_group", "placement" -> @@ -156,13 +156,13 @@ let prototyped_of_field = function let prototyped_of_message = function | "Driver_variant", "select" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "rescan" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "deselect" -> - Some "25.1.0-next" + Some "25.2.0" | "Host_driver", "select" -> - Some "25.1.0-next" + Some "25.2.0" | "Observer", "set_components" -> Some "23.14.0" | "Observer", "set_endpoints" -> @@ -206,7 +206,7 @@ let prototyped_of_message = function | "host", "set_https_only" -> Some "22.27.0" | "host", "rescan_drivers" -> - Some "25.1.0-next" + Some "25.2.0" | "host", "set_numa_affinity_policy" -> Some "24.0.0" | "VM", "get_secureboot_readiness" -> From fc84b33c6925c1ad763660cccabc295d45febfa5 Mon Sep 17 00:00:00 2001 From: Rob Hoes Date: Tue, 21 Jan 2025 10:44:44 +0000 Subject: [PATCH 18/53] Remove unused function Signed-off-by: Rob Hoes --- ocaml/xenopsd/xc/xenops_server_xen.ml | 48 --------------------------- 1 file changed, 48 deletions(-) diff --git a/ocaml/xenopsd/xc/xenops_server_xen.ml b/ocaml/xenopsd/xc/xenops_server_xen.ml index 7f6ede23895..071eae2e062 100644 --- a/ocaml/xenopsd/xc/xenops_server_xen.ml +++ b/ocaml/xenopsd/xc/xenops_server_xen.ml @@ -2385,54 +2385,6 @@ module VM = struct Option.is_some (event_wait internal_updates task timeout is_vm_event vm_has_shutdown) - (* Mount a filesystem somewhere, with optional type *) - let mount ?(ty = None) src dest write = - let ty = match ty with None -> [] | Some ty -> ["-t"; ty] in - run !Xc_resources.mount - (ty @ [src; dest; "-o"; (if write then "rw" else "ro")]) - |> ignore_string - - let timeout = 300. - - (* 5 minutes: something is seriously wrong if we hit this timeout *) - - exception Umount_timeout - - (** Unmount a mountpoint. Retries every 5 secs for a total of 5mins before - returning failure *) - let umount ?(retry = true) dest = - let finished = ref false in - let start = Unix.gettimeofday () in - while (not !finished) && Unix.gettimeofday () -. start < timeout do - try - run !Xc_resources.umount [dest] |> ignore_string ; - finished := true - with e -> - if not retry then raise e ; - debug - "Caught exception (%s) while unmounting %s: pausing before retrying" - (Printexc.to_string e) dest ; - Thread.delay 5. - done ; - if not !finished then raise Umount_timeout - - let with_mounted_dir_ro device f = - let mount_point = Filename.temp_file "xenops_mount_" "" in - Unix.unlink mount_point ; - Unix.mkdir mount_point 0o640 ; - finally - (fun () -> - mount ~ty:(Some "ext2") device mount_point false ; - f mount_point - ) - (fun () -> - ( try umount mount_point - with e -> debug "Caught %s" (Printexc.to_string e) - ) ; - try Unix.rmdir mount_point - with e -> debug "Caught %s" (Printexc.to_string e) - ) - (* A raw image is a file or device in contrast to a directory where would need to open a file *) let is_raw_image path = From 0936fca3c43366e0969f05a5a434895499c719b7 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Mon, 28 Oct 2024 13:54:57 +0000 Subject: [PATCH 19/53] Add more logging to the vhd-tool Signed-off-by: Vincent Liu --- ocaml/vhd-tool/src/dune | 1 + ocaml/vhd-tool/src/impl.ml | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ocaml/vhd-tool/src/dune b/ocaml/vhd-tool/src/dune index f7ab6341f77..5981ca37751 100644 --- a/ocaml/vhd-tool/src/dune +++ b/ocaml/vhd-tool/src/dune @@ -32,6 +32,7 @@ tapctl xapi-stdext-std xapi-stdext-unix + xapi-log xen-api-client-lwt ) (preprocess diff --git a/ocaml/vhd-tool/src/impl.ml b/ocaml/vhd-tool/src/impl.ml index 52f2b3aa501..a3c1fca1fa4 100644 --- a/ocaml/vhd-tool/src/impl.ml +++ b/ocaml/vhd-tool/src/impl.ml @@ -17,6 +17,8 @@ open Lwt module F = Vhd_format.F.From_file (Vhd_format_lwt.IO) module In = Vhd_format.F.From_input (Input) +module D = Debug.Make (struct let name = "vhd_impl" end) + module Channel_In = Vhd_format.F.From_input (struct include Lwt @@ -229,9 +231,10 @@ let stream_nbd _common c s prezeroed _ ?(progress = no_progress_bar) () = } in - Client.negotiate c "" >>= fun (server, _size, _flags) -> + Client.negotiate c "" >>= fun (server, size, _flags) -> (* Work to do is: non-zero data to write + empty sectors if the target is not prezeroed *) + D.debug "%s nbd negotiation done, size is %Ld" __FUNCTION__ size ; let total_work = let open Vhd_format.F in Int64.( @@ -1088,7 +1091,7 @@ let write_stream common s destination destination_protocol prezeroed progress x | None -> let t = List.hd possible_protocols in - Printf.fprintf stderr "Using protocol: %s\n%!" (string_of_protocol t) ; + D.info "Using protocol: %s\n%!" (string_of_protocol t) ; t in if not (List.mem destination_protocol possible_protocols) then From af3e3ea6d1669bf88f98f874ca4b246ab09c0e24 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Mon, 28 Oct 2024 15:25:15 +0000 Subject: [PATCH 20/53] chore: Some housekeeping on comments and minor refactoring Signed-off-by: Vincent Liu --- ocaml/tests/test_storage_migrate_state.ml | 11 ++++++----- ocaml/xapi/xapi_vm_migrate.ml | 6 ++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ocaml/tests/test_storage_migrate_state.ml b/ocaml/tests/test_storage_migrate_state.ml index d822b7ef393..c6940c8dafb 100644 --- a/ocaml/tests/test_storage_migrate_state.ml +++ b/ocaml/tests/test_storage_migrate_state.ml @@ -44,14 +44,15 @@ let sample_send_state = } let sample_receive_state = + let open Storage_interface in Storage_migrate.State.Receive_state. { - sr= Storage_interface.Sr.of_string "my_sr" - ; dummy_vdi= Storage_interface.Vdi.of_string "dummy_vdi" - ; leaf_vdi= Storage_interface.Vdi.of_string "leaf_vdi" + sr= Sr.of_string "my_sr" + ; dummy_vdi= Vdi.of_string "dummy_vdi" + ; leaf_vdi= Vdi.of_string "leaf_vdi" ; leaf_dp= "leaf_dp" - ; parent_vdi= Storage_interface.Vdi.of_string "parent_vdi" - ; remote_vdi= Storage_interface.Vdi.of_string "remote_vdi" + ; parent_vdi= Vdi.of_string "parent_vdi" + ; remote_vdi= Vdi.of_string "remote_vdi" } let sample_copy_state = diff --git a/ocaml/xapi/xapi_vm_migrate.ml b/ocaml/xapi/xapi_vm_migrate.ml index 3b561e370ab..9b2912b4d50 100644 --- a/ocaml/xapi/xapi_vm_migrate.ml +++ b/ocaml/xapi/xapi_vm_migrate.ml @@ -1515,7 +1515,8 @@ let migrate_send' ~__context ~vm ~dest ~live:_ ~vdi_map ~vif_map ~vgpu_map ) vifs in - (* Destroy the local datapaths - this allows the VDIs to properly detach, invoking the migrate_finalize calls *) + (* Destroy the local datapaths - this allows the VDIs to properly detach, + invoking the migrate_finalize calls *) List.iter (fun mirror_record -> if mirror_record.mr_mirrored then @@ -1537,7 +1538,8 @@ let migrate_send' ~__context ~vm ~dest ~live:_ ~vdi_map ~vif_map ~vgpu_map TaskHelper.exn_if_cancelling ~__context ; TaskHelper.set_not_cancellable ~__context ) ; - (* It's acceptable for the VM not to exist at this point; shutdown commutes with storage migrate *) + (* It's acceptable for the VM not to exist at this point; shutdown commutes + with storage migrate *) ( try Xapi_xenops.Events_from_xenopsd.with_suppressed queue_name dbg vm_uuid (fun () -> From a8ba0b9ecf71d9f61ded9b9a2129f8295427a130 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Mon, 4 Nov 2024 14:54:17 +0000 Subject: [PATCH 21/53] Refactor feature processing logic in `Smint` The previous feature processing logic was a bit ad hoc with various random conversion functions. Abstract them into one module and refactor a few functions to make their uses more explicit. Also try to encourage users, when processing features, to convert them into `Feature.t` and use the provided functions in this module rather than writing `List.mem` directly. Signed-off-by: Vincent Liu --- ocaml/tests/test_sm_features.ml | 20 +-- ocaml/xapi/sm.ml | 2 +- ocaml/xapi/sm_exec.ml | 4 +- ocaml/xapi/smint.ml | 245 ++++++++++++++++--------------- ocaml/xapi/storage_mux.ml | 10 +- ocaml/xapi/storage_smapiv1.ml | 10 +- ocaml/xapi/xapi_dr_task.ml | 3 +- ocaml/xapi/xapi_host.ml | 2 +- ocaml/xapi/xapi_pool.ml | 9 +- ocaml/xapi/xapi_sm.ml | 12 +- ocaml/xapi/xapi_sr.ml | 2 +- ocaml/xapi/xapi_sr_operations.ml | 35 +++-- ocaml/xapi/xapi_vdi.ml | 4 +- ocaml/xapi/xapi_vm_migrate.ml | 21 ++- ocaml/xapi/xapi_vm_snapshot.ml | 2 +- 15 files changed, 201 insertions(+), 180 deletions(-) diff --git a/ocaml/tests/test_sm_features.ml b/ocaml/tests/test_sm_features.ml index 091d58d4f6e..43bce4c3807 100644 --- a/ocaml/tests/test_sm_features.ml +++ b/ocaml/tests/test_sm_features.ml @@ -20,7 +20,7 @@ type sm_data_sequence = { (* Text feature list we get back as part of sr_get_driver_info. *) raw: string list ; (* SMAPIv1 driver info. *) - smapiv1_features: Smint.feature list + smapiv1_features: Smint.Feature.t list ; (* SMAPIv2 driver info. *) smapiv2_features: string list ; (* SM object created in the database. *) @@ -40,7 +40,6 @@ let string_of_sm_object sm = ) let test_sequences = - let open Smint in [ (* Test NFS driver features as of Clearwater. *) { @@ -179,14 +178,14 @@ module ParseSMAPIv1Features = Generic.MakeStateless (struct module Io = struct type input_t = string list - type output_t = Smint.feature list + type output_t = Smint.Feature.t list let string_of_input_t = Test_printers.(list string) - let string_of_output_t = Test_printers.(list Smint.string_of_feature) + let string_of_output_t = Test_printers.(list Smint.Feature.to_string) end - let transform = Smint.parse_capability_int64_features + let transform = Smint.Feature.parse_capability_int64 let tests = `QuickAndAutoDocumented @@ -198,16 +197,16 @@ end) module CreateSMAPIv2Features = Generic.MakeStateless (struct module Io = struct - type input_t = Smint.feature list + type input_t = Smint.Feature.t list type output_t = string list - let string_of_input_t = Test_printers.(list Smint.string_of_feature) + let string_of_input_t = Test_printers.(list Smint.Feature.to_string) let string_of_output_t = Test_printers.(list string) end - let transform = List.map Smint.string_of_feature + let transform = List.map Smint.Feature.to_string let tests = `QuickAndAutoDocumented @@ -276,9 +275,10 @@ module CompatSMFeatures = Generic.MakeStateless (struct end let transform l = + let open Smint.Feature in List.split l |> fun (x, y) -> - (Smint.parse_string_int64_features x, Smint.parse_string_int64_features y) - |> fun (x, y) -> Smint.compat_features x y |> List.map Smint.unparse_feature + (parse_string_int64 x, parse_string_int64 y) |> fun (x, y) -> + compat_features x y |> List.map unparse let tests = let r1, r2 = test_intersection_sequences in diff --git a/ocaml/xapi/sm.ml b/ocaml/xapi/sm.ml index 40e9b11e3e2..1d198cf3f98 100644 --- a/ocaml/xapi/sm.ml +++ b/ocaml/xapi/sm.ml @@ -117,7 +117,7 @@ let sr_detach ~dbg dconf driver sr = let sr_probe ~dbg dconf driver sr_sm_config = with_dbg ~dbg ~name:"sr_probe" @@ fun di -> let dbg = Debug_info.to_string di in - if List.mem_assoc Sr_probe (features_of_driver driver) then + if Feature.(has_capability Sr_probe (features_of_driver driver)) then Locking_helpers.Named_mutex.execute serialize_attach_detach (fun () -> debug "sr_probe" driver (sprintf "sm_config=[%s]" diff --git a/ocaml/xapi/sm_exec.ml b/ocaml/xapi/sm_exec.ml index d97e8f41e9b..d689b019bcc 100644 --- a/ocaml/xapi/sm_exec.ml +++ b/ocaml/xapi/sm_exec.ml @@ -551,8 +551,8 @@ let parse_sr_get_driver_info driver (xml : Xml.xml) = let strings = XMLRPC.From.array XMLRPC.From.string (safe_assoc "capabilities" info) in - let features = Smint.parse_capability_int64_features strings in - let text_features = List.map Smint.string_of_feature features in + let features = Smint.Feature.parse_capability_int64 strings in + let text_features = List.map Smint.Feature.to_string features in (* Parse the driver options *) let configuration = List.map diff --git a/ocaml/xapi/smint.ml b/ocaml/xapi/smint.ml index 8797e0d7cf6..2cdf474fd8a 100644 --- a/ocaml/xapi/smint.ml +++ b/ocaml/xapi/smint.ml @@ -21,99 +21,96 @@ open D type vdi_info = {vdi_info_uuid: string option; vdi_info_location: string} -(** Very primitive first attempt at a set of backend features *) -type capability = - | Sr_create - | Sr_delete - | Sr_attach - | Sr_detach - | Sr_scan - | Sr_probe - | Sr_update - | Sr_supports_local_caching - | Sr_stats - | Sr_metadata - | Sr_trim - | Sr_multipath - | Vdi_create - | Vdi_delete - | Vdi_attach - | Vdi_detach - | Vdi_mirror - | Vdi_clone - | Vdi_snapshot - | Vdi_resize - | Vdi_activate - | Vdi_activate_readonly - | Vdi_deactivate - | Vdi_update - | Vdi_introduce - | Vdi_resize_online - | Vdi_generate_config - | Vdi_attach_offline - | Vdi_reset_on_boot - | Vdi_configure_cbt - | Large_vdi (** Supports >2TB VDIs *) - | Thin_provisioning - | Vdi_read_caching - -type feature = capability * int64 - -let string_to_capability_table = - [ - ("SR_CREATE", Sr_create) - ; ("SR_DELETE", Sr_delete) - ; ("SR_ATTACH", Sr_attach) - ; ("SR_DETACH", Sr_detach) - ; ("SR_SCAN", Sr_scan) - ; ("SR_PROBE", Sr_probe) - ; ("SR_UPDATE", Sr_update) - ; ("SR_SUPPORTS_LOCAL_CACHING", Sr_supports_local_caching) - ; ("SR_METADATA", Sr_metadata) - ; ("SR_TRIM", Sr_trim) - ; ("SR_MULTIPATH", Sr_multipath) - ; ("SR_STATS", Sr_stats) - ; ("VDI_CREATE", Vdi_create) - ; ("VDI_DELETE", Vdi_delete) - ; ("VDI_ATTACH", Vdi_attach) - ; ("VDI_DETACH", Vdi_detach) - ; ("VDI_MIRROR", Vdi_mirror) - ; ("VDI_RESIZE", Vdi_resize) - ; ("VDI_RESIZE_ONLINE", Vdi_resize_online) - ; ("VDI_CLONE", Vdi_clone) - ; ("VDI_SNAPSHOT", Vdi_snapshot) - ; ("VDI_ACTIVATE", Vdi_activate) - ; ("VDI_ACTIVATE_READONLY", Vdi_activate_readonly) - ; ("VDI_DEACTIVATE", Vdi_deactivate) - ; ("VDI_UPDATE", Vdi_update) - ; ("VDI_INTRODUCE", Vdi_introduce) - ; ("VDI_GENERATE_CONFIG", Vdi_generate_config) - ; ("VDI_ATTACH_OFFLINE", Vdi_attach_offline) - ; ("VDI_RESET_ON_BOOT", Vdi_reset_on_boot) - ; ("VDI_CONFIG_CBT", Vdi_configure_cbt) - ; ("LARGE_VDI", Large_vdi) - ; ("THIN_PROVISIONING", Thin_provisioning) - ; ("VDI_READ_CACHING", Vdi_read_caching) - ] - -let capability_to_string_table = - List.map (fun (k, v) -> (v, k)) string_to_capability_table - -let string_of_capability c = List.assoc c capability_to_string_table - -let string_of_feature (c, v) = - Printf.sprintf "%s/%Ld" (string_of_capability c) v - -let has_capability (c : capability) fl = List.mem_assoc c fl - -let capability_of_feature : feature -> capability = fst - -let known_features = List.map fst string_to_capability_table - -let unparse_feature (f, v) = f ^ "/" ^ Int64.to_string v - -let parse_string_int64_features features = - let scan feature = +module Feature = struct + (** Very primitive first attempt at a set of backend features *) + type capability = + | Sr_create + | Sr_delete + | Sr_attach + | Sr_detach + | Sr_scan + | Sr_probe + | Sr_update + | Sr_supports_local_caching + | Sr_stats + | Sr_metadata + | Sr_trim + | Sr_multipath + | Vdi_create + | Vdi_delete + | Vdi_attach + | Vdi_detach + | Vdi_mirror + | Vdi_clone + | Vdi_snapshot + | Vdi_resize + | Vdi_activate + | Vdi_activate_readonly + | Vdi_deactivate + | Vdi_update + | Vdi_introduce + | Vdi_resize_online + | Vdi_generate_config + | Vdi_attach_offline + | Vdi_reset_on_boot + | Vdi_configure_cbt + | Large_vdi (** Supports >2TB VDIs *) + | Thin_provisioning + | Vdi_read_caching + + type t = capability * int64 + + let string_to_capability_table = + [ + ("SR_CREATE", Sr_create) + ; ("SR_DELETE", Sr_delete) + ; ("SR_ATTACH", Sr_attach) + ; ("SR_DETACH", Sr_detach) + ; ("SR_SCAN", Sr_scan) + ; ("SR_PROBE", Sr_probe) + ; ("SR_UPDATE", Sr_update) + ; ("SR_SUPPORTS_LOCAL_CACHING", Sr_supports_local_caching) + ; ("SR_METADATA", Sr_metadata) + ; ("SR_TRIM", Sr_trim) + ; ("SR_MULTIPATH", Sr_multipath) + ; ("SR_STATS", Sr_stats) + ; ("VDI_CREATE", Vdi_create) + ; ("VDI_DELETE", Vdi_delete) + ; ("VDI_ATTACH", Vdi_attach) + ; ("VDI_DETACH", Vdi_detach) + ; ("VDI_MIRROR", Vdi_mirror) + ; ("VDI_RESIZE", Vdi_resize) + ; ("VDI_RESIZE_ONLINE", Vdi_resize_online) + ; ("VDI_CLONE", Vdi_clone) + ; ("VDI_SNAPSHOT", Vdi_snapshot) + ; ("VDI_ACTIVATE", Vdi_activate) + ; ("VDI_ACTIVATE_READONLY", Vdi_activate_readonly) + ; ("VDI_DEACTIVATE", Vdi_deactivate) + ; ("VDI_UPDATE", Vdi_update) + ; ("VDI_INTRODUCE", Vdi_introduce) + ; ("VDI_GENERATE_CONFIG", Vdi_generate_config) + ; ("VDI_ATTACH_OFFLINE", Vdi_attach_offline) + ; ("VDI_RESET_ON_BOOT", Vdi_reset_on_boot) + ; ("VDI_CONFIG_CBT", Vdi_configure_cbt) + ; ("LARGE_VDI", Large_vdi) + ; ("THIN_PROVISIONING", Thin_provisioning) + ; ("VDI_READ_CACHING", Vdi_read_caching) + ] + + let capability_to_string_table = + List.map (fun (k, v) -> (v, k)) string_to_capability_table + + let known_features = List.map fst string_to_capability_table + + let capability_to_string c = List.assoc c capability_to_string_table + + let to_string (c, v) = Printf.sprintf "%s/%Ld" (capability_to_string c) v + + let capability_of : t -> capability = fst + + let unparse (f, v) = f ^ "/" ^ Int64.to_string v + + let string_int64_of_string_opt feature = match String.split_on_char '/' feature with | [] -> None @@ -131,30 +128,46 @@ let parse_string_int64_features features = | feature :: _ -> error "SM.feature: unknown feature %s" feature ; None - in - features - |> List.filter_map scan - |> List.sort_uniq (fun (x, _) (y, _) -> compare x y) -(** [compat_features features1 features2] finds the compatible features in the input -features lists. We assume features backwards compatible, i.e. if there are FOO/1 and + (** [compat_features features1 features2] finds the compatible features in the input + features lists. We assume features backwards compatible, i.e. if there are FOO/1 and FOO/2 are present, then we assume they can both do FOO/1*) -let compat_features features1 features2 = - let features2 = List.to_seq features2 |> Hashtbl.of_seq in - List.filter_map - (fun (f1, v1) -> - match Hashtbl.find_opt features2 f1 with - | Some v2 -> - Some (f1, Int64.min v1 v2) - | None -> - None - ) - features1 - -let parse_capability_int64_features strings = - List.map - (function c, v -> (List.assoc c string_to_capability_table, v)) - (parse_string_int64_features strings) + let compat_features features1 features2 = + let features2 = List.to_seq features2 |> Hashtbl.of_seq in + List.filter_map + (fun (f1, v1) -> + match Hashtbl.find_opt features2 f1 with + | Some v2 -> + Some (f1, Int64.min v1 v2) + | None -> + None + ) + features1 + + let of_string_int64_opt (c, v) = + List.assoc_opt c string_to_capability_table |> Option.map (fun c -> (c, v)) + + (** [has_capability c fl] will test weather the required capability [c] is present + in the feature list [fl]. Callers should use this function to test if a feature + is available rather than directly using membership functions on a feature list + as this function might have special logic for some features. *) + let has_capability (c : capability) fl = List.mem_assoc c fl + + (** [parse_string_int64 features] takes a [features] list in its plain string + forms such as "VDI_MIRROR/2" and parses them into the form of (VDI_MIRROR, 2). + If the number is malformated, default to (VDI_MIRROR, 1). It will also deduplicate + based on the capability ONLY, and randomly choose a verion, based on the order + it appears in the input list. + *) + let parse_string_int64 features = + List.filter_map string_int64_of_string_opt features + |> List.sort_uniq (fun (x, _) (y, _) -> compare x y) + + (** [parse_capability_int64 features] is similar to [parse_string_int64_features features] + but parses the input list into a [t list] *) + let parse_capability_int64 features = + parse_string_int64 features |> List.filter_map of_string_int64_opt +end type sr_driver_info = { sr_driver_filename: string @@ -164,7 +177,7 @@ type sr_driver_info = { ; sr_driver_copyright: string ; sr_driver_version: string ; sr_driver_required_api_version: string - ; sr_driver_features: feature list + ; sr_driver_features: Feature.t list ; sr_driver_text_features: string list ; sr_driver_configuration: (string * string) list ; sr_driver_required_cluster_stack: string list diff --git a/ocaml/xapi/storage_mux.ml b/ocaml/xapi/storage_mux.ml index dc49d2e75b7..d780f48aebc 100644 --- a/ocaml/xapi/storage_mux.ml +++ b/ocaml/xapi/storage_mux.ml @@ -37,7 +37,7 @@ type plugin = { processor: processor ; backend_domain: string ; query_result: query_result - ; features: Smint.feature list + ; features: Smint.Feature.t list } let plugins : (sr, plugin) Hashtbl.t = Hashtbl.create 10 @@ -53,7 +53,7 @@ let debug_printer rpc call = let register sr rpc d info = with_lock m (fun () -> let features = - Smint.parse_capability_int64_features info.Storage_interface.features + Smint.Feature.parse_capability_int64 info.Storage_interface.features in Hashtbl.replace plugins sr { @@ -88,7 +88,7 @@ let sr_has_capability sr capability = with_lock m (fun () -> match Hashtbl.find_opt plugins sr with | Some x -> - Smint.has_capability capability x.features + Smint.Feature.has_capability capability x.features | None -> false ) @@ -648,7 +648,9 @@ module Mux = struct | None -> failwith "DP not found" in - if (not read_write) && sr_has_capability sr Smint.Vdi_activate_readonly + if + (not read_write) + && sr_has_capability sr Smint.Feature.Vdi_activate_readonly then ( info "The VDI was attached read-only: calling activate_readonly" ; C.VDI.activate_readonly (Debug_info.to_string di) dp sr vdi vm diff --git a/ocaml/xapi/storage_smapiv1.ml b/ocaml/xapi/storage_smapiv1.ml index 0bb0dd9d267..7c338a0b7c3 100644 --- a/ocaml/xapi/storage_smapiv1.ml +++ b/ocaml/xapi/storage_smapiv1.ml @@ -607,7 +607,10 @@ module SMAPIv1 : Server_impl = struct ~key:"content_id" ) ; (* If the backend doesn't advertise the capability then do nothing *) - if List.mem_assoc Smint.Vdi_activate (Sm.features_of_driver _type) + if + Smint.Feature.( + has_capability Vdi_activate (Sm.features_of_driver _type) + ) then Sm.vdi_activate ~dbg device_config _type sr self read_write else @@ -638,7 +641,10 @@ module SMAPIv1 : Server_impl = struct ~value:Uuidx.(to_string (make ())) ) ; (* If the backend doesn't advertise the capability then do nothing *) - if List.mem_assoc Smint.Vdi_deactivate (Sm.features_of_driver _type) + if + Smint.Feature.( + has_capability Vdi_deactivate (Sm.features_of_driver _type) + ) then Sm.vdi_deactivate ~dbg device_config _type sr self else diff --git a/ocaml/xapi/xapi_dr_task.ml b/ocaml/xapi/xapi_dr_task.ml index 415a4e45c8f..40a9a992c9e 100644 --- a/ocaml/xapi/xapi_dr_task.ml +++ b/ocaml/xapi/xapi_dr_task.ml @@ -101,7 +101,8 @@ let create ~__context ~_type ~device_config ~whitelist = (* Check if licence allows disaster recovery. *) Pool_features.assert_enabled ~__context ~f:Features.DR ; (* Check that the SR type supports metadata. *) - if not (List.mem_assoc Smint.Sr_metadata (Sm.features_of_driver _type)) then + if not Smint.Feature.(has_capability Sr_metadata (Sm.features_of_driver _type)) + then raise (Api_errors.Server_error ( Api_errors.operation_not_allowed diff --git a/ocaml/xapi/xapi_host.ml b/ocaml/xapi/xapi_host.ml index 99b1c4db0ae..d509edcc758 100644 --- a/ocaml/xapi/xapi_host.ml +++ b/ocaml/xapi/xapi_host.ml @@ -2232,7 +2232,7 @@ let enable_local_storage_caching ~__context ~host ~sr = let shared = Db.SR.get_shared ~__context ~self:sr in let has_required_capability = let caps = Sm.features_of_driver ty in - List.mem_assoc Smint.Sr_supports_local_caching caps + Smint.Feature.(has_capability Sr_supports_local_caching caps) in debug "shared: %b. List.length pbds: %d. has_required_capability: %b" shared (List.length pbds) has_required_capability ; diff --git a/ocaml/xapi/xapi_pool.ml b/ocaml/xapi/xapi_pool.ml index 2f471932c14..b73d7fbf0dd 100644 --- a/ocaml/xapi/xapi_pool.ml +++ b/ocaml/xapi/xapi_pool.ml @@ -849,7 +849,8 @@ let pre_join_checks ~__context ~rpc ~session_id ~force = let features_compatible coor_features candidate_features = (* The pool features must not be reduced or downgraded, although it is fine the other way around. *) - Smint.compat_features coor_features candidate_features = coor_features + Smint.Feature.compat_features coor_features candidate_features + = coor_features in let pool_sms = Client.SM.get_all_records ~rpc ~session_id in List.iter @@ -3156,8 +3157,10 @@ let enable_local_storage_caching ~__context ~self:_ = (fun (_, _, srrec) -> (not srrec.API.sR_shared) && List.length srrec.API.sR_PBDs = 1 - && List.mem_assoc Smint.Sr_supports_local_caching - (Sm.features_of_driver srrec.API.sR_type) + && Smint.Feature.( + has_capability Sr_supports_local_caching + (Sm.features_of_driver srrec.API.sR_type) + ) ) hosts_and_srs in diff --git a/ocaml/xapi/xapi_sm.ml b/ocaml/xapi/xapi_sm.ml index 9badc179c06..769484ddd7f 100644 --- a/ocaml/xapi/xapi_sm.ml +++ b/ocaml/xapi/xapi_sm.ml @@ -36,7 +36,7 @@ let create_from_query_result ~__context q = let r = Ref.make () and u = Uuidx.to_string (Uuidx.make ()) in let open Storage_interface in if String.lowercase_ascii q.driver <> "storage_access" then ( - let features = Smint.parse_string_int64_features q.features in + let features = Smint.Feature.parse_string_int64 q.features in let capabilities = List.map fst features in info "%s Registering SM plugin %s (version %s)" __FUNCTION__ (String.lowercase_ascii q.driver) @@ -59,7 +59,7 @@ to pending features of host [self]. It then returns a list of currently pending let addto_pending_hosts_features ~__context self new_features = let host = Helpers.get_localhost ~__context in let new_features = - List.map (fun (f, v) -> Smint.unparse_feature (f, v)) new_features + List.map (fun (f, v) -> Smint.Feature.unparse (f, v)) new_features in let curr_pending_features = Db.SM.get_host_pending_features ~__context ~self @@ -74,7 +74,7 @@ let addto_pending_hosts_features ~__context self new_features = ) curr_pending_features ; List.map - (fun (h, f) -> (h, Smint.parse_string_int64_features f)) + (fun (h, f) -> (h, Smint.Feature.parse_string_int64 f)) curr_pending_features let valid_hosts_pending_features ~__context pending_features = @@ -84,14 +84,14 @@ let valid_hosts_pending_features ~__context pending_features = [] ) else List.map snd pending_features |> fun l -> - List.fold_left Smint.compat_features + List.fold_left Smint.Feature.compat_features (* The list in theory cannot be empty due to the if condition check, but do this just in case *) (List.nth_opt l 0 |> Option.fold ~none:[] ~some:Fun.id) (List.tl l) let remove_valid_features_from_pending ~__context ~self valid_features = - let valid_features = List.map Smint.unparse_feature valid_features in + let valid_features = List.map Smint.Feature.unparse valid_features in let new_pending_feature = Db.SM.get_host_pending_features ~__context ~self |> List.map (fun (h, pending_features) -> @@ -107,7 +107,7 @@ let update_from_query_result ~__context (self, r) q_result = let driver_filename = Sm_exec.cmd_name q_result.driver in let existing_features = Db.SM.get_features ~__context ~self in let new_features = - Smint.parse_string_int64_features q_result.features + Smint.Feature.parse_string_int64 q_result.features |> find_pending_features existing_features |> addto_pending_hosts_features ~__context self |> valid_hosts_pending_features ~__context diff --git a/ocaml/xapi/xapi_sr.ml b/ocaml/xapi/xapi_sr.ml index a40a644ba04..61d90cf21ac 100644 --- a/ocaml/xapi/xapi_sr.ml +++ b/ocaml/xapi/xapi_sr.ml @@ -482,7 +482,7 @@ let find_or_create_rrd_vdi ~__context ~sr = let should_manage_stats ~__context sr = let sr_record = Db.SR.get_record_internal ~__context ~self:sr in let sr_features = Xapi_sr_operations.features_of_sr ~__context sr_record in - Smint.(has_capability Sr_stats sr_features) + Smint.Feature.(has_capability Sr_stats sr_features) && Helpers.i_am_srmaster ~__context ~sr let maybe_push_sr_rrds ~__context ~sr = diff --git a/ocaml/xapi/xapi_sr_operations.ml b/ocaml/xapi/xapi_sr_operations.ml index eef09a7d9eb..263f002d474 100644 --- a/ocaml/xapi/xapi_sr_operations.ml +++ b/ocaml/xapi/xapi_sr_operations.ml @@ -76,20 +76,21 @@ let disallowed_during_rpu : API.storage_operations_set = List.filter (fun x -> not (List.mem x all_rpu_ops)) all_ops let sm_cap_table : (API.storage_operations * _) list = + let open Smint.Feature in [ - (`vdi_create, Smint.Vdi_create) - ; (`vdi_destroy, Smint.Vdi_delete) - ; (`vdi_resize, Smint.Vdi_resize) - ; (`vdi_introduce, Smint.Vdi_introduce) - ; (`vdi_mirror, Smint.Vdi_mirror) - ; (`vdi_enable_cbt, Smint.Vdi_configure_cbt) - ; (`vdi_disable_cbt, Smint.Vdi_configure_cbt) - ; (`vdi_data_destroy, Smint.Vdi_configure_cbt) - ; (`vdi_list_changed_blocks, Smint.Vdi_configure_cbt) - ; (`vdi_set_on_boot, Smint.Vdi_reset_on_boot) - ; (`update, Smint.Sr_update) + (`vdi_create, Vdi_create) + ; (`vdi_destroy, Vdi_delete) + ; (`vdi_resize, Vdi_resize) + ; (`vdi_introduce, Vdi_introduce) + ; (`vdi_mirror, Vdi_mirror) + ; (`vdi_enable_cbt, Vdi_configure_cbt) + ; (`vdi_disable_cbt, Vdi_configure_cbt) + ; (`vdi_data_destroy, Vdi_configure_cbt) + ; (`vdi_list_changed_blocks, Vdi_configure_cbt) + ; (`vdi_set_on_boot, Vdi_reset_on_boot) + ; (`update, Sr_update) ; (* We fake clone ourselves *) - (`vdi_snapshot, Smint.Vdi_snapshot) + (`vdi_snapshot, Vdi_snapshot) ] type table = (API.storage_operations, (string * string list) option) Hashtbl.t @@ -104,7 +105,7 @@ let features_of_sr_internal ~__context ~_type = | (_, sm) :: _ -> List.filter_map (fun (name, v) -> - try Some (List.assoc name Smint.string_to_capability_table, v) + try Some (List.assoc name Smint.Feature.string_to_capability_table, v) with Not_found -> None ) sm.Db_actions.sM_features @@ -139,16 +140,14 @@ let valid_operations ~__context ?op record _ref' : table = Multiple simultaneous PBD.unplug operations are ok. *) let check_sm_features ~__context record = + let open Smint.Feature in (* First consider the backend SM features *) let sm_features = features_of_sr ~__context record in (* Then filter out the operations we don't want to see for the magic tools SR *) let sm_features = if record.Db_actions.sR_is_tools_sr then List.filter - (fun f -> - not - Smint.(List.mem (capability_of_feature f) [Vdi_create; Vdi_delete]) - ) + (fun f -> not (List.mem (capability_of f) [Vdi_create; Vdi_delete])) sm_features else sm_features @@ -157,7 +156,7 @@ let valid_operations ~__context ?op record _ref' : table = List.filter (fun op -> List.mem_assoc op sm_cap_table - && not (Smint.has_capability (List.assoc op sm_cap_table) sm_features) + && not (has_capability (List.assoc op sm_cap_table) sm_features) ) all_ops in diff --git a/ocaml/xapi/xapi_vdi.ml b/ocaml/xapi/xapi_vdi.ml index a2978de0b7f..addfd704dac 100644 --- a/ocaml/xapi/xapi_vdi.ml +++ b/ocaml/xapi/xapi_vdi.ml @@ -23,7 +23,7 @@ open D (* current/allowed operations checking *) let feature_of_op = - let open Smint in + let open Smint.Feature in function | `forget | `copy | `force_unlock | `blocked -> None @@ -53,7 +53,7 @@ let check_sm_feature_error (op : API.vdi_operations) sm_features sr = | None -> Ok () | Some feature -> - if Smint.(has_capability feature sm_features) then + if Smint.Feature.(has_capability feature sm_features) then Ok () else Error (Api_errors.sr_operation_not_supported, [Ref.string_of sr]) diff --git a/ocaml/xapi/xapi_vm_migrate.ml b/ocaml/xapi/xapi_vm_migrate.ml index 9b2912b4d50..e6fd38a2990 100644 --- a/ocaml/xapi/xapi_vm_migrate.ml +++ b/ocaml/xapi/xapi_vm_migrate.ml @@ -162,36 +162,31 @@ open Storage_interface let assert_sr_support_operations ~__context ~vdi_map ~remote ~ops = let op_supported_on_source_sr vdi ops = + let open Smint.Feature in (* Check VDIs must not be present on SR which doesn't have required capability *) let source_sr = Db.VDI.get_SR ~__context ~self:vdi in let sr_record = Db.SR.get_record_internal ~__context ~self:source_sr in let sr_features = Xapi_sr_operations.features_of_sr ~__context sr_record in - if not (List.for_all (fun op -> Smint.(has_capability op sr_features)) ops) - then + if not (List.for_all (fun op -> has_capability op sr_features) ops) then raise (Api_errors.Server_error (Api_errors.sr_does_not_support_migration, [Ref.string_of source_sr]) ) in let op_supported_on_dest_sr sr ops sm_record remote = + let open Smint.Feature in (* Check VDIs must not be mirrored to SR which doesn't have required capability *) let sr_type = XenAPI.SR.get_type ~rpc:remote.rpc ~session_id:remote.session ~self:sr in - let sm_capabilities = + let sm_features = match List.filter (fun (_, r) -> r.API.sM_type = sr_type) sm_record with | [(_, plugin)] -> - plugin.API.sM_capabilities + plugin.API.sM_features |> List.filter_map of_string_int64_opt | _ -> [] in - if - not - (List.for_all - (fun op -> List.mem Smint.(string_of_capability op) sm_capabilities) - ops - ) - then + if not (List.for_all (fun op -> has_capability op sm_features) ops) then raise (Api_errors.Server_error (Api_errors.sr_does_not_support_migration, [Ref.string_of sr]) @@ -1755,7 +1750,9 @@ let assert_can_migrate ~__context ~vm ~dest ~live:_ ~vdi_map ~vif_map ~options ) vms_vdis ; (* operations required for migration *) - let required_sr_operations = [Smint.Vdi_mirror; Smint.Vdi_snapshot] in + let required_sr_operations = + [Smint.Feature.Vdi_mirror; Smint.Feature.Vdi_snapshot] + in let host_from = Helpers.LocalObject source_host_ref in ( match migration_type ~__context ~remote with | `intra_pool -> diff --git a/ocaml/xapi/xapi_vm_snapshot.ml b/ocaml/xapi/xapi_vm_snapshot.ml index 49f745a8845..4ef856d1630 100644 --- a/ocaml/xapi/xapi_vm_snapshot.ml +++ b/ocaml/xapi/xapi_vm_snapshot.ml @@ -104,7 +104,7 @@ let checkpoint ~__context ~vm ~new_name = in (* Check if SR has snapshot feature *) let sr_has_snapshot_feature sr = - Smint.has_capability Vdi_snapshot + Smint.Feature.(has_capability Vdi_snapshot) (Xapi_sr_operations.features_of_sr ~__context sr) in List.iter From 3c3e905477fcb738db4711c4e34f667e60b37dfb Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Thu, 31 Oct 2024 16:51:12 +0000 Subject: [PATCH 22/53] Move `transform_storage_exn` to Storage_utils This is so that `Storage_migrate` can use this function, otherwise there would be a dependency cycle. This function also sounds like a utility function. Also add an mli file for `Storage_utils`. Signed-off-by: Vincent Liu --- ocaml/xapi/storage_access.ml | 45 +----------------------- ocaml/xapi/storage_access.mli | 3 -- ocaml/xapi/storage_utils.ml | 46 ++++++++++++++++++++++++ ocaml/xapi/storage_utils.mli | 66 +++++++++++++++++++++++++++++++++++ ocaml/xapi/xapi_pbd.ml | 4 +-- ocaml/xapi/xapi_sr.ml | 12 +++---- ocaml/xapi/xapi_vdi.ml | 26 ++++++-------- quality-gate.sh | 2 +- 8 files changed, 132 insertions(+), 72 deletions(-) create mode 100644 ocaml/xapi/storage_utils.mli diff --git a/ocaml/xapi/storage_access.ml b/ocaml/xapi/storage_access.ml index c92651bc576..4568863f8ca 100644 --- a/ocaml/xapi/storage_access.ml +++ b/ocaml/xapi/storage_access.ml @@ -21,6 +21,7 @@ let finally = Xapi_stdext_pervasives.Pervasiveext.finally module XenAPI = Client.Client open Storage_interface +open Storage_utils module D = Debug.Make (struct let name = "storage_access" end) @@ -30,50 +31,6 @@ let s_of_vdi = Vdi.string_of let s_of_sr = Sr.string_of -let transform_storage_exn f = - let get_sr_ref sr_uuid = - Server_helpers.exec_with_new_task "transform_storage_exn" (fun __context -> - Db.SR.get_by_uuid ~__context ~uuid:sr_uuid - ) - in - try f () with - | Storage_error (Backend_error (code, params)) as e -> - Backtrace.reraise e (Api_errors.Server_error (code, params)) - | Storage_error (Backend_error_with_backtrace (code, backtrace :: params)) as - e -> - let backtrace = Backtrace.Interop.of_json "SM" backtrace in - Backtrace.add e backtrace ; - Backtrace.reraise e (Api_errors.Server_error (code, params)) - | Storage_error (Sr_unhealthy (sr, health)) as e -> - let advice = - match health with - | Unavailable -> - "try reboot" - | Unreachable -> - "try again later" - | _health -> - "" - in - let sr = get_sr_ref sr in - Backtrace.reraise e - (Api_errors.Server_error - ( Api_errors.sr_unhealthy - , [Ref.string_of sr; Storage_interface.show_sr_health health; advice] - ) - ) - | Api_errors.Server_error _ as e -> - raise e - | Storage_error (No_storage_plugin_for_sr sr) as e -> - let sr = get_sr_ref sr in - Backtrace.reraise e - (Api_errors.Server_error (Api_errors.sr_not_attached, [Ref.string_of sr]) - ) - | e -> - Backtrace.reraise e - (Api_errors.Server_error - (Api_errors.internal_error, [Printexc.to_string e]) - ) - (* Start a set of servers for all SMAPIv1 plugins *) let start_smapiv1_servers () = let drivers = Sm.supported_drivers () in diff --git a/ocaml/xapi/storage_access.mli b/ocaml/xapi/storage_access.mli index 28cf3108dee..b781e9e9f26 100644 --- a/ocaml/xapi/storage_access.mli +++ b/ocaml/xapi/storage_access.mli @@ -61,9 +61,6 @@ val reset : __context:Context.t -> vm:API.ref_VM -> unit (** [reset __context vm] declares that [vm] has reset and if it's a driver domain, we expect it to lose all state. *) -val transform_storage_exn : (unit -> 'a) -> 'a -(** [transform_storage_exn f] runs [f], rethrowing any storage error as a nice XenAPI error *) - val attach_and_activate : __context:Context.t -> vbd:API.ref_VBD diff --git a/ocaml/xapi/storage_utils.ml b/ocaml/xapi/storage_utils.ml index 16397af6434..dd7d6b6e63d 100644 --- a/ocaml/xapi/storage_utils.ml +++ b/ocaml/xapi/storage_utils.ml @@ -12,6 +12,8 @@ * GNU Lesser General Public License for more details. *) +open Storage_interface + let string_of_vdi_type vdi_type = Rpc.string_of_rpc (API.rpc_of_vdi_type vdi_type) @@ -127,3 +129,47 @@ let rpc ~srcstr ~dststr {url; pool_secret; verify_cert} = intra_pool_rpc_of_ip ~srcstr ~dststr ~ip in redirectable_rpc ~original ~redirect_to_ip + +let transform_storage_exn f = + let get_sr_ref sr_uuid = + Server_helpers.exec_with_new_task "transform_storage_exn" (fun __context -> + Db.SR.get_by_uuid ~__context ~uuid:sr_uuid + ) + in + try f () with + | Storage_error (Backend_error (code, params)) as e -> + Backtrace.reraise e (Api_errors.Server_error (code, params)) + | Storage_error (Backend_error_with_backtrace (code, backtrace :: params)) as + e -> + let backtrace = Backtrace.Interop.of_json "SM" backtrace in + Backtrace.add e backtrace ; + Backtrace.reraise e (Api_errors.Server_error (code, params)) + | Storage_error (Sr_unhealthy (sr, health)) as e -> + let advice = + match health with + | Unavailable -> + "try reboot" + | Unreachable -> + "try again later" + | _health -> + "" + in + let sr = get_sr_ref sr in + Backtrace.reraise e + (Api_errors.Server_error + ( Api_errors.sr_unhealthy + , [Ref.string_of sr; Storage_interface.show_sr_health health; advice] + ) + ) + | Api_errors.Server_error _ as e -> + raise e + | Storage_error (No_storage_plugin_for_sr sr) as e -> + let sr = get_sr_ref sr in + Backtrace.reraise e + (Api_errors.Server_error (Api_errors.sr_not_attached, [Ref.string_of sr]) + ) + | e -> + Backtrace.reraise e + (Api_errors.Server_error + (Api_errors.internal_error, [Printexc.to_string e]) + ) diff --git a/ocaml/xapi/storage_utils.mli b/ocaml/xapi/storage_utils.mli new file mode 100644 index 00000000000..50e3a80e7f8 --- /dev/null +++ b/ocaml/xapi/storage_utils.mli @@ -0,0 +1,66 @@ +(* Copyright (C) Cloud Software Group Inc. + 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. +*) + +val string_of_vdi_type : + [< `cbt_metadata + | `crashdump + | `ephemeral + | `ha_statefile + | `metadata + | `pvs_cache + | `redo_log + | `rrd + | `suspend + | `system + | `user ] + -> string + +val vdi_type_of_string : + string + -> [> `cbt_metadata + | `crashdump + | `ephemeral + | `ha_statefile + | `metadata + | `pvs_cache + | `redo_log + | `rrd + | `suspend + | `system + | `user ] + +val redirectable_rpc : + redirect_to_ip:(ip:string -> Rpc.call -> Rpc.response) + -> original:(Rpc.call -> Rpc.response) + -> Rpc.call + -> Rpc.response + +type connection_args = { + url: Http.Url.t + ; pool_secret: SecretString.t option + ; verify_cert: Stunnel.verification_config option +} + +val localhost_connection_args : unit -> connection_args + +val intra_pool_connection_args_of_ip : string -> connection_args + +val connection_args_of_uri : verify_dest:bool -> string -> connection_args + +val intra_pool_rpc_of_ip : + srcstr:string -> dststr:string -> ip:string -> Rpc.call -> Rpc.response + +val rpc : + srcstr:string -> dststr:string -> connection_args -> Rpc.call -> Rpc.response + +val transform_storage_exn : (unit -> 'a) -> 'a +(** [transform_storage_exn f] runs [f], rethrowing any storage error as a nice XenAPI error *) diff --git a/ocaml/xapi/xapi_pbd.ml b/ocaml/xapi/xapi_pbd.ml index 7ba1fd8642d..a9625dc3c62 100644 --- a/ocaml/xapi/xapi_pbd.ml +++ b/ocaml/xapi/xapi_pbd.ml @@ -188,7 +188,7 @@ let plug ~__context ~self = check_sharing_constraint ~__context ~sr ; let dbg = Ref.string_of (Context.get_task_id __context) in let device_config = Db.PBD.get_device_config ~__context ~self in - Storage_access.transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.SR.attach dbg (Storage_interface.Sr.of_string (Db.SR.get_uuid ~__context ~self:sr) @@ -264,7 +264,7 @@ let unplug ~__context ~self = ) ; let dbg = Ref.string_of (Context.get_task_id __context) in let uuid = Db.SR.get_uuid ~__context ~self:sr in - Storage_access.transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.SR.detach dbg (Storage_interface.Sr.of_string uuid) ) ; Storage_access.unbind ~__context ~pbd:self ; diff --git a/ocaml/xapi/xapi_sr.ml b/ocaml/xapi/xapi_sr.ml index 61d90cf21ac..b6d8caf5dda 100644 --- a/ocaml/xapi/xapi_sr.ml +++ b/ocaml/xapi/xapi_sr.ml @@ -234,7 +234,7 @@ let call_probe ~__context ~host:_ ~device_config ~_type ~sm_config ~f = let rpc = rpc end)) in let dbg = Context.string_of_task __context in - Storage_access.transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> Client.SR.probe dbg queue device_config sm_config |> f ) @@ -570,7 +570,7 @@ let update ~__context ~sr = let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = rpc end)) in - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> let sr' = Db.SR.get_uuid ~__context ~self:sr |> Storage_interface.Sr.of_string in @@ -764,7 +764,7 @@ let scan ~__context ~sr = end)) in let sr' = Ref.string_of sr in SRScanThrottle.execute (fun () -> - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> let sr_uuid = Db.SR.get_uuid ~__context ~self:sr in (* CA-399757: Do not update_vdis unless we are sure that the db was not changed during the scan. If it was, retry the scan operation. This @@ -851,13 +851,12 @@ let set_shared ~__context ~sr ~value = Db.SR.set_shared ~__context ~self:sr ~value let set_name_label ~__context ~sr ~value = - let open Storage_access in let task = Context.get_task_id __context in let sr' = Db.SR.get_uuid ~__context ~self:sr in let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = Storage_access.rpc end)) in - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.SR.set_name_label (Ref.string_of task) (Storage_interface.Sr.of_string sr') value @@ -865,13 +864,12 @@ let set_name_label ~__context ~sr ~value = Db.SR.set_name_label ~__context ~self:sr ~value let set_name_description ~__context ~sr ~value = - let open Storage_access in let task = Context.get_task_id __context in let sr' = Db.SR.get_uuid ~__context ~self:sr in let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = Storage_access.rpc end)) in - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.SR.set_name_description (Ref.string_of task) (Storage_interface.Sr.of_string sr') value diff --git a/ocaml/xapi/xapi_vdi.ml b/ocaml/xapi/xapi_vdi.ml index addfd704dac..3713f189040 100644 --- a/ocaml/xapi/xapi_vdi.ml +++ b/ocaml/xapi/xapi_vdi.ml @@ -641,7 +641,7 @@ let create ~__context ~name_label ~name_description ~sR ~virtual_size ~_type let rpc = rpc end)) in let sm_vdi = - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.VDI.create (Ref.string_of task) (Db.SR.get_uuid ~__context ~self:sR |> Storage_interface.Sr.of_string) vdi_info @@ -721,7 +721,6 @@ let introduce ~__context ~uuid ~name_label ~name_description ~sR ~_type ~sharable ~read_only:_ ~other_config ~location ~xenstore_data ~sm_config ~managed:_ ~virtual_size:_ ~physical_utilisation:_ ~metadata_of_pool:_ ~is_a_snapshot:_ ~snapshot_time:_ ~snapshot_of:_ = - let open Storage_access in debug "introduce uuid=%s name_label=%s sm_config=[ %s ]" uuid name_label (String.concat "; " (List.map (fun (k, v) -> k ^ " = " ^ v) sm_config)) ; Sm.assert_pbd_is_plugged ~__context ~sr:sR ; @@ -747,7 +746,7 @@ let introduce ~__context ~uuid ~name_label ~name_description ~sR ~_type end)) in Sm.assert_pbd_is_plugged ~__context ~sr:sR ; let vdi_info = - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.VDI.introduce (Ref.string_of task) sr' uuid sm_config location ) in @@ -835,7 +834,7 @@ let snapshot ~__context ~vdi ~driver_params = let rpc = Storage_access.rpc end)) in let newvdi = - Storage_access.transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> try snapshot_and_clone C.VDI.snapshot ~__context ~vdi ~driver_params with Storage_interface.Storage_error (Unimplemented _) -> debug @@ -995,7 +994,7 @@ let destroy_and_data_destroy_common ~__context ~self | `data_destroy _ -> C.VDI.data_destroy in - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> call_f (Ref.string_of task) (Db.SR.get_uuid ~__context ~self:sr |> Storage_interface.Sr.of_string) (Storage_interface.Vdi.of_string location) @@ -1042,7 +1041,7 @@ let data_destroy = _data_destroy ~timeout:4 let resize ~__context ~vdi ~size = Sm.assert_pbd_is_plugged ~__context ~sr:(Db.VDI.get_SR ~__context ~self:vdi) ; Xapi_vdi_helpers.assert_managed ~__context ~vdi ; - Storage_access.transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = Storage_access.rpc end)) in @@ -1068,7 +1067,7 @@ let generate_config ~__context ~host:_ ~vdi = ) let clone ~__context ~vdi ~driver_params = - Storage_access.transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> try let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = Storage_access.rpc @@ -1246,7 +1245,6 @@ let set_metadata_of_pool ~__context ~self ~value = let set_on_boot ~__context ~self ~value = let sr = Db.VDI.get_SR ~__context ~self in Sm.assert_pbd_is_plugged ~__context ~sr ; - let open Storage_access in let task = Context.get_task_id __context in let sr' = Db.SR.get_uuid ~__context ~self:sr |> Storage_interface.Sr.of_string @@ -1257,7 +1255,7 @@ let set_on_boot ~__context ~self ~value = let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = Storage_access.rpc end)) in - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.VDI.set_persistent (Ref.string_of task) sr' vdi' (value = `persist) ) ; Db.VDI.set_on_boot ~__context ~self ~value @@ -1266,7 +1264,6 @@ let set_allow_caching ~__context ~self ~value = Db.VDI.set_allow_caching ~__context ~self ~value let set_name_label ~__context ~self ~value = - let open Storage_access in let task = Context.get_task_id __context in let sr = Db.VDI.get_SR ~__context ~self in let sr' = @@ -1278,13 +1275,12 @@ let set_name_label ~__context ~self ~value = let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = Storage_access.rpc end)) in - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.VDI.set_name_label (Ref.string_of task) sr' vdi' value ) ; update ~__context ~vdi:self let set_name_description ~__context ~self ~value = - let open Storage_access in let task = Context.get_task_id __context in let sr = Db.VDI.get_SR ~__context ~self in let sr' = @@ -1296,7 +1292,7 @@ let set_name_description ~__context ~self ~value = let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = Storage_access.rpc end)) in - transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.VDI.set_name_description (Ref.string_of task) sr' vdi' value ) ; update ~__context ~vdi:self @@ -1362,7 +1358,7 @@ let change_cbt_status ~__context ~self ~new_cbt_enabled ~caller_name = let call_f = if new_cbt_enabled then C.VDI.enable_cbt else C.VDI.disable_cbt in - Storage_access.transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> call_f (Ref.string_of task) sr' vdi' ) ; Db.VDI.set_cbt_enabled ~__context ~self ~value:new_cbt_enabled @@ -1404,7 +1400,7 @@ let list_changed_blocks ~__context ~vdi_from ~vdi_to = let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct let rpc = Storage_access.rpc end)) in - Storage_access.transform_storage_exn (fun () -> + Storage_utils.transform_storage_exn (fun () -> C.VDI.list_changed_blocks (Ref.string_of task) sr' vdi_from vdi_to ) diff --git a/quality-gate.sh b/quality-gate.sh index ca50cac6ab2..e59b8e40ccb 100755 --- a/quality-gate.sh +++ b/quality-gate.sh @@ -25,7 +25,7 @@ verify-cert () { } mli-files () { - N=498 + N=497 X="ocaml/tests" X+="|ocaml/quicktest" X+="|ocaml/message-switch/core_test" From 784ea9811efd5abedf4196c9a6efc87378dea7ba Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Fri, 25 Oct 2024 18:22:45 +0100 Subject: [PATCH 23/53] CP-45016: Implement several SMAPIv2->SMAPIv3 APIs for SXM This includes: - VDI.compose for composing VDIs. Note the ordering of the parameter `parent` and `child` is reversed, due to the inconsistency between SMAPIv2 and SMAPIv3 - DP.attach_info which calls the idempotent DP.attach3 on a already attached VDI to get the attach info again. - VDI.{add_to,remove_from}sm_config for xapi to keep track of the mirror vdi - VDI.set_content_id for determining similarity between VDIs Signed-off-by: Vincent Liu --- ocaml/xapi-idl/storage/storage_interface.ml | 13 ++-- ocaml/xapi-idl/storage/storage_skeleton.ml | 2 +- ocaml/xapi-storage-script/main.ml | 72 +++++++++++++++++++-- ocaml/xapi/smint.ml | 2 + ocaml/xapi/storage_migrate.ml | 4 +- ocaml/xapi/storage_mux.ml | 9 ++- ocaml/xapi/storage_smapiv1_wrapper.ml | 2 +- 7 files changed, 87 insertions(+), 17 deletions(-) diff --git a/ocaml/xapi-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index aa5754fabb9..6bb78a9ba6b 100644 --- a/ocaml/xapi-idl/storage/storage_interface.ml +++ b/ocaml/xapi-idl/storage/storage_interface.ml @@ -512,6 +512,9 @@ module StorageAPI (R : RPC) = struct @-> returning unit_p err ) + (** [attach_info context dbg sr vdi dp vm] returns the information as returned + by the [attach3 dbg dp sr vdi vm _] call. Callers of this function should ensure + that VDIs are already attached before calling this function. *) let attach_info = let backend_p = Param.mk ~name:"backend" backend in declare "DP.attach_info" @@ -519,7 +522,7 @@ module StorageAPI (R : RPC) = struct "[DP.attach_info sr vdi dp]: returns the params of the dp (the \ return value of VDI.attach2)" ] - (dbg_p @-> sr_p @-> vdi_p @-> dp_p @-> returning backend_p err) + (dbg_p @-> sr_p @-> vdi_p @-> dp_p @-> vm_p @-> returning backend_p err) (** *) let diagnostics = @@ -824,7 +827,7 @@ module StorageAPI (R : RPC) = struct @-> returning backend_p err ) - (** [attach3 task dp sr vdi read_write] returns the [params] for a given + (** [attach3 task dp sr vdi vm read_write] returns the [params] for a given [vdi] in [sr] which can be written to if (but not necessarily only if) [read_write] is true *) let attach3 = @@ -1108,7 +1111,7 @@ module type Server_impl = sig -> unit val attach_info : - context -> dbg:debug_info -> sr:sr -> vdi:vdi -> dp:dp -> backend + context -> dbg:debug_info -> sr:sr -> vdi:vdi -> dp:dp -> vm:vm -> backend val diagnostics : context -> unit -> string @@ -1409,8 +1412,8 @@ module Server (Impl : Server_impl) () = struct S.DP.destroy2 (fun dbg dp sr vdi vm allow_leak -> Impl.DP.destroy2 () ~dbg ~dp ~sr ~vdi ~vm ~allow_leak ) ; - S.DP.attach_info (fun dbg sr vdi dp -> - Impl.DP.attach_info () ~dbg ~sr ~vdi ~dp + S.DP.attach_info (fun dbg sr vdi dp vm -> + Impl.DP.attach_info () ~dbg ~sr ~vdi ~dp ~vm ) ; S.DP.diagnostics (fun () -> Impl.DP.diagnostics () ()) ; S.DP.stat_vdi (fun dbg sr vdi () -> Impl.DP.stat_vdi () ~dbg ~sr ~vdi ()) ; diff --git a/ocaml/xapi-idl/storage/storage_skeleton.ml b/ocaml/xapi-idl/storage/storage_skeleton.ml index cced1a7f6f5..53ac696eea4 100644 --- a/ocaml/xapi-idl/storage/storage_skeleton.ml +++ b/ocaml/xapi-idl/storage/storage_skeleton.ml @@ -39,7 +39,7 @@ module DP = struct let destroy2 ctx ~dbg ~dp ~sr ~vdi ~vm ~allow_leak = u "DP.destroy2" - let attach_info ctx ~dbg ~sr ~vdi ~dp = u "DP.attach_info" + let attach_info ctx ~dbg ~sr ~vdi ~dp ~vm = u "DP.attach_info" let diagnostics ctx () = u "DP.diagnostics" diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index e29f4937ab5..6d4f9744d16 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -356,6 +356,10 @@ let _clone_on_boot_key = "clone-on-boot" let _vdi_type_key = "vdi-type" +let _vdi_content_id_key = "content_id" + +let _sm_config_prefix_key = "_sm_config_" + let _snapshot_time_key = "snapshot_time" let _is_a_snapshot_key = "is_a_snapshot" @@ -749,7 +753,7 @@ let vdi_of_volume x = { vdi= Vdi.of_string x.Xapi_storage.Control.key ; uuid= x.Xapi_storage.Control.uuid - ; content_id= "" + ; content_id= find_string _vdi_content_id_key ~default:"" ; name_label= x.Xapi_storage.Control.name ; name_description= x.Xapi_storage.Control.description ; ty= find_string _vdi_type_key ~default:"" @@ -762,7 +766,24 @@ let vdi_of_volume x = ; cbt_enabled= Option.value x.Xapi_storage.Control.cbt_enabled ~default:false ; virtual_size= x.Xapi_storage.Control.virtual_size ; physical_utilisation= x.Xapi_storage.Control.physical_utilisation - ; sm_config= [] + ; (* All the sm_config stored is of the form (k, v) where k will be prefixed by + "_sm_config_". For example if the sm_config passed was ("base_mirror", 3), + then it will be stored as ()"_sm_config_base_mirror", mirror_id). The + code below removed this prefix and extracts the actual key. *) + sm_config= + List.filter_map + (fun (k, v) -> + if String.starts_with ~prefix:_sm_config_prefix_key k then + Some + ( String.sub k + (String.length _sm_config_prefix_key) + (String.length k - String.length _sm_config_prefix_key) + , v + ) + else + None + ) + x.Xapi_storage.Control.keys ; sharable= x.Xapi_storage.Control.sharable ; persistent= true } @@ -1479,6 +1500,10 @@ let bind ~volume_script_dir = |> wrap in S.VDI.attach3 vdi_attach3_impl ; + let dp_attach_info_impl dbg sr vdi dp vm = + vdi_attach3_impl dbg dp sr vdi vm () + in + S.DP.attach_info dp_attach_info_impl ; let vdi_activate_common dbg dp sr vdi' vm' readonly = (let vdi = Storage_interface.Vdi.string_of vdi' in let domain = domain_of ~dp ~vm' in @@ -1732,27 +1757,60 @@ let bind ~volume_script_dir = set ~dbg ~sr ~vdi ~key:_vdi_type_key ~value:"cbt_metadata" in S.VDI.data_destroy vdi_data_destroy_impl ; + let vdi_compose_impl dbg sr parent child = + wrap + @@ + let* sr = Attached_SRs.find sr in + let child = Storage_interface.Vdi.string_of child in + let parent = Storage_interface.Vdi.string_of parent in + return_volume_rpc (fun () -> + Volume_client.compose (volume_rpc ~dbg) dbg sr child parent + ) + in + S.VDI.compose vdi_compose_impl ; + let vdi_set_content_id_impl dbg sr vdi content_id = + wrap + @@ + let* sr = Attached_SRs.find sr in + let vdi = Storage_interface.Vdi.string_of vdi in + let* () = set ~dbg ~sr ~vdi ~key:_vdi_content_id_key ~value:content_id in + return () + in + S.VDI.set_content_id vdi_set_content_id_impl ; + let vdi_add_to_sm_config_impl dbg sr vdi key value = + wrap + @@ + let* sr = Attached_SRs.find sr in + let vdi = Storage_interface.Vdi.string_of vdi in + let* () = set ~dbg ~sr ~vdi ~key:(_sm_config_prefix_key ^ key) ~value in + return () + in + S.VDI.add_to_sm_config vdi_add_to_sm_config_impl ; + let vdi_remove_from_sm_config_impl dbg sr vdi key = + wrap + @@ + let* sr = Attached_SRs.find sr in + let vdi = Storage_interface.Vdi.string_of vdi in + let* () = unset ~dbg ~sr ~vdi ~key:(_sm_config_prefix_key ^ key) in + return () + in + S.VDI.remove_from_sm_config vdi_remove_from_sm_config_impl ; let u name _ = failwith ("Unimplemented: " ^ name) in S.get_by_name (u "get_by_name") ; - S.VDI.compose (u "VDI.compose") ; S.VDI.get_by_name (u "VDI.get_by_name") ; S.DATA.MIRROR.receive_start (u "DATA.MIRROR.receive_start") ; S.UPDATES.get (u "UPDATES.get") ; S.SR.update_snapshot_info_dest (u "SR.update_snapshot_info_dest") ; S.DATA.MIRROR.list (u "DATA.MIRROR.list") ; S.TASK.stat (u "TASK.stat") ; - S.VDI.remove_from_sm_config (u "VDI.remove_from_sm_config") ; S.DP.diagnostics (u "DP.diagnostics") ; S.TASK.destroy (u "TASK.destroy") ; S.DP.destroy (u "DP.destroy") ; - S.VDI.add_to_sm_config (u "VDI.add_to_sm_config") ; S.VDI.similar_content (u "VDI.similar_content") ; S.DATA.copy (u "DATA.copy") ; S.DP.stat_vdi (u "DP.stat_vdi") ; S.DATA.MIRROR.receive_finalize (u "DATA.MIRROR.receive_finalize") ; S.DP.create (u "DP.create") ; - S.VDI.set_content_id (u "VDI.set_content_id") ; - S.DP.attach_info (u "DP.attach_info") ; S.TASK.cancel (u "TASK.cancel") ; S.VDI.attach (u "VDI.attach") ; S.VDI.attach2 (u "VDI.attach2") ; diff --git a/ocaml/xapi/smint.ml b/ocaml/xapi/smint.ml index 2cdf474fd8a..02c2166a185 100644 --- a/ocaml/xapi/smint.ml +++ b/ocaml/xapi/smint.ml @@ -54,6 +54,7 @@ module Feature = struct | Vdi_attach_offline | Vdi_reset_on_boot | Vdi_configure_cbt + | Vdi_compose | Large_vdi (** Supports >2TB VDIs *) | Thin_provisioning | Vdi_read_caching @@ -92,6 +93,7 @@ module Feature = struct ; ("VDI_ATTACH_OFFLINE", Vdi_attach_offline) ; ("VDI_RESET_ON_BOOT", Vdi_reset_on_boot) ; ("VDI_CONFIG_CBT", Vdi_configure_cbt) + ; ("VDI_COMPOSE", Vdi_compose) ; ("LARGE_VDI", Large_vdi) ; ("THIN_PROVISIONING", Thin_provisioning) ; ("VDI_READ_CACHING", Vdi_read_caching) diff --git a/ocaml/xapi/storage_migrate.ml b/ocaml/xapi/storage_migrate.ml index 3279846a5a5..e56969b6622 100644 --- a/ocaml/xapi/storage_migrate.ml +++ b/ocaml/xapi/storage_migrate.ml @@ -790,7 +790,7 @@ module MigrateLocal = struct let verify_cert = if verify_dest then Stunnel_client.pool () else None in let transport = Xmlrpc_client.transport_of_url ~verify_cert dest_url in debug "Searching for data path: %s" dp ; - let attach_info = Local.DP.attach_info "nbd" sr vdi dp in + let attach_info = Local.DP.attach_info dbg sr vdi dp (Vm.of_string "0") in on_fail := (fun () -> Remote.DATA.MIRROR.receive_cancel dbg mirror_id) :: !on_fail ; let tapdev = @@ -1318,7 +1318,7 @@ let post_detach_hook ~sr ~vdi ~dp:_ = let nbd_handler req s sr vdi dp = debug "sr=%s vdi=%s dp=%s" sr vdi dp ; let sr, vdi = Storage_interface.(Sr.of_string sr, Vdi.of_string vdi) in - let attach_info = Local.DP.attach_info "nbd" sr vdi dp in + let attach_info = Local.DP.attach_info "nbd" sr vdi dp (Vm.of_string "0") in req.Http.Request.close <- true ; match tapdisk_of_attach_info attach_info with | Some tapdev -> diff --git a/ocaml/xapi/storage_mux.ml b/ocaml/xapi/storage_mux.ml index d780f48aebc..bb2c4814414 100644 --- a/ocaml/xapi/storage_mux.ml +++ b/ocaml/xapi/storage_mux.ml @@ -253,7 +253,14 @@ module Mux = struct let diagnostics () = Storage_smapiv1_wrapper.Impl.DP.diagnostics () - let attach_info () = Storage_smapiv1_wrapper.Impl.DP.attach_info () + let attach_info _context ~dbg ~sr ~vdi ~dp ~vm = + with_dbg ~name:"DP.attach_info" ~dbg @@ fun di -> + info "%s dbg:%s sr:%s vdi:%s dp:%s vm:%s" __FUNCTION__ dbg (s_of_sr sr) + (s_of_vdi vdi) dp (s_of_vm vm) ; + let module C = StorageAPI (Idl.Exn.GenClient (struct + let rpc = of_sr sr + end)) in + C.DP.attach_info (Debug_info.to_string di) sr vdi dp vm let stat_vdi () = Storage_smapiv1_wrapper.Impl.DP.stat_vdi () end diff --git a/ocaml/xapi/storage_smapiv1_wrapper.ml b/ocaml/xapi/storage_smapiv1_wrapper.ml index 55067efd9de..960456105ee 100644 --- a/ocaml/xapi/storage_smapiv1_wrapper.ml +++ b/ocaml/xapi/storage_smapiv1_wrapper.ml @@ -1137,7 +1137,7 @@ functor in String.concat "" (List.map (fun x -> x ^ "\n") lines) - let attach_info _context ~dbg:_ ~sr ~vdi ~dp = + let attach_info _context ~dbg:_ ~sr ~vdi ~dp ~vm:_ = let srs = Host.list !Host.host in let sr_state = List.assoc sr srs in let vdi_state = Hashtbl.find sr_state.Sr.vdis vdi in From 3377d2b6a72c62a6a4d9ed42c8289653f5f1b565 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Tue, 10 Dec 2024 14:49:26 +0000 Subject: [PATCH 24/53] CP-45016: Implement `import_activate` SMAPIv2 call This API call prepares a nbd server that will be used for mirroring. This gets mapped to different logic on SMAPIv1 and SMAPIv3. - for SMAPIv1, this will reuse the old logic of xapi using Tapctl to retrieve this information from tapdisk - for SMAPIv3, this uses the new `import_activate` call to prepare an nbd server Also adjust the position of the module `DATA` to use the `DP` module. Signed-off-by: Vincent Liu --- ocaml/xapi-idl/storage/storage_interface.ml | 32 +++++ ocaml/xapi-idl/storage/storage_skeleton.ml | 3 + ocaml/xapi-storage-script/main.ml | 24 ++++ ocaml/xapi/storage_migrate.ml | 51 ++++---- ocaml/xapi/storage_mux.ml | 11 ++ ocaml/xapi/storage_smapiv1.ml | 4 + ocaml/xapi/storage_smapiv1_wrapper.ml | 122 +++++++++++++------- 7 files changed, 180 insertions(+), 67 deletions(-) diff --git a/ocaml/xapi-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index 6bb78a9ba6b..c3cc92d86b2 100644 --- a/ocaml/xapi-idl/storage/storage_interface.ml +++ b/ocaml/xapi-idl/storage/storage_interface.ml @@ -251,6 +251,8 @@ let string_of_vdi_info (x : vdi_info) = Jsonrpc.to_string (rpc_of vdi_info x) "datapaths". *) type dp = string [@@deriving rpcty] +type sock_path = string [@@deriving rpcty] + type dp_stat_t = { superstate: Vdi_automaton.state ; dps: (string * Vdi_automaton.state) list @@ -443,6 +445,8 @@ module StorageAPI (R : RPC) = struct let dp_p = Param.mk ~name:"dp" dp + let sock_path_p = Param.mk ~name:"sock_path" sock_path + let device_config_p = Param.mk ~name:"device_config" ~description:["Backend-specific keys to specify the storage for the SR"] @@ -1042,6 +1046,20 @@ module StorageAPI (R : RPC) = struct Param.mk ~name:"mirrors" TypeCombinators.(list (pair Mirror.(id, t))) in declare "DATA.MIRROR.list" [] (dbg_p @-> returning result_p err) + + (** [import_activate dbg dp sr vdi vm] returns a server socket address to + which a fd can be passed via SCM_RIGHTS for mirroring purposes.*) + let import_activate = + declare "DATA.MIRROR.import_activate" [] + (dbg_p + @-> dp_p + @-> sr_p + @-> vdi_p + @-> vm_p + @-> returning sock_path_p err + ) + + end end @@ -1371,6 +1389,16 @@ module type Server_impl = sig val receive_cancel : context -> dbg:debug_info -> id:Mirror.id -> unit val list : context -> dbg:debug_info -> (Mirror.id * Mirror.t) list + + val import_activate : + context + -> dbg:debug_info + -> dp:dp + -> sr:sr + -> vdi:vdi + -> vm:vm + -> sock_path + end end @@ -1542,6 +1570,10 @@ module Server (Impl : Server_impl) () = struct Impl.DATA.MIRROR.receive_finalize () ~dbg ~id ) ; S.DATA.MIRROR.list (fun dbg -> Impl.DATA.MIRROR.list () ~dbg) ; + S.DATA.MIRROR.import_activate (fun dbg dp sr vdi vm -> + Impl.DATA.MIRROR.import_activate () ~dbg ~dp ~sr ~vdi ~vm + ) ; + S.Policy.get_backend_vm (fun dbg vm sr vdi -> Impl.Policy.get_backend_vm () ~dbg ~vm ~sr ~vdi ) ; diff --git a/ocaml/xapi-idl/storage/storage_skeleton.ml b/ocaml/xapi-idl/storage/storage_skeleton.ml index 53ac696eea4..589c16f5135 100644 --- a/ocaml/xapi-idl/storage/storage_skeleton.ml +++ b/ocaml/xapi-idl/storage/storage_skeleton.ml @@ -171,6 +171,9 @@ module DATA = struct let receive_cancel ctx ~dbg ~id = u "DATA.MIRROR.receive_cancel" let list ctx ~dbg = u "DATA.MIRROR.list" + + let import_activate ctx ~dbg ~dp ~sr ~vdi ~vm = + u "DATA.MIRROR.import_activate" end end diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index 6d4f9744d16..095b6bdeddb 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -1795,6 +1795,30 @@ let bind ~volume_script_dir = return () in S.VDI.remove_from_sm_config vdi_remove_from_sm_config_impl ; + let data_import_activate_impl dbg _dp sr vdi' vm' = + wrap + @@ + let vdi = Storage_interface.Vdi.string_of vdi' in + let domain = Storage_interface.Vm.string_of vm' in + Attached_SRs.find sr >>>= fun sr -> + (* Discover the URIs using Volume.stat *) + stat ~dbg ~sr ~vdi >>>= fun response -> + ( match + List.assoc_opt _clone_on_boot_key response.Xapi_storage.Control.keys + with + | None -> + return response + | Some temporary -> + stat ~dbg ~sr ~vdi:temporary + ) + >>>= fun response -> + choose_datapath domain response >>>= fun (rpc, _datapath, uri, domain) -> + return_data_rpc (fun () -> + Datapath_client.import_activate (rpc ~dbg) dbg uri domain + ) + in + S.DATA.MIRROR.import_activate data_import_activate_impl ; + let u name _ = failwith ("Unimplemented: " ^ name) in S.get_by_name (u "get_by_name") ; S.VDI.get_by_name (u "VDI.get_by_name") ; diff --git a/ocaml/xapi/storage_migrate.ml b/ocaml/xapi/storage_migrate.ml index e56969b6622..950824a864c 100644 --- a/ocaml/xapi/storage_migrate.ml +++ b/ocaml/xapi/storage_migrate.ml @@ -1315,35 +1315,32 @@ let post_detach_hook ~sr ~vdi ~dp:_ = (Thread.id t) ) -let nbd_handler req s sr vdi dp = - debug "sr=%s vdi=%s dp=%s" sr vdi dp ; +let nbd_handler req s ?(vm = "0") sr vdi dp = + debug "%s: vm=%s sr=%s vdi=%s dp=%s" __FUNCTION__ vm sr vdi dp ; let sr, vdi = Storage_interface.(Sr.of_string sr, Vdi.of_string vdi) in - let attach_info = Local.DP.attach_info "nbd" sr vdi dp (Vm.of_string "0") in req.Http.Request.close <- true ; - match tapdisk_of_attach_info attach_info with - | Some tapdev -> - let minor = Tapctl.get_minor tapdev in - let pid = Tapctl.get_tapdisk_pid tapdev in - let path = - Printf.sprintf "/var/run/blktap-control/nbdserver%d.%d" pid minor - in - Http_svr.headers s (Http.http_200_ok () @ ["Transfer-encoding: nbd"]) ; - let control_fd = Unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in - finally - (fun () -> - Unix.connect control_fd (Unix.ADDR_UNIX path) ; - let msg = dp in - let len = String.length msg in - let written = Unixext.send_fd_substring control_fd msg 0 len [] s in - if written <> len then ( - error "Failed to transfer fd to %s" path ; - Http_svr.headers s (Http.http_404_missing ~version:"1.0" ()) ; - req.Http.Request.close <- true - ) - ) - (fun () -> Unix.close control_fd) - | None -> - () + let vm = Vm.of_string vm in + let path = + Storage_utils.transform_storage_exn (fun () -> + Local.DATA.MIRROR.import_activate "nbd" dp sr vdi vm + ) + in + Http_svr.headers s (Http.http_200_ok () @ ["Transfer-encoding: nbd"]) ; + let control_fd = Unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in + finally + (fun () -> + Unix.connect control_fd (Unix.ADDR_UNIX path) ; + let msg = dp in + let len = String.length msg in + let written = Unixext.send_fd_substring control_fd msg 0 len [] s in + + if written <> len then ( + error "Failed to transfer fd to %s" path ; + Http_svr.headers s (Http.http_404_missing ~version:"1.0" ()) ; + req.Http.Request.close <- true + ) + ) + (fun () -> Unix.close control_fd) let with_task_and_thread ~dbg f = let task = diff --git a/ocaml/xapi/storage_mux.ml b/ocaml/xapi/storage_mux.ml index bb2c4814414..b974f392c94 100644 --- a/ocaml/xapi/storage_mux.ml +++ b/ocaml/xapi/storage_mux.ml @@ -862,6 +862,17 @@ module Mux = struct let receive_cancel () ~dbg = with_dbg ~name:"DATA.MIRROR.receive_cancel" ~dbg @@ fun {log= dbg; _} -> Storage_migrate.receive_cancel ~dbg + + let import_activate () ~dbg ~dp ~sr ~vdi ~vm = + with_dbg ~name:"DATA.MIRROR.import_activate" ~dbg @@ fun di -> + info "%s dbg:%s dp:%s sr:%s vdi:%s vm:%s" __FUNCTION__ dbg dp + (s_of_sr sr) (s_of_vdi vdi) (s_of_vm vm) ; + let module C = StorageAPI (Idl.Exn.GenClient (struct + let rpc = of_sr sr + end)) in + C.DATA.MIRROR.import_activate (Debug_info.to_string di) dp sr vdi vm + + end end diff --git a/ocaml/xapi/storage_smapiv1.ml b/ocaml/xapi/storage_smapiv1.ml index 7c338a0b7c3..23987d33e1b 100644 --- a/ocaml/xapi/storage_smapiv1.ml +++ b/ocaml/xapi/storage_smapiv1.ml @@ -1228,6 +1228,10 @@ module SMAPIv1 : Server_impl = struct let receive_finalize _context ~dbg:_ ~id:_ = assert false let receive_cancel _context ~dbg:_ ~id:_ = assert false + + let import_activate _context ~dbg:_ ~dp:_ ~sr:_ ~vdi:_ ~vm:_ = + assert false + end end diff --git a/ocaml/xapi/storage_smapiv1_wrapper.ml b/ocaml/xapi/storage_smapiv1_wrapper.ml index 960456105ee..0467ceb7696 100644 --- a/ocaml/xapi/storage_smapiv1_wrapper.ml +++ b/ocaml/xapi/storage_smapiv1_wrapper.ml @@ -932,46 +932,6 @@ functor let dbg = Debug_info.to_string di in Impl.get_by_name context ~dbg ~name - module DATA = struct - let copy context ~dbg ~sr ~vdi ~url ~dest = - info "DATA.copy dbg:%s sr:%s vdi:%s url:%s dest:%s" dbg (s_of_sr sr) - (s_of_vdi vdi) url (s_of_sr dest) ; - Impl.DATA.copy context ~dbg ~sr ~vdi ~url ~dest - - module MIRROR = struct - let start context ~dbg ~sr ~vdi ~dp ~url ~dest = - info "DATA.MIRROR.start dbg:%s sr:%s vdi:%s url:%s dest:%s" dbg - (s_of_sr sr) (s_of_vdi vdi) url (s_of_sr dest) ; - Impl.DATA.MIRROR.start context ~dbg ~sr ~vdi ~dp ~url ~dest - - let stop context ~dbg ~id = - info "DATA.MIRROR.stop dbg:%s id:%s" dbg id ; - Impl.DATA.MIRROR.stop context ~dbg ~id - - let list context ~dbg = - info "DATA.MIRROR.active dbg:%s" dbg ; - Impl.DATA.MIRROR.list context ~dbg - - let stat context ~dbg ~id = - info "DATA.MIRROR.stat dbg:%s id:%s" dbg id ; - Impl.DATA.MIRROR.stat context ~dbg ~id - - let receive_start context ~dbg ~sr ~vdi_info ~id ~similar = - info "DATA.MIRROR.receive_start dbg:%s sr:%s id:%s similar:[%s]" dbg - (s_of_sr sr) id - (String.concat "," similar) ; - Impl.DATA.MIRROR.receive_start context ~dbg ~sr ~vdi_info ~id ~similar - - let receive_finalize context ~dbg ~id = - info "DATA.MIRROR.receive_finalize dbg:%s id:%s" dbg id ; - Impl.DATA.MIRROR.receive_finalize context ~dbg ~id - - let receive_cancel context ~dbg ~id = - info "DATA.MIRROR.receive_cancel dbg:%s id:%s" dbg id ; - Impl.DATA.MIRROR.receive_cancel context ~dbg ~id - end - end - module DP = struct let create _context ~dbg:_ ~id = id @@ -1176,6 +1136,88 @@ functor ) end + module DATA = struct + let copy context ~dbg ~sr ~vdi ~url ~dest = + info "DATA.copy dbg:%s sr:%s vdi:%s url:%s dest:%s" dbg (s_of_sr sr) + (s_of_vdi vdi) url (s_of_sr dest) ; + Impl.DATA.copy context ~dbg ~sr ~vdi ~url ~dest + + module MIRROR = struct + let start context ~dbg ~sr ~vdi ~dp ~url ~dest = + info "DATA.MIRROR.start dbg:%s sr:%s vdi:%s url:%s dest:%s" dbg + (s_of_sr sr) (s_of_vdi vdi) url (s_of_sr dest) ; + Impl.DATA.MIRROR.start context ~dbg ~sr ~vdi ~dp ~url ~dest + + let stop context ~dbg ~id = + info "DATA.MIRROR.stop dbg:%s id:%s" dbg id ; + Impl.DATA.MIRROR.stop context ~dbg ~id + + let list context ~dbg = + info "DATA.MIRROR.active dbg:%s" dbg ; + Impl.DATA.MIRROR.list context ~dbg + + let stat context ~dbg ~id = + info "DATA.MIRROR.stat dbg:%s id:%s" dbg id ; + Impl.DATA.MIRROR.stat context ~dbg ~id + + let receive_start context ~dbg ~sr ~vdi_info ~id ~similar = + info "DATA.MIRROR.receive_start dbg:%s sr:%s id:%s similar:[%s]" dbg + (s_of_sr sr) id + (String.concat "," similar) ; + Impl.DATA.MIRROR.receive_start context ~dbg ~sr ~vdi_info ~id ~similar + + let receive_finalize context ~dbg ~id = + info "DATA.MIRROR.receive_finalize dbg:%s id:%s" dbg id ; + Impl.DATA.MIRROR.receive_finalize context ~dbg ~id + + let receive_cancel context ~dbg ~id = + info "DATA.MIRROR.receive_cancel dbg:%s id:%s" dbg id ; + Impl.DATA.MIRROR.receive_cancel context ~dbg ~id + + (* tapdisk supports three kind of nbd servers, the old style nbdserver, + the new style nbd server and a real nbd server. The old and new style nbd servers + are "special" nbd servers that accept fds passed via SCM_RIGHTS and handle + connection based on that fd. The real nbd server is a "normal" nbd server + that accepts nbd connections from nbd clients, and it does not support fd + passing. *) + let get_nbd_server_common context ~dbg ~dp ~sr ~vdi ~vm ~style = + info "%s DATA.MIRROR.get_nbd_server dbg:%s dp:%s sr:%s vdi:%s vm:%s" + __FUNCTION__ dbg dp (s_of_sr sr) (s_of_vdi vdi) (s_of_vm vm) ; + let attach_info = + DP.attach_info context ~dbg:"nbd" ~sr ~vdi ~dp ~vm + in + match Storage_migrate.tapdisk_of_attach_info attach_info with + | Some tapdev -> + let minor = Tapctl.get_minor tapdev in + let pid = Tapctl.get_tapdisk_pid tapdev in + let path = + match style with + | `newstyle -> + Printf.sprintf "/var/run/blktap-control/nbdserver-new%d.%d" + pid minor + | `oldstyle -> + Printf.sprintf "/var/run/blktap-control/nbdserver%d.%d" pid + minor + | `real -> + Printf.sprintf "/var/run/blktap-control/nbd%d.%d" pid minor + in + debug "%s nbd server path is %s" __FUNCTION__ path ; + path + | None -> + raise + (Storage_interface.Storage_error + (Backend_error + ( Api_errors.internal_error + , ["No tapdisk attach info found"] + ) + ) + ) + + let import_activate context ~dbg ~dp ~sr ~vdi ~vm = + get_nbd_server_common context ~dbg ~dp ~sr ~vdi ~vm ~style:`oldstyle + end + end + module SR = struct include Storage_skeleton.SR From 044dc15ee4e32e7d6d31de2c927330ea5bc674c4 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Mon, 28 Oct 2024 14:34:08 +0000 Subject: [PATCH 25/53] CP-45016: Add support for specifying nbd export in sparse_dd Previously we hardcoded the export to be empty string when the nbd client negotiates with the server. This patch allows specifying this as a protocol parameter. This is useful for SXM of SMAPIv3 SRs when we copy the snapshot from source to destination, where the export name is needed when we copy using nbd. Signed-off-by: Vincent Liu --- ocaml/vhd-tool/cli/dune | 2 +- ocaml/vhd-tool/cli/sparse_dd.ml | 25 ++++++++++++++++++++- ocaml/vhd-tool/src/dune | 2 +- ocaml/vhd-tool/src/impl.ml | 35 ++++++++++++++++-------------- ocaml/vhd-tool/src/streamCommon.ml | 17 +++++++++++---- ocaml/vhd-tool/test/dune | 2 +- ocaml/xapi/dune | 1 + ocaml/xapi/sparse_dd_wrapper.ml | 27 ++++++++++++++++------- ocaml/xapi/storage_migrate.ml | 17 +++++++++++++-- 9 files changed, 94 insertions(+), 34 deletions(-) diff --git a/ocaml/vhd-tool/cli/dune b/ocaml/vhd-tool/cli/dune index aca350c9f45..5dea7468c10 100644 --- a/ocaml/vhd-tool/cli/dune +++ b/ocaml/vhd-tool/cli/dune @@ -4,7 +4,7 @@ (libraries astring - local_lib + vhd_lib cmdliner cstruct forkexec diff --git a/ocaml/vhd-tool/cli/sparse_dd.ml b/ocaml/vhd-tool/cli/sparse_dd.ml index 19dc6422a27..e593a1bb049 100644 --- a/ocaml/vhd-tool/cli/sparse_dd.ml +++ b/ocaml/vhd-tool/cli/sparse_dd.ml @@ -65,6 +65,10 @@ let sni = ref None let cert_bundle_path = ref None +let dest_proto = ref None + +let nbd_export = ref "" + let string_opt = function None -> "None" | Some x -> x let machine_readable_progress = ref false @@ -158,6 +162,16 @@ let options = , (fun () -> string_opt !cert_bundle_path) , "path to a CA certificate bundle" ) + ; ( "dest-proto" + , Arg.String (fun x -> dest_proto := Some x) + , (fun () -> string_opt !dest_proto) + , "destination protocol to be used for copying the disk" + ) + ; ( "nbd-export" + , Arg.String (fun x -> nbd_export := x) + , (fun () -> !nbd_export) + , "nbd export name to be used, only useful when dest-proto is nbd" + ) ] let startswith prefix x = @@ -288,6 +302,15 @@ let _ = debug "Must have -size argument\n" ; exit 1 ) ; + let dest_proto = + match !dest_proto with + | Some "nbd" -> + Some (StreamCommon.Nbd !nbd_export) + | Some x -> + Some (StreamCommon.protocol_of_string x) + | None -> + None + in let size = !size in let base = !base in (* Helper function to bring an int into valid range *) @@ -485,7 +508,7 @@ let _ = in let t = stream_t >>= fun s -> - Impl.write_stream common s destination None !prezeroed progress None + Impl.write_stream common s destination dest_proto !prezeroed progress None !good_ciphersuites verify_cert in if destination_format = "vhd" then diff --git a/ocaml/vhd-tool/src/dune b/ocaml/vhd-tool/src/dune index 5981ca37751..7aa9d0de704 100644 --- a/ocaml/vhd-tool/src/dune +++ b/ocaml/vhd-tool/src/dune @@ -4,7 +4,7 @@ (language c) (names direct_copy_stubs) ) - (name local_lib) + (name vhd_lib) (wrapped false) (libraries astring diff --git a/ocaml/vhd-tool/src/impl.ml b/ocaml/vhd-tool/src/impl.ml index a3c1fca1fa4..d067846f565 100644 --- a/ocaml/vhd-tool/src/impl.ml +++ b/ocaml/vhd-tool/src/impl.ml @@ -220,7 +220,7 @@ let[@warning "-27"] stream_human _common _ s _ _ ?(progress = no_progress_bar) Printf.printf "# end of stream\n" ; return None -let stream_nbd _common c s prezeroed _ ?(progress = no_progress_bar) () = +let stream_nbd _common c s prezeroed ~export ?(progress = no_progress_bar) () = let open Nbd_unix in let c = { @@ -231,7 +231,7 @@ let stream_nbd _common c s prezeroed _ ?(progress = no_progress_bar) () = } in - Client.negotiate c "" >>= fun (server, size, _flags) -> + Client.negotiate c export >>= fun (server, size, _flags) -> (* Work to do is: non-zero data to write + empty sectors if the target is not prezeroed *) D.debug "%s nbd negotiation done, size is %Ld" __FUNCTION__ size ; @@ -988,7 +988,7 @@ let write_stream common s destination destination_protocol prezeroed progress return (c, [NoProtocol; Human; Tar]) | File_descr fd -> Channels.of_raw_fd fd >>= fun c -> - return (c, [Nbd; NoProtocol; Chunked; Human; Tar]) + return (c, [dummy_nbd; NoProtocol; Chunked; Human; Tar]) | Sockaddr sockaddr -> let sock = socket sockaddr in Lwt.catch @@ -996,7 +996,7 @@ let write_stream common s destination destination_protocol prezeroed progress (fun e -> Lwt_unix.close sock >>= fun () -> Lwt.fail e) >>= fun () -> Channels.of_raw_fd sock >>= fun c -> - return (c, [Nbd; NoProtocol; Chunked; Human; Tar]) + return (c, [dummy_nbd; NoProtocol; Chunked; Human; Tar]) | Https uri' | Http uri' -> ( (* TODO: https is not currently implemented *) let port = @@ -1016,7 +1016,6 @@ let write_stream common s destination destination_protocol prezeroed progress let host = Scanf.ksscanf host (fun _ _ -> host) "[%s@]" Fun.id in Lwt_unix.getaddrinfo host (string_of_int port) [] >>= fun he -> if he = [] then raise Not_found ; - let sockaddr = (List.hd he).Unix.ai_addr in let sock = socket sockaddr in Lwt.catch @@ -1077,7 +1076,7 @@ let write_stream common s destination destination_protocol prezeroed progress List.mem_assoc te headers && List.assoc te headers = "nbd" in if advertises_nbd then - return (c, [Nbd]) + return (c, [dummy_nbd]) else return (c, [Chunked; NoProtocol]) else @@ -1094,7 +1093,12 @@ let write_stream common s destination destination_protocol prezeroed progress D.info "Using protocol: %s\n%!" (string_of_protocol t) ; t in - if not (List.mem destination_protocol possible_protocols) then + (* when checking for the validity of the protocol, we only care about the protocol + itself and not the export name, if it was nbd. Convert all Nbd export to Nbd "" *) + let equal_to_dest_proto = + ( = ) (match destination_protocol with Nbd _ -> dummy_nbd | y -> y) + in + if not (List.exists equal_to_dest_proto possible_protocols) then fail (Failure (Printf.sprintf "this destination only supports protocols: [ %s ]" @@ -1104,18 +1108,17 @@ let write_stream common s destination destination_protocol prezeroed progress else let start = Unix.gettimeofday () in ( match destination_protocol with - | Nbd -> - stream_nbd + | Nbd export -> + stream_nbd common c s prezeroed ~export ~progress () | Human -> - stream_human + stream_human common c s prezeroed tar_filename_prefix ~progress () | Chunked -> - stream_chunked + stream_chunked common c s prezeroed tar_filename_prefix ~progress () | Tar -> - stream_tar + stream_tar common c s prezeroed tar_filename_prefix ~progress () | NoProtocol -> - stream_raw + stream_raw common c s prezeroed tar_filename_prefix ~progress () ) - common c s prezeroed tar_filename_prefix ~progress () >>= fun p -> c.Channels.close () >>= fun () -> match p with @@ -1322,7 +1325,7 @@ let serve common_options source source_fd source_format source_protocol let supported_formats = ["raw"] in if not (List.mem destination_format supported_formats) then failwith (Printf.sprintf "%s is not a supported format" destination_format) ; - let supported_protocols = [NoProtocol; Chunked; Nbd; Tar] in + let supported_protocols = [NoProtocol; Chunked; dummy_nbd; Tar] in if not (List.mem source_protocol supported_protocols) then failwith (Printf.sprintf "%s is not a supported source protocol" @@ -1412,7 +1415,7 @@ let serve common_options source source_fd source_format source_protocol match (source_format, source_protocol) with | "raw", NoProtocol -> serve_raw_to_raw common_options size - | "raw", Nbd -> + | "raw", Nbd _ -> serve_nbd_to_raw common_options size | "raw", Chunked -> serve_chunked_to_raw common_options diff --git a/ocaml/vhd-tool/src/streamCommon.ml b/ocaml/vhd-tool/src/streamCommon.ml index 9ec8e243205..57aae1236d1 100644 --- a/ocaml/vhd-tool/src/streamCommon.ml +++ b/ocaml/vhd-tool/src/streamCommon.ml @@ -12,11 +12,20 @@ * GNU Lesser General Public License for more details. *) -type protocol = Nbd | Chunked | Human | Tar | NoProtocol +type protocol = + | Nbd of string (* export name used by the nbd client during negotiation*) + | Chunked + | Human + | Tar + | NoProtocol + +(** This dummy nbd has no export name in it, it is introduced for backwards compatability +reasons. *) +let dummy_nbd = Nbd "" let protocol_of_string = function | "nbd" -> - Nbd + dummy_nbd | "chunked" -> Chunked | "human" -> @@ -29,8 +38,8 @@ let protocol_of_string = function failwith (Printf.sprintf "Unsupported protocol: %s" x) let string_of_protocol = function - | Nbd -> - "nbd" + | Nbd export -> + "nbd:" ^ export | Chunked -> "chunked" | Human -> diff --git a/ocaml/vhd-tool/test/dune b/ocaml/vhd-tool/test/dune index 77bbe519bc3..b31c295ccd7 100644 --- a/ocaml/vhd-tool/test/dune +++ b/ocaml/vhd-tool/test/dune @@ -4,7 +4,7 @@ alcotest alcotest-lwt lwt - local_lib + vhd_lib vhd-format vhd-format-lwt ) diff --git a/ocaml/xapi/dune b/ocaml/xapi/dune index 22b5376b8d6..b0a5c6efc31 100644 --- a/ocaml/xapi/dune +++ b/ocaml/xapi/dune @@ -159,6 +159,7 @@ uri uuid uuidm + vhd_lib x509 xapi_aux xapi-backtrace diff --git a/ocaml/xapi/sparse_dd_wrapper.ml b/ocaml/xapi/sparse_dd_wrapper.ml index c2a0f2112a7..0195fc38884 100644 --- a/ocaml/xapi/sparse_dd_wrapper.ml +++ b/ocaml/xapi/sparse_dd_wrapper.ml @@ -72,7 +72,8 @@ end exception Cancelled (** Use the new external sparse_dd program *) -let dd_internal progress_cb base prezeroed verify_cert infile outfile size = +let dd_internal progress_cb base prezeroed verify_cert ?(proto = None) infile + outfile size = let pipe_read, pipe_write = Unix.pipe () in let to_close = ref [pipe_read; pipe_write] in let close x = @@ -87,6 +88,15 @@ let dd_internal progress_cb base prezeroed verify_cert infile outfile size = match Forkhelpers.with_logfile_fd "sparse_dd" (fun log_fd -> let sparse_dd_path = !Xapi_globs.sparse_dd in + let proto_args = + match proto with + | None -> + [] + | Some (StreamCommon.Nbd export) -> + ["-dest-proto"; "nbd"; "-nbd-export"; export] + | Some p -> + ["-dest-proto"; StreamCommon.string_of_protocol p] + in let verify_args = match verify_cert with | None -> @@ -119,6 +129,7 @@ let dd_internal progress_cb base prezeroed verify_cert infile outfile size = ; (if prezeroed then ["-prezeroed"] else []) ; (match base with None -> [] | Some x -> ["-base"; x]) ; verify_args + ; proto_args ] in debug "%s %s" sparse_dd_path (String.concat " " args) ; @@ -164,7 +175,7 @@ let dd_internal progress_cb base prezeroed verify_cert infile outfile size = | Forkhelpers.Success _ -> progress_cb (Finished None) | Forkhelpers.Failure (log, End_of_file) -> - error "Error while trying to read progress from sparse_dd" ; + error "Error while trying to read progress from sparse_dd: %s" log ; raise (Api_errors.Server_error (Api_errors.vdi_copy_failed, [log])) | Forkhelpers.Failure (log, exn) -> error "Failure from sparse_dd: %s raising %s" log @@ -179,13 +190,13 @@ let dd_internal progress_cb base prezeroed verify_cert infile outfile size = ) (fun () -> close pipe_read ; close pipe_write) -let dd ?(progress_cb = fun _ -> ()) ?base ~verify_cert prezeroed = +let dd ?(progress_cb = fun _ -> ()) ?base ~verify_cert ?proto prezeroed = dd_internal (function Continuing x -> progress_cb x | _ -> ()) - base prezeroed verify_cert + base prezeroed ~proto verify_cert -let start ?(progress_cb = fun _ -> ()) ?base ~verify_cert prezeroed infile - outfile size = +let start ?(progress_cb = fun _ -> ()) ?base ~verify_cert ?(proto = None) + prezeroed infile outfile size = let m = Mutex.create () in let c = Condition.create () in let pid = ref None in @@ -206,8 +217,8 @@ let start ?(progress_cb = fun _ -> ()) ?base ~verify_cert prezeroed infile let _ = Thread.create (fun () -> - dd_internal thread_progress_cb base prezeroed verify_cert infile outfile - size + dd_internal thread_progress_cb base prezeroed verify_cert ~proto infile + outfile size ) () in diff --git a/ocaml/xapi/storage_migrate.ml b/ocaml/xapi/storage_migrate.ml index 950824a864c..e304895be74 100644 --- a/ocaml/xapi/storage_migrate.ml +++ b/ocaml/xapi/storage_migrate.ml @@ -562,7 +562,20 @@ module MigrateLocal = struct finally (fun () -> debug "activating RW datapath %s on remote" remote_dp ; - ignore (Remote.VDI.attach2 dbg remote_dp dest dest_vdi true) ; + let backend = + Remote.VDI.attach3 dbg remote_dp dest dest_vdi vm true + in + let _, _, _, nbds = + Storage_interface.implementations_of_backend backend + in + let proto = + match nbds with + | [] -> + None + | uri :: _ -> + let _socket, export = Storage_interface.parse_nbd_uri uri in + Some (StreamCommon.Nbd export) + in Remote.VDI.activate dbg remote_dp dest dest_vdi ; with_activated_disk ~dbg ~sr ~vdi:base_vdi ~dp:base_dp (fun base_path -> @@ -574,7 +587,7 @@ module MigrateLocal = struct let dd = Sparse_dd_wrapper.start ~progress_cb:(progress_callback 0.05 0.9 task) - ~verify_cert ?base:base_path true (Option.get src) + ~verify_cert ~proto ?base:base_path true (Option.get src) dest_vdi_url remote_vdi.virtual_size in Storage_task.with_cancel task From cdcaa9d3854fbede8e69fe25fc9244261536ad77 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Mon, 28 Oct 2024 15:19:24 +0000 Subject: [PATCH 26/53] CP-45016: Implement inbound SXM SMAPIv3 SRs This is the main commit that implements the tracking logic of SXM for inbound SMAPIv3 SRs. It mainly consists of the following changes: - Adding parameters to various SMAPIv2 calls to track the domain slice that is invoking the SMAPI calls. For SXM, we track two particular domains: the mirror domain and the copy domain, corresponding to two of the operations happening during migration: mirror of the VDI and copy of the base image. Although this was not needed for SMAPIv1 calls, it is now necessary for SMAPIv3 calls since it requires explicit tracking of the domain slices. - Extend various SMAPIv2 calls with the new domain parameter since SMAPIv3 has one qemu-dp process per domain, and therefore requires us to keep track of the domain parameter. - Invoking the `import_activate` call in the new `nbd_handler` on the receiving end to prepare an nbd server for mirroring. Signed-off-by: Vincent Liu --- ocaml/xapi-idl/storage/storage_interface.ml | 53 +++++++++++-- ocaml/xapi-idl/storage/storage_skeleton.ml | 8 +- ocaml/xapi-storage-cli/main.ml | 6 +- ocaml/xapi-storage-script/main.ml | 1 + ocaml/xapi/storage_migrate.ml | 85 ++++++++++++--------- ocaml/xapi/storage_mux.ml | 4 + ocaml/xapi/storage_smapiv1.ml | 10 ++- ocaml/xapi/storage_smapiv1_wrapper.ml | 18 ++++- ocaml/xapi/xapi_services.ml | 3 + ocaml/xapi/xapi_vm_migrate.ml | 47 ++++++++++-- 10 files changed, 174 insertions(+), 61 deletions(-) diff --git a/ocaml/xapi-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index c3cc92d86b2..f7b2cdb185f 100644 --- a/ocaml/xapi-idl/storage/storage_interface.ml +++ b/ocaml/xapi-idl/storage/storage_interface.ml @@ -989,6 +989,7 @@ module StorageAPI (R : RPC) = struct (dbg_p @-> sr_p @-> vdi_p + @-> vm_p @-> url_p @-> dest_p @-> verify_dest_p @@ -996,6 +997,10 @@ module StorageAPI (R : RPC) = struct ) module MIRROR = struct + let mirror_vm_p = Param.mk ~name:"mirror_vm" Vm.t + + let copy_vm_p = Param.mk ~name:"copy_vm" Vm.t + (** [start task sr vdi url sr2] creates a VDI in remote [url]'s [sr2] and writes data synchronously. It returns the id of the VDI.*) let start = @@ -1004,6 +1009,8 @@ module StorageAPI (R : RPC) = struct @-> sr_p @-> vdi_p @-> dp_p + @-> mirror_vm_p + @-> copy_vm_p @-> url_p @-> dest_p @-> verify_dest_p @@ -1020,7 +1027,11 @@ module StorageAPI (R : RPC) = struct let result_p = Param.mk ~name:"result" Mirror.t in declare "DATA.MIRROR.stat" [] (dbg_p @-> id_p @-> returning result_p err) - (** Called on the receiving end *) + (** Called on the receiving end + @deprecated This function is deprecated, and is only here to keep backward + compatibility with old xapis that call Remote.DATA.MIRROR.receive_start during SXM. + Use the receive_start2 function instead. + *) let receive_start = let similar_p = Param.mk ~name:"similar" Mirror.similars in let result = Param.mk ~name:"result" Mirror.mirror_receive_result in @@ -1033,6 +1044,20 @@ module StorageAPI (R : RPC) = struct @-> returning result err ) + (** Called on the receiving end to prepare for receipt of the storage.*) + let receive_start2 = + let similar_p = Param.mk ~name:"similar" Mirror.similars in + let result = Param.mk ~name:"result" Mirror.mirror_receive_result in + declare "DATA.MIRROR.receive_start2" [] + (dbg_p + @-> sr_p + @-> VDI.vdi_info_p + @-> id_p + @-> similar_p + @-> vm_p + @-> returning result err + ) + let receive_finalize = declare "DATA.MIRROR.receive_finalize" [] (dbg_p @-> id_p @-> returning unit_p err) @@ -1354,6 +1379,7 @@ module type Server_impl = sig -> dbg:debug_info -> sr:sr -> vdi:vdi + -> vm:vm -> url:string -> dest:sr -> verify_dest:bool @@ -1366,6 +1392,8 @@ module type Server_impl = sig -> sr:sr -> vdi:vdi -> dp:dp + -> mirror_vm:vm + -> copy_vm:vm -> url:string -> dest:sr -> verify_dest:bool @@ -1384,6 +1412,16 @@ module type Server_impl = sig -> similar:Mirror.similars -> Mirror.mirror_receive_result + val receive_start2 : + context + -> dbg:debug_info + -> sr:sr + -> vdi_info:vdi_info + -> id:Mirror.id + -> similar:Mirror.similars + -> vm:vm + -> Mirror.mirror_receive_result + val receive_finalize : context -> dbg:debug_info -> id:Mirror.id -> unit val receive_cancel : context -> dbg:debug_info -> id:Mirror.id -> unit @@ -1552,17 +1590,22 @@ module Server (Impl : Server_impl) () = struct Impl.VDI.list_changed_blocks () ~dbg ~sr ~vdi_from ~vdi_to ) ; S.get_by_name (fun dbg name -> Impl.get_by_name () ~dbg ~name) ; - S.DATA.copy (fun dbg sr vdi url dest verify_dest -> - Impl.DATA.copy () ~dbg ~sr ~vdi ~url ~dest ~verify_dest + S.DATA.copy (fun dbg sr vdi vm url dest verify_dest -> + Impl.DATA.copy () ~dbg ~sr ~vdi ~vm ~url ~dest ~verify_dest ) ; - S.DATA.MIRROR.start (fun dbg sr vdi dp url dest verify_dest -> - Impl.DATA.MIRROR.start () ~dbg ~sr ~vdi ~dp ~url ~dest ~verify_dest + S.DATA.MIRROR.start + (fun dbg sr vdi dp mirror_vm copy_vm url dest verify_dest -> + Impl.DATA.MIRROR.start () ~dbg ~sr ~vdi ~dp ~mirror_vm ~copy_vm ~url + ~dest ~verify_dest ) ; S.DATA.MIRROR.stop (fun dbg id -> Impl.DATA.MIRROR.stop () ~dbg ~id) ; S.DATA.MIRROR.stat (fun dbg id -> Impl.DATA.MIRROR.stat () ~dbg ~id) ; S.DATA.MIRROR.receive_start (fun dbg sr vdi_info id similar -> Impl.DATA.MIRROR.receive_start () ~dbg ~sr ~vdi_info ~id ~similar ) ; + S.DATA.MIRROR.receive_start2 (fun dbg sr vdi_info id similar vm -> + Impl.DATA.MIRROR.receive_start2 () ~dbg ~sr ~vdi_info ~id ~similar ~vm + ) ; S.DATA.MIRROR.receive_cancel (fun dbg id -> Impl.DATA.MIRROR.receive_cancel () ~dbg ~id ) ; diff --git a/ocaml/xapi-idl/storage/storage_skeleton.ml b/ocaml/xapi-idl/storage/storage_skeleton.ml index 589c16f5135..c37abe5cb43 100644 --- a/ocaml/xapi-idl/storage/storage_skeleton.ml +++ b/ocaml/xapi-idl/storage/storage_skeleton.ml @@ -152,12 +152,13 @@ end let get_by_name ctx ~dbg ~name = u "get_by_name" module DATA = struct - let copy ctx ~dbg ~sr ~vdi ~url ~dest = u "DATA.copy" + let copy ctx ~dbg ~sr ~vdi ~vm ~url ~dest = u "DATA.copy" module MIRROR = struct (** [start task sr vdi url sr2] creates a VDI in remote [url]'s [sr2] and writes data synchronously. It returns the id of the VDI.*) - let start ctx ~dbg ~sr ~vdi ~dp ~url ~dest = u "DATA.MIRROR.start" + let start ctx ~dbg ~sr ~vdi ~dp ~mirror_vm ~copy_vm ~url ~dest = + u "DATA.MIRROR.start" let stop ctx ~dbg ~id = u "DATA.MIRROR.stop" @@ -166,6 +167,9 @@ module DATA = struct let receive_start ctx ~dbg ~sr ~vdi_info ~id ~similar = u "DATA.MIRROR.receive_start" + let receive_start2 ctx ~dbg ~sr ~vdi_info ~id ~similar ~vm = + u "DATA.MIRROR.receive_start2" + let receive_finalize ctx ~dbg ~id = u "DATA.MIRROR.receive_finalize" let receive_cancel ctx ~dbg ~id = u "DATA.MIRROR.receive_cancel" diff --git a/ocaml/xapi-storage-cli/main.ml b/ocaml/xapi-storage-cli/main.ml index 9355cd5b4c3..c64a4f6fcd9 100644 --- a/ocaml/xapi-storage-cli/main.ml +++ b/ocaml/xapi-storage-cli/main.ml @@ -311,6 +311,10 @@ let on_vdi' f common_opts sr vdi = ) common_opts sr vdi +let mirror_vm = Vm.of_string "SXM_mirror" + +let copy_vm = Vm.of_string "SXM_copy" + let mirror_start common_opts sr vdi dp url dest verify_dest = on_vdi' (fun sr vdi -> @@ -319,7 +323,7 @@ let mirror_start common_opts sr vdi dp url dest verify_dest = let url = get_opt url "Need a URL" in let dest = get_opt dest "Need a destination SR" in let task = - Client.DATA.MIRROR.start dbg sr vdi dp url + Client.DATA.MIRROR.start dbg sr vdi dp mirror_vm copy_vm url (Storage_interface.Sr.of_string dest) verify_dest in diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index 095b6bdeddb..ff52881742e 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -1823,6 +1823,7 @@ let bind ~volume_script_dir = S.get_by_name (u "get_by_name") ; S.VDI.get_by_name (u "VDI.get_by_name") ; S.DATA.MIRROR.receive_start (u "DATA.MIRROR.receive_start") ; + S.DATA.MIRROR.receive_start2 (u "DATA.MIRROR.receive_start2") ; S.UPDATES.get (u "UPDATES.get") ; S.SR.update_snapshot_info_dest (u "SR.update_snapshot_info_dest") ; S.DATA.MIRROR.list (u "DATA.MIRROR.list") ; diff --git a/ocaml/xapi/storage_migrate.ml b/ocaml/xapi/storage_migrate.ml index e304895be74..b34904b21c2 100644 --- a/ocaml/xapi/storage_migrate.ml +++ b/ocaml/xapi/storage_migrate.ml @@ -362,13 +362,11 @@ let tapdisk_of_attach_info (backend : Storage_interface.backend) = (Storage_interface.(rpc_of backend) backend |> Rpc.to_string) ; None -let vm_of_s = Storage_interface.Vm.of_string - -let with_activated_disk ~dbg ~sr ~vdi ~dp f = +let with_activated_disk ~dbg ~sr ~vdi ~dp ~vm f = let attached_vdi = Option.map (fun vdi -> - let backend = Local.VDI.attach3 dbg dp sr vdi (vm_of_s "0") false in + let backend = Local.VDI.attach3 dbg dp sr vdi vm false in (vdi, backend) ) vdi @@ -383,10 +381,10 @@ let with_activated_disk ~dbg ~sr ~vdi ~dp f = in match (files, blockdevs, nbds) with | {path} :: _, _, _ | _, {path} :: _, _ -> - Local.VDI.activate3 dbg dp sr vdi (vm_of_s "0") ; + Local.VDI.activate3 dbg dp sr vdi vm ; (path, false) | _, _, nbd :: _ -> - Local.VDI.activate3 dbg dp sr vdi (vm_of_s "0") ; + Local.VDI.activate3 dbg dp sr vdi vm ; let unix_socket_path, export_name = Storage_interface.parse_nbd_uri nbd in @@ -423,14 +421,12 @@ let with_activated_disk ~dbg ~sr ~vdi ~dp f = () ) path_and_nbd ; - Option.iter - (fun vdi -> Local.VDI.deactivate dbg dp sr vdi (vm_of_s "0")) - vdi + Option.iter (fun vdi -> Local.VDI.deactivate dbg dp sr vdi vm) vdi ) ) (fun () -> Option.iter - (fun (vdi, _) -> Local.VDI.detach dbg dp sr vdi (vm_of_s "0")) + (fun (vdi, _) -> Local.VDI.detach dbg dp sr vdi vm) attached_vdi ) @@ -462,7 +458,7 @@ they tend to be executed on the sender side. although there is not a hard rule on what is executed on the sender side, this provides some heuristics. *) module MigrateLocal = struct (** [copy_into_vdi] is similar to [copy_into_sr] but requires a [dest_vdi] parameter *) - let copy_into_vdi ~task ~dbg ~sr ~vdi ~url ~dest ~dest_vdi ~verify_dest = + let copy_into_vdi ~task ~dbg ~sr ~vdi ~vm ~url ~dest ~dest_vdi ~verify_dest = let remote_url = Storage_utils.connection_args_of_uri ~verify_dest url in let module Remote = StorageAPI (Idl.Exn.GenClient (struct let rpc = @@ -532,7 +528,8 @@ module MigrateLocal = struct let dest_vdi_url = let url' = Http.Url.of_string url in Http.Url.set_uri url' - (Printf.sprintf "%s/nbd/%s/%s/%s" (Http.Url.get_uri url') + (Printf.sprintf "%s/nbd/%s/%s/%s/%s" (Http.Url.get_uri url') + (Storage_interface.Vm.string_of vm) (Storage_interface.Sr.string_of dest) (Storage_interface.Vdi.string_of dest_vdi) remote_dp @@ -576,10 +573,10 @@ module MigrateLocal = struct let _socket, export = Storage_interface.parse_nbd_uri uri in Some (StreamCommon.Nbd export) in - Remote.VDI.activate dbg remote_dp dest dest_vdi ; - with_activated_disk ~dbg ~sr ~vdi:base_vdi ~dp:base_dp + Remote.VDI.activate3 dbg remote_dp dest dest_vdi vm ; + with_activated_disk ~dbg ~sr ~vdi:base_vdi ~dp:base_dp ~vm (fun base_path -> - with_activated_disk ~dbg ~sr ~vdi:(Some vdi) ~dp:leaf_dp + with_activated_disk ~dbg ~sr ~vdi:(Some vdi) ~dp:leaf_dp ~vm (fun src -> let verify_cert = if verify_dest then Stunnel_client.pool () else None @@ -621,7 +618,7 @@ module MigrateLocal = struct (** [copy_into_sr] does not requires a dest vdi to be provided, instead, it will find the nearest vdi on the [dest] sr, and if there is no such vdi, it will create one. *) - let copy_into_sr ~task ~dbg ~sr ~vdi ~url ~dest ~verify_dest = + let copy_into_sr ~task ~dbg ~sr ~vdi ~vm ~url ~dest ~verify_dest = debug "copy sr:%s vdi:%s url:%s dest:%s verify_dest:%B" (Storage_interface.Sr.string_of sr) (Storage_interface.Vdi.string_of vdi) @@ -706,8 +703,8 @@ module MigrateLocal = struct Remote.VDI.create dbg dest {local_vdi with sm_config= []} in let remote_copy = - copy_into_vdi ~task ~dbg ~sr ~vdi ~url ~dest ~dest_vdi:remote_base.vdi - ~verify_dest + copy_into_vdi ~task ~dbg ~sr ~vdi ~vm ~url ~dest + ~dest_vdi:remote_base.vdi ~verify_dest |> vdi_info in let snapshot = Remote.VDI.snapshot dbg dest remote_copy in @@ -723,10 +720,17 @@ module MigrateLocal = struct | e -> raise (Storage_error (Internal_error (Printexc.to_string e))) - let start ~task ~dbg ~sr ~vdi ~dp ~url ~dest ~verify_dest = - SXM.info "%s sr:%s vdi:%s url:%s dest:%s verify_dest:%B" __FUNCTION__ + let start ~task ~dbg ~sr ~vdi ~dp ~mirror_vm ~copy_vm ~url ~dest ~verify_dest + = + SXM.info + "%s sr:%s vdi:%s dp: %s mirror_vm: %s copy_vm: %s url:%s dest:%s \ + verify_dest:%B" + __FUNCTION__ (Storage_interface.Sr.string_of sr) (Storage_interface.Vdi.string_of vdi) + dp + (Storage_interface.Vm.string_of mirror_vm) + (Storage_interface.Vm.string_of copy_vm) url (Storage_interface.Sr.string_of dest) verify_dest ; @@ -742,8 +746,6 @@ module MigrateLocal = struct try List.find (fun x -> x.vdi = vdi) vdis with Not_found -> failwith "Local VDI not found" in - let id = State.mirror_id_of (sr, local_vdi.vdi) in - debug "Adding to active local mirrors before sending: id=%s" id ; let mirror_id = State.mirror_id_of (sr, local_vdi.vdi) in debug "%s: Adding to active local mirrors before sending: id=%s" __FUNCTION__ mirror_id ; @@ -782,18 +784,20 @@ module MigrateLocal = struct similar_vdis ) ) ; - let result_ty = - Remote.DATA.MIRROR.receive_start dbg dest local_vdi mirror_id similars + let (Mirror.Vhd_mirror result) = + Remote.DATA.MIRROR.receive_start2 dbg dest local_vdi mirror_id similars + mirror_vm in - let result = match result_ty with Mirror.Vhd_mirror x -> x in (* Enable mirroring on the local machine *) let mirror_dp = result.Mirror.mirror_datapath in let uri = - Printf.sprintf "/services/SM/nbd/%s/%s/%s" + Printf.sprintf "/services/SM/nbd/%s/%s/%s/%s" + (Storage_interface.Vm.string_of mirror_vm) (Storage_interface.Sr.string_of dest) (Storage_interface.Vdi.string_of result.Mirror.mirror_vdi.vdi) mirror_dp in + debug "%s: uri of http request for mirroring is %s" __FUNCTION__ uri ; let dest_url = Http.Url.set_uri remote_url uri in let request = Http.Request.make @@ -803,7 +807,7 @@ module MigrateLocal = struct let verify_cert = if verify_dest then Stunnel_client.pool () else None in let transport = Xmlrpc_client.transport_of_url ~verify_cert dest_url in debug "Searching for data path: %s" dp ; - let attach_info = Local.DP.attach_info dbg sr vdi dp (Vm.of_string "0") in + let attach_info = Local.DP.attach_info dbg sr vdi dp mirror_vm in on_fail := (fun () -> Remote.DATA.MIRROR.receive_cancel dbg mirror_id) :: !on_fail ; let tapdev = @@ -908,8 +912,8 @@ module MigrateLocal = struct (* Copy the snapshot to the remote *) let new_parent = Storage_task.with_subtask task "copy" (fun () -> - copy_into_vdi ~task ~dbg ~sr ~vdi:snapshot.vdi ~url ~dest - ~dest_vdi:result.Mirror.copy_diffs_to ~verify_dest + copy_into_vdi ~task ~dbg ~sr ~vdi:snapshot.vdi ~vm:copy_vm ~url + ~dest ~dest_vdi:result.Mirror.copy_diffs_to ~verify_dest ) |> vdi_info in @@ -1123,7 +1127,7 @@ end (** module [MigrateRemote] is similar to [MigrateLocal], but most of these functions tend to be executed on the receiver side. *) module MigrateRemote = struct - let receive_start ~dbg ~sr ~vdi_info ~id ~similar = + let receive_start2 ~dbg ~sr ~vdi_info ~id ~similar ~vm = let on_fail : (unit -> unit) list ref = ref [] in let vdis = Local.SR.scan dbg sr in (* We drop cbt_metadata VDIs that do not have any actual data *) @@ -1138,8 +1142,8 @@ module MigrateRemote = struct on_fail := (fun () -> Local.VDI.destroy dbg sr dummy.vdi) :: !on_fail ; debug "Created dummy snapshot for mirror receive: %s" (string_of_vdi_info dummy) ; - let _ = Local.VDI.attach3 dbg leaf_dp sr leaf.vdi (vm_of_s "0") true in - Local.VDI.activate3 dbg leaf_dp sr leaf.vdi (vm_of_s "0") ; + let _ : backend = Local.VDI.attach3 dbg leaf_dp sr leaf.vdi vm true in + Local.VDI.activate3 dbg leaf_dp sr leaf.vdi vm ; let nearest = List.fold_left (fun acc content_id -> @@ -1219,6 +1223,9 @@ module MigrateRemote = struct !on_fail ; raise e + let receive_start ~dbg ~sr ~vdi_info ~id ~similar = + receive_start2 ~dbg ~sr ~vdi_info ~id ~similar ~vm:(Vm.of_string "0") + let receive_finalize ~dbg ~id = let recv_state = State.find_active_receive_mirror id in let open State.Receive_state in @@ -1384,16 +1391,16 @@ let with_task_and_thread ~dbg f = this way so that they all stay in one place rather than being spread around the file. *) -let copy ~dbg ~sr ~vdi ~url ~dest ~verify_dest = +let copy ~dbg ~sr ~vdi ~vm ~url ~dest ~verify_dest = with_task_and_thread ~dbg (fun task -> - MigrateLocal.copy_into_sr ~task ~dbg:(Debug_info.to_string dbg) ~sr ~vdi - ~url ~dest ~verify_dest + MigrateLocal.copy_into_sr ~task ~dbg:dbg.Debug_info.log ~sr ~vdi ~vm ~url + ~dest ~verify_dest ) -let start ~dbg ~sr ~vdi ~dp ~url ~dest ~verify_dest = +let start ~dbg ~sr ~vdi ~dp ~mirror_vm ~copy_vm ~url ~dest ~verify_dest = with_task_and_thread ~dbg (fun task -> - MigrateLocal.start ~task ~dbg:(Debug_info.to_string dbg) ~sr ~vdi ~dp ~url - ~dest ~verify_dest + MigrateLocal.start ~task ~dbg:dbg.Debug_info.log ~sr ~vdi ~dp ~mirror_vm + ~copy_vm ~url ~dest ~verify_dest ) (* XXX: PR-1255: copy the xenopsd 'raise Exception' pattern *) @@ -1413,6 +1420,8 @@ let stat = MigrateLocal.stat let receive_start = MigrateRemote.receive_start +let receive_start2 = MigrateRemote.receive_start2 + let receive_finalize = MigrateRemote.receive_finalize let receive_cancel = MigrateRemote.receive_cancel diff --git a/ocaml/xapi/storage_mux.ml b/ocaml/xapi/storage_mux.ml index b974f392c94..f973c9d108e 100644 --- a/ocaml/xapi/storage_mux.ml +++ b/ocaml/xapi/storage_mux.ml @@ -855,6 +855,10 @@ module Mux = struct with_dbg ~name:"DATA.MIRROR.receive_start" ~dbg @@ fun {log= dbg; _} -> Storage_migrate.receive_start ~dbg + let receive_start2 () ~dbg = + with_dbg ~name:"DATA.MIRROR.receive_start2" ~dbg @@ fun {log= dbg; _} -> + Storage_migrate.receive_start2 ~dbg + let receive_finalize () ~dbg = with_dbg ~name:"DATA.MIRROR.receive_finalize" ~dbg @@ fun {log= dbg; _} -> Storage_migrate.receive_finalize ~dbg diff --git a/ocaml/xapi/storage_smapiv1.ml b/ocaml/xapi/storage_smapiv1.ml index 23987d33e1b..c1b68d5100a 100644 --- a/ocaml/xapi/storage_smapiv1.ml +++ b/ocaml/xapi/storage_smapiv1.ml @@ -1208,12 +1208,12 @@ module SMAPIv1 : Server_impl = struct let get_by_name _context ~dbg:_ ~name:_ = assert false module DATA = struct - let copy _context ~dbg:_ ~sr:_ ~vdi:_ ~url:_ ~dest:_ ~verify_dest:_ = + let copy _context ~dbg:_ ~sr:_ ~vdi:_ ~vm:_ ~url:_ ~dest:_ ~verify_dest:_ = assert false module MIRROR = struct - let start _context ~dbg:_ ~sr:_ ~vdi:_ ~dp:_ ~url:_ ~dest:_ ~verify_dest:_ - = + let start _context ~dbg:_ ~sr:_ ~vdi:_ ~dp:_ ~mirror_vm:_ ~copy_vm:_ + ~url:_ ~dest:_ ~verify_dest:_ = assert false let stop _context ~dbg:_ ~id:_ = assert false @@ -1225,6 +1225,10 @@ module SMAPIv1 : Server_impl = struct let receive_start _context ~dbg:_ ~sr:_ ~vdi_info:_ ~id:_ ~similar:_ = assert false + let receive_start2 _context ~dbg:_ ~sr:_ ~vdi_info:_ ~id:_ ~similar:_ + ~vm:_ = + assert false + let receive_finalize _context ~dbg:_ ~id:_ = assert false let receive_cancel _context ~dbg:_ ~id:_ = assert false diff --git a/ocaml/xapi/storage_smapiv1_wrapper.ml b/ocaml/xapi/storage_smapiv1_wrapper.ml index 0467ceb7696..acc4da359b3 100644 --- a/ocaml/xapi/storage_smapiv1_wrapper.ml +++ b/ocaml/xapi/storage_smapiv1_wrapper.ml @@ -1137,16 +1137,17 @@ functor end module DATA = struct - let copy context ~dbg ~sr ~vdi ~url ~dest = + let copy context ~dbg ~sr ~vdi ~vm ~url ~dest = info "DATA.copy dbg:%s sr:%s vdi:%s url:%s dest:%s" dbg (s_of_sr sr) (s_of_vdi vdi) url (s_of_sr dest) ; - Impl.DATA.copy context ~dbg ~sr ~vdi ~url ~dest + Impl.DATA.copy context ~dbg ~sr ~vdi ~vm ~url ~dest module MIRROR = struct - let start context ~dbg ~sr ~vdi ~dp ~url ~dest = + let start context ~dbg ~sr ~vdi ~dp ~mirror_vm ~copy_vm ~url ~dest = info "DATA.MIRROR.start dbg:%s sr:%s vdi:%s url:%s dest:%s" dbg (s_of_sr sr) (s_of_vdi vdi) url (s_of_sr dest) ; - Impl.DATA.MIRROR.start context ~dbg ~sr ~vdi ~dp ~url ~dest + Impl.DATA.MIRROR.start context ~dbg ~sr ~vdi ~dp ~mirror_vm ~copy_vm + ~url ~dest let stop context ~dbg ~id = info "DATA.MIRROR.stop dbg:%s id:%s" dbg id ; @@ -1166,6 +1167,15 @@ functor (String.concat "," similar) ; Impl.DATA.MIRROR.receive_start context ~dbg ~sr ~vdi_info ~id ~similar + let receive_start2 context ~dbg ~sr ~vdi_info ~id ~similar ~vm = + info + "DATA.MIRROR.receive_start2 dbg:%s sr:%s id:%s similar:[%s] vm:%s" + dbg (s_of_sr sr) id + (String.concat "," similar) + (s_of_vm vm) ; + Impl.DATA.MIRROR.receive_start2 context ~dbg ~sr ~vdi_info ~id + ~similar ~vm + let receive_finalize context ~dbg ~id = info "DATA.MIRROR.receive_finalize dbg:%s id:%s" dbg id ; Impl.DATA.MIRROR.receive_finalize context ~dbg ~id diff --git a/ocaml/xapi/xapi_services.ml b/ocaml/xapi/xapi_services.ml index 8a71c2aca0c..70425ac5adf 100644 --- a/ocaml/xapi/xapi_services.ml +++ b/ocaml/xapi/xapi_services.ml @@ -203,6 +203,9 @@ let put_handler (req : Http.Request.t) s _ = ignore (Import_raw_vdi.import (Some vdi) req s ()) | [""; services; "SM"; "nbd"; sr; vdi; dp] when services = _services -> Storage_migrate.nbd_handler req s sr vdi dp + | [""; services; "SM"; "nbd"; vm; sr; vdi; dp] when services = _services + -> + Storage_migrate.nbd_handler req s ~vm sr vdi dp | _ -> Http_svr.headers s (Http.http_404_missing ~version:"1.0" ()) ; req.Http.Request.close <- true diff --git a/ocaml/xapi/xapi_vm_migrate.ml b/ocaml/xapi/xapi_vm_migrate.ml index e6fd38a2990..50e2b674c37 100644 --- a/ocaml/xapi/xapi_vm_migrate.ml +++ b/ocaml/xapi/xapi_vm_migrate.ml @@ -732,6 +732,10 @@ type vdi_mirror = { snapshot_of: [`VDI] API.Ref.t ; (* API's snapshot_of reference *) do_mirror: bool (* Whether we should mirror or just copy the VDI *) + ; mirror_vm: Vm.t + (* The domain slice to which SMAPI calls should be made when mirroring this vdi *) + ; copy_vm: Vm.t + (* The domain slice to which SMAPI calls should be made when copying this vdi *) } (* For VMs (not snapshots) xenopsd does not allow remapping, so we @@ -797,7 +801,28 @@ let get_vdi_mirror __context vm vdi do_mirror = Storage_interface.Sr.of_string (Db.SR.get_uuid ~__context ~self:(Db.VDI.get_SR ~__context ~self:vdi)) in - {vdi; dp; location; sr; xenops_locator; size; snapshot_of; do_mirror} + let hash x = + let s = Digest.string x |> Digest.to_hex in + String.sub s 0 5 + in + let copy_vm = + Ref.string_of vm |> hash |> ( ^ ) "COPY" |> Storage_interface.Vm.of_string + in + let mirror_vm = + Ref.string_of vm |> hash |> ( ^ ) "MIR" |> Storage_interface.Vm.of_string + in + { + vdi + ; dp + ; location + ; sr + ; xenops_locator + ; size + ; snapshot_of + ; do_mirror + ; copy_vm + ; mirror_vm + } (* We ignore empty or CD VBDs - nothing to do there. Possible redundancy here: I don't think any VBDs other than CD VBDs can be 'empty' *) @@ -923,6 +948,8 @@ let vdi_copy_fun __context dbg vdi_map remote is_intra_pool remote_vdis so_far let with_remote_vdi remote_vdi cont = debug "Executing remote scan to ensure VDI is known to xapi" ; let remote_vdi_str = Storage_interface.Vdi.string_of remote_vdi in + debug "%s Executing remote scan to ensure VDI %s is known to xapi " + __FUNCTION__ remote_vdi_str ; XenAPI.SR.scan ~rpc:remote.rpc ~session_id:remote.session ~sr:dest_sr_ref ; let query = Printf.sprintf "(field \"location\"=\"%s\") and (field \"SR\"=\"%s\")" @@ -980,8 +1007,8 @@ let vdi_copy_fun __context dbg vdi_map remote is_intra_pool remote_vdis so_far let mirror_to_remote new_dp = let task = if not vconf.do_mirror then - SMAPI.DATA.copy dbg vconf.sr vconf.location remote.sm_url dest_sr - is_intra_pool + SMAPI.DATA.copy dbg vconf.sr vconf.location vconf.copy_vm remote.sm_url + dest_sr is_intra_pool else (* Though we have no intention of "write", here we use the same mode as the associated VBD on a mirrored VDIs (i.e. always RW). This avoids problem @@ -989,17 +1016,21 @@ let vdi_copy_fun __context dbg vdi_map remote is_intra_pool remote_vdis so_far let read_write = true in (* DP set up is only essential for MIRROR.start/stop due to their open ended pattern. It's not necessary for copy which will take care of that itself. *) - let vm = Storage_interface.Vm.of_string "0" in ignore - (SMAPI.VDI.attach3 dbg new_dp vconf.sr vconf.location vm read_write) ; - SMAPI.VDI.activate dbg new_dp vconf.sr vconf.location ; + (SMAPI.VDI.attach3 dbg new_dp vconf.sr vconf.location vconf.mirror_vm + read_write + ) ; + SMAPI.VDI.activate3 dbg new_dp vconf.sr vconf.location vconf.mirror_vm ; let id = Storage_migrate.State.mirror_id_of (vconf.sr, vconf.location) in + debug "%s mirror_vm is %s copy_vm is %s" __FUNCTION__ + (Vm.string_of vconf.mirror_vm) + (Vm.string_of vconf.copy_vm) ; (* Layering violation!! *) ignore (Storage_access.register_mirror __context id) ; - SMAPI.DATA.MIRROR.start dbg vconf.sr vconf.location new_dp remote.sm_url - dest_sr is_intra_pool + SMAPI.DATA.MIRROR.start dbg vconf.sr vconf.location new_dp + vconf.mirror_vm vconf.copy_vm remote.sm_url dest_sr is_intra_pool in let mapfn x = let total = Int64.to_float total_size in From 407427739a46fa7edd15d33c02fb237b5628bd8e Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Tue, 10 Dec 2024 15:02:05 +0000 Subject: [PATCH 27/53] CP-45016: Implement checking of mirroring features SXM using qemu datapaths is done in two parts: inbound and outbound, hence the storage side has declared a `VDI_MIRROR_IN` features. Implement the checking logic for this feature here. This is done in two parts: 1. Check the relevant SM for the feature `VDI_MIRROR_IN`. This is done on the destination host. If this fails, then storage migration will be prevented from happening in the beginning. Consider this as a pre-check. 2. Check in xapi-storage-script that the chosen datapath supports mirroring. Technically mirroring is a datapath feature so it makes sense to check it there. But currently there is no easy way to register a datapath feature into the SM object in xapi database, so instead the volume plugins for SMAPIv3 SRs would also declare this feature. However, we still need a final check on the datapath features to make sure the selected datapath really supports this feature. Note this check happens while trying to set up the mirror, after storage migration has started, and will give a relatively cryptic error to the user. The end result of this is that if either the volume plugin does not declare `VDI_MIRROR_IN`/`VDI_MIRROR` or the dp plugin does not declare any of these two, SXM will fail (upfront/mid way). Note current SXM of SMAPIv1 SRs are not affected since `VDI_MIRROR` is a superset of `VDI_MIRROR_IN`. Signed-off-by: Vincent Liu --- ocaml/xapi-storage-script/main.ml | 13 +++++++++---- ocaml/xapi/smint.ml | 13 ++++++++++--- ocaml/xapi/xapi_vm_migrate.ml | 18 +++++++++++------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index ff52881742e..fa5fcf7a491 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -352,6 +352,8 @@ end let _nonpersistent = "NONPERSISTENT" +let _vdi_mirror_in = "VDI_MIRROR_IN" + let _clone_on_boot_key = "clone-on-boot" let _vdi_type_key = "vdi-type" @@ -1812,10 +1814,13 @@ let bind ~volume_script_dir = stat ~dbg ~sr ~vdi:temporary ) >>>= fun response -> - choose_datapath domain response >>>= fun (rpc, _datapath, uri, domain) -> - return_data_rpc (fun () -> - Datapath_client.import_activate (rpc ~dbg) dbg uri domain - ) + choose_datapath domain response >>>= fun (rpc, datapath, uri, domain) -> + if Datapath_plugins.supports_feature datapath _vdi_mirror_in then + return_data_rpc (fun () -> + Datapath_client.import_activate (rpc ~dbg) dbg uri domain + ) + else + fail (Storage_interface.Errors.Unimplemented _vdi_mirror_in) in S.DATA.MIRROR.import_activate data_import_activate_impl ; diff --git a/ocaml/xapi/smint.ml b/ocaml/xapi/smint.ml index 02c2166a185..b5c290afcb7 100644 --- a/ocaml/xapi/smint.ml +++ b/ocaml/xapi/smint.ml @@ -41,6 +41,7 @@ module Feature = struct | Vdi_attach | Vdi_detach | Vdi_mirror + | Vdi_mirror_in | Vdi_clone | Vdi_snapshot | Vdi_resize @@ -80,6 +81,7 @@ module Feature = struct ; ("VDI_ATTACH", Vdi_attach) ; ("VDI_DETACH", Vdi_detach) ; ("VDI_MIRROR", Vdi_mirror) + ; ("VDI_MIRROR_IN", Vdi_mirror_in) ; ("VDI_RESIZE", Vdi_resize) ; ("VDI_RESIZE_ONLINE", Vdi_resize_online) ; ("VDI_CLONE", Vdi_clone) @@ -151,9 +153,14 @@ module Feature = struct (** [has_capability c fl] will test weather the required capability [c] is present in the feature list [fl]. Callers should use this function to test if a feature - is available rather than directly using membership functions on a feature list - as this function might have special logic for some features. *) - let has_capability (c : capability) fl = List.mem_assoc c fl + is available rather than directly using membership functions on a feature list + as this function might have special logic for some features. *) + let has_capability (c : capability) (fl : t list) = + List.exists + (fun (c', _v) -> + match (c, c') with Vdi_mirror_in, Vdi_mirror -> true | c, c' -> c = c' + ) + fl (** [parse_string_int64 features] takes a [features] list in its plain string forms such as "VDI_MIRROR/2" and parses them into the form of (VDI_MIRROR, 2). diff --git a/ocaml/xapi/xapi_vm_migrate.ml b/ocaml/xapi/xapi_vm_migrate.ml index 50e2b674c37..e1bd60ecf64 100644 --- a/ocaml/xapi/xapi_vm_migrate.ml +++ b/ocaml/xapi/xapi_vm_migrate.ml @@ -160,7 +160,8 @@ end)) open Storage_interface -let assert_sr_support_operations ~__context ~vdi_map ~remote ~ops = +let assert_sr_support_operations ~__context ~vdi_map ~remote ~local_ops + ~remote_ops = let op_supported_on_source_sr vdi ops = let open Smint.Feature in (* Check VDIs must not be present on SR which doesn't have required capability *) @@ -211,8 +212,8 @@ let assert_sr_support_operations ~__context ~vdi_map ~remote ~ops = in List.filter (fun (vdi, sr) -> not (is_sr_matching vdi sr)) vdi_map |> List.iter (fun (vdi, sr) -> - op_supported_on_source_sr vdi ops ; - op_supported_on_dest_sr sr ops sm_record remote + op_supported_on_source_sr vdi local_ops ; + op_supported_on_dest_sr sr remote_ops sm_record remote ) (** Check that none of the VDIs that are mapped to a different SR have CBT @@ -1781,8 +1782,9 @@ let assert_can_migrate ~__context ~vm ~dest ~live:_ ~vdi_map ~vif_map ~options ) vms_vdis ; (* operations required for migration *) - let required_sr_operations = - [Smint.Feature.Vdi_mirror; Smint.Feature.Vdi_snapshot] + let required_src_sr_operations = Smint.Feature.[Vdi_snapshot; Vdi_mirror] in + let required_dst_sr_operations = + Smint.Feature.[Vdi_snapshot; Vdi_mirror_in] in let host_from = Helpers.LocalObject source_host_ref in ( match migration_type ~__context ~remote with @@ -1796,7 +1798,8 @@ let assert_can_migrate ~__context ~vm ~dest ~live:_ ~vdi_map ~vif_map ~options (Api_errors.Server_error (Api_errors.not_supported_during_upgrade, [])) ; (* Check VDIs are not migrating to or from an SR which doesn't have required_sr_operations *) assert_sr_support_operations ~__context ~vdi_map ~remote - ~ops:required_sr_operations ; + ~local_ops:required_src_sr_operations + ~remote_ops:required_dst_sr_operations ; let snapshot = Db.VM.get_record ~__context ~self:vm in let do_cpuid_check = not force in Xapi_vm_helpers.assert_can_boot_here ~__context ~self:vm @@ -1828,7 +1831,8 @@ let assert_can_migrate ~__context ~vm ~dest ~live:_ ~vdi_map ~vif_map ~options let power_state = Db.VM.get_power_state ~__context ~self:vm in (* Check VDIs are not migrating to or from an SR which doesn't have required_sr_operations *) assert_sr_support_operations ~__context ~vdi_map ~remote - ~ops:required_sr_operations ; + ~local_ops:required_src_sr_operations + ~remote_ops:required_dst_sr_operations ; (* The copy mode is only allow on stopped VM *) if (not force) && copy && power_state <> `Halted then raise From 508d476463a693c84627a2d4cab0dedbec3e1572 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Fri, 15 Nov 2024 18:16:25 +0000 Subject: [PATCH 28/53] CP-45016: Delay VDI.compose in SXM `VDI.compose` operation links a child volume to a parent, which changes the internal structure of the volume. This is unsafe to do while the mirroring datapath is activated. Instead, we move `VDI.compose` into `receive_finalize`, which is when the source VM is paused, and the mirroring datapath deactivated. Implement `receive_finalize2` for this purpose, and also maintain backwards compatibility. Note this change does not make the dummy snapshot of the mirror VDI on the destination host redundant, as there are two purposes for this dummy snapshot: 1. to prevent GC from doing leaf coalescing; 2. to get a differencing disk, which is needed for VDI.compose. The first purpose is no longer needed due to this change, while the second purpose still remains valid. Signed-off-by: Vincent Liu --- ocaml/tests/test_storage_migrate_state.ml | 1 + ocaml/xapi-idl/storage/storage_interface.ml | 18 ++++++++ ocaml/xapi-idl/storage/storage_skeleton.ml | 2 + ocaml/xapi-storage-script/main.ml | 1 + ocaml/xapi/storage_migrate.ml | 49 +++++++++++++++------ ocaml/xapi/storage_mux.ml | 4 ++ ocaml/xapi/storage_smapiv1.ml | 2 + ocaml/xapi/storage_smapiv1_wrapper.ml | 4 ++ 8 files changed, 68 insertions(+), 13 deletions(-) diff --git a/ocaml/tests/test_storage_migrate_state.ml b/ocaml/tests/test_storage_migrate_state.ml index c6940c8dafb..42087887995 100644 --- a/ocaml/tests/test_storage_migrate_state.ml +++ b/ocaml/tests/test_storage_migrate_state.ml @@ -53,6 +53,7 @@ let sample_receive_state = ; leaf_dp= "leaf_dp" ; parent_vdi= Vdi.of_string "parent_vdi" ; remote_vdi= Vdi.of_string "remote_vdi" + ; mirror_vm= Vm.of_string "mirror_vm" } let sample_copy_state = diff --git a/ocaml/xapi-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index f7b2cdb185f..1e8b8f3d23b 100644 --- a/ocaml/xapi-idl/storage/storage_interface.ml +++ b/ocaml/xapi-idl/storage/storage_interface.ml @@ -1058,10 +1058,23 @@ module StorageAPI (R : RPC) = struct @-> returning result err ) + (** Called on the receiving end + @deprecated This function is deprecated, and is only here to keep backward + compatibility with old xapis that call Remote.DATA.MIRROR.receive_finalize + during SXM. Use the receive_finalize2 function instead. + *) let receive_finalize = declare "DATA.MIRROR.receive_finalize" [] (dbg_p @-> id_p @-> returning unit_p err) + (** [receive_finalize2 dbg id] will stop the mirroring process and compose + the snapshot VDI with the mirror VDI. It also cleans up the storage resources + used by mirroring. It is called after the the source VM is paused. This fucntion + should be used in conjunction with [receive_start2] *) + let receive_finalize2 = + declare "DATA.MIRROR.receive_finalize2" [] + (dbg_p @-> id_p @-> returning unit_p err) + let receive_cancel = declare "DATA.MIRROR.receive_cancel" [] (dbg_p @-> id_p @-> returning unit_p err) @@ -1424,6 +1437,8 @@ module type Server_impl = sig val receive_finalize : context -> dbg:debug_info -> id:Mirror.id -> unit + val receive_finalize2 : context -> dbg:debug_info -> id:Mirror.id -> unit + val receive_cancel : context -> dbg:debug_info -> id:Mirror.id -> unit val list : context -> dbg:debug_info -> (Mirror.id * Mirror.t) list @@ -1612,6 +1627,9 @@ module Server (Impl : Server_impl) () = struct S.DATA.MIRROR.receive_finalize (fun dbg id -> Impl.DATA.MIRROR.receive_finalize () ~dbg ~id ) ; + S.DATA.MIRROR.receive_finalize2 (fun dbg id -> + Impl.DATA.MIRROR.receive_finalize2 () ~dbg ~id + ) ; S.DATA.MIRROR.list (fun dbg -> Impl.DATA.MIRROR.list () ~dbg) ; S.DATA.MIRROR.import_activate (fun dbg dp sr vdi vm -> Impl.DATA.MIRROR.import_activate () ~dbg ~dp ~sr ~vdi ~vm diff --git a/ocaml/xapi-idl/storage/storage_skeleton.ml b/ocaml/xapi-idl/storage/storage_skeleton.ml index c37abe5cb43..c87ca4eba01 100644 --- a/ocaml/xapi-idl/storage/storage_skeleton.ml +++ b/ocaml/xapi-idl/storage/storage_skeleton.ml @@ -172,6 +172,8 @@ module DATA = struct let receive_finalize ctx ~dbg ~id = u "DATA.MIRROR.receive_finalize" + let receive_finalize2 ctx ~dbg ~id = u "DATA.MIRROR.receive_finalize2" + let receive_cancel ctx ~dbg ~id = u "DATA.MIRROR.receive_cancel" let list ctx ~dbg = u "DATA.MIRROR.list" diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index fa5fcf7a491..a630d1a353d 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -1840,6 +1840,7 @@ let bind ~volume_script_dir = S.DATA.copy (u "DATA.copy") ; S.DP.stat_vdi (u "DP.stat_vdi") ; S.DATA.MIRROR.receive_finalize (u "DATA.MIRROR.receive_finalize") ; + S.DATA.MIRROR.receive_finalize2 (u "DATA.MIRROR.receive_finalize2") ; S.DP.create (u "DP.create") ; S.TASK.cancel (u "TASK.cancel") ; S.VDI.attach (u "VDI.attach") ; diff --git a/ocaml/xapi/storage_migrate.ml b/ocaml/xapi/storage_migrate.ml index b34904b21c2..05915200d3b 100644 --- a/ocaml/xapi/storage_migrate.ml +++ b/ocaml/xapi/storage_migrate.ml @@ -38,6 +38,7 @@ module State = struct ; leaf_dp: dp ; parent_vdi: Vdi.t ; remote_vdi: Vdi.t + ; mirror_vm: Vm.t } [@@deriving rpcty] @@ -761,7 +762,7 @@ module MigrateLocal = struct ; watchdog= None } in - + State.add mirror_id (State.Send_op alm) ; debug "%s Added mirror %s to active local mirrors" __FUNCTION__ mirror_id ; (* A list of cleanup actions to perform if the operation should fail. *) @@ -920,15 +921,9 @@ module MigrateLocal = struct debug "Local VDI %s = remote VDI %s" (Storage_interface.Vdi.string_of snapshot.vdi) (Storage_interface.Vdi.string_of new_parent.vdi) ; - Remote.VDI.compose dbg dest result.Mirror.copy_diffs_to - result.Mirror.mirror_vdi.vdi ; - Remote.VDI.remove_from_sm_config dbg dest result.Mirror.mirror_vdi.vdi - "base_mirror" ; debug "Local VDI %s now mirrored to remote VDI: %s" (Storage_interface.Vdi.string_of local_vdi.vdi) (Storage_interface.Vdi.string_of result.Mirror.mirror_vdi.vdi) ; - debug "Destroying dummy VDI on remote" ; - Remote.VDI.destroy dbg dest result.Mirror.dummy_vdi ; debug "Destroying snapshot on src" ; Local.VDI.destroy dbg sr snapshot.vdi ; Some (Mirror_id mirror_id) @@ -1127,7 +1122,7 @@ end (** module [MigrateRemote] is similar to [MigrateLocal], but most of these functions tend to be executed on the receiver side. *) module MigrateRemote = struct - let receive_start2 ~dbg ~sr ~vdi_info ~id ~similar ~vm = + let receive_start_common ~dbg ~sr ~vdi_info ~id ~similar ~vm = let on_fail : (unit -> unit) list ref = ref [] in let vdis = Local.SR.scan dbg sr in (* We drop cbt_metadata VDIs that do not have any actual data *) @@ -1138,9 +1133,11 @@ module MigrateRemote = struct let leaf = Local.VDI.create dbg sr vdi_info in info "Created leaf VDI for mirror receive: %s" (string_of_vdi_info leaf) ; on_fail := (fun () -> Local.VDI.destroy dbg sr leaf.vdi) :: !on_fail ; + (* dummy VDI is created so that the leaf VDI becomes a differencing disk, + useful for calling VDI.compose later on *) let dummy = Local.VDI.snapshot dbg sr leaf in on_fail := (fun () -> Local.VDI.destroy dbg sr dummy.vdi) :: !on_fail ; - debug "Created dummy snapshot for mirror receive: %s" + debug "%s Created dummy snapshot for mirror receive: %s" __FUNCTION__ (string_of_vdi_info dummy) ; let _ : backend = Local.VDI.attach3 dbg leaf_dp sr leaf.vdi vm true in Local.VDI.activate3 dbg leaf_dp sr leaf.vdi vm ; @@ -1202,6 +1199,7 @@ module MigrateRemote = struct ; leaf_dp ; parent_vdi= parent.vdi ; remote_vdi= vdi_info.vdi + ; mirror_vm= vm } ) ; let nearest_content_id = Option.map (fun x -> x.content_id) nearest in @@ -1224,7 +1222,10 @@ module MigrateRemote = struct raise e let receive_start ~dbg ~sr ~vdi_info ~id ~similar = - receive_start2 ~dbg ~sr ~vdi_info ~id ~similar ~vm:(Vm.of_string "0") + receive_start_common ~dbg ~sr ~vdi_info ~id ~similar ~vm:(Vm.of_string "0") + + let receive_start2 ~dbg ~sr ~vdi_info ~id ~similar ~vm = + receive_start_common ~dbg ~sr ~vdi_info ~id ~similar ~vm let receive_finalize ~dbg ~id = let recv_state = State.find_active_receive_mirror id in @@ -1232,6 +1233,26 @@ module MigrateRemote = struct Option.iter (fun r -> Local.DP.destroy dbg r.leaf_dp false) recv_state ; State.remove_receive_mirror id + let receive_finalize2 ~dbg ~id = + let recv_state = State.find_active_receive_mirror id in + let open State.Receive_state in + Option.iter + (fun r -> + SXM.info + "%s Mirror done. Compose on the dest sr %s parent %s and leaf %s" + __FUNCTION__ (Sr.string_of r.sr) + (Vdi.string_of r.parent_vdi) + (Vdi.string_of r.leaf_vdi) ; + Local.DP.destroy2 dbg r.leaf_dp r.sr r.leaf_vdi r.mirror_vm false ; + Local.VDI.compose dbg r.sr r.parent_vdi r.leaf_vdi ; + (* On SMAPIv3, compose would have removed the now invalid dummy vdi, so + there is no need to destroy it anymore, while this is necessary on SMAPIv1 SRs. *) + log_and_ignore_exn (fun () -> Local.VDI.destroy dbg r.sr r.dummy_vdi) ; + Local.VDI.remove_from_sm_config dbg r.sr r.leaf_vdi "base_mirror" + ) + recv_state ; + State.remove_receive_mirror id + let receive_cancel ~dbg ~id = let receive_state = State.find_active_receive_mirror id in let open State.Receive_state in @@ -1320,11 +1341,11 @@ let post_detach_hook ~sr ~vdi ~dp:_ = let t = Thread.create (fun () -> - debug "Calling receive_finalize" ; + debug "Calling receive_finalize2" ; log_and_ignore_exn (fun () -> - Remote.DATA.MIRROR.receive_finalize "Mirror-cleanup" id + Remote.DATA.MIRROR.receive_finalize2 "Mirror-cleanup" id ) ; - debug "Finished calling receive_finalize" ; + debug "Finished calling receive_finalize2" ; State.remove_local_mirror id ; debug "Removed active local mirror: %s" id ) @@ -1424,6 +1445,8 @@ let receive_start2 = MigrateRemote.receive_start2 let receive_finalize = MigrateRemote.receive_finalize +let receive_finalize2 = MigrateRemote.receive_finalize2 + let receive_cancel = MigrateRemote.receive_cancel (* The remote end of this call, SR.update_snapshot_info_dest, is implemented in diff --git a/ocaml/xapi/storage_mux.ml b/ocaml/xapi/storage_mux.ml index f973c9d108e..b46a3008a76 100644 --- a/ocaml/xapi/storage_mux.ml +++ b/ocaml/xapi/storage_mux.ml @@ -863,6 +863,10 @@ module Mux = struct with_dbg ~name:"DATA.MIRROR.receive_finalize" ~dbg @@ fun {log= dbg; _} -> Storage_migrate.receive_finalize ~dbg + let receive_finalize2 () ~dbg = + with_dbg ~name:"DATA.MIRROR.receive_finalize2" ~dbg + @@ fun {log= dbg; _} -> Storage_migrate.receive_finalize2 ~dbg + let receive_cancel () ~dbg = with_dbg ~name:"DATA.MIRROR.receive_cancel" ~dbg @@ fun {log= dbg; _} -> Storage_migrate.receive_cancel ~dbg diff --git a/ocaml/xapi/storage_smapiv1.ml b/ocaml/xapi/storage_smapiv1.ml index c1b68d5100a..244cbe34565 100644 --- a/ocaml/xapi/storage_smapiv1.ml +++ b/ocaml/xapi/storage_smapiv1.ml @@ -1231,6 +1231,8 @@ module SMAPIv1 : Server_impl = struct let receive_finalize _context ~dbg:_ ~id:_ = assert false + let receive_finalize2 _context ~dbg:_ ~id:_ = assert false + let receive_cancel _context ~dbg:_ ~id:_ = assert false let import_activate _context ~dbg:_ ~dp:_ ~sr:_ ~vdi:_ ~vm:_ = diff --git a/ocaml/xapi/storage_smapiv1_wrapper.ml b/ocaml/xapi/storage_smapiv1_wrapper.ml index acc4da359b3..effa084f1f3 100644 --- a/ocaml/xapi/storage_smapiv1_wrapper.ml +++ b/ocaml/xapi/storage_smapiv1_wrapper.ml @@ -1180,6 +1180,10 @@ functor info "DATA.MIRROR.receive_finalize dbg:%s id:%s" dbg id ; Impl.DATA.MIRROR.receive_finalize context ~dbg ~id + let receive_finalize2 context ~dbg ~id = + info "DATA.MIRROR.receive_finalize2 dbg:%s id:%s" dbg id ; + Impl.DATA.MIRROR.receive_finalize2 context ~dbg ~id + let receive_cancel context ~dbg ~id = info "DATA.MIRROR.receive_cancel dbg:%s id:%s" dbg id ; Impl.DATA.MIRROR.receive_cancel context ~dbg ~id From c46ed3fd45337df6df306486780da4ffe3cf685e Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Tue, 21 Jan 2025 14:21:54 +0000 Subject: [PATCH 29/53] CA-404693 prohibit selecting driver variant if h/w not present Introduce a new error and raise it if a host driver variant is selected for hardware that is not present. Signed-off-by: Christian Lindig --- ocaml/idl/datamodel_errors.ml | 3 +++ ocaml/xapi-consts/api_errors.ml | 2 ++ ocaml/xapi/xapi_host_driver.ml | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/ocaml/idl/datamodel_errors.ml b/ocaml/idl/datamodel_errors.ml index fed2f830db1..5105df678ea 100644 --- a/ocaml/idl/datamodel_errors.ml +++ b/ocaml/idl/datamodel_errors.ml @@ -2010,6 +2010,9 @@ let _ = error Api_errors.too_many_groups [] ~doc:"VM can only belong to one group." () ; + error Api_errors.host_driver_no_hardware ["driver variant"] + ~doc:"No hardware present for this host driver variant" () ; + message (fst Api_messages.ha_pool_overcommitted) ~doc: diff --git a/ocaml/xapi-consts/api_errors.ml b/ocaml/xapi-consts/api_errors.ml index 54bdd6f6660..8d31f1e7dd0 100644 --- a/ocaml/xapi-consts/api_errors.ml +++ b/ocaml/xapi-consts/api_errors.ml @@ -1403,3 +1403,5 @@ let telemetry_next_collection_too_late = let illegal_in_fips_mode = add_error "ILLEGAL_IN_FIPS_MODE" let too_many_groups = add_error "TOO_MANY_GROUPS" + +let host_driver_no_hardware = add_error "HOST_DRIVER_NO_HARDWARE" diff --git a/ocaml/xapi/xapi_host_driver.ml b/ocaml/xapi/xapi_host_driver.ml index be66e3093f0..a4061a7f9f0 100644 --- a/ocaml/xapi/xapi_host_driver.ml +++ b/ocaml/xapi/xapi_host_driver.ml @@ -21,6 +21,9 @@ module Tool = Xapi_host_driver_tool let invalid_value field value = raise Api_errors.(Server_error (invalid_value, [field; value])) +let no_hardware driver_variant = + raise Api_errors.(Server_error (host_driver_no_hardware, [driver_variant])) + module Variant = struct let create ~__context ~name ~version ~driver ~hw_present ~priority ~dev_status = @@ -68,6 +71,8 @@ module Variant = struct let drv = Db.Driver_variant.get_driver ~__context ~self in let d = Db.Host_driver.get_record ~__context ~self:drv in let v = Db.Driver_variant.get_record ~__context ~self in + if v.API.driver_variant_hardware_present = false then + no_hardware (Ref.string_of self) ; let stdout = Tool.call ["select"; d.API.host_driver_name; v.API.driver_variant_name] in @@ -133,6 +138,8 @@ let select ~__context ~self ~variant = D.debug "%s selecting driver %s variant %s" __FUNCTION__ drv var ; let d = Db.Host_driver.get_record ~__context ~self in let v = Db.Driver_variant.get_record ~__context ~self:variant in + if v.API.driver_variant_hardware_present = false then + no_hardware (Ref.string_of variant) ; let stdout = Tool.call ["select"; d.API.host_driver_name; v.API.driver_variant_name] in From 8ebbd44df6828dae8dbd977c1f9aff98b7ee0961 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Tue, 10 Dec 2024 14:50:27 +0000 Subject: [PATCH 30/53] CP-45016: Implement get_nbd_server SMAPI{v1,v2,v3} calls This new function returns the real nbd server of the underlying storage backend. This nbd server will not accept any fd passing. This is in contrast to import_activate, which returns a "special" nbd server that will accept fds via SCM_RIGHTS. Signed-off-by: Vincent Liu --- ocaml/xapi-idl/storage/storage_interface.ml | 26 ++++++++++- ocaml/xapi-idl/storage/storage_skeleton.ml | 3 ++ ocaml/xapi-storage-script/main.ml | 49 ++++++++++++++++----- ocaml/xapi/storage_mux.ml | 9 +++- ocaml/xapi/storage_smapiv1.ml | 1 + ocaml/xapi/storage_smapiv1_wrapper.ml | 3 ++ 6 files changed, 77 insertions(+), 14 deletions(-) diff --git a/ocaml/xapi-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index 1e8b8f3d23b..90705a3ab1a 100644 --- a/ocaml/xapi-idl/storage/storage_interface.ml +++ b/ocaml/xapi-idl/storage/storage_interface.ml @@ -1097,7 +1097,19 @@ module StorageAPI (R : RPC) = struct @-> returning sock_path_p err ) - + (** [get_nbd_server dbg dp sr vdi vm] returns the address of a generic nbd + server that can be connected to. Depending on the backend, this will either + be a nbd server backed by tapdisk or qemu-dp. Note this is different + from [import_activate] as the returned server does not accept fds. *) + let get_nbd_server = + declare "DATA.MIRROR.get_nbd_server" [] + (dbg_p + @-> dp_p + @-> sr_p + @-> vdi_p + @-> vm_p + @-> returning sock_path_p err + ) end end @@ -1452,6 +1464,14 @@ module type Server_impl = sig -> vm:vm -> sock_path + val get_nbd_server : + context + -> dbg:debug_info + -> dp:dp + -> sr:sr + -> vdi:vdi + -> vm:vm + -> sock_path end end @@ -1634,7 +1654,9 @@ module Server (Impl : Server_impl) () = struct S.DATA.MIRROR.import_activate (fun dbg dp sr vdi vm -> Impl.DATA.MIRROR.import_activate () ~dbg ~dp ~sr ~vdi ~vm ) ; - + S.DATA.MIRROR.get_nbd_server (fun dbg dp sr vdi vm -> + Impl.DATA.MIRROR.get_nbd_server () ~dbg ~dp ~sr ~vdi ~vm + ) ; S.Policy.get_backend_vm (fun dbg vm sr vdi -> Impl.Policy.get_backend_vm () ~dbg ~vm ~sr ~vdi ) ; diff --git a/ocaml/xapi-idl/storage/storage_skeleton.ml b/ocaml/xapi-idl/storage/storage_skeleton.ml index c87ca4eba01..ab84ed7712e 100644 --- a/ocaml/xapi-idl/storage/storage_skeleton.ml +++ b/ocaml/xapi-idl/storage/storage_skeleton.ml @@ -180,6 +180,9 @@ module DATA = struct let import_activate ctx ~dbg ~dp ~sr ~vdi ~vm = u "DATA.MIRROR.import_activate" + + let get_nbd_server ctx ~dbg ~dp ~sr ~vdi ~vm = + u "DATA.MIRROR.get_nbd_server" end end diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index a630d1a353d..a5b86ff533e 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -837,6 +837,16 @@ let choose_datapath ?(persistent = true) domain response = | (script_dir, scheme, u) :: _us -> return (fork_exec_rpc ~script_dir, scheme, u, domain) +let convert_implementation = function + | Xapi_storage.Data.XenDisk {params; extra; backend_type} -> + Storage_interface.XenDisk {params; extra; backend_type} + | BlockDevice {path} -> + BlockDevice {path} + | File {path} -> + File {path} + | Nbd {uri} -> + Nbd {uri} + (* Bind the implementations *) let bind ~volume_script_dir = (* Each plugin has its own version, see the call to listen @@ -1482,16 +1492,6 @@ let bind ~volume_script_dir = (let vdi = Storage_interface.Vdi.string_of vdi' in let domain = domain_of ~dp ~vm' in vdi_attach_common dbg sr vdi domain >>>= fun response -> - let convert_implementation = function - | Xapi_storage.Data.XenDisk {params; extra; backend_type} -> - Storage_interface.XenDisk {params; extra; backend_type} - | BlockDevice {path} -> - BlockDevice {path} - | File {path} -> - File {path} - | Nbd {uri} -> - Nbd {uri} - in return { Storage_interface.implementations= @@ -1823,7 +1823,34 @@ let bind ~volume_script_dir = fail (Storage_interface.Errors.Unimplemented _vdi_mirror_in) in S.DATA.MIRROR.import_activate data_import_activate_impl ; - + let get_nbd_server_impl dbg _dp sr vdi' vm' = + wrap + @@ + let vdi = Storage_interface.Vdi.string_of vdi' in + let domain = Storage_interface.Vm.string_of vm' in + vdi_attach_common dbg sr vdi domain >>>= function + | response -> ( + let _, _, _, nbds = + Storage_interface.implementations_of_backend + { + Storage_interface.implementations= + List.map convert_implementation + response.Xapi_storage.Data.implementations + } + in + match nbds with + | ({uri} as nbd) :: _ -> + info (fun m -> + m "%s qemu-dp nbd server address is %s" __FUNCTION__ uri + ) + >>= fun () -> + let socket, _export = Storage_interface.parse_nbd_uri nbd in + return socket + | _ -> + fail (backend_error "No nbd server found" []) + ) + in + S.DATA.MIRROR.get_nbd_server get_nbd_server_impl ; let u name _ = failwith ("Unimplemented: " ^ name) in S.get_by_name (u "get_by_name") ; S.VDI.get_by_name (u "VDI.get_by_name") ; diff --git a/ocaml/xapi/storage_mux.ml b/ocaml/xapi/storage_mux.ml index b46a3008a76..9b1118c76e7 100644 --- a/ocaml/xapi/storage_mux.ml +++ b/ocaml/xapi/storage_mux.ml @@ -880,7 +880,14 @@ module Mux = struct end)) in C.DATA.MIRROR.import_activate (Debug_info.to_string di) dp sr vdi vm - + let get_nbd_server () ~dbg ~dp ~sr ~vdi ~vm = + with_dbg ~name:"DATA.MIRROR.get_nbd_server" ~dbg @@ fun di -> + info "%s dbg:%s dp:%s sr:%s vdi:%s vm:%s" __FUNCTION__ dbg dp + (s_of_sr sr) (s_of_vdi vdi) (s_of_vm vm) ; + let module C = StorageAPI (Idl.Exn.GenClient (struct + let rpc = of_sr sr + end)) in + C.DATA.MIRROR.get_nbd_server (Debug_info.to_string di) dp sr vdi vm end end diff --git a/ocaml/xapi/storage_smapiv1.ml b/ocaml/xapi/storage_smapiv1.ml index 244cbe34565..87a80d0cadc 100644 --- a/ocaml/xapi/storage_smapiv1.ml +++ b/ocaml/xapi/storage_smapiv1.ml @@ -1238,6 +1238,7 @@ module SMAPIv1 : Server_impl = struct let import_activate _context ~dbg:_ ~dp:_ ~sr:_ ~vdi:_ ~vm:_ = assert false + let get_nbd_server _context ~dbg:_ ~dp:_ ~sr:_ ~vdi:_ ~vm:_ = assert false end end diff --git a/ocaml/xapi/storage_smapiv1_wrapper.ml b/ocaml/xapi/storage_smapiv1_wrapper.ml index effa084f1f3..fbb30a2177f 100644 --- a/ocaml/xapi/storage_smapiv1_wrapper.ml +++ b/ocaml/xapi/storage_smapiv1_wrapper.ml @@ -1229,6 +1229,9 @@ functor let import_activate context ~dbg ~dp ~sr ~vdi ~vm = get_nbd_server_common context ~dbg ~dp ~sr ~vdi ~vm ~style:`oldstyle + + let get_nbd_server context ~dbg ~dp ~sr ~vdi ~vm = + get_nbd_server_common context ~dbg ~dp ~sr ~vdi ~vm ~style:`real end end From 5b9e892775ef26cf61ab9f3cb9ad78623404b462 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Thu, 5 Dec 2024 17:18:56 +0000 Subject: [PATCH 31/53] CP-45016: Implement a new nbd proxy handler The new http handler nbd_proxy accepts a connection from an nbd client, and acts as a proxy between the client and the nbd server of the underlying storage backend, as returned by get_nbd_server. This proxy is used in place of the old style of fd passing to tapdisks for VDI copying during storage migration, since the new nbd server from qemu-dp does not support accepting fds. This might also be used in the future for outbound storage migration for mirroring. Signed-off-by: Vincent Liu --- ocaml/xapi/storage_migrate.ml | 34 +++++++++++++++++++++++++++++++--- ocaml/xapi/xapi_services.ml | 3 +++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/ocaml/xapi/storage_migrate.ml b/ocaml/xapi/storage_migrate.ml index 05915200d3b..2686208d733 100644 --- a/ocaml/xapi/storage_migrate.ml +++ b/ocaml/xapi/storage_migrate.ml @@ -529,7 +529,7 @@ module MigrateLocal = struct let dest_vdi_url = let url' = Http.Url.of_string url in Http.Url.set_uri url' - (Printf.sprintf "%s/nbd/%s/%s/%s/%s" (Http.Url.get_uri url') + (Printf.sprintf "%s/nbdproxy/%s/%s/%s/%s" (Http.Url.get_uri url') (Storage_interface.Vm.string_of vm) (Storage_interface.Sr.string_of dest) (Storage_interface.Vdi.string_of dest_vdi) @@ -537,7 +537,7 @@ module MigrateLocal = struct ) |> Http.Url.to_string in - debug "copy remote NBD URL = %s" dest_vdi_url ; + debug "%s copy remote NBD URL = %s" __FUNCTION__ dest_vdi_url ; let id = State.copy_id_of (sr, vdi) in debug "Persisting state for copy (id=%s)" id ; State.add id @@ -762,7 +762,7 @@ module MigrateLocal = struct ; watchdog= None } in - + State.add mirror_id (State.Send_op alm) ; debug "%s Added mirror %s to active local mirrors" __FUNCTION__ mirror_id ; (* A list of cleanup actions to perform if the operation should fail. *) @@ -1383,6 +1383,34 @@ let nbd_handler req s ?(vm = "0") sr vdi dp = ) (fun () -> Unix.close control_fd) +(** nbd_proxy is a http handler but will turn the http connection into an nbd connection. +It proxies the connection between the sender and the generic nbd server, as returned +by [get_nbd_server dp sr vdi vm]. *) +let nbd_proxy req s vm sr vdi dp = + debug "%s: vm=%s sr=%s vdi=%s dp=%s" __FUNCTION__ vm sr vdi dp ; + let sr, vdi = Storage_interface.(Sr.of_string sr, Vdi.of_string vdi) in + req.Http.Request.close <- true ; + let vm = Vm.of_string vm in + let path = + Storage_utils.transform_storage_exn (fun () -> + Local.DATA.MIRROR.get_nbd_server "nbd" dp sr vdi vm + ) + in + debug "%s got nbd server path %s" __FUNCTION__ path ; + Http_svr.headers s (Http.http_200_ok () @ ["Transfer-encoding: nbd"]) ; + let control_fd = Unixext.open_connection_unix_fd path in + finally + (fun () -> + let s' = Unix.dup s in + let control_fd' = Unix.dup control_fd in + debug "%s: Connected; running proxy (between fds: %d and %d)" __FUNCTION__ + (Unixext.int_of_file_descr control_fd') + (Unixext.int_of_file_descr s') ; + Unixext.proxy s' control_fd' ; + debug "%s: proxy exited" __FUNCTION__ + ) + (fun () -> Unix.close control_fd) + let with_task_and_thread ~dbg f = let task = Storage_task.add tasks dbg.Debug_info.log (fun task -> diff --git a/ocaml/xapi/xapi_services.ml b/ocaml/xapi/xapi_services.ml index 70425ac5adf..a413e4c3630 100644 --- a/ocaml/xapi/xapi_services.ml +++ b/ocaml/xapi/xapi_services.ml @@ -206,6 +206,9 @@ let put_handler (req : Http.Request.t) s _ = | [""; services; "SM"; "nbd"; vm; sr; vdi; dp] when services = _services -> Storage_migrate.nbd_handler req s ~vm sr vdi dp + | [""; services; "SM"; "nbdproxy"; vm; sr; vdi; dp] + when services = _services -> + Storage_migrate.nbd_proxy req s vm sr vdi dp | _ -> Http_svr.headers s (Http.http_404_missing ~version:"1.0" ()) ; req.Http.Request.close <- true From 889dfa67525da1aa39514b4254d1e980be815c4c Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Mon, 4 Nov 2024 15:18:44 +0000 Subject: [PATCH 32/53] CP-45016: Clean up the source VM earlier Previously the source VM is cleaned up at the very end of the migration, after the destination VM is up and running. This has caused problems for SMAPIv3 SRs as the new VM needs to plug in the new VBD which requires a datapath activate on the VDI which is currently in use by the old VM (and the mirroring process). This window needs to be reduced since we cannot have two datapath activated at the same time. Signed-off-by: Vincent Liu --- ocaml/xenopsd/lib/xenops_server.ml | 42 ++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/ocaml/xenopsd/lib/xenops_server.ml b/ocaml/xenopsd/lib/xenops_server.ml index e3f0a77f5e8..bdab2c0e913 100644 --- a/ocaml/xenopsd/lib/xenops_server.ml +++ b/ocaml/xenopsd/lib/xenops_server.ml @@ -2732,6 +2732,23 @@ and perform_exn ?result (op : operation) (t : Xenops_task.task_handle) : unit = ) ; debug "VM.migrate: Synchronisation point 1" in + let pause_src_vm () = + debug + "VM.migrate: pause src vm before allowing destination to proceed" ; + (* cleanup tmp src VM *) + let atomics = + [ + VM_hook_script_stable + ( id + , Xenops_hooks.VM_pre_destroy + , Xenops_hooks.reason__suspend + , new_src_id + ) + ] + @ atomics_of_operation (VM_shutdown (new_src_id, None)) + in + perform_atomics atomics t + in let final_handshake () = Handshake.send vm_fd Handshake.Success ; debug "VM.migrate: Synchronisation point 3" ; @@ -2772,7 +2789,10 @@ and perform_exn ?result (op : operation) (t : Xenops_task.task_handle) : unit = the main VM migration sequence. *) match VGPU_DB.ids id with | [] -> - first_handshake () ; save () ; final_handshake () + first_handshake () ; + save () ; + pause_src_vm () ; + final_handshake () | (_vm_id, dev_id) :: _ -> let url = make_url "/migrate/vgpu/" @@ -2789,20 +2809,12 @@ and perform_exn ?result (op : operation) (t : Xenops_task.task_handle) : unit = first_handshake () ; save ~vgpu_fd:(FD vgpu_fd) () ) ; + pause_src_vm () ; final_handshake () ) ; - (* cleanup tmp src VM *) - let atomics = - [ - VM_hook_script_stable - ( id - , Xenops_hooks.VM_pre_destroy - , Xenops_hooks.reason__suspend - , new_src_id - ) - ] - @ atomics_of_operation (VM_shutdown (new_src_id, None)) - @ [ + let cleanup_src_vm () = + let atomics = + [ VM_hook_script_stable ( id , Xenops_hooks.VM_post_destroy @@ -2811,8 +2823,10 @@ and perform_exn ?result (op : operation) (t : Xenops_task.task_handle) : unit = ) ; VM_remove new_src_id ] + in + perform_atomics atomics t in - perform_atomics atomics t + cleanup_src_vm () | VM_receive_memory { vmr_id= id From 567cac220a54031fbfbb98d620c98fd41d66c85b Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Thu, 5 Dec 2024 17:43:56 +0000 Subject: [PATCH 33/53] Improve doc and logging Improve the documentation of functions such as `VDI.compose` and `DATA.MIRROR.receive_cancel`. Also use more consistent logging style. Signed-off-by: Vincent Liu --- ocaml/xapi-idl/storage/storage_interface.ml | 9 +- ocaml/xapi/storage_mux.ml | 92 +++++++++++++-------- ocaml/xapi/xapi_vm_migrate.ml | 1 + 3 files changed, 66 insertions(+), 36 deletions(-) diff --git a/ocaml/xapi-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index 90705a3ab1a..34ca1938b23 100644 --- a/ocaml/xapi-idl/storage/storage_interface.ml +++ b/ocaml/xapi-idl/storage/storage_interface.ml @@ -913,8 +913,8 @@ module StorageAPI (R : RPC) = struct declare "VDI.set_content_id" [] (dbg_p @-> sr_p @-> vdi_p @-> content_id_p @-> returning unit_p err) - (** [compose task sr vdi1 vdi2] layers the updates from [vdi2] onto [vdi1], - modifying [vdi2] *) + (** [compose task sr parent child] layers the updates from [child] onto [parent], + modifying [child] *) let compose = let vdi1_p = Param.mk ~name:"vdi1" Vdi.t in let vdi2_p = Param.mk ~name:"vdi2" Vdi.t in @@ -1044,7 +1044,8 @@ module StorageAPI (R : RPC) = struct @-> returning result err ) - (** Called on the receiving end to prepare for receipt of the storage.*) + (** Called on the receiving end to prepare for receipt of the storage. This + function should be used in conjunction with [receive_finalize2]*) let receive_start2 = let similar_p = Param.mk ~name:"similar" Mirror.similars in let result = Param.mk ~name:"result" Mirror.mirror_receive_result in @@ -1075,6 +1076,8 @@ module StorageAPI (R : RPC) = struct declare "DATA.MIRROR.receive_finalize2" [] (dbg_p @-> id_p @-> returning unit_p err) + (** [receive_cancel dbg id] is called in the case of migration failure to + do the clean up.*) let receive_cancel = declare "DATA.MIRROR.receive_cancel" [] (dbg_p @-> id_p @-> returning unit_p err) diff --git a/ocaml/xapi/storage_mux.ml b/ocaml/xapi/storage_mux.ml index 9b1118c76e7..7acba0c8823 100644 --- a/ocaml/xapi/storage_mux.ml +++ b/ocaml/xapi/storage_mux.ml @@ -835,41 +835,67 @@ module Mux = struct with_dbg ~name:"DATA.copy" ~dbg @@ fun dbg -> Storage_migrate.copy ~dbg module MIRROR = struct - let start () ~dbg = - with_dbg ~name:"DATA.MIRROR.start" ~dbg @@ fun dbg -> - Storage_migrate.start ~dbg - - let stop () ~dbg = - with_dbg ~name:"DATA.MIRROR.stop" ~dbg @@ fun {log= dbg; _} -> - Storage_migrate.stop ~dbg + let start () ~dbg ~sr ~vdi ~dp ~mirror_vm ~copy_vm ~url ~dest ~verify_dest + = + with_dbg ~name:"DATA.MIRROR.start" ~dbg @@ fun di -> + info + "%s dbg:%s sr: %s vdi: %s dp:%s mirror_vm: %s copy_vm: %s url: %s \ + dest sr: %s verify_dest: %B" + __FUNCTION__ dbg (s_of_sr sr) (s_of_vdi vdi) dp (s_of_vm mirror_vm) + (s_of_vm copy_vm) url (s_of_sr dest) verify_dest ; + Storage_migrate.start ~dbg:di ~sr ~vdi ~dp ~mirror_vm ~copy_vm ~url + ~dest ~verify_dest + + let stop () ~dbg ~id = + with_dbg ~name:"DATA.MIRROR.stop" ~dbg @@ fun di -> + info "%s dbg:%s mirror_id: %s" __FUNCTION__ dbg id ; + Storage_migrate.stop ~dbg:di.log ~id let list () ~dbg = - with_dbg ~name:"DATA.MIRROR.list" ~dbg @@ fun {log= dbg; _} -> - Storage_migrate.list ~dbg - - let stat () ~dbg = - with_dbg ~name:"DATA.MIRROR.stat" ~dbg @@ fun {log= dbg; _} -> - Storage_migrate.stat ~dbg - - let receive_start () ~dbg = - with_dbg ~name:"DATA.MIRROR.receive_start" ~dbg @@ fun {log= dbg; _} -> - Storage_migrate.receive_start ~dbg - - let receive_start2 () ~dbg = - with_dbg ~name:"DATA.MIRROR.receive_start2" ~dbg @@ fun {log= dbg; _} -> - Storage_migrate.receive_start2 ~dbg - - let receive_finalize () ~dbg = - with_dbg ~name:"DATA.MIRROR.receive_finalize" ~dbg - @@ fun {log= dbg; _} -> Storage_migrate.receive_finalize ~dbg - - let receive_finalize2 () ~dbg = - with_dbg ~name:"DATA.MIRROR.receive_finalize2" ~dbg - @@ fun {log= dbg; _} -> Storage_migrate.receive_finalize2 ~dbg - - let receive_cancel () ~dbg = - with_dbg ~name:"DATA.MIRROR.receive_cancel" ~dbg @@ fun {log= dbg; _} -> - Storage_migrate.receive_cancel ~dbg + with_dbg ~name:"DATA.MIRROR.list" ~dbg @@ fun di -> + info "%s dbg: %s" __FUNCTION__ dbg ; + Storage_migrate.list ~dbg:di.log + + let stat () ~dbg ~id = + with_dbg ~name:"DATA.MIRROR.stat" ~dbg @@ fun di -> + info "%s dbg: %s mirror_id: %s" __FUNCTION__ di.log id ; + Storage_migrate.stat ~dbg:di.log ~id + + let receive_start () ~dbg ~sr ~vdi_info ~id ~similar = + with_dbg ~name:"DATA.MIRROR.receive_start" ~dbg @@ fun di -> + info "%s dbg: %s sr: %s vdi_info: %s mirror_id: %s similar: %s" + __FUNCTION__ dbg (s_of_sr sr) + (string_of_vdi_info vdi_info) + id + (String.concat ";" similar) ; + Storage_migrate.receive_start ~dbg:di.log ~sr ~vdi_info ~id ~similar + + let receive_start2 () ~dbg ~sr ~vdi_info ~id ~similar ~vm = + with_dbg ~name:"DATA.MIRROR.receive_start2" ~dbg @@ fun di -> + info "%s dbg: %s sr: %s vdi_info: %s mirror_id: %s similar: %s vm: %s" + __FUNCTION__ dbg (s_of_sr sr) + (string_of_vdi_info vdi_info) + id + (String.concat ";" similar) + (s_of_vm vm) ; + info "%s dbg:%s" __FUNCTION__ dbg ; + Storage_migrate.receive_start2 ~dbg:di.log ~sr ~vdi_info ~id ~similar + ~vm + + let receive_finalize () ~dbg ~id = + with_dbg ~name:"DATA.MIRROR.receive_finalize" ~dbg @@ fun di -> + info "%s dbg: %s mirror_id: %s" __FUNCTION__ dbg id ; + Storage_migrate.receive_finalize ~dbg:di.log ~id + + let receive_finalize2 () ~dbg ~id = + with_dbg ~name:"DATA.MIRROR.receive_finalize2" ~dbg @@ fun di -> + info "%s dbg: %s mirror_id: %s" __FUNCTION__ dbg id ; + Storage_migrate.receive_finalize2 ~dbg:di.log ~id + + let receive_cancel () ~dbg ~id = + with_dbg ~name:"DATA.MIRROR.receive_cancel" ~dbg @@ fun di -> + info "%s dbg: %s mirror_id: %s" __FUNCTION__ dbg id ; + Storage_migrate.receive_cancel ~dbg:di.log ~id let import_activate () ~dbg ~dp ~sr ~vdi ~vm = with_dbg ~name:"DATA.MIRROR.import_activate" ~dbg @@ fun di -> diff --git a/ocaml/xapi/xapi_vm_migrate.ml b/ocaml/xapi/xapi_vm_migrate.ml index e1bd60ecf64..23ebd9c5c91 100644 --- a/ocaml/xapi/xapi_vm_migrate.ml +++ b/ocaml/xapi/xapi_vm_migrate.ml @@ -479,6 +479,7 @@ let remove_stale_pcis ~__context ~vm = in List.iter remove stale_pcis +(** Called on the destination side *) let pool_migrate_complete ~__context ~vm ~host:_ = let id = Db.VM.get_uuid ~__context ~self:vm in debug "VM.pool_migrate_complete %s" id ; From 6e30b841ee247dc106c4362f1ae99aead03a26f1 Mon Sep 17 00:00:00 2001 From: Rob Hoes Date: Tue, 21 Jan 2025 15:50:58 +0000 Subject: [PATCH 34/53] Update XE_SR_ERRORCODES.xml from SM Signed-off-by: Rob Hoes --- ocaml/sdk-gen/csharp/XE_SR_ERRORCODES.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ocaml/sdk-gen/csharp/XE_SR_ERRORCODES.xml b/ocaml/sdk-gen/csharp/XE_SR_ERRORCODES.xml index 725d14feb78..47fefd83086 100644 --- a/ocaml/sdk-gen/csharp/XE_SR_ERRORCODES.xml +++ b/ocaml/sdk-gen/csharp/XE_SR_ERRORCODES.xml @@ -534,6 +534,11 @@ A Failure occurred accessing an API object 153 + + APIProtocolError + A protocol error was received when accessing the API + 154 + From 65afcc7bce384eb7fa2ed4d2f0ed33dd5066be57 Mon Sep 17 00:00:00 2001 From: Rob Hoes Date: Tue, 21 Jan 2025 15:51:18 +0000 Subject: [PATCH 35/53] CA-404611: SXM: check power-state just before metadata export A VM's power-state may change during a cross-pool storage migration, as a VM that was originally running may shut down while the disks are being copied (or vice versa). This case is handled, because copying disks can take a very long time. VM metadata is exported and imported into the remote pool after copying has completed, and the VM power-state is used to decide whether to do CPUID checks. Unfortunately, the power-state is currently taken from the DB right at the start if the function, which does not take into account that is may change. Straightforward fix. Signed-off-by: Rob Hoes --- ocaml/xapi/xapi_vm_migrate.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocaml/xapi/xapi_vm_migrate.ml b/ocaml/xapi/xapi_vm_migrate.ml index 3b561e370ab..8a8f7f181e0 100644 --- a/ocaml/xapi/xapi_vm_migrate.ml +++ b/ocaml/xapi/xapi_vm_migrate.ml @@ -1194,7 +1194,6 @@ let migrate_send' ~__context ~vm ~dest ~live:_ ~vdi_map ~vif_map ~vgpu_map We look at the VDIs of the VM, the VDIs of all of the snapshots, and any suspend-image VDIs. *) let vm_uuid = Db.VM.get_uuid ~__context ~self:vm in - let power_state = Db.VM.get_power_state ~__context ~self:vm in let vbds = Db.VM.get_VBDs ~__context ~self:vm in let vifs = Db.VM.get_VIFs ~__context ~self:vm in let snapshots = Db.VM.get_snapshots ~__context ~self:vm in @@ -1466,6 +1465,7 @@ let migrate_send' ~__context ~vm ~dest ~live:_ ~vdi_map ~vif_map ~vgpu_map ) vgpu_map in + let power_state = Db.VM.get_power_state ~__context ~self:vm in inter_pool_metadata_transfer ~__context ~remote ~vm ~vdi_map ~vif_map ~vgpu_map ~dry_run:false ~live:true ~copy ~check_cpu:((not force) && power_state <> `Halted) From d3bad20cd934a686ddd428a4ec49aba277876995 Mon Sep 17 00:00:00 2001 From: Rob Hoes Date: Tue, 21 Jan 2025 16:04:32 +0000 Subject: [PATCH 36/53] CA-404611: live import: only check CPUID if VM is not Halted This is just an additional safety check following the previous commit. Signed-off-by: Rob Hoes --- ocaml/xapi/import.ml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ocaml/xapi/import.ml b/ocaml/xapi/import.ml index 3dd311d72e9..6f2c29acca8 100644 --- a/ocaml/xapi/import.ml +++ b/ocaml/xapi/import.ml @@ -76,7 +76,9 @@ type config = { let is_live config = match config.import_type with Metadata_import {live; _} -> live | _ -> false -let needs_cpu_check config = +let needs_cpu_check config vm_record = + vm_record.API.vM_power_state <> `Halted + && match config.import_type with | Metadata_import {check_cpu; _} -> check_cpu @@ -519,7 +521,7 @@ module VM : HandlerTools = struct | Replace (_, vm_record) | Clean_import vm_record -> if is_live config then assert_can_live_import __context vm_record ; - ( if needs_cpu_check config then + ( if needs_cpu_check config vm_record then let vmm_record = find_in_export (Ref.string_of vm_record.API.vM_metrics) From 3406a648d209d9ba7a0b0294fd60898a992f209b Mon Sep 17 00:00:00 2001 From: Changlei Li Date: Tue, 14 Jan 2025 15:38:20 +0800 Subject: [PATCH 37/53] CA-399260: Keep both new and old certs during the switchover In host-refresh-server-certificate, host generates and applies new pool certificate after stunnel restart. There is 5s that other hosts don't trust the new certificate. Then operations related with xapi:pool SNI tls connection will fail. Both the old and new certificates shall be trusted during the switchover to avoid this. At the end of the refresh procedure, remove_stale_cert will rename the new pem to pem and update ca bundle. So, both pem and new pem shall be filled into bundle file in update-ca-bundle.sh. As the new pem is a temp file and has a risk to be deleted by other instance between find and cat. Ignore the cat new pem fail in the script. If the new pem doesn't exist at the moment, just skip it and go on. Signed-off-by: Changlei Li --- scripts/update-ca-bundle.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/update-ca-bundle.sh b/scripts/update-ca-bundle.sh index a6a647d3810..61420be4910 100755 --- a/scripts/update-ca-bundle.sh +++ b/scripts/update-ca-bundle.sh @@ -11,9 +11,16 @@ regen_bundle () { mkdir -p "$CERTS_DIR" CERTS=$(find "$CERTS_DIR" -not -name '*.new.pem' -name '*.pem') + NEW_CERTS=$(find "$CERTS_DIR" -name '*.new.pem') rm -f "$BUNDLE.tmp" touch "$BUNDLE.tmp" + for NEW_CERT in $NEW_CERTS; do + # If cat new cert command fails, do not error and exit, just skip it + if cat "$NEW_CERT" >> "$BUNDLE.tmp"; then + echo "" >> "$BUNDLE.tmp" + fi + done for CERT in $CERTS; do cat "$CERT" >> "$BUNDLE.tmp" echo "" >> "$BUNDLE.tmp" From 8f374c7692f1ddeea685e1f27e94546347da929e Mon Sep 17 00:00:00 2001 From: Konstantina Chremmou Date: Tue, 21 Jan 2025 17:45:24 +0000 Subject: [PATCH 38/53] Added preprocessor directive so that the assembly internals are visible to XenServerTest only when specified. Signed-off-by: Konstantina Chremmou --- .github/workflows/generate-and-build-sdks.yml | 1 + ocaml/sdk-gen/csharp/autogen/src/Converters.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/generate-and-build-sdks.yml b/.github/workflows/generate-and-build-sdks.yml index 39645cf68bf..53a9b8452cb 100644 --- a/.github/workflows/generate-and-build-sdks.yml +++ b/.github/workflows/generate-and-build-sdks.yml @@ -168,6 +168,7 @@ jobs: run: | dotnet test source/XenServerTest ` --disable-build-servers ` + -p:DefineConstants=BUILD_FOR_TEST ` --verbosity=normal - name: Build C# SDK diff --git a/ocaml/sdk-gen/csharp/autogen/src/Converters.cs b/ocaml/sdk-gen/csharp/autogen/src/Converters.cs index 6f828fdc0a6..7811cb00817 100644 --- a/ocaml/sdk-gen/csharp/autogen/src/Converters.cs +++ b/ocaml/sdk-gen/csharp/autogen/src/Converters.cs @@ -36,7 +36,9 @@ using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; +#if BUILD_FOR_TEST [assembly: InternalsVisibleTo("XenServerTest")] +#endif namespace XenAPI { From 42512ab5a0ab7bbc6b30280af150fc76b3c814a5 Mon Sep 17 00:00:00 2001 From: Mark Syms Date: Wed, 22 Jan 2025 18:26:16 +0000 Subject: [PATCH 39/53] Remove dangling use of python-future from rrdd.py Signed-off-by: Mark Syms --- ocaml/xcp-rrdd/scripts/rrdd/rrdd.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ocaml/xcp-rrdd/scripts/rrdd/rrdd.py b/ocaml/xcp-rrdd/scripts/rrdd/rrdd.py index 76dc4fd7974..bb482027096 100644 --- a/ocaml/xcp-rrdd/scripts/rrdd/rrdd.py +++ b/ocaml/xcp-rrdd/scripts/rrdd/rrdd.py @@ -46,11 +46,6 @@ from __future__ import print_function -from future import standard_library -standard_library.install_aliases() -from builtins import str -from builtins import range -from builtins import object import http.client import os import json From f2a94456b820058f6da04513441e58359c8240db Mon Sep 17 00:00:00 2001 From: Pau Ruiz Safont Date: Thu, 23 Jan 2025 10:23:44 +0000 Subject: [PATCH 40/53] opam: add missing dependencies These were added for the SMAPIv3-inbound VM migrations Signed-off-by: Pau Ruiz Safont --- dune-project | 2 ++ xapi-debug.opam | 1 + xapi.opam | 1 + 3 files changed, 4 insertions(+) diff --git a/dune-project b/dune-project index 15ff4a5fbfa..8d762f8e07a 100644 --- a/dune-project +++ b/dune-project @@ -271,6 +271,7 @@ xapi-stdext-pervasives xapi-stdext-unix xen-api-client + xen-api-client-lwt xenctrl xenstore_transport xmlm @@ -396,6 +397,7 @@ (xapi-tracing (= :version)) (xapi-tracing-export (= :version)) (xapi-types (= :version)) + (xen-api-client-lwt (= :version)) xenctrl ; for quicktest xenstore_transport xmlm diff --git a/xapi-debug.opam b/xapi-debug.opam index 025e969e140..f8550f7508b 100644 --- a/xapi-debug.opam +++ b/xapi-debug.opam @@ -58,6 +58,7 @@ depends: [ "xapi-stdext-pervasives" "xapi-stdext-unix" "xen-api-client" + "xen-api-client-lwt" "xenctrl" "xenstore_transport" "xmlm" diff --git a/xapi.opam b/xapi.opam index 098d8463442..21d157b2161 100644 --- a/xapi.opam +++ b/xapi.opam @@ -86,6 +86,7 @@ depends: [ "xapi-tracing" {= version} "xapi-tracing-export" {= version} "xapi-types" {= version} + "xen-api-client-lwt" {= version} "xenctrl" "xenstore_transport" "xmlm" From 37d92373d4a836cc6e89ed1fcc7241f60970caea Mon Sep 17 00:00:00 2001 From: Alex Brett Date: Fri, 24 Jan 2025 17:08:23 +0000 Subject: [PATCH 41/53] CA-405404: Fix path to dracut On all platforms tested the canonical location for dracut is `/usr/bin/dracut`. Older platforms symlink to it from `/usr/sbin/dracut`, whilst newer ones do not, thus on these platforms xapi is unable to find dracut and fails to start. Adjust the path to the canonical location to ensure it operates correctly on all platforms. Also adjust a reference in network_utils.ml (whilst newer platforms also seem to have the symlink from `/sbin/dracut`, it would seem to make sense to point to the canonical location regardless). Signed-off-by: Alex Brett --- ocaml/networkd/lib/network_utils.ml | 2 +- ocaml/xapi/xapi_globs.ml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ocaml/networkd/lib/network_utils.ml b/ocaml/networkd/lib/network_utils.ml index 6a37a7dfc5c..1c8c8cd1a27 100644 --- a/ocaml/networkd/lib/network_utils.ml +++ b/ocaml/networkd/lib/network_utils.ml @@ -63,7 +63,7 @@ let bonding_dir = "/proc/net/bonding/" let uname = ref "/usr/bin/uname" -let dracut = ref "/sbin/dracut" +let dracut = ref "/usr/bin/dracut" let modinfo = ref "/sbin/modinfo" diff --git a/ocaml/xapi/xapi_globs.ml b/ocaml/xapi/xapi_globs.ml index c768b1f577f..29075949474 100644 --- a/ocaml/xapi/xapi_globs.ml +++ b/ocaml/xapi/xapi_globs.ml @@ -935,7 +935,7 @@ let depmod = ref "/usr/sbin/depmod" let driver_tool = ref "/opt/xensource/debug/drivertool.sh" -let dracut = ref "/usr/sbin/dracut" +let dracut = ref "/usr/bin/dracut" let udevadm = ref "/usr/sbin/udevadm" From 458b1386ea9b9a95d08ede71f38f0fa09a4f156a Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Fri, 24 Jan 2025 12:00:00 +0100 Subject: [PATCH 42/53] docs: Update doc/README.md and Hugo Relearn (to 5.23.0 for now) Minor updates of the Hugo documentation: - The current Ubuntu snap package of Hugo is not supported by the docs. We should take a first minor step towards fixing this. - `doc/README.md` is outdated and should be updated. It says that the Ubuntu snap of Hugo works, but it does not anymore. Fix this by updating the outdated information. - An initial fix is to update the Relearn theme from 5.20.x to 5.23.0: - It does not introduce breaking changes. - It introduces more straightforward page links and deprecates older syntax. - Fix the warnings by updating relative links accordingly. Signed-off-by: Bernhard Kaindl --- doc/README.md | 48 +++++++++++++++++++-- doc/content/design/cpu-levelling-v2.md | 2 +- doc/content/design/local-database.md | 2 +- doc/content/xapi/cli/_index.md | 4 +- doc/content/xapi/guides/howtos/add-class.md | 4 +- doc/go.mod | 2 +- 6 files changed, 52 insertions(+), 10 deletions(-) diff --git a/doc/README.md b/doc/README.md index 01879806e59..a2d2c7f531d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,9 +1,51 @@ -Quick start guide: +# Quick start guide: - Visit https://xapi-project.github.io/new-docs/ to view the current documentation. + +## Required software + +The docs use Hugo and the [Hugo Relearn theme](https://mcshelby.github.io/hugo-theme-relearn), +an enhanced fork of the popular Hugo Learn theme. + +### Compatible versions + +Due to a number of gradual changes in Hugo and Relearn, +the docs are currently only compatible with specific older versions of Hugo and Relearn. + +Hugo v0.121.0 to ~v0.127.0 (the current version of the Ubuntu `snap` is too recent) +- Fixes to support newer versions are forthcoming. + +Hugo Relearn 5.24.0 (defined by a git tag in doc/go.mod) +- Note: Hugo Relearn >= 5.25 currently trigger additional warnings due to deprecations. +- Further updates fix this situation are forthcoming step by step. + +Hugo Relearn >= 5.24.0 and < 6.x are expected to work: +- https://mcshelby.github.io/hugo-theme-relearn/introduction/releasenotes/5/index.html#5-24-0 +- Breaking changes in Relearn 6.0.0: + https://mcshelby.github.io/hugo-theme-relearn/introduction/releasenotes/6/#6-0-0 + +## Installation + - Install Hugo; follow the guidance on https://gohugo.io/getting-started/installing. - You'll need Go as well: see https://go.dev/ - - On Ubuntu 22.04 and older, use `sudo snap install hugo` to get the needed newer version of `hugo`. + You'll need to install Go as well: see https://go.dev/ + - Hugo installation is described at https://gohugo.io/installation + - On Ubuntu 24.04, the version installed by `apt` works. + - On Ubuntu 22.04 and older: + - `apt-get install hugo` would install a version that is too old. + - `sudo snap install hugo` installs a too recent version + + - To install Hugo from source, you need a recent `golang-1.2x` compiler: + - On Ubuntu 22.04, this can be done with: + ```bash + sudo apt install golang-1.23-go + # Add it to your path, assuming your .local/bin/ is early in your PATH: + ln -s /usr/lib/go-1.23/bin/go ~/.local/bin/go + go version + go install github.com/gohugoio/hugo@v0.127.0 + ``` + +## Development + - Run a local server: `hugo server` - Open a browser at http://127.0.0.1:1313/new-docs/ - Add content to `doc/content/`: diff --git a/doc/content/design/cpu-levelling-v2.md b/doc/content/design/cpu-levelling-v2.md index 2192c1665a3..48e52afcf63 100644 --- a/doc/content/design/cpu-levelling-v2.md +++ b/doc/content/design/cpu-levelling-v2.md @@ -29,7 +29,7 @@ The old XS 5.6-style Heterogeneous Pool feature that is based around hardware-le History ======= -- Original XS 5.6 design: [heterogeneous-pools](../heterogeneous-pools) +- Original XS 5.6 design: [heterogeneous-pools](heterogeneous-pools) - Changes made in XS 5.6 FP1 for the DR feature (added CPUID checks upon migration) - XS 6.1: migration checks extended for cross-pool scenario diff --git a/doc/content/design/local-database.md b/doc/content/design/local-database.md index 2393df63760..6480e2a6b58 100644 --- a/doc/content/design/local-database.md +++ b/doc/content/design/local-database.md @@ -25,7 +25,7 @@ We propose to: this should reduce the number of RPCs across the network. In a later phase we can move to a completely -[distributed database](../distributed-database). +[distributed database](distributed-database/index). Replicating the database ------------------------ diff --git a/doc/content/xapi/cli/_index.md b/doc/content/xapi/cli/_index.md index a4ae338390a..d2aba33e30d 100644 --- a/doc/content/xapi/cli/_index.md +++ b/doc/content/xapi/cli/_index.md @@ -179,5 +179,5 @@ Yet other commands do not actually do any XenAPI calls, but instead get "helpful The following tutorials show how to extend the CLI (and XenAPI): -- [Adding a field]({{< relref "../guides/howtos/add-field.md" >}}) -- [Adding an operation]({{< relref "../guides/howtos/add-function.md" >}}) +- [Adding a field](../guides/howtos/add-field) +- [Adding a function](../guides/howtos/add-function) diff --git a/doc/content/xapi/guides/howtos/add-class.md b/doc/content/xapi/guides/howtos/add-class.md index 9e4680059da..aee29a69429 100644 --- a/doc/content/xapi/guides/howtos/add-class.md +++ b/doc/content/xapi/guides/howtos/add-class.md @@ -6,8 +6,8 @@ This document describes how to add a new class to the data model that defines the Xen Server API. It complements two other documents that describe how to extend an existing class: -* [Adding a Field]({{< ref add-field.md >}}) -* [Adding a Function]({{< ref add-function.md >}}) +* [Adding a field](add-field) +* [Adding a function](add-function) As a running example, we will use the addition of a class that is part of the design for the PVS Direct feature. PVS Direct introduces diff --git a/doc/go.mod b/doc/go.mod index 2a4ecc5844d..2e145daa17e 100644 --- a/doc/go.mod +++ b/doc/go.mod @@ -2,4 +2,4 @@ module xapi-project.github.io go 1.20 -require github.com/McShelby/hugo-theme-relearn v0.0.0-20230905210935-196188b7f3bd // indirect +require github.com/McShelby/hugo-theme-relearn v0.0.0-20231029175538-7ae1435626d7 // indirect From 7010a98971290f19062d89a9edad2ab2dea0ea9c Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Fri, 24 Jan 2025 12:00:00 +0100 Subject: [PATCH 43/53] Fix currently broken links to toolstack/features: HA, snapshots Signed-off-by: Bernhard Kaindl --- doc/content/design/multiple-cluster-managers.md | 4 ++-- doc/content/design/ocfs2/index.md | 2 +- doc/content/xen-api/topics/snapshots.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/content/design/multiple-cluster-managers.md b/doc/content/design/multiple-cluster-managers.md index 6c0e783fe66..458fac47c8b 100644 --- a/doc/content/design/multiple-cluster-managers.md +++ b/doc/content/design/multiple-cluster-managers.md @@ -14,7 +14,7 @@ revision_history: Introduction ------------ -Xapi currently uses a cluster manager called [xhad](../../features/HA/HA.html). Sometimes other software comes with its own built-in way of managing clusters, which would clash with xhad (example: xhad could choose to fence node 'a' while the other system could fence node 'b' resulting in a total failure). To integrate xapi with this other software we have 2 choices: +Xapi currently uses a cluster manager called [xhad](../toolstack/features/HA/index). Sometimes other software comes with its own built-in way of managing clusters, which would clash with xhad (example: xhad could choose to fence node 'a' while the other system could fence node 'b' resulting in a total failure). To integrate xapi with this other software we have 2 choices: 1. modify the other software to take membership information from xapi; or 2. modify xapi to take membership information from this other software. @@ -70,4 +70,4 @@ The `xapi.conf` file will have a new field: `cluster-stack-root` which will have In `Pool.enable_ha` with `cluster_stack="foo"` we will verify that the subdirectory `/foo` exists. If it does not exist, then the call will fail with `UNKNOWN_CLUSTER_STACK`. -Alternative cluster stacks will need to conform to the exact same interface as [xhad](../../features/HA/HA.html). +Alternative cluster stacks will need to conform to the exact same interface as [xhad](../toolstack/features/HA). diff --git a/doc/content/design/ocfs2/index.md b/doc/content/design/ocfs2/index.md index c8d0852e0a9..c21dfda8280 100644 --- a/doc/content/design/ocfs2/index.md +++ b/doc/content/design/ocfs2/index.md @@ -461,7 +461,7 @@ Summary of the impact on the admin OCFS2 is fundamentally a different type of storage to all existing storage types supported by xapi. OCFS2 relies upon O2CB, which provides -[Host-level High Availability](../../../features/HA/HA.html). All HA implementations +[Host-level High Availability](../../toolstack/features/HA/index). All HA implementations (including O2CB and `xhad`) impose restrictions on the server admin to prevent unnecessary host "fencing" (i.e. crashing). Once we have OCFS2 as a feature, we will have to live with these restrictions which previously only diff --git a/doc/content/xen-api/topics/snapshots.md b/doc/content/xen-api/topics/snapshots.md index 91b5969ce42..4e6d042420a 100644 --- a/doc/content/xen-api/topics/snapshots.md +++ b/doc/content/xen-api/topics/snapshots.md @@ -11,7 +11,7 @@ They can be used for: - experiments (take snapshot, try something, revert back again) - golden images (install OS, get it just right, clone it 1000s of times) -Read more about [Snapshots: the High-Level Feature](../features/snapshots/snapshots.html). +Read more about [Snapshots: the High-Level Feature](../../toolstack/features/snapshots/index). Taking a VDI snapshot ===================== From 26264f90f23eb8f3a60f34e4963af3207d808946 Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Fri, 24 Jan 2025 12:00:00 +0100 Subject: [PATCH 44/53] Update 3 links to the hierachy of the new-docs site Signed-off-by: Bernhard Kaindl --- doc/content/toolstack/features/DR/index.md | 2 +- doc/content/toolstack/features/snapshots/index.md | 2 +- doc/content/xenopsd/architecture/_index.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/content/toolstack/features/DR/index.md b/doc/content/toolstack/features/DR/index.md index a958cb9f27a..5a6ddc7dbc8 100644 --- a/doc/content/toolstack/features/DR/index.md +++ b/doc/content/toolstack/features/DR/index.md @@ -2,7 +2,7 @@ title = "Disaster Recovery" +++ -The [HA](../HA/HA.html) feature will restart VMs after hosts have failed, but what +The [HA](HA) feature will restart VMs after hosts have failed, but what happens if a whole site (e.g. datacenter) is lost? A disaster recovery configuration is shown in the following diagram: diff --git a/doc/content/toolstack/features/snapshots/index.md b/doc/content/toolstack/features/snapshots/index.md index 789f35fa1f6..05185f53df9 100644 --- a/doc/content/toolstack/features/snapshots/index.md +++ b/doc/content/toolstack/features/snapshots/index.md @@ -8,7 +8,7 @@ Snapshots represent the state of a VM, or a disk (VDI) at a point in time. They - experiments (take snapshot, try something, revert back again) - golden images (install OS, get it just right, clone it 1000s of times) -Read more about [the Snapshot APIs](../../xen-api/snapshots.html). +Read more about [the Snapshot APIs](../../../xen-api/topics/snapshots). Disk snapshots ============== diff --git a/doc/content/xenopsd/architecture/_index.md b/doc/content/xenopsd/architecture/_index.md index 4c70bdca2e8..0f4d5eccea5 100644 --- a/doc/content/xenopsd/architecture/_index.md +++ b/doc/content/xenopsd/architecture/_index.md @@ -17,7 +17,7 @@ Managing a VM means: - providing updates to clients when things change (reboots, console becomes available, guest agent says something etc). -For a full list of features, consult the [features list](features.html). +For a full list of features, consult the [feature list](../features). Each Xenopsd instance has a unique name on the host. A typical name is From 3a6a64e1471f6dff319597a68832e5cc46909b2d Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Fri, 24 Jan 2025 12:00:00 +0100 Subject: [PATCH 45/53] CI: When only Hugo docs change, other workflows do not need to run Signed-off-by: Bernhard Kaindl --- .github/workflows/main.yml | 7 +++++++ .github/workflows/other.yml | 7 +++++++ .github/workflows/shellcheck.yaml | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 580b27f6288..e80122bd007 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,15 @@ name: Build and test on: + # When only Hugo docs change, this workflow is not required: push: + paths-ignore: + - 'doc/**' + - '.github/workflows/hugo.yml' pull_request: + paths-ignore: + - 'doc/**' + - '.github/workflows/hugo.yml' schedule: # run daily, this refreshes the cache - cron: "13 2 * * *" diff --git a/.github/workflows/other.yml b/.github/workflows/other.yml index d6ad9c849a6..90d6f57a2d0 100644 --- a/.github/workflows/other.yml +++ b/.github/workflows/other.yml @@ -1,8 +1,15 @@ name: Build and test (other) on: + # When only Hugo docs change, this workflow is not required: push: + paths-ignore: + - 'doc/**' + - '.github/workflows/hugo.yml' pull_request: + paths-ignore: + - 'doc/**' + - '.github/workflows/hugo.yml' schedule: # run daily, this refreshes the cache - cron: "13 2 * * *" diff --git a/.github/workflows/shellcheck.yaml b/.github/workflows/shellcheck.yaml index b078eaba549..2a41d80da51 100644 --- a/.github/workflows/shellcheck.yaml +++ b/.github/workflows/shellcheck.yaml @@ -2,6 +2,10 @@ name: ShellCheck on: pull_request: + # When only Hugo docs change, this workflow is not required: + paths-ignore: + - 'doc/**' + - '.github/workflows/hugo.yml' merge_group: concurrency: # On new push, cancel old workflows from the same PR, branch or tag: From ea9372f4c3797ffdcbaaea692f8527192dae0641 Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Fri, 24 Jan 2025 12:00:00 +0100 Subject: [PATCH 46/53] Hugo docs: Fix {{site.baseurl}} links to use relative internal links instead Signed-off-by: Bernhard Kaindl --- doc/content/design/integrated-gpu-passthrough/index.md | 2 +- doc/content/toolstack/features/VGPU/index.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/content/design/integrated-gpu-passthrough/index.md b/doc/content/design/integrated-gpu-passthrough/index.md index 4b9a827ef5d..a0f4292f259 100644 --- a/doc/content/design/integrated-gpu-passthrough/index.md +++ b/doc/content/design/integrated-gpu-passthrough/index.md @@ -11,7 +11,7 @@ Introduction ------------ Passthrough of discrete GPUs has been -[available since XenServer 6.0]({{site.baseurl}}/xapi/design/gpu-passthrough.html). +[available since XenServer 6.0](../gpu-passthrough.md). With some extensions, we will also be able to support passthrough of integrated GPUs. diff --git a/doc/content/toolstack/features/VGPU/index.md b/doc/content/toolstack/features/VGPU/index.md index 83c5ea41fac..fa301d68a6e 100644 --- a/doc/content/toolstack/features/VGPU/index.md +++ b/doc/content/toolstack/features/VGPU/index.md @@ -89,7 +89,7 @@ hardware and config files present in dom0. They exist in the pool database, and a primary key is used to avoid duplication. In XenServer 6.x the tuple of `(vendor_name, model_name)` was used as the primary key, however this was not ideal as these values are subject to change. XenServer 7.0 switched to a -[new primary key]({{site.baseurl}}/xapi/futures/vgpu-type-identifiers.html) +[new primary key](../../../design/vgpu-type-identifiers) generated from static metadata, falling back to the old method for backwards compatibility. @@ -109,12 +109,12 @@ on PGPUs. In XenServer 6.x, all VGPU config was added to the VM's `platform` field at startup, and this information was used by xenopsd to start the display emulator. -See the relevant code [here][5]. +See the relevant code in [ocaml/xapi/vgpuops.ml][5]. In XenServer 7.0, to facilitate support of VGPU on Intel hardware in parallel with the existing NVIDIA support, VGPUs were made first-class objects in the -xapi-xenopsd interface. The interface is described -[here]({{site.baseurl}}/features/futures/gpu-support-evolution.html). +xapi-xenopsd interface. The interface is described in the design document on +the [GPU support evolution](../../../design/gpu-support-evolution). ## VM startup From 38c3eb57de4772ec8a05332df2f98436f2a2b211 Mon Sep 17 00:00:00 2001 From: Rob Hoes Date: Thu, 23 Jan 2025 17:58:24 +0000 Subject: [PATCH 47/53] CA-403759: Initialise licensing after no-other-masters check When the coordinator restarts. the no-other-masters check in the startup sequence does two things for each pool member: 1. It checks that the host agrees that it is are not the coordinator. 2. It unblocks the host's master_connection thread, which is likely waiting for a reconnection delay to expire, which may be up to 256 seconds (exponential backoff is used). The delay is interrupted to immediately unblock DB calls. Licensing initialisation comes earlier in the startup sequence, but under certain circumstance make calls to other host, in particular after an upgrade. A this time, hosts may still be blocked on the master_connection for up to 256 s, which adds an unnecessary delay to the coordinator's startup sequence and therefore the usability of the API. Address this by reversing the order of the two startup actions. Signed-off-by: Rob Hoes --- ocaml/xapi/xapi.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocaml/xapi/xapi.ml b/ocaml/xapi/xapi.ml index fd5c0650266..f4e9a6f0f68 100644 --- a/ocaml/xapi/xapi.ml +++ b/ocaml/xapi/xapi.ml @@ -1218,7 +1218,6 @@ let server_init () = , [] , Monitor_master.update_configuration_from_master ) - ; ("Initialising licensing", [], handle_licensing) ; ( "message_hook_thread" , [Startup.NoExnRaising] , Xapi_message.start_message_hook_thread ~__context @@ -1252,6 +1251,7 @@ let server_init () = , [Startup.OnlyMaster] , check_no_other_masters ) + ; ("Initialising licensing", [], handle_licensing) ; ( "Registering periodic functions" , [] , fun () -> Xapi_periodic_scheduler_init.register ~__context From f78df2ec41c2486367ffc53edba705d300bdb26b Mon Sep 17 00:00:00 2001 From: Rob Hoes Date: Wed, 22 Jan 2025 18:20:26 +0000 Subject: [PATCH 48/53] master_connection: remove unreachable case Signed-off-by: Rob Hoes --- ocaml/database/master_connection.ml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ocaml/database/master_connection.ml b/ocaml/database/master_connection.ml index ed9bfbd2826..8a06e5cb66a 100644 --- a/ocaml/database/master_connection.ml +++ b/ocaml/database/master_connection.ml @@ -248,11 +248,7 @@ let do_db_xml_rpc_persistent_with_reopen ~host:_ ~path (req : string) : "Connection to master died: time taken so far in this call '%f'; will \ %s" time_sofar - ( if !connection_timeout < 0. then - "never timeout" - else - Printf.sprintf "timeout after '%f'" !connection_timeout - ) ; + (Printf.sprintf "timeout after '%f'" !connection_timeout) ; if time_sofar > !connection_timeout && !connection_timeout >= 0. then if !restart_on_connection_timeout then ( debug "Exceeded timeout for retrying master connection: restarting xapi" ; From f346848b12b9ae8a40829993c4b8fabca00384b2 Mon Sep 17 00:00:00 2001 From: Rob Hoes Date: Wed, 22 Jan 2025 18:26:29 +0000 Subject: [PATCH 49/53] master_connection: logging once is enough Signed-off-by: Rob Hoes --- ocaml/database/master_connection.ml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ocaml/database/master_connection.ml b/ocaml/database/master_connection.ml index 8a06e5cb66a..d7faff1cd62 100644 --- a/ocaml/database/master_connection.ml +++ b/ocaml/database/master_connection.ml @@ -235,14 +235,11 @@ let do_db_xml_rpc_persistent_with_reopen ~host:_ ~path (req : string) : let time_sofar = Unix.gettimeofday () -. time_call_started in if !connection_timeout < 0. then ( if not !surpress_no_timeout_logs then ( - debug - "Connection to master died. I will continue to retry indefinitely \ - (supressing future logging of this message)." ; error "Connection to master died. I will continue to retry indefinitely \ - (supressing future logging of this message)." - ) ; - surpress_no_timeout_logs := true + (supressing future logging of this message)." ; + surpress_no_timeout_logs := true + ) ) else debug "Connection to master died: time taken so far in this call '%f'; will \ From fa6a82cb4ccdf892910e2e2edf2b34b3ea420006 Mon Sep 17 00:00:00 2001 From: Rob Hoes Date: Tue, 28 Jan 2025 15:18:12 +0000 Subject: [PATCH 50/53] CA-400272: pool.set_igmp_snooping_enabled: ignore non-managed PIFs The call currently calls `network.attach` on all networks, but this fails for PIFs that are not managed by xapi. Simply skip those. Signed-off-by: Rob Hoes --- ocaml/xapi/xapi_pool.ml | 1 + 1 file changed, 1 insertion(+) diff --git a/ocaml/xapi/xapi_pool.ml b/ocaml/xapi/xapi_pool.ml index b73d7fbf0dd..97e0617bff1 100644 --- a/ocaml/xapi/xapi_pool.ml +++ b/ocaml/xapi/xapi_pool.ml @@ -3309,6 +3309,7 @@ let set_igmp_snooping_enabled ~__context ~self ~value = if pif_record.API.pIF_VLAN = -1L && pif_record.API.pIF_bond_slave_of = Ref.null + && pif_record.API.pIF_managed then Client.Network.attach ~rpc ~session_id ~network ~host ; fail' From bd3024bd6cf3d806d485203446bddcea894c8768 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Tue, 28 Jan 2025 15:23:09 +0000 Subject: [PATCH 51/53] Revert "CP-45016: Clean up the source VM earlier" This reverts commit 889dfa67525da1aa39514b4254d1e980be815c4c. As there is no need to clean up the source VM earlier at all, VM_save already does the job of deactivating the source VM datapath. Signed-off-by: Vincent Liu --- ocaml/xenopsd/lib/xenops_server.ml | 42 ++++++++++-------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/ocaml/xenopsd/lib/xenops_server.ml b/ocaml/xenopsd/lib/xenops_server.ml index bdab2c0e913..e3f0a77f5e8 100644 --- a/ocaml/xenopsd/lib/xenops_server.ml +++ b/ocaml/xenopsd/lib/xenops_server.ml @@ -2732,23 +2732,6 @@ and perform_exn ?result (op : operation) (t : Xenops_task.task_handle) : unit = ) ; debug "VM.migrate: Synchronisation point 1" in - let pause_src_vm () = - debug - "VM.migrate: pause src vm before allowing destination to proceed" ; - (* cleanup tmp src VM *) - let atomics = - [ - VM_hook_script_stable - ( id - , Xenops_hooks.VM_pre_destroy - , Xenops_hooks.reason__suspend - , new_src_id - ) - ] - @ atomics_of_operation (VM_shutdown (new_src_id, None)) - in - perform_atomics atomics t - in let final_handshake () = Handshake.send vm_fd Handshake.Success ; debug "VM.migrate: Synchronisation point 3" ; @@ -2789,10 +2772,7 @@ and perform_exn ?result (op : operation) (t : Xenops_task.task_handle) : unit = the main VM migration sequence. *) match VGPU_DB.ids id with | [] -> - first_handshake () ; - save () ; - pause_src_vm () ; - final_handshake () + first_handshake () ; save () ; final_handshake () | (_vm_id, dev_id) :: _ -> let url = make_url "/migrate/vgpu/" @@ -2809,12 +2789,20 @@ and perform_exn ?result (op : operation) (t : Xenops_task.task_handle) : unit = first_handshake () ; save ~vgpu_fd:(FD vgpu_fd) () ) ; - pause_src_vm () ; final_handshake () ) ; - let cleanup_src_vm () = - let atomics = - [ + (* cleanup tmp src VM *) + let atomics = + [ + VM_hook_script_stable + ( id + , Xenops_hooks.VM_pre_destroy + , Xenops_hooks.reason__suspend + , new_src_id + ) + ] + @ atomics_of_operation (VM_shutdown (new_src_id, None)) + @ [ VM_hook_script_stable ( id , Xenops_hooks.VM_post_destroy @@ -2823,10 +2811,8 @@ and perform_exn ?result (op : operation) (t : Xenops_task.task_handle) : unit = ) ; VM_remove new_src_id ] - in - perform_atomics atomics t in - cleanup_src_vm () + perform_atomics atomics t | VM_receive_memory { vmr_id= id From 764ec0df40daf80f30a3bb78026eb7b0f2b273d1 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Tue, 28 Jan 2025 17:30:13 +0000 Subject: [PATCH 52/53] CA-405502: Change post_detach to post_deactivate Previously the `post_detach_hook` was run after the VDI is detached on the source VM, which is at the very end of the SXM, where the source VM is shutdown. However, the job of `post_detach_hook` is to call `Remote.receive_finalize` which will destroy the mirroring datapath. This should have been called as soon as we deactivate the datapath on the source VM, at which point the source VM will stop writing using that datapath. This commit changes `post_detach_hook` to `post_deactivate_hook` and moves its calling locations accordingly. Signed-off-by: Vincent Liu --- ocaml/xapi/storage_migrate.ml | 2 +- ocaml/xapi/storage_smapiv1_wrapper.ml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ocaml/xapi/storage_migrate.ml b/ocaml/xapi/storage_migrate.ml index 2686208d733..6f153515ed7 100644 --- a/ocaml/xapi/storage_migrate.ml +++ b/ocaml/xapi/storage_migrate.ml @@ -1320,7 +1320,7 @@ let pre_deactivate_hook ~dbg:_ ~dp:_ ~sr ~vdi = s.failed <- true ) -let post_detach_hook ~sr ~vdi ~dp:_ = +let post_deactivate_hook ~sr ~vdi ~dp:_ = let open State.Send_state in let id = State.mirror_id_of (sr, vdi) in State.find_active_local_mirror id diff --git a/ocaml/xapi/storage_smapiv1_wrapper.ml b/ocaml/xapi/storage_smapiv1_wrapper.ml index fbb30a2177f..f71e2b21d99 100644 --- a/ocaml/xapi/storage_smapiv1_wrapper.ml +++ b/ocaml/xapi/storage_smapiv1_wrapper.ml @@ -422,10 +422,10 @@ functor | Vdi_automaton.Deactivate -> Storage_migrate.pre_deactivate_hook ~dbg ~dp ~sr ~vdi ; Impl.VDI.deactivate context ~dbg ~dp ~sr ~vdi ~vm ; + Storage_migrate.post_deactivate_hook ~sr ~vdi ~dp ; vdi_t | Vdi_automaton.Detach -> Impl.VDI.detach context ~dbg ~dp ~sr ~vdi ~vm ; - Storage_migrate.post_detach_hook ~sr ~vdi ~dp ; vdi_t in Sr.add_or_replace vdi new_vdi_t sr_t ; From f06041cc4f73901e258633f08b6082f49a13256b Mon Sep 17 00:00:00 2001 From: Konstantina Chremmou Date: Wed, 29 Jan 2025 17:14:18 +0000 Subject: [PATCH 53/53] Typo. Only throw assertions at Debug time. Signed-off-by: Konstantina Chremmou --- ocaml/sdk-gen/csharp/autogen/src/XenRef.cs | 2 +- ocaml/sdk-gen/csharp/gen_csharp_binding.ml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ocaml/sdk-gen/csharp/autogen/src/XenRef.cs b/ocaml/sdk-gen/csharp/autogen/src/XenRef.cs index d71646a2974..7c0697b1498 100644 --- a/ocaml/sdk-gen/csharp/autogen/src/XenRef.cs +++ b/ocaml/sdk-gen/csharp/autogen/src/XenRef.cs @@ -43,7 +43,7 @@ public partial class XenRef where T : XenObject /// May not be null. public XenRef(string opaqueRef) { - System.Diagnostics.Trace.Assert(opaqueRef != null, "'opaqueRef' parameter must not be null"); + System.Diagnostics.Debug.Assert(opaqueRef != null, "'opaqueRef' parameter must not be null"); this.opaqueRef = opaqueRef; } diff --git a/ocaml/sdk-gen/csharp/gen_csharp_binding.ml b/ocaml/sdk-gen/csharp/gen_csharp_binding.ml index c9112b680e3..e01780d751a 100644 --- a/ocaml/sdk-gen/csharp/gen_csharp_binding.ml +++ b/ocaml/sdk-gen/csharp/gen_csharp_binding.ml @@ -1186,7 +1186,7 @@ and json_serialization_attr fr = sprintf "\n [JsonConverter(typeof(StringStringMapConverter))]" | Map (Ref u, Set String) -> sprintf - "\n [JsonConverer(typeof(XenRefStringSetMapConverter<%s>))]" + "\n [JsonConverter(typeof(XenRefStringSetMapConverter<%s>))]" (exposed_class_name u) | Map (Ref _, _) | Map (_, Ref _) -> failwith (sprintf "Need converter for %s" fr.field_name)