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/.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: 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/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/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/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/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/MVD/index.md b/doc/content/toolstack/features/MVD/index.md new file mode 100644 index 00000000000..ed31722295f --- /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, 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 + 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/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 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/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/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 ===================== 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 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 diff --git a/dune-project b/dune-project index 651c039bc22..806b80b189b 100644 --- a/dune-project +++ b/dune-project @@ -278,6 +278,7 @@ xapi-stdext-pervasives xapi-stdext-unix xen-api-client + xen-api-client-lwt xenctrl xenstore_transport xmlm @@ -405,6 +406,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/ocaml/database/master_connection.ml b/ocaml/database/master_connection.ml index ed9bfbd2826..d7faff1cd62 100644 --- a/ocaml/database/master_connection.ml +++ b/ocaml/database/master_connection.ml @@ -235,24 +235,17 @@ 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 \ %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" ; 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); +} diff --git a/ocaml/idl/datamodel.ml b/ocaml/idl/datamodel.ml index a1e605a8cc5..75137852c71 100644 --- a/ocaml/idl/datamodel.ml +++ b/ocaml/idl/datamodel.ml @@ -10453,6 +10453,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) *) @@ -10545,6 +10547,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 = @@ -10700,6 +10703,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..c520aa47899 --- /dev/null +++ b/ocaml/idl/datamodel_driver_variant.ml @@ -0,0 +1,54 @@ +(* + 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 + +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 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" + "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_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/idl/datamodel_host.ml b/ocaml/idl/datamodel_host.ml index 78b68a35722..10607e7e382 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, "self", "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..0d1becae339 --- /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" + "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" + "Information about the driver" + ] + ~messages:[select; deselect; rescan] () diff --git a/ocaml/idl/datamodel_lifecycle.ml b/ocaml/idl/datamodel_lifecycle.ml index 9e3007f4744..738a5ef397f 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 "25.2.0" + | "Host_driver" -> + Some "25.2.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 "25.2.0" + | "Driver_variant", "priority" -> + Some "25.2.0" + | "Driver_variant", "hardware_present" -> + Some "25.2.0" + | "Driver_variant", "version" -> + Some "25.2.0" + | "Driver_variant", "driver" -> + Some "25.2.0" + | "Driver_variant", "name" -> + Some "25.2.0" + | "Driver_variant", "uuid" -> + Some "25.2.0" + | "Host_driver", "info" -> + Some "25.2.0" + | "Host_driver", "description" -> + Some "25.2.0" + | "Host_driver", "type" -> + Some "25.2.0" + | "Host_driver", "selected_variant" -> + Some "25.2.0" + | "Host_driver", "active_variant" -> + Some "25.2.0" + | "Host_driver", "variants" -> + Some "25.2.0" + | "Host_driver", "friendly_name" -> + Some "25.2.0" + | "Host_driver", "name" -> + Some "25.2.0" + | "Host_driver", "host" -> + Some "25.2.0" + | "Host_driver", "uuid" -> + Some "25.2.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 "25.2.0" + | "Host_driver", "rescan" -> + Some "25.2.0" + | "Host_driver", "deselect" -> + Some "25.2.0" + | "Host_driver", "select" -> + Some "25.2.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 "25.2.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..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 = "18df8c33434e3df1982e11ec55d1f3f8" +let last_known_schema_hash = "458f20f5270a5615c7ee92be8a383172" 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/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/sdk-gen/csharp/FriendlyErrorNames.resx b/ocaml/sdk-gen/csharp/FriendlyErrorNames.resx index 7562889272e..69b5ae29fb9 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 @@ -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/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 + 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 { 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 +} 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) diff --git a/ocaml/tests/dune b/ocaml/tests/dune index ce8fe96c195..f26f17cab5d 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,22 +105,23 @@ 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-threads.scheduler xapi-stdext-unix + xapi-test-utils + xapi-tracing + xapi-types + xapi_cli_server + xapi_database + xapi_host_driver_helpers + xapi_internal xml-light2 yojson ) @@ -168,6 +170,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/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/tests/test_storage_migrate_state.ml b/ocaml/tests/test_storage_migrate_state.ml index d822b7ef393..42087887995 100644 --- a/ocaml/tests/test_storage_migrate_state.ml +++ b/ocaml/tests/test_storage_migrate_state.ml @@ -44,14 +44,16 @@ 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" + ; mirror_vm= Vm.of_string "mirror_vm" } let sample_copy_state = diff --git a/ocaml/util/dune b/ocaml/util/dune index 7a21f9bb24b..488cf4f444f 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 xapi-stdext-unix) + (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..4910ed8d11f --- /dev/null +++ b/ocaml/util/xapi_host_driver_helpers.ml @@ -0,0 +1,131 @@ +(* + 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 int n = Int32.to_int n + +let ( // ) = Filename.concat + +(** Read a (small) file into a string *) +let read path = Xapi_stdext_unix.Unixext.string_of_file path + +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/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 f7ab6341f77..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 @@ -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..d067846f565 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 @@ -218,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 = { @@ -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 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 ; let total_work = let open Vhd_format.F in Int64.( @@ -985,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 @@ -993,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 = @@ -1013,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 @@ -1074,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 @@ -1088,10 +1090,15 @@ 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 + (* 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 ]" @@ -1101,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 @@ -1319,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" @@ -1409,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-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 4f61e843140..265c6f86a3a 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 failwithfmt 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" + ; "priority" + ; "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"] @@ -7967,6 +8000,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 -> + failwithfmt "%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..178bc45c6bc 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -5290,6 +5290,214 @@ 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 () -> + 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" + ~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 () -> + 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 () -> + 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) + ) + () + ] + } + let vtpm_record rpc session_id vtpm = let _ref = ref vtpm in let empty_record = 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-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index aa5754fabb9..34ca1938b23 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"] @@ -512,6 +516,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 +526,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 +831,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 = @@ -906,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 @@ -982,6 +989,7 @@ module StorageAPI (R : RPC) = struct (dbg_p @-> sr_p @-> vdi_p + @-> vm_p @-> url_p @-> dest_p @-> verify_dest_p @@ -989,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 = @@ -997,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 @@ -1013,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 @@ -1026,10 +1044,40 @@ module StorageAPI (R : RPC) = struct @-> returning result err ) + (** 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 + declare "DATA.MIRROR.receive_start2" [] + (dbg_p + @-> sr_p + @-> VDI.vdi_info_p + @-> id_p + @-> similar_p + @-> vm_p + @-> 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) + + (** [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) @@ -1039,6 +1087,32 @@ 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 + ) + + (** [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 @@ -1108,7 +1182,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 @@ -1333,6 +1407,7 @@ module type Server_impl = sig -> dbg:debug_info -> sr:sr -> vdi:vdi + -> vm:vm -> url:string -> dest:sr -> verify_dest:bool @@ -1345,6 +1420,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 @@ -1363,11 +1440,41 @@ 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_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 + + val import_activate : + context + -> dbg:debug_info + -> dp:dp + -> sr:sr + -> vdi:vdi + -> vm:vm + -> sock_path + + val get_nbd_server : + context + -> dbg:debug_info + -> dp:dp + -> sr:sr + -> vdi:vdi + -> vm:vm + -> sock_path end end @@ -1409,8 +1516,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 ()) ; @@ -1521,24 +1628,38 @@ 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 ) ; 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 + ) ; + 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 cced1a7f6f5..ab84ed7712e 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" @@ -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,11 +167,22 @@ 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_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" + + 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-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 cba7ec89d56..ae725b98459 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -352,10 +352,16 @@ 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" +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 +755,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 +768,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 } @@ -814,6 +837,16 @@ let choose_datapath ?(persistent = true) response = | (script_dir, scheme, u) :: _us -> return (fork_exec_rpc ~script_dir, scheme, u) +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 @@ -1459,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= @@ -1479,6 +1502,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 @@ -1729,27 +1756,116 @@ 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 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 response >>>= fun (rpc, datapath, uri) -> + 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 ; + 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.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.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") ; 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.DATA.MIRROR.receive_finalize2 (u "DATA.MIRROR.receive_finalize2") ; 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/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..597cd7b5704 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 ~self: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 72170c5b6a2..3088a830a63 100644 --- a/ocaml/xapi/dune +++ b/ocaml/xapi/dune @@ -161,6 +161,7 @@ uri uuid uuidm + vhd_lib x509 xapi_aux xapi-backtrace @@ -211,6 +212,7 @@ xxhash yojson zstd + xapi_host_driver_helpers ) (preprocess (per_module ((pps ppx_sexp_conv) Cert_distrib) 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) diff --git a/ocaml/xapi/message_forwarding.ml b/ocaml/xapi/message_forwarding.ml index 63b27076a1a..c843b563918 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 ~self = + let uuid = host_uuid ~__context self in + info "Host.rescan_drivers: host = '%s'" uuid ; + 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 = let uuid = host_uuid ~__context self in info "Host.set_https_only: self = %s ; value = %b" uuid value ; @@ -6599,6 +6607,45 @@ 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 "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 -> + 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 "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 -> + Client.Host_driver.deselect ~rpc ~session_id ~self + ) + + let rescan ~__context ~host = + 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 + ) + end + + module Driver_variant = struct + let select ~__context ~self = + 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 + 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/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 03416b9b14f..1da0c6c7e83 100644 --- a/ocaml/xapi/sm_exec.ml +++ b/ocaml/xapi/sm_exec.ml @@ -558,8 +558,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..b5c290afcb7 100644 --- a/ocaml/xapi/smint.ml +++ b/ocaml/xapi/smint.ml @@ -21,99 +21,100 @@ 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_mirror_in + | 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 + | Vdi_compose + | 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_MIRROR_IN", Vdi_mirror_in) + ; ("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) + ; ("VDI_COMPOSE", Vdi_compose) + ; ("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 +132,51 @@ 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 : 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). + 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 +186,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/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_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_migrate.ml b/ocaml/xapi/storage_migrate.ml index 3279846a5a5..6f153515ed7 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] @@ -362,13 +363,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 +382,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 +422,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 +459,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,14 +529,15 @@ 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/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) remote_dp ) |> 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 @@ -562,11 +560,24 @@ 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) ; - Remote.VDI.activate dbg remote_dp dest dest_vdi ; - with_activated_disk ~dbg ~sr ~vdi:base_vdi ~dp:base_dp + 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.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 @@ -574,7 +585,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 @@ -608,7 +619,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) @@ -693,8 +704,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 @@ -710,10 +721,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 ; @@ -729,8 +747,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 ; @@ -769,18 +785,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 @@ -790,7 +808,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 mirror_vm in on_fail := (fun () -> Remote.DATA.MIRROR.receive_cancel dbg mirror_id) :: !on_fail ; let tapdev = @@ -895,23 +913,17 @@ 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 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) @@ -1110,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_start ~dbg ~sr ~vdi_info ~id ~similar = + 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 *) @@ -1121,12 +1133,14 @@ 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 _ = 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 -> @@ -1185,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 @@ -1206,12 +1221,38 @@ module MigrateRemote = struct !on_fail ; raise e + let receive_start ~dbg ~sr ~vdi_info ~id ~similar = + 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 let open State.Receive_state in 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 @@ -1279,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 @@ -1300,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 ) @@ -1315,35 +1356,60 @@ 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 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) + +(** 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 = @@ -1374,16 +1440,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 *) @@ -1403,8 +1469,12 @@ let stat = MigrateLocal.stat let receive_start = MigrateRemote.receive_start +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 dc49d2e75b7..7acba0c8823 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 ) @@ -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 @@ -648,7 +655,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 @@ -826,33 +835,85 @@ 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_finalize () ~dbg = - with_dbg ~name:"DATA.MIRROR.receive_finalize" ~dbg - @@ fun {log= dbg; _} -> Storage_migrate.receive_finalize ~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 -> + 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 - let receive_cancel () ~dbg = - with_dbg ~name:"DATA.MIRROR.receive_cancel" ~dbg @@ fun {log= dbg; _} -> - Storage_migrate.receive_cancel ~dbg + 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 0bb0dd9d267..87a80d0cadc 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 @@ -1202,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 @@ -1219,9 +1225,20 @@ 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_finalize2 _context ~dbg:_ ~id:_ = assert false + let receive_cancel _context ~dbg:_ ~id:_ = assert false + + 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 55067efd9de..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 ; @@ -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 @@ -1137,7 +1097,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 @@ -1176,6 +1136,105 @@ functor ) end + module DATA = struct + 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 ~vm ~url ~dest + + module MIRROR = struct + 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 ~mirror_vm ~copy_vm + ~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_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 + + 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 + + (* 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 + + let get_nbd_server context ~dbg ~dp ~sr ~vdi ~vm = + get_nbd_server_common context ~dbg ~dp ~sr ~vdi ~vm ~style:`real + end + end + module SR = struct include Storage_skeleton.SR 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.ml b/ocaml/xapi/xapi.ml index dc1a1e7e04a..2834cd15b3c 100644 --- a/ocaml/xapi/xapi.ml +++ b/ocaml/xapi/xapi.ml @@ -1227,7 +1227,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 @@ -1261,6 +1260,7 @@ let server_init () = , [Startup.OnlyMaster] , check_no_other_masters ) + ; ("Initialising licensing", [], handle_licensing) ; ( "Registering periodic functions" , [] , fun () -> Xapi_periodic_scheduler_init.register ~__context 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_globs.ml b/ocaml/xapi/xapi_globs.ml index 548b333dbdc..c07c3d9b739 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/bin/dracut" + +let udevadm = ref "/usr/sbin/udevadm" + let yum_repos_config_dir = ref "/etc/yum.repos.d" let remote_repository_prefix = ref "remote" @@ -1690,6 +1700,11 @@ let other_options = ; event_from_entry ; event_from_task_entry ; event_next_entry + ; ( "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. @@ -1787,6 +1802,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..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 ; @@ -3089,6 +3089,9 @@ let apply_updates ~__context ~self ~hash = Db.Host.set_last_update_hash ~__context ~self ~value:hash ; warnings +let rescan_drivers ~__context ~self = + Xapi_host_driver.scan ~__context ~host:self + 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..c81b4e7b219 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 -> self: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..a4061a7f9f0 --- /dev/null +++ b/ocaml/xapi/xapi_host_driver.ml @@ -0,0 +1,212 @@ +(* + 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 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 + = + 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 "%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 + 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 + 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 + 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 + +(** 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 + ~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: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 + | 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 + 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 + 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 + 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 = 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 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 + 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]. 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 = + Tool.Mock.install () ; + let null = Ref.null in + 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.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..f55d03ad3c0 --- /dev/null +++ b/ocaml/xapi/xapi_host_driver_tool.ml @@ -0,0 +1,656 @@ +(* + 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 *) + +open Debug.Make (struct let name = __MODULE__ end) + +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 + +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 + +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..5eb40825f09 --- /dev/null +++ b/ocaml/xapi/xapi_host_driver_tool.mli @@ -0,0 +1,46 @@ +(* + 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 *) + +val call : string list -> string +(** invoke drivertool with argumtns and return stdout *) + +(** install a mock drivertool.sh *) +module Mock : sig + val install : unit -> unit +end 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_pool.ml b/ocaml/xapi/xapi_pool.ml index 2f471932c14..97e0617bff1 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 @@ -3306,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' diff --git a/ocaml/xapi/xapi_services.ml b/ocaml/xapi/xapi_services.ml index 8a71c2aca0c..a413e4c3630 100644 --- a/ocaml/xapi/xapi_services.ml +++ b/ocaml/xapi/xapi_services.ml @@ -203,6 +203,12 @@ 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 + | [""; 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 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..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 ) @@ -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 = @@ -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_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..3713f189040 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]) @@ -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/ocaml/xapi/xapi_vm_migrate.ml b/ocaml/xapi/xapi_vm_migrate.ml index 3b561e370ab..fb0c3aba577 100644 --- a/ocaml/xapi/xapi_vm_migrate.ml +++ b/ocaml/xapi/xapi_vm_migrate.ml @@ -160,38 +160,34 @@ 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 *) 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]) @@ -216,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 @@ -483,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 ; @@ -737,6 +734,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 @@ -802,7 +803,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' *) @@ -928,6 +950,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\")" @@ -985,8 +1009,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 @@ -994,17 +1018,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 @@ -1194,7 +1222,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 +1493,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) @@ -1515,7 +1543,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 +1566,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 () -> @@ -1753,7 +1783,10 @@ 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_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 | `intra_pool -> @@ -1766,7 +1799,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 @@ -1798,7 +1832,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 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 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 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/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 = diff --git a/quality-gate.sh b/quality-gate.sh index cfef6614e00..e59b8e40ccb 100755 --- a/quality-gate.sh +++ b/quality-gate.sh @@ -25,15 +25,19 @@ verify-cert () { } mli-files () { - N=496 + N=497 + 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 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" 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 915cc192de6..7cfbd383bd5 100644 --- a/xapi.opam +++ b/xapi.opam @@ -88,6 +88,7 @@ depends: [ "xapi-tracing" {= version} "xapi-tracing-export" {= version} "xapi-types" {= version} + "xen-api-client-lwt" {= version} "xenctrl" "xenstore_transport" "xmlm"