vagrant-xapi 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE +21 -0
  4. data/README.md +460 -4
  5. data/docs/architecture.md +1420 -0
  6. data/docs/operations.md +531 -0
  7. data/lib/vagrant-xapi/action/base.rb +103 -0
  8. data/lib/vagrant-xapi/action/cloud_init.rb +130 -0
  9. data/lib/vagrant-xapi/action/create.rb +127 -0
  10. data/lib/vagrant-xapi/action/destroy.rb +68 -0
  11. data/lib/vagrant-xapi/action/network.rb +229 -0
  12. data/lib/vagrant-xapi/action/package.rb +103 -0
  13. data/lib/vagrant-xapi/action/power.rb +185 -0
  14. data/lib/vagrant-xapi/action/snapshots.rb +43 -0
  15. data/lib/vagrant-xapi/action.rb +317 -0
  16. data/lib/vagrant-xapi/api/client.rb +393 -0
  17. data/lib/vagrant-xapi/api/errors.rb +98 -0
  18. data/lib/vagrant-xapi/api/http_transfer.rb +332 -0
  19. data/lib/vagrant-xapi/api/redactor.rb +77 -0
  20. data/lib/vagrant-xapi/api/session_registry.rb +59 -0
  21. data/lib/vagrant-xapi/api/task.rb +156 -0
  22. data/lib/vagrant-xapi/api/transport.rb +249 -0
  23. data/lib/vagrant-xapi/cap.rb +90 -0
  24. data/lib/vagrant-xapi/command/adopt.rb +92 -0
  25. data/lib/vagrant-xapi/command/base.rb +79 -0
  26. data/lib/vagrant-xapi/command/console.rb +87 -0
  27. data/lib/vagrant-xapi/command/diff.rb +267 -0
  28. data/lib/vagrant-xapi/command/hosts.rb +63 -0
  29. data/lib/vagrant-xapi/command/info.rb +94 -0
  30. data/lib/vagrant-xapi/command/marked.rb +85 -0
  31. data/lib/vagrant-xapi/command/orphans.rb +96 -0
  32. data/lib/vagrant-xapi/command/root.rb +55 -0
  33. data/lib/vagrant-xapi/command/templates.rb +91 -0
  34. data/lib/vagrant-xapi/config.rb +333 -0
  35. data/lib/vagrant-xapi/errors.rb +113 -0
  36. data/lib/vagrant-xapi/extensions.rb +80 -0
  37. data/lib/vagrant-xapi/glue.rb +330 -0
  38. data/lib/vagrant-xapi/legacy/plugin.rb +61 -0
  39. data/lib/vagrant-xapi/legacy/xenapi.rb +129 -0
  40. data/lib/vagrant-xapi/plugin.rb +58 -0
  41. data/lib/vagrant-xapi/provider.rb +135 -0
  42. data/lib/vagrant-xapi/services/adoption.rb +73 -0
  43. data/lib/vagrant-xapi/services/base.rb +242 -0
  44. data/lib/vagrant-xapi/services/box_source.rb +218 -0
  45. data/lib/vagrant-xapi/services/console.rb +115 -0
  46. data/lib/vagrant-xapi/services/content_diff.rb +96 -0
  47. data/lib/vagrant-xapi/services/destroyer.rb +104 -0
  48. data/lib/vagrant-xapi/services/diff_disks.rb +346 -0
  49. data/lib/vagrant-xapi/services/differ.rb +382 -0
  50. data/lib/vagrant-xapi/services/disk_image.rb +122 -0
  51. data/lib/vagrant-xapi/services/disks.rb +281 -0
  52. data/lib/vagrant-xapi/services/exporter.rb +128 -0
  53. data/lib/vagrant-xapi/services/file_diff.rb +249 -0
  54. data/lib/vagrant-xapi/services/guest_files.rb +730 -0
  55. data/lib/vagrant-xapi/services/hosts.rb +55 -0
  56. data/lib/vagrant-xapi/services/image_upload.rb +172 -0
  57. data/lib/vagrant-xapi/services/inventory.rb +225 -0
  58. data/lib/vagrant-xapi/services/ip_detector.rb +107 -0
  59. data/lib/vagrant-xapi/services/lock.rb +293 -0
  60. data/lib/vagrant-xapi/services/lookup.rb +181 -0
  61. data/lib/vagrant-xapi/services/networks.rb +248 -0
  62. data/lib/vagrant-xapi/services/power.rb +108 -0
  63. data/lib/vagrant-xapi/services/rfb.rb +129 -0
  64. data/lib/vagrant-xapi/services/snapshots.rb +81 -0
  65. data/lib/vagrant-xapi/services/template_cache.rb +263 -0
  66. data/lib/vagrant-xapi/services/tunnels.rb +217 -0
  67. data/lib/vagrant-xapi/services/vm_builder.rb +215 -0
  68. data/lib/vagrant-xapi/version.rb +7 -0
  69. data/lib/vagrant-xapi/xenserver/config.rb +69 -0
  70. data/lib/vagrant-xapi/xenserver/migrate.rb +103 -0
  71. data/lib/vagrant-xapi/xenserver/plugin.rb +96 -0
  72. data/lib/vagrant-xapi/xenserver/provider.rb +22 -0
  73. data/lib/vagrant-xapi/xenserver/takeover.rb +79 -0
  74. data/lib/vagrant-xapi.rb +21 -2
  75. data/lib/xenapi.rb +22 -0
  76. data/locales/en.yml +517 -0
  77. data/locales/legacy.en.yml +6 -0
  78. metadata +86 -5
@@ -0,0 +1,531 @@
1
+ # Operations
2
+
3
+ How to run vagrant-xapi against a pool, keep the pool clean, find out what
4
+ went wrong, and work on the plugin itself. For the design behind it see
5
+ [architecture.md](architecture.md); for the provider options see the
6
+ [README](../README.md).
7
+
8
+ ## 1. What is needed
9
+
10
+ On the local machine:
11
+
12
+ | Tool | Needed for |
13
+ | --- | --- |
14
+ | Vagrant 2.4.x | everything |
15
+ | `ssh` | `vagrant ssh`, port forwarding tunnels, `ssh_via_host` |
16
+ | `rsync` | rsync synced folders |
17
+ | `qemu-img` | qcow2 boxes on a host that cannot import qcow2, and qcow2 images with compressed clusters (most cloud images and many libvirt boxes), which XAPI cannot import directly (`brew install qemu`, `apt install qemu-utils`) |
18
+ | `mkisofs`/`genisoimage` (Linux) or `hdiutil` (macOS) | cloud-init; Vagrant's `create_iso` host capability uses them |
19
+ | a VNC viewer | `vagrant xapi console`; macOS Screen Sharing works (it gets the one-time password in the URL) |
20
+
21
+ On the pool:
22
+
23
+ - XenServer 8 or XCP-ng 8.3.
24
+ - A default SR, or `sr` set in the Vagrantfile.
25
+ - The template `Other install media` (present on every standard install);
26
+ needed to import disk-image boxes.
27
+ - For `snapshot_memory` and `suspend`: a suspend SR and PV drivers or a guest
28
+ agent in the guest.
29
+ - For `vtpm`: a version with the `VTPM` class (XAPI is checked at login).
30
+
31
+ ## 2. Connecting
32
+
33
+ Put the credentials in the environment, not in a Vagrantfile in git:
34
+
35
+ ```sh
36
+ export VAGRANT_XAPI_HOST=xcp-master.example.com
37
+ export VAGRANT_XAPI_USERNAME=vagrant-svc
38
+ export VAGRANT_XAPI_PASSWORD='...'
39
+ # optional: the network of the first VIF when a Vagrantfile has no
40
+ # public_network (name, UUID or bridge such as xenbr1)
41
+ export VAGRANT_XAPI_DEFAULT_NETWORK=xenbr1
42
+ # optional: TLS for self-signed certificates (the pins of all hosts,
43
+ # separated by commas) or your own CA, instead of tls_fingerprint or
44
+ # tls_ca_file in the Vagrantfile
45
+ export VAGRANT_XAPI_TLS_FINGERPRINT=AB:CD:...,12:34:...
46
+ export VAGRANT_XAPI_TLS_CA_FILE=~/certs/pool-ca.pem
47
+ ```
48
+
49
+ Vagrantfiles written for vagrant-xenserver (`config.vm.provider
50
+ "xenserver"` with `xs_*` options) keep working unchanged; see "Migrating
51
+ from vagrant-xenserver" in the README and section 17 of architecture.md.
52
+ Only the stand-in for the `xenapi` gem, for triggers that call XAPI
53
+ themselves, is temporary.
54
+
55
+ `host` may be any host of the pool; a pool member redirects to the master.
56
+ With fingerprint pinning that needs the pins of all hosts (see below).
57
+
58
+ ### TLS
59
+
60
+ The certificate is always verified. Three ways to make that work:
61
+
62
+ 1. **A certificate from a CA the local machine trusts**: nothing to configure.
63
+ 2. **Your own CA**: `xapi.tls_ca_file = "~/certs/pool-ca.pem"`.
64
+ 3. **Self-signed** (the default after installing XCP-ng/XenServer): pin the
65
+ SHA-256 fingerprint of the host certificate.
66
+
67
+ ```sh
68
+ openssl s_client -connect xcp-master.example.com:443 </dev/null 2>/dev/null \
69
+ | openssl x509 -noout -fingerprint -sha256
70
+ # sha256 Fingerprint=AB:CD:...
71
+ ```
72
+
73
+ Without a pin, the error for a self-signed certificate shows the same
74
+ fingerprint of the certificate it got. Before you pin it, check it on the
75
+ host itself, where nothing can sit in between:
76
+
77
+ ```sh
78
+ # on the host (dom0)
79
+ openssl x509 -noout -fingerprint -sha256 -in /etc/xensource/xapi-ssl.pem
80
+ ```
81
+
82
+ ```ruby
83
+ config.vm.provider :xapi do |xapi|
84
+ xapi.tls_fingerprint = "AB:CD:..." # with or without colons
85
+ end
86
+ ```
87
+
88
+ Every pool member has its own certificate. One pin only matches one host, so
89
+ either pin the master and use it as `host` (with a single pin on a member,
90
+ the plugin stops and names the master), or pin the certificates of all
91
+ hosts. Then any host can be `host`, and a change of master (HA, or
92
+ `xe pool-designate-new-master`) does not break the configuration:
93
+
94
+ ```sh
95
+ vagrant xapi hosts # once connected to the master: every host's fingerprint
96
+ ```
97
+
98
+ ```ruby
99
+ config.vm.provider :xapi do |xapi|
100
+ xapi.tls_fingerprint = ["AB:CD:...", "12:34:..."] # or one string with commas
101
+ end
102
+ ```
103
+
104
+ Uploads, downloads and consoles that run on another
105
+ member use the certificate the master reports for that member
106
+ (`host.get_server_certificate`), so they need no extra configuration. After
107
+ a certificate renewal (e.g. `xe host-refresh-server-certificate`) the pin
108
+ must be updated; the error says "certificate fingerprint ... does not
109
+ match".
110
+
111
+ `vagrant xapi hosts --pem` prints the certificates themselves, e.g. for a
112
+ `tls_ca_file`. With a CA file the hostname is checked, so the address used
113
+ must be in the certificate: the member's, and the master's as XAPI reports
114
+ it on the redirect (its IP address). The self-signed certificates of
115
+ XCP-ng and XenServer do not contain those IP addresses; pin them instead.
116
+
117
+ ### Which user and role
118
+
119
+ With `pool-admin` everything works. The plugin needs to:
120
+
121
+ - create, change, start, stop and destroy VMs, VDIs, VBDs, VIFs, snapshots;
122
+ - import and export (`/import`, `/import_raw_vdi`, `/export`);
123
+ - write `pool.other_config` (locks);
124
+ - create and destroy networks (internal networks for `private_network`).
125
+
126
+ `vm-admin` covers the VM part, but may not cover the last two. Then `vagrant
127
+ up` stops with "The XAPI user may not write the pool's other_config ...".
128
+
129
+ ## 3. Boxes
130
+
131
+ ### Kinds
132
+
133
+ | Box provider | Use |
134
+ | --- | --- |
135
+ | `xapi` | Our own format: `metadata.json` (`provider`, `format`, `architecture`) and `box.<format>` (xva, vhd, qcow2 or raw) |
136
+ | `xenserver` | Boxes of vagrant-xenserver: `box.xva`, `box.vhd`, or a Vagrantfile with `xva_url` |
137
+ | `libvirt` | qcow2 boxes from the libvirt ecosystem (e.g. `generic/*`); they need cloud-init and a static IP when they have no Xen guest agent |
138
+
139
+ Vagrant chooses between providers of the same box version in this order:
140
+ `xapi`, `xenserver`, `libvirt`.
141
+
142
+ Always set `config.vm.box_architecture = "amd64"`: XenServer and XCP-ng only
143
+ run amd64 guests, and on a Mac with Apple silicon Vagrant otherwise looks for
144
+ an arm64 box. For the same reason, add a box *file* with
145
+ `vagrant box add --architecture amd64 ...`: Vagrant stores a local box file
146
+ under the local machine's architecture (it ignores `architecture` in its
147
+ `metadata.json`), and a Vagrantfile asking for amd64 then does not find it
148
+ and tries to download it instead ("could not be found in the remote
149
+ catalog"). Boxes from a registry carry their architecture and need nothing
150
+ extra. To remove a box added under the wrong architecture, use
151
+ `vagrant box remove <name> --all-architectures --force` or delete
152
+ `~/.vagrant.d/boxes/<name with / as -VAGRANTSLASH->/<version>/arm64`;
153
+ `vagrant box remove --architecture` does not match any box (Vagrant
154
+ compares the option as a string with symbols).
155
+
156
+ ### Building a box
157
+
158
+ The recommended route is
159
+ [packer-plugin-xenserver](https://github.com/vatesfr/packer-plugin-xenserver),
160
+ producing an XVA with a guest agent (xe-guest-utilities or xen-guest-agent)
161
+ and cloud-init installed. Then:
162
+
163
+ ```sh
164
+ cat > metadata.json <<'EOF'
165
+ {"provider": "xapi", "format": "xva", "architecture": "amd64"}
166
+ EOF
167
+ cat > Vagrantfile <<'EOF'
168
+ Vagrant.configure("2") do |config|
169
+ config.vm.provider :xapi do |xapi|
170
+ xapi.firmware = :uefi # whatever the image was built with
171
+ end
172
+ end
173
+ EOF
174
+ cp output/debian-12.xva box.xva
175
+ tar czf debian-12.box metadata.json Vagrantfile box.xva
176
+ vagrant box add --architecture amd64 --name acme/debian-12 debian-12.box
177
+ ```
178
+
179
+ Or make a box from a running machine with `vagrant package` (XVA by default,
180
+ `package_format = :vhd` or `:qcow2` for only the first disk). Remove SSH host
181
+ keys and `/etc/machine-id` in the guest first if the box will be shared.
182
+
183
+ The box needs the Vagrant insecure key (or a configured
184
+ `config.ssh.private_key_path`) for the `vagrant` user, as with any provider.
185
+
186
+ ## 4. Templates
187
+
188
+ Each box version is imported once per SR as a template named
189
+ `vagrant-xapi: <box> <version>`. Templates are shared by every user of the
190
+ pool and take disk space on the SR.
191
+
192
+ ```sh
193
+ vagrant xapi templates list # box, version, SR, size, clones
194
+ vagrant xapi templates prune --keep 1 # per box: keep the newest version
195
+ vagrant xapi templates remove "acme/debian-12 1.2.0"
196
+ ```
197
+
198
+ `prune` and `remove` show which other users still have clones, and ask for
199
+ confirmation (`--force` skips that). Removing a template does not break its
200
+ clones: the storage layer keeps the shared base disk. The next `vagrant up`
201
+ with that box version imports it again.
202
+
203
+ The first `up` with a new box (or on a new SR) uploads and imports it; this
204
+ can take a while for large boxes (`task_timeout`, default one hour). A second
205
+ user starting the same box at the same time waits for the first import
206
+ ("Waiting for lock 'template....' held by ...") instead of importing again.
207
+
208
+ ## 5. Finding what the plugin created
209
+
210
+ Everything the plugin creates is marked (full list in architecture.md,
211
+ section 6). `vagrant xapi marked` lists it through XAPI, with the normal
212
+ connection settings, from any directory whose Vagrantfile uses the xapi
213
+ provider:
214
+
215
+ ```sh
216
+ vagrant xapi marked # everything, of all users of the pool
217
+ vagrant xapi marked --mine # only your VMs, with their snapshots, disks, networks
218
+ vagrant xapi marked --project ~/src/shop # VMs, snapshots, networks of one project
219
+ vagrant xapi marked --vm <uuid> # one VM, its snapshots and its disks
220
+ vagrant xapi marked --box acme/debian-12 # the templates of one box
221
+ vagrant xapi marked --plain | grep '^vdi' # tab-separated, for scripts (or --json)
222
+ ```
223
+
224
+ Filters can be repeated and combined; an object is listed when it matches
225
+ any of them. The project path may be given as you see it (symlinks are
226
+ resolved, e.g. `/var` → `/private/var` on macOS).
227
+
228
+ The same with `xe` on dom0:
229
+
230
+ ```sh
231
+ # All VMs of one user
232
+ xe vm-list other-config:vagrant-xapi.user=alice params=uuid,name-label,power-state
233
+
234
+ # All VMs of one project directory
235
+ xe vm-list other-config:vagrant-xapi.project=/home/alice/src/shop
236
+
237
+ # Templates made by the plugin
238
+ xe template-list params=uuid,name-label | grep -B1 'vagrant-xapi:'
239
+
240
+ # Disks of one VM (including extra disks, ISOs and snapshot disks)
241
+ xe vdi-list other-config:vagrant-xapi.vm=<vm-uuid> params=uuid,name-label,virtual-size
242
+
243
+ # Snapshot copies a `vagrant xapi diff` is using, or left after it was killed
244
+ xe vdi-list name-label="vagrant-xapi diff of <vm-uuid>" params=uuid,other-config
245
+
246
+ # Internal networks created for private_network
247
+ xe network-list params=uuid,name-label,other-config | grep -B2 'vagrant-xapi.network'
248
+
249
+ # Locks currently held
250
+ xe pool-param-get uuid=$(xe pool-list --minimal) param-name=other-config | tr ';' '\n' | grep vagrant-xapi.lock
251
+ ```
252
+
253
+ ### Orphans
254
+
255
+ When a project directory is removed without `vagrant destroy`, its VMs keep
256
+ running. Find and remove them from any project with an xapi Vagrantfile:
257
+
258
+ ```sh
259
+ vagrant xapi orphans # list VMs of this Vagrant installation whose machine is gone
260
+ vagrant xapi orphans --destroy # remove them, their disks, snapshots and unused internal networks
261
+ ```
262
+
263
+ Orphans are VMs of this Vagrant installation: VMs carry a random
264
+ `vagrant-xapi.client_id`, stored in
265
+ `~/.vagrant.d/data/vagrant-xapi/client-id`. (VMs from before that key match on
266
+ login name and local hostname.) A VM is not an orphan while any Vagrant
267
+ machine still points at it, also with a custom `VAGRANT_DOTFILE_PATH`. VMs of
268
+ colleagues are never listed. Removing the `client-id` file makes earlier VMs
269
+ look foreign; keep it with your Vagrant home. An administrator can find all
270
+ marked VMs with the `xe` commands above and remove them with `xe
271
+ vm-uninstall`.
272
+
273
+ ### Taking over an existing VM
274
+
275
+ ```sh
276
+ vagrant xapi adopt web 1c2d...-uuid # VM created by vagrant-xapi
277
+ vagrant xapi adopt web 1c2d...-uuid --force # any other VM
278
+ ```
279
+
280
+ After `adopt`, `vagrant destroy` removes that VM and its disks.
281
+
282
+ Machines of a vagrant-xenserver Vagrantfile are taken over without `adopt`:
283
+ the first vagrant command finds the VM by the reference vagrant-xenserver
284
+ stored, marks it and its own disks, and stores its UUID. A reference that no
285
+ longer exists means the VM is gone (not created), as with vagrant-xenserver;
286
+ the plugin never takes a VM by its name. A VM that vagrant-xapi meanwhile
287
+ marked for another machine is left alone, with a warning.
288
+
289
+ ## 6. Locks
290
+
291
+ Locks are keys `vagrant-xapi.lock.<name>` in the pool's `other_config`, with
292
+ the owner and an expiry time (pool master's clock) in the value. While a lock
293
+ is held, its owner refreshes `<name>.alive.0`/`.alive.1` every few minutes.
294
+ Locks are released when the operation ends, also on errors and Ctrl-C. The
295
+ lock of a crashed process stops being refreshed and is taken over once its
296
+ expiry and heartbeats have passed: template locks after 15 minutes, network
297
+ and placement locks after 10 minutes at most. Two takeovers never run at
298
+ once: a takeover first sets `vagrant-xapi.lock.<name>.takeover` (gone again
299
+ after a minute at most). To remove a stuck lock by hand (also its `.alive.*`
300
+ keys):
301
+
302
+ ```sh
303
+ POOL=$(xe pool-list --minimal)
304
+ xe pool-param-get uuid=$POOL param-name=other-config param-key=vagrant-xapi.lock.<name>
305
+ xe pool-param-remove uuid=$POOL param-name=other-config param-key=vagrant-xapi.lock.<name>
306
+ ```
307
+
308
+ Only do that when the owner named in the value is really gone; otherwise two
309
+ imports of the same box can run at the same time.
310
+
311
+ ## 7. Troubleshooting
312
+
313
+ Every error is a short message without a stack trace. A stack trace is a
314
+ bug: please report it with the output of `VAGRANT_LOG=debug`.
315
+
316
+ | Message (start) | Cause | What to do |
317
+ | --- | --- | --- |
318
+ | `XAPI refused the login` | Wrong username or password | Check `VAGRANT_XAPI_USERNAME`/`PASSWORD` |
319
+ | `Cannot connect to the XAPI host` | Host unreachable, wrong port, firewall | `curl -vk https://<host>/` from the local machine |
320
+ | `provider :xenserver runs in vagrant-xenserver compatibility mode` | A Vagrantfile written for vagrant-xenserver | Nothing; it keeps working. For the newer options, move to `:xapi` and run `vagrant xapi migrate` (README, "Migrating from vagrant-xenserver") |
321
+ | `a trigger uses XenApi::Client ... This stand-in is temporary` | A trigger calls XAPI through the `xenapi` gem of vagrant-xenserver | Replace the trigger with the provider's options (`cpus`, `config.vm.disk`) before the stand-in is removed |
322
+ | `vagrant-xenserver is installed too, so vagrant-xapi leaves the :xenserver provider to it` | Both plugins installed | `vagrant plugin uninstall vagrant-xenserver` |
323
+ | `Either a public network must be specified or the Host Internal Management Network parameter must be enabled` | A message of vagrant-xenserver itself: it is still installed and runs the `:xenserver` Vagrantfile | `vagrant plugin uninstall vagrant-xenserver` |
324
+ | `VM ..., which vagrant-xenserver created for this machine, now belongs to machine ...` | The VM vagrant-xenserver stored for this machine is marked for another machine (e.g. a copied project); this machine is now not created | Nothing, or `vagrant xapi adopt <uuid> --force` if it really is this machine's VM |
325
+ | `Machine '...' is in use by another Vagrant process` (`vagrant xapi migrate`) | Another vagrant command holds the machine | Run `vagrant xapi migrate` again when it has finished |
326
+ | `Stale VM reference, looking the VM up by name instead` (from a trigger) | Trigger code of a vagrant-xenserver Vagrantfile uses `machine.id` as VM reference; vagrant-xapi stores the UUID | Nothing: the trigger finds the VM by name. Gone once the trigger is replaced by plugin options |
327
+ | `The TLS certificate of ... could not be verified` | Self-signed certificate, wrong pin, renewed certificate; or `host` is a pool member and the master's certificate is not among the pins (the message names both) | The message shows the SHA-256 fingerprint of the certificate it got; check it on the host and set `tls_fingerprint` or `VAGRANT_XAPI_TLS_FINGERPRINT` (section 2). Once the master is pinned, `vagrant xapi hosts` shows every host's fingerprint |
328
+ | `The XAPI connection is not configured` | Host, username or password missing | Set the environment variables or the provider options |
329
+ | `The XAPI user lacks the permission` / `may not write the pool's other_config` | RBAC role too small | Use a role with more rights (section 2) |
330
+ | `Warning: The guest closed the SSH connection right away ... Retrying...` | sshd refuses new connections for a while after failed logins (OpenSSH 9.8+ `PerSourcePenalties`), e.g. while cloud-init on the first boot is still creating the `vagrant` user | Nothing; the plugin keeps trying until `boot_timeout` and repeats the warning every minute. If it never ends: check that no other machine uses the IP (another VM with the same static IP answers, refuses the key and is then penalized), and look at the VM with `vagrant xapi console` |
331
+ | A VNC viewer asks for a password (`vagrant xapi console`) | The console proxy asks for a one-time password | Use the password `vagrant xapi console` printed; it is valid while the command runs |
332
+ | `console runs without a password` | Vagrant's Ruby has no 3DES (e.g. a FIPS-only OpenSSL) | Nothing needed; the console works without password, but other local users could connect while it is open |
333
+ | The console (`vagrant xapi console`) shows GRUB's `error: out of memory` after `Loading initial ramdisk` | UEFI with too little memory: the firmware takes its share. Templates of disk-image boxes have 1 GiB; older ones, imported before that, still have the 256 MiB of XAPI's "Other install media" | Set `xapi.memory` (e.g. 2048), or remove the old template (`vagrant xapi templates remove`) so it is imported again |
334
+ | `The guest did not report an IP within ... seconds` | No guest agent in the box, VM not booting (e.g. a BIOS box with `firmware = :uefi`, or no signed boot loader with `secure_boot`), or no IP on the first network | `vagrant xapi console` to look at the VM; install a guest agent, or set a static `ip:` on the first network and `ip_detection = [:static]` |
335
+ | `The VM stopped ... while waiting for its IP` | The guest crashed or powered off during boot | `vagrant xapi console`, check firmware (`:bios`/`:uefi`) matches the image |
336
+ | `Network '...' was not found in the pool` | Typo, or the network has another name on this pool | `xe network-list params=name-label,bridge` |
337
+ | `There are N network objects named ...` | Duplicate names | Use the UUID |
338
+ | `Storage repository '...' was not found` / `The pool has no default SR` | Wrong `sr`, or no default SR | `xe sr-list`; set `sr` |
339
+ | `The storage repository is full` | Not enough space for the import or the clone | Free space, prune templates, or use another `sr` |
340
+ | `The host does not have enough free memory` | Not enough RAM on the host | Lower `memory`, or set `affinity_host` to another host |
341
+ | `This host cannot import qcow2 directly and qemu-img is not installed` | qcow2 box on a host whose XAPI cannot import qcow2 | Install `qemu-img` on the local machine |
342
+ | `... is a qcow2 image with compressed clusters` / `uses qcow2 features that XAPI cannot import` / `XAPI could not import ... as qcow2` | XAPI's qcow2 import cannot read this image (compressed clusters, zstd, extended L2 entries) | Install `qemu-img` on the local machine; the plugin converts the image to VHD |
343
+ | `Waiting for lock ... held by ...` (keeps waiting) | Another import of the same box runs, or a stale lock | Wait; or check the owner and remove the lock (section 6) |
344
+ | `The VM cannot be suspended: ... no working PV drivers` | No guest agent or PV drivers | Install them in the box, or use `halt` |
345
+ | `The VM cannot be suspended: the pool has no suspend SR` | No suspend SR | Set a suspend image SR on the pool |
346
+ | `Disk '...' cannot shrink` | `size` smaller than the existing disk | Disks only grow; recreate the machine to shrink |
347
+ | `Disk '...' can only grow while the VM is halted` | Resize on a running VM | Use `vagrant reload` |
348
+ | `The SSH tunnel for port(s) ... did not start` | Host port in use, or SSH to the guest fails | See `.vagrant/machines/<name>/xapi/tunnels.log`; pick another host port or set `auto_correct: true` |
349
+ | `This pool does not support vTPM` | XAPI without `VTPM` class | Remove `vtpm = true` |
350
+ | `Box format ... is not supported` / `Boxes for provider ... cannot be used` | Wrong box | Use an xapi, xenserver or libvirt box |
351
+ | `The box names ..., which is not a file inside the box directory` | The box's `metadata.json` or a symlink points outside the box | Do not use the box; ask its maker to fix it |
352
+ | `The qcow2 image ... refers to a backing file` / `uses an external data file` | A qcow2 overlay, not a self-contained image | Flatten it: `qemu-img convert -O qcow2 in.qcow2 out.qcow2` |
353
+ | `VM ... has no vagrant-xapi marking` | `destroy` of a VM the plugin did not create | Leave it, or `vagrant xapi adopt --force` it first |
354
+ | `VM ... belongs to machine '...' of <dir>, not to this machine and project` | The project directory was copied or moved; the VM is marked for the other one | After a move: `vagrant xapi adopt [vm] <uuid>`. After a copy: leave it; the original project owns the VM |
355
+ | `A network named '...' already exists and does not belong to this project` | A `private_network` name is used by another project | Choose another `name:`, or use `network: "<name>"` to attach to it on purpose |
356
+ | `VM ... belongs to machine '...' of ... (user ...)` on `adopt` | Adopting someone else's VM | Check the UUID; add `--force` only if you really take it over |
357
+ | `... is a member of a pool; its master is ...` | `host` is a pool member and `tls_fingerprint` is set | Use the master as `host` and pin its certificate |
358
+ | `vagrant xapi diff reads the files through the guest` | `diff` of a halted or suspended machine | `vagrant up` first |
359
+ | `Machine '...' uses the provider ...` on `diff --with` | The other machine is not a machine of vagrant-xapi | Compare with a machine of the `:xapi` (or `:xenserver`) provider |
360
+ | `The guest uses crypto_LUKS` (or btrfs, ZFS, RAID) on `diff` | A copy of such disks cannot be attached next to the running system | Compare two running machines with `--with` |
361
+ | `The guest lacks ...` on `diff` | Not a Linux guest with GNU findutils, coreutils and util-linux | Install them in the box |
362
+ | `The file listing from the guest is incomplete or malformed` on `diff` | find in the guest could not write its whole listing, usually because the guest's `/tmp` is full | Free space in the guest's `/tmp` (often RAM) and run `diff` again |
363
+ | `The snapshot disk contains crypto_LUKS` (or btrfs, ZFS, RAID) | The disk layout changed since the snapshot was taken | Compare with `--with`; if `lsblk` in the guest still shows volumes of the copy, reboot the guest |
364
+ | `The guest lacks vgimportclone (lvm2)` | The guest (or the snapshot) uses LVM, but lvm2 is not complete | Install lvm2 in the box |
365
+ | `A command in the guest failed (vgimportclone)` or `(vgchange)` | LVM could not import or activate the copy (e.g. a shared VG, or thin LVs without thin-provisioning-tools) | The message has LVM's text; `vagrant xapi diff` cleaned up |
366
+ | `Could not deactivate the volume group vagrant_xapi_diff_...` | A logical volume of the copy was still open | Nothing; its devices go once they are closed. `sudo dmsetup ls` in the guest shows them |
367
+ | `XAPI could not attach the snapshot disk to the running VM` | No PV drivers or guest agent, so XAPI refuses to hotplug a disk | Install xe-guest-utilities or xen-guest-agent in the box |
368
+ | `Could not remove the snapshot copy <uuid> of the destroyed VM ...` | `vagrant destroy` could not remove a copy of an interrupted `diff`, also not after the VM was gone | Nothing removes it later: `vagrant xapi marked --vm <vm-uuid>` lists it, `xe vdi-destroy uuid=<uuid>` removes it |
369
+ | `Could not remove the snapshot disk ...` | A `diff` could not unplug or destroy its copy (e.g. the guest kept it busy) | Run `vagrant xapi diff` again or `vagrant destroy`; both remove it. `vagrant reload` removes it too while it is still attached to the VM, not a copy whose VBD is already gone. Until then do not reboot from inside the guest: an attached copy has the same file system UUIDs |
370
+ | `... snapshot copy (copies) of an interrupted vagrant xapi diff could not be removed from VM ...` on `up`, `reload` or `package` | A copy of an interrupted `diff` is still attached and XAPI refused to remove it (the warning before it says why) | Fix the cause and run the command again, or remove the copy by hand as the message says; until then the VM does not start, because the copy has the same file system UUIDs |
371
+ | `Could not install the udev rule that keeps udev and systemd in the guest on its own disks ...` | `diff` could not write `/run/udev/rules.d/99-vagrant-xapi-diff.rules` or reload udev in the guest | The diff ran; check with `findmnt` that `/boot` and other fstab mounts by UUID are still mounted, and `sudo mount -a` if not |
372
+ | `XAPI did not finish cloning the snapshot disk ...` | `VDI.clone` took longer than `task_timeout`, or the connection was lost | A clone that appears later has no marking, so no cleanup removes it: find it with the `xe vdi-list` command in the message and remove it with `xe vdi-destroy` |
373
+ | `XAPI returned an error: <CODE>` | An XAPI error without its own text | Look the code up in the XAPI docs; the message names the call |
374
+
375
+ SSH problems:
376
+
377
+ - With `ssh_via_host`, the host key of dom0 must be in your `known_hosts`
378
+ (the ProxyCommand runs with `BatchMode=yes` and normal host key checking):
379
+ `ssh root@<host-address> true` once.
380
+ - `vagrant ssh-config` shows exactly what Vagrant uses.
381
+
382
+ ### Debug logging
383
+
384
+ ```sh
385
+ VAGRANT_LOG=debug vagrant up 2>debug.log
386
+ grep -E '^ *[A-Z]+ xapi' debug.log
387
+ ```
388
+
389
+ The plugin's lines start with `xapi:` (the client and services) or
390
+ `xapi_<step>:` (the provider and each action step, e.g. `xapi_createvm:`).
391
+
392
+ Every XAPI call is logged as `xapi -> <method> <params>` and
393
+ `xapi <- <method> ok|error`. The password and the session id are replaced by
394
+ `[REDACTED]`/`<session>`; check before sharing a log anyway (VM names and
395
+ paths are in it).
396
+
397
+ ### Files in the machine directory
398
+
399
+ `.vagrant/machines/<name>/xapi/`:
400
+
401
+ | File | Contents |
402
+ | --- | --- |
403
+ | `id` | VM UUID (Vagrant) |
404
+ | `forwarded_ports.json` | Running SSH tunnels: PID, addresses, the host port in use and the one in the Vagrantfile |
405
+ | `tunnels.log` | stderr of the SSH tunnels |
406
+ | `disk_meta` | Disks configured by `config.vm.disk` (Vagrant) |
407
+ | `action_cloud_init` | Sentinel: cloud-init ran for this VM (Vagrant) |
408
+ | `xapi_cloud_init_iso`, `vagrant-xapi-cidata-<sha>.iso` | Checksum and path of the cloud-init ISO |
409
+
410
+ In the Vagrant home, `~/.vagrant.d/data/vagrant-xapi/`:
411
+
412
+ | File | Contents |
413
+ | --- | --- |
414
+ | `digests.json` | Box digests, keyed on path, size and mtime; deleting it only costs one recomputation |
415
+ | `client-id` | Random id of this installation, used to recognise its own VMs (orphans) |
416
+
417
+ ## 8. Development
418
+
419
+ ### Environment
420
+
421
+ Ruby 3.0–3.4 (Vagrant does not support 3.5 or 4.x yet) with bundler.
422
+
423
+ ```sh
424
+ bundle install
425
+ bundle exec rake # unit tests
426
+ bundle exec rspec spec/unit/api # one directory
427
+ bundle exec vagrant <cmd> # Vagrant (pinned in the Gemfile) with this plugin loaded
428
+ ```
429
+
430
+ The Gemfile has Vagrant (pinned to a release tag, currently v2.4.9) from
431
+ git in group `:development` and the plugin itself in group `:plugins` (as `gem "vagrant-xapi", path:
432
+ "."`), which `bundle exec vagrant` loads. Set `VAGRANT_CHECKPOINT_DISABLE=1`
433
+ to stop Vagrant's version check. Running Vagrant outside its installer
434
+ complains about a missing `bsdtar`; that only matters for `vagrant box add`
435
+ of packed boxes and for `vagrant package` compression.
436
+
437
+ If native gems fail to link (for example with a Homebrew Ruby whose
438
+ `libruby` is built against Homebrew's glibc), build them with Homebrew's
439
+ compiler by putting `gcc`/`cc`/`g++` shims that point to `gcc-NN`/`g++-NN`
440
+ first in `PATH`.
441
+
442
+ ### Where things go
443
+
444
+ - XAPI calls only in `lib/vagrant-xapi/api` and `services`; services never
445
+ require Vagrant.
446
+ - Every user-facing text in `locales/en.yml` (`messages`, `errors`,
447
+ `config`, `commands`, `states`). Do not use `%{format}`, `%{scope}` or other
448
+ I18n reserved names as parameters.
449
+ - New errors: raise `Services::Error.new(:key, **params)` in a service and add
450
+ `vagrant_xapi.errors.key`; `spec/unit/vagrant/errors_spec.rb` checks every
451
+ key renders.
452
+ - New XAPI calls the fake does not know: add a `x_<Class>_<method>` method to
453
+ `spec/support/fake_xapi.rb` (generic getters, setters, `add_to_*`,
454
+ `remove_from_*`, `create`, `destroy`, `get_by_*` already work), and
455
+ remember that the fake only proves what we think XAPI does.
456
+ - Anything the plugin creates in XAPI must carry a marking, and cleanup must
457
+ check it.
458
+
459
+ ### Tests
460
+
461
+ - `spec/unit/api`: client and TLS (the TLS tests use a real local TLS
462
+ server, with WebMock disabled).
463
+ - `spec/unit/services`: one spec per service, against the fake XAPI.
464
+ - `spec/unit/vagrant`: config, errors, the real action stacks in a temporary
465
+ `Vagrant::Environment` (box placed straight in `VAGRANT_HOME`; SSH
466
+ communicator and guest stubbed), and the subcommands.
467
+
468
+ ### CI
469
+
470
+ `.github/workflows/ci.yml` runs on every pull request and every push to
471
+ `master`:
472
+
473
+ - the unit tests on Ruby 3.1 to 3.4, the versions Vagrant's own CI tests
474
+ (Gemfile.lock is not checked in, so each run resolves the gems anew);
475
+ - `shellcheck` on the acceptance script;
476
+ - `gem build`, failing when the gem has a runtime dependency or lacks one of
477
+ the files listed under Releasing.
478
+
479
+ The actions are pinned to a commit; bump them deliberately. The acceptance
480
+ scenarios need a real pool and do not run in CI.
481
+
482
+ ### Acceptance tests
483
+
484
+ Against a real pool; see [test/acceptance/README.md](../test/acceptance/README.md)
485
+ for the environment variables.
486
+
487
+ ```sh
488
+ test/acceptance/scenarios.sh # A1-A19
489
+ test/acceptance/scenarios.sh A3 A13 # some
490
+ ```
491
+
492
+ Run them before a release. The scenarios check the pool with
493
+ `vagrant xapi marked`, so they only need the normal connection settings, no
494
+ access to dom0.
495
+
496
+ ## 9. Releasing
497
+
498
+ 1. Run the unit tests and the acceptance scenarios.
499
+ 2. Bump `VagrantPlugins::Xapi::VERSION` in `lib/vagrant-xapi/version.rb` and
500
+ add the changes to `CHANGELOG.md`, with the release date instead of
501
+ "unreleased".
502
+ 3. `gem build vagrant-xapi.gemspec` and check the contents:
503
+ `tar -xOf vagrant-xapi-*.gem data.tar.gz | tar -tz` must list `lib/`,
504
+ `locales/en.yml`, `docs/architecture.md`, `docs/operations.md`,
505
+ `README.md`, `LICENSE`, `CHANGELOG.md`, and the gem must have no runtime
506
+ dependencies.
507
+ 4. Try it in a clean Vagrant: `vagrant plugin install ./vagrant-xapi-X.Y.Z.gem`.
508
+ 5. Once that is merged into `master`, build the gem again from a clean
509
+ checkout of `master` and push it to rubygems.org, with an account that
510
+ owns `vagrant-xapi` there (MFA is required, so `gem push` asks for a
511
+ one-time code). The gem is public: `spec.files` takes every matching file
512
+ in the working tree, tracked or not, so `git status` must be clean.
513
+
514
+ ```sh
515
+ git status --short --ignored lib locales docs README.md LICENSE CHANGELOG.md # must print nothing
516
+ gem build vagrant-xapi.gemspec
517
+ gem push vagrant-xapi-X.Y.Z.gem
518
+ ```
519
+
520
+ A pushed version cannot be replaced; a mistake needs a new version
521
+ (`gem yank` only hides it from new installs).
522
+ 6. Tag the release on GitHub, with the release's section of `CHANGELOG.md`
523
+ as its notes:
524
+
525
+ ```sh
526
+ gh release create vX.Y.Z -R exonet/vagrant-xapi \
527
+ --target master --title vX.Y.Z --notes-file notes.md
528
+ ```
529
+
530
+ The gem is not attached: rubygems.org is the only place it comes from
531
+ (`docs/architecture.md`, section 15).
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "log4r"
4
+
5
+ require_relative "../errors"
6
+ require_relative "../glue"
7
+
8
+ module VagrantPlugins
9
+ module Xapi
10
+ module Action
11
+ # Shared helpers for our middleware.
12
+ class Base
13
+ def initialize(app, env)
14
+ @app = app
15
+ @logger = Log4r::Logger.new("vagrantplugins::xapi_#{self.class.name.split("::").last.downcase}")
16
+ end
17
+
18
+ protected
19
+
20
+ def ctx(env)
21
+ env[:xapi_ctx] ||= Glue.context(env[:machine], env, client: env[:xapi])
22
+ end
23
+
24
+ def vm(env)
25
+ ref = Glue.vm_ref(env[:machine], ctx(env))
26
+ raise Vagrant::Errors::VMNotCreatedError unless ref
27
+
28
+ ref
29
+ end
30
+
31
+ def wrap(&block)
32
+ Errors.wrap(&block)
33
+ end
34
+
35
+ def config(env)
36
+ env[:machine].provider_config
37
+ end
38
+ end
39
+
40
+ # First middleware of every stack: client and service errors become
41
+ # readable VagrantErrors, never stack traces.
42
+ class TranslateErrors
43
+ def initialize(app, _env)
44
+ @app = app
45
+ end
46
+
47
+ def call(env)
48
+ @app.call(env)
49
+ rescue Api::Error, Services::Error => e
50
+ raise Errors.translate(e)
51
+ end
52
+ end
53
+
54
+ # Puts the shared XAPI session in env[:xapi].
55
+ class ConnectXapi < Base
56
+ def call(env)
57
+ env[:xapi] = Glue.client(env[:machine])
58
+ env[:xapi_ctx] = Glue.context(env[:machine], env, client: env[:xapi])
59
+ previous = Thread.current[:vagrant_xapi_cancel]
60
+ Thread.current[:vagrant_xapi_cancel] = -> { env[:interrupted] ? true : false }
61
+ begin
62
+ @app.call(env)
63
+ ensure
64
+ Thread.current[:vagrant_xapi_cancel] = previous
65
+ end
66
+ end
67
+ end
68
+
69
+ # env[:result] = whether the VM exists.
70
+ class IsCreated < Base
71
+ def call(env)
72
+ env[:result] = env[:machine].state.id != :not_created
73
+ @app.call(env)
74
+ end
75
+ end
76
+
77
+ # Raises when the machine is not running (for ssh, ssh_run).
78
+ class CheckRunning < Base
79
+ def call(env)
80
+ state = env[:machine].state.id
81
+ raise Vagrant::Errors::VMNotCreatedError if state == :not_created
82
+ raise Vagrant::Errors::VMNotRunningError if state != :running
83
+
84
+ @app.call(env)
85
+ end
86
+ end
87
+
88
+ # Prints a message from our locale file.
89
+ class Message < Base
90
+ def initialize(app, env, key, level = :info)
91
+ super(app, env)
92
+ @key = key
93
+ @level = level
94
+ end
95
+
96
+ def call(env)
97
+ env[:ui].public_send(@level, I18n.t("vagrant_xapi.messages.#{@key}"))
98
+ @app.call(env)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end