Compare commits

...

1021 Commits

Author SHA1 Message Date
Julien Fontanet
03b28057ad wip 2024-08-08 13:49:58 +02:00
Julien Fontanet
04ed6406c1 wip 2024-08-08 13:49:58 +02:00
Julien Fontanet
f47ec95a38 wip 2024-08-08 13:49:58 +02:00
Julien Fontanet
091e20d635 WiP 2024-08-08 13:49:58 +02:00
Julien Fontanet
e75cccf1be feat(web-core/stores/ui): default theme is now _auto_
Introduced by 7f4f86073
2024-08-08 13:47:52 +02:00
Julien Fontanet
0e6bc063db fix(xo-server/isHyperThreadingEnabled): better XCP-ng>=8.3 support 2024-08-07 16:02:12 +02:00
Julien Fontanet
0291d42af7 fix(xo-web/home/sr): fix shared sort
It was not working at all because current system only accepts strings for `sortBy` property.
2024-08-07 15:00:40 +02:00
Julien Fontanet
656786eb86 fix(proxy/authentication): keep token synced with config 2024-08-07 14:56:10 +02:00
Mathieu
4dfd77b2d2 feat(xo-server/rest-api/dashboard): add backup repositories info (#7882) 2024-08-06 17:51:20 +02:00
Mathieu
895a551c7b fix(xo-server/rest-api/dashboard): ignore EOL hosts if getHVSupportedVersions unavailable (#7901)
Introduced by ddfdce9
2024-08-06 16:51:58 +02:00
Pierre Donias
e750a66c02 fix(xo-web/self): remove unit in CPU usage total count (#7894)
Introduced by #7353
2024-08-06 09:33:40 +02:00
Julien Fontanet
4d768252f0 feat(@vates/json-hash): 0.2.0 2024-08-05 17:56:58 +02:00
Dan Pollak
f0f5cdba72 fix(docs,xo-web): trials are now 30 days (#7896) 2024-08-05 17:38:55 +02:00
Julien Fontanet
8cc3d0cbaf feat(json-hash): use base64url instead of base64 2024-08-05 17:12:09 +02:00
Julien Fontanet
3284021964 fix(json-hash): strict mode and Node version
Introduced by 14325fe99
2024-08-05 14:43:28 +02:00
Julien Fontanet
e333332131 feat(@vates/json-hash): 0.1.0 2024-08-05 14:38:36 +02:00
Julien Fontanet
14325fe992 feat(json-hash): extract into own package 2024-08-05 14:37:33 +02:00
Mathieu
ddfdce9da5 feat(rest-api): add number of EOL hosts in the dashboard endpoint (#7895) 2024-08-05 10:14:01 +02:00
Dan Pollak
a2c62117a1 fix(xo-server,xo-web): optionnal → optional (#7897)
Signed-off-by: Dan Pollak <danpollak2@gmail.com>
2024-08-05 08:12:02 +02:00
Olivier Lambert
cb6cfc3a74 docs(airgap): improve the air gap doc and add our fully offline XOA s… (#7863)
docs(airgap): improve the air gap doc and add our fully offline XOA solution

Signed-off-by: Olivier Lambert <olivier.lambert@vates.fr>
2024-08-01 09:17:48 +02:00
Mathieu
9d61e0232d feat(xo-web): create a backup job schedule selector (#7804)
New UI component `SelectSchedule` that will be used for chaining backup jobs.
2024-07-31 17:31:42 +02:00
b-Nollet
fedab820f1 chore(xo-lite): updating package.json (#7892)
Introduced by #7890
2024-07-31 15:54:15 +02:00
OlivierFL
7eac1d1c5a fix(xo-6/css): wrong lang in components style tags (#7880)
The wrong CSS lang (`scss` instead of `postcss`) was used in some components.
2024-07-31 14:24:57 +02:00
b-Nollet
06a9ab6718 feat(lite): 0.2.7 (#7890) 2024-07-31 12:24:17 +02:00
b-Nollet
d0bd6562d0 feat: release 5.97.0 (#7889) 2024-07-31 11:21:44 +02:00
b-Nollet
7b60fab7ea feat: technical release (#7887) 2024-07-31 10:12:03 +02:00
Pierre Donias
045cae24fb fix(xo-web/VM/Stats): do not show guest tools warning when VM is offline (#7883)
Introduced by #7831
2024-07-31 09:51:41 +02:00
Olivier Lambert
bd7e82cb6b docs(supported hosts): update to add support on XS 8 and XCP-ng 8.3 (#7885) 2024-07-31 09:13:52 +02:00
b-Nollet
307179b142 feat: technical release (#7879) 2024-07-30 16:10:41 +02:00
b-Nollet
0a3c32f9d2 feat(backups): add reportWhen 'skipped and failure' (#7869)
Replace the "don't send reports for skipped backups" toggle by an option in "Report when"
2024-07-30 15:01:25 +02:00
OlivierFL
124d96f31a fix(xo-6): add comments in host and pool pages to fix Vite build (#7878)
The components `/host/[id]/index.vue` and `pool/[id]/index.vue` only use
`definePage()` macro.
The macro is removed during the build, leaving an empty script tag that causes
the build to fail.
This PR addresses this issue by adding a comment in the components to avoid
empty content in `<script>` tags at build time.
2024-07-30 14:57:27 +02:00
Pierre Donias
916418b6cf feat(lite): local XO Lite will try to load app from lite.xen-orchestra.com first (#7870) 2024-07-30 11:13:05 +02:00
MlssFrncJrg
7592734c9a feat(xo-web/VM/Stats): warning when guest tools are not installed (#7831)
Memory usage is not collected without guest tools. That means that the stats
graph shown in the VM page is pointless.
2024-07-30 10:20:41 +02:00
b-Nollet
b1088e2a5b fix(xo-web/backup): replace reportWhen raw value with message (#7876)
Replace `reportWhen`'s raw value by a message (e.g. *never* is replaced by the
`reportWhenNever` message, which is *Never*)
2024-07-30 09:49:53 +02:00
Mathieu
83715bedc6 fix(xo-web/VM/new): use new secureboot API (#7874)
Fixes #7500
2024-07-29 17:48:41 +02:00
Mathieu
e30f289073 feat(xo-server/xo-web/host): display 2CRSi informations in general tab (#7838) 2024-07-29 17:33:32 +02:00
OlivierFL
0977542dbd fix(web-core/novnc): add custom declaration file to fix type-check error (#7875)
See #7824
See 287f38cfd2/types/novnc-core
See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/70022

The types definitions in DefinitelyTyped/NoVnc are outdated and don’t reflect
the changes made in the NoVnc package.
This causes an error when running `yarn type-check`.

While waiting for https://github.com/DefinitelyTyped/DefinitelyTyped/pull/70022
to be merged, a temporary workaround is to add a custom `novnc.d.ts` (moved from
`lite/src/types/novnc.d.ts`) declaration file in `web-core` to fix the
`type-check` error.
2024-07-29 16:49:37 +02:00
MlssFrncJrg
c9699c8f02 feat(xo-web/VM): display accurate Secure Boot status (#7751) 2024-07-29 14:12:27 +02:00
Thierry Goettelmann
636c88539f feat(web-core): new useItemCounter composable (#7868) 2024-07-26 13:51:01 +02:00
Paloma
333012bd29 feat(xo-web): add host status to xo6 dashboard (#7799) 2024-07-26 11:09:15 +02:00
OlivierFL
8fd516040b feat(xo-6/host): add VMs table list view (#7851) 2024-07-26 10:49:45 +02:00
Paloma
2017ec26ad feat(xo-web): add vms status for xo6 dashboard (#7795) 2024-07-26 10:33:39 +02:00
Thierry Goettelmann
72d6da38c5 feat(web-core): update heroes + add stories (#7866)
- `StateHero`, `LoadingHero` and `ComingSoonHero` can now be used inside a card by passing `type="card"` or inside a page by passing `type="page"`
- `LoadingHero` has now a `disabled?: boolean` prop and can now take a slot which will be displayed when `disabled` is `true`
- Added stories for `StateHero`, `ComingSoonHero`, `ObjectNotFoundHero` and `LoadingHero`
2024-07-26 10:01:20 +02:00
Paloma
59784315a8 feat(xo-core): add donut with legends ui component (#7829) 2024-07-26 09:00:29 +02:00
Mathieu
c5f6bf574f fix(xo-web/SR): remove newSrExistingSr modal for NFS SR (#7865)
Introduced by 659c735
See https://xcp-ng.org/forum/topic/9425
2024-07-25 17:45:50 +02:00
Jonas
991b4641ae docs(installation): add systemd service (#7848) 2024-07-25 15:53:07 +02:00
b-Nollet
0fee31b345 feat: technical release (#7864) 2024-07-25 15:35:03 +02:00
ventra007
d58ee9611f docs(backups): grammar fixes in the Concurrency section (#7859) 2024-07-25 15:08:27 +02:00
Julien Fontanet
da9d796d3f Revert "fix(xo-web/host/advanced): fix netdata link"
This reverts commit b393d72eb9.
2024-07-25 14:31:15 +02:00
Pierre Donias
e4c66bc0e8 fix(CHANGELOG): missing package to release (#7862)
Introduced by #7776
2024-07-25 14:09:29 +02:00
mathieu
d61429852d fix(xo-server-backup-reports/metadata): fix task.tasks is not iterable 2024-07-25 13:59:27 +02:00
Pizzosaure
a06e78c77c feat(xo-web/backup): add 'report recipients' when creating a metadata backup 2024-07-25 13:59:27 +02:00
Julien Fontanet
e66bdda6a1 feat(mixins/Tasks): buffer task events (#7841)
Fixes zammad#26718
Fixes zammad#26778
Fixes zammad#26865
Fixes zammad#26885
Fixes zammad#26980
Fixes zammad#27205
Fixes https://xcp-ng.org/forum/post/79281
Fixes https://xcp-ng.org/forum/post/80010
2024-07-25 12:41:41 +02:00
Mathieu
659c735b70 feat(xo-web/SR): add confirmation modal before SR creation (#7845) 2024-07-25 11:25:06 +02:00
OlivierFL
35c6248251 feat(xo-6/tree-view): improve indentation of empty items (#7861) 2024-07-25 10:46:03 +02:00
Thierry Goettelmann
4344e1567c fix(web-6/tree-view): issue with item without children (#7860)
- Remove the responsibility to exclude branches with no children from `branch.ts`
- `PoolTreeItem` and `HostTreeItem` no longer create a sublist if they have no children
2024-07-25 09:59:25 +02:00
Florent Beauchamp
2291505865 test(nbd-client): testing half succeeding multi nbd 2024-07-24 17:23:47 +02:00
Florent Beauchamp
92acd16779 feat(backups): improve error message when CBT backups aren't possible 2024-07-24 17:23:47 +02:00
Florent Beauchamp
3c5307cd64 feat(nbd-client): filter out unreachable hosts 2024-07-24 17:23:47 +02:00
Florent Beauchamp
f55ffc7d06 fix(backups): NBD backups must respect default backup network 2024-07-24 17:23:47 +02:00
Florent Beauchamp
6c118e6af5 fix(backups): incorrect assert check 2024-07-24 16:50:57 +02:00
Florent Beauchamp
d0984a7cb4 fix(backups): remove snapshot data only after the transfer is finished
* _removeUnusedSnapshots is called before and after the backup
* in some case it can delete the snapshot data before the transfer is done
2024-07-24 16:50:57 +02:00
Florent BEAUCHAMP
76336fa422 fix(xo-server): VHD is a disposable (#7856)
Introduced by 24d584e905
2024-07-24 16:44:14 +02:00
Julien Fontanet
2a9f5bcf53 fix(CHANGELOG.unreleased): add missing xen-api
Introduced by 79690fcdf
2024-07-24 16:39:31 +02:00
Julien Fontanet
9d3ec46086 feat(xo-server/self service): admins alway bypass quotas (#7855)
Fixes Zammad#27050
Fixes https://team.vates.fr/vates/pl/jb5xsh3bijf17fjokq3uczg4bo
2024-07-24 16:21:33 +02:00
Julien Fontanet
79690fcdfa feat(xen-api): cache support for call & callAsync (#7853)
* cache support for call & callAsync
* composite key is based on hashed JSON of function call arguments
2024-07-24 15:56:53 +02:00
b-Nollet
23e42c1764 feat: technical release (#7857) 2024-07-24 15:05:17 +02:00
Julien Fontanet
4b8f7146f6 feat: use new deploy page URL 2024-07-24 10:48:16 +02:00
Julien Fontanet
8172a327e4 fix(xo-web/HomeTags): strict tag filtering
Fixes https://team.vates.fr/vates/pl/776boet4qty55jkt4srabaz4hc

Exact match and respect letter case.
2024-07-23 22:28:41 +02:00
Pierre Donias
919d215b64 fix(xo-web/VM): properly identify self user to hide unauthorized actions (#7854) 2024-07-23 17:07:07 +02:00
b-Nollet
161169f295 feat(backups): option to not send reports for skipped runs (#7832) 2024-07-23 14:45:11 +02:00
b-Nollet
e91b7f8571 feat(xo-server-backup-reports): include info (#7815)
Show info of backup tasks in MJML template, for example show when NBD is used.

Currently it only displays a the message associated with the info, not its data (unlike e44933e2f7/packages/xo-web/src/xo-app/logs/backup-ng/log-alert-body.js (L131-L159)). It would be possible to display the info data, but it would probably not be done in a clean way in the current template (and I'm not sure it would be that useful).

From https://xcp-ng.org/forum/post/79833
2024-07-23 12:06:22 +02:00
OlivierFL
60291e317c feat(xo-6/pool): add hosts and VMs table list views (#7842) 2024-07-22 17:46:45 +02:00
OlivierFL
c704f156fc fix(xo-6/xo-lite): tree-view vertical line display (#7847)
Introduced by fe1f5cb

The `TreeItemLabel` component `expanded` property wasn’t handled correctly when displaying the last item of a sublist.
2024-07-22 17:42:41 +02:00
Thierry Goettelmann
9a811d7f1e feat(web-stack): upgrade Vue Router to 4.4 (#7850)
- Upgrade `vue-router` to `4.4` and `unplugin-vue-router` to `0.10`
- Disable `no-undef` ESLint rule since it's already handled by TypeScript ESLint
2024-07-22 17:01:04 +02:00
OlivierFL
a97d55c53c feat(xo-stack): add text-ellipsis utility class (#7843) 2024-07-22 16:31:44 +02:00
Julien Fontanet
8542b83f7c fix(xen-api/cli): use @xen-orchestra/log
Follows 0702454fa
2024-07-22 15:12:47 +02:00
Pierre Donias
73755c0e8c feat(lite/XoaButton): support xo-server's setting http.publicUrl (#7849)
See https://xcp-ng.org/forum/topic/9392

`xo-server` has a config setting `http.publicUrl` that allows the user to force
XO to report a custom URL in the pool's other_config (along with the
`networkInterfaces` fallback). With this change, the "Access XOA" button in
XO Lite will open that URL if it exists and fallback to `networkInterfaces`
otherwise.
2024-07-22 11:10:34 +02:00
Julien Fontanet
0702454fa8 feat(xen-api): use @xen-orchestra/log 2024-07-19 16:05:16 +02:00
Jonas
a0c3920d00 docs(users): SAML instructions for use with Google Workspace (#7846)
This shows a working example of how to connect XO SAML-plugin with Google Workspace SAML.

It's a rough example, with simple screenshots. And I've mentioned the caveat that a successfull authentication points the user to the login screen. Needing to browse into the bare URL, for access into the dahsboard. But at least it works!
2024-07-19 09:55:59 +02:00
Florent BEAUCHAMP
d4637e3335 fix(vmware-explorer): handling disks without storage (#7840)
Some VM may have disks without any storage declared. Since we
didn't find the cause, this PR ensure we don't break the VM listing.

Fixes zammad#26677
2024-07-18 16:35:06 +02:00
Jonas
6fcb838b70 feat(xo-web/i18n): add Swedish translation (#7844) 2024-07-18 10:17:30 +02:00
Julien Fontanet
065c44cc92 feat(xapi/parseDateTime): support timezones
Fixes https://team.vates.fr/vates/pl/ifht7eyhiffhtyxwywxsqwonma

`host.get_server_localtime` will include timezones in the near future.
2024-07-17 11:45:42 +02:00
Mathieu
b539b5f5e3 fix(xo-web/VM/Advanced): fix 'not enough permission' error for super admins when attaching PCIs (#7793) 2024-07-16 17:11:50 +02:00
Thierry Goettelmann
d1ce35f20a feat(web): add task store + tasks quick view (#7813) 2024-07-16 16:45:08 +02:00
Thierry Goettelmann
3f754fdc05 feat(web-core): tasks quick view (#7812) 2024-07-16 16:41:27 +02:00
Thierry Goettelmann
57d2d301ae feat(web): add polling option to xo store config builder (#7809)
This is a temporary solution. A better implementation will follow.
2024-07-16 11:12:51 +02:00
OlivierFL
d2507025a9 feat(xo-core): add HeadBar component (#7806) 2024-07-16 10:43:45 +02:00
OlivierFL
de934ec609 feat(xo-core): add CellText component (#7801) 2024-07-15 16:24:12 +02:00
OlivierFL
6894841038 feat(xo-core): add CellObject component (#7798) 2024-07-15 16:05:27 +02:00
John P. Cooper
c5e4f05d3b feat(xo-6): Initial PWA manifest for Xen Orchestra 6 (#7445)
* Update index.html

Rebased on latest commits.

* Create manifest.webmanifest

The initial PWA web manifest but rebased for latest commits.

* Update CHANGELOG.unreleased.md

Rebased on the latest commits as the conflicts were too major.

* Update CHANGELOG.unreleased.md

* Update CHANGELOG.unreleased.md

* Make URLs relative and remove changelog entry

---------

Co-authored-by: Pierre Donias <pierre.donias@gmail.com>
2024-07-15 15:28:33 +02:00
OlivierFL
dad3dc5926 feat(xo-core): add ObjectLink component (#7794) 2024-07-15 15:24:24 +02:00
Thierry Goettelmann
3fe46cae64 feat(web): fix dev token (#7779) 2024-07-15 14:58:41 +02:00
OlivierFL
14c5d2c4ec feat(xo-core): add SearchBar component (#7726) 2024-07-15 14:57:23 +02:00
Mathieu
4a4122b9a6 feat(xo-server/xo-web/VM_coalesceLeaf): implement Coalesce Leaf (#7810) 2024-07-15 11:26:34 +02:00
Dan Pollak
acffdbec8f fix(xo-server/rest-api): add dashboard endpoint to index (#7835)
Signed-off-by: Dan Pollak <danpollak2@gmail.com>
2024-07-15 11:11:28 +02:00
OlivierFL
f9220cd272 fix(xo-core/eslint/i18n): add "fa" to the list of excluded locales for missing keys checks (#7833)
Introduced by 28f94b3

Fix ESLint errors for missing `fa` locale translation keys.
2024-07-12 14:33:53 +02:00
Julien Fontanet
518b0f77bd fix(eslint): restrict require-atomic-updates to limit number of warnings 2024-07-12 12:07:40 +02:00
Mathieu
b35a5cffa6 feat(xo-server/rest-api): implement dashboard endpoint (#7823) 2024-07-11 10:30:49 +02:00
OlivierFL
bb88834fcd fix(xo-core/xo-lite): update NoVncClient import (#7824)
Introduced by e44933e

b35cf6dd12 has resulted in a change to the import path of `NoVncClient` in `web-core` and `XO Lite` console components.
2024-07-10 15:16:42 +02:00
Julien Fontanet
e492be3a67 feat(CHANGELOG.unreleased): add Security section for future releases
Related to zammad#26875
2024-07-09 18:00:27 +02:00
Julien Fontanet
a223207ff3 docs(configuration/redis): replace incorrect info
Fixes zammad#26893
2024-07-09 17:26:06 +02:00
OlivierFL
4d42f86b2c fix(xo-6/header): make XO5 link work (#7719) 2024-07-09 14:56:43 +02:00
Julien Fontanet
da3aa59781 docs(xoa): extraneous → secondary 2024-07-09 13:22:43 +02:00
Julien Fontanet
2b46e6cf30 docs(xoa): extraneous IP addresses 2024-07-09 13:12:13 +02:00
Julien Fontanet
9406d20672 docs(xoa): show CIDR syntax for xoa network static 2024-07-09 13:12:13 +02:00
Pierre Donias
0e6d3ae27a chore(xo-web/i18n): update russian locale 2024-07-09 11:01:53 +02:00
a.anushko@spbu.ru
ee3bc185da fix(xo-web/i18n/ru): fix typos, remove wrong translations, add translations 2024-07-09 11:01:53 +02:00
Jokar-xen
28f94b3888 feat(xo-web,web-core,lite/i18n): add Persian (#7775) 2024-07-09 10:27:57 +02:00
Julien Fontanet
e44933e2f7 chore: update dev deps 2024-07-08 14:41:20 +02:00
MlssFrncJrg
30dd89243f feat(XO6): create link reusable helper (#7681)
Add helper to handle external links
2024-07-08 11:25:02 +02:00
Julien Fontanet
96b7673e27 feat: release 5.96.0 2024-07-05 17:28:52 +02:00
Julien Fontanet
69a312a0c4 feat(xo-server): 5.148.2 2024-07-05 16:57:19 +02:00
Julien Fontanet
a0e6c8b115 feat(@xen-orchestra/proxy): 0.28.8 2024-07-05 16:57:14 +02:00
Julien Fontanet
dc047d47ea feat(@xen-orchestra/xapi): 7.1.1 2024-07-05 16:56:55 +02:00
Julien Fontanet
a00b24f6b8 fix(xapi/VM_snapshot): destroy snapshot VDIs instead of original ones
Fixes https://xcp-ng.org/forum/post/79769

This code, only used for running VMs, triggered `VDI_IN_USE` errors and left the
created VM snapshot with all the VDIs.
2024-07-05 16:44:58 +02:00
Julien Fontanet
7601dee7f2 fix(xapi/VM_snapshot): log original error when failing to destroy a ignored VDI 2024-07-05 16:41:42 +02:00
Julien Fontanet
1ace3cbbe8 feat(@xen-orchestra/proxy): 0.28.7 2024-07-05 11:16:32 +02:00
Julien Fontanet
8651e61210 feat(xo-server): 5.148.1 2024-07-05 10:46:58 +02:00
Julien Fontanet
6c6b6b9fa1 feat(@xen-orchestra/backups): 0.52.1 2024-07-05 10:46:49 +02:00
Julien Fontanet
6374e0d3f5 feat(@xen-orchestra/xapi): 7.1.0 2024-07-05 10:38:02 +02:00
Julien Fontanet
664d4b1af6 feat(backups/worker): forced exit after 30s 2024-07-05 10:32:08 +02:00
Julien Fontanet
0117e9dbc8 chore(mixins/Tasks): don't duplicate log deletion logic 2024-07-04 17:44:39 +02:00
Julien Fontanet
174e32173f fix(xo-server/sr.getAllUnhealthyVdiChainsLength): lodash.throttle instead of debounceWithKey
`debounceWithKey` should not be used because the API method will always be called with a different
empty object as param which will render the cache useless.

Fortunately `lodash.throttle` can be used here as `sr.getAllUnhealthyVdiChainsLength` is synchronous
and has no meaningful parameter.
2024-07-04 17:43:07 +02:00
Mathieu
0e4a300058 feat: technical release (#7808) 2024-07-04 16:42:52 +02:00
Florent Beauchamp
76b54ec579 feat(backups): expose setting to purge snapshot data
* when using CBT backup, no rolling snapshot and NBD
* this will delete the snapshot data , thus limiting the pressure on the SR
* snapshot won't be listed in the UI
* snapshot won't be usable to revert a VM
2024-07-04 16:24:25 +02:00
Florent BEAUCHAMP
7b256ab8ee feat(backups): enable Change Block Tracking Backups (#7750)
Enable CBT for all backups

* enable cbt each time a VM should be backuped by nbd
* if there are no rolling snapshot on a job =>
the snapshots data are only kept during the transfer.
Snapshot data deletion is behing a config flag 'purgeSnapshotData' (default false )

adverse effect:

* rolling back to previous XO version will make full backups
* removed read ahead in nbd , performance gain was low with concurrency
and it make code more complex
2024-07-04 16:24:25 +02:00
Florent Beauchamp
7a2bb5104c fix(backups): Cannot destructure property 'other_config' of 'undefined'
introduced by a85f44d699
2024-07-04 16:24:25 +02:00
Florent Beauchamp
56c881fd2a fix(backups): ensure vhd stream are exactly the right length 2024-07-04 16:24:25 +02:00
Florent Beauchamp
db89485efe fix(backups): fix a off by one error on delta chain length 2024-07-04 16:24:25 +02:00
Julien Fontanet
3c6a7a9575 feat(xo-server/rest-api): expose servers 2024-07-04 15:52:48 +02:00
Julien Fontanet
992d636bcd feat(xo-server/rest-api): expose a VM's VDIs 2024-07-03 15:41:59 +02:00
Mathieu
253aac4eaa feat(xo-web/SR/XOSTOR): add state column to the resource list table (#7784) 2024-06-28 16:25:56 +02:00
Mathieu
ea551a5175 chore(xo-web/SR/XOSTOR): remove unused import (#7785)
Introduced by 1ab62d4
2024-06-28 16:19:47 +02:00
OlivierFL
fe1f5cbad7 feat(xo-6/tree-view): add simple search (#7780) 2024-06-28 15:05:56 +02:00
OlivierFL
ce4c2e17af feat(xo-core): add UiInput component (#7724) 2024-06-28 14:52:29 +02:00
Mathieu
e5608680ec feat: technical release (#7787) 2024-06-28 09:49:00 +02:00
Julien Fontanet
6ee7a274d9 Revert "feat(backups): enable Change Block Tracking Backups (#7750)"
This reverts commit 2db21d3086.
2024-06-27 23:00:22 +02:00
b-Nollet
ffc93250f0 feat(xo-server): add progress to RPU and RPR XO-task (#7768) 2024-06-27 22:01:41 +02:00
b-Nollet
52b27ae884 fix(backups): fix delete first on incremental replication (#7739) 2024-06-27 21:39:20 +02:00
b-Nollet
f5cee45c02 feat(backup-reports): mjml template (#7747) 2024-06-27 21:36:59 +02:00
Julien Fontanet
818a5b7f5b fix(xapi/VM_snapshot): sets ignoreVbds to undefined
Introduced by 37a0d9da7

Fixes https://xcp-ng.org/forum/post/79317
2024-06-27 18:23:40 +02:00
Julien Fontanet
19a2f4e002 fix(xapi/VDI_export): destroy NBD task after export
Introduced by 2db21d308

Fixes https://xcp-ng.org/forum/post/79129
2024-06-27 18:20:42 +02:00
Mathieu
83920589db fix(xo-server-sdn-controller): install CA certificate on pools after plugin config update (#7755) 2024-06-27 17:25:48 +02:00
MlssFrncJrg
bc1db9960a fix(xo-web): correct link for the pool secure boot setup (#7783) 2024-06-27 16:54:56 +02:00
Julien Fontanet
5bf1aea761 fix(xo-server/test.createTask): various fixes
Initialize progress and correctly update name
2024-06-27 16:20:26 +02:00
Julien Fontanet
dcf6885dfa feat(xo-server): support [NOSNAP] tag on disks
Fixes https://xcp-ng.org/forum/post/79179
2024-06-27 15:37:56 +02:00
Julien Fontanet
37a0d9da7c feat(xapi/VM_snapshot): ignoredNoBakVdis → ignoredVdisTag
This new param is more flexible and permits the use of other tags.
2024-06-27 15:24:19 +02:00
Mathieu
abcbeb8719 feat: technical release (#7781) 2024-06-27 10:01:22 +02:00
Thierry Goettelmann
8db808a415 feat(web-stack): better layout system (#7777)
- `[core]` Created a StateHero component
  - `[core]` Created a `ComingSoonHero` component
  - `[core]` Created a `LoadingHero` component
  - `[core]` Created an `ObjectNotFound` component
- `[core;lite]` Moved `object-not-found` translation from Lite to Core
- `[core]` Updated `CoreLayout` to remove panel-related code
- `[web]` Renamed `DefaultLayout` to `AppLayout`
- `[web]` Wrap `RouterView` with `AppLayout` in `App.vue`
- `[web]` Removed all slots from `App.vue` but `default` one
- `[web]` Created `web/pages/vm/[id].vue` which handle layout around all `/vm/*` pages
2024-06-27 09:22:22 +02:00
Julien Fontanet
9383306231 fix(xo-server/rest-api): remove incorrect comma operator
Introduced by 800655a9a

Fortunately this did not have any impact.
2024-06-26 14:07:54 +02:00
Julien Fontanet
3235963cfb fix(xo-web/host/advanced): use relative path
This is required to avoid breaking reverse proxies with a preppended path.
2024-06-25 16:40:33 +02:00
Julien Fontanet
b393d72eb9 fix(xo-web/host/advanced): fix netdata link
Fixes #7702
2024-06-25 16:14:35 +02:00
Julien Fontanet
cd2942af71 chore(xo-server-netbox): unify unload handling 2024-06-25 09:03:26 +02:00
Julien Fontanet
e92cb66954 chore(xo-server-netbox/configure): use state.loaded 2024-06-25 09:03:26 +02:00
Pierre Donias
dac0b20a19 fix(xo-server-auth-ldap): do not use syntax "key=value" when key is "DN" (#7759)
See Zammad#25659
2024-06-24 17:19:29 +02:00
Julien Fontanet
0fc5d60791 feat(scripts/normalize-packages): licenses default to ISC for @vates/ 2024-06-24 17:12:17 +02:00
Julien Fontanet
2df144d43d feat(scripts/new-pkg): create a new package in the monorepo 2024-06-24 17:12:17 +02:00
Mathieu
758e3c47b8 fix(xo-web/VM/advanced): fix 'firmware not supported' for UEFI (#7767)
Introduced by 68e99a2
See https://xcp-ng.org/forum/topic/8878/uefi-firmware-not-supported
2024-06-24 14:58:23 +02:00
Mathieu
cfcc78b6d8 feat(xo-web/XOSTOR): column that links to SR (#7764) 2024-06-24 14:38:11 +02:00
Julien Fontanet
5f5580925d docs(xen-api): more CLI examples 2024-06-24 11:29:38 +02:00
Mathieu
a667e7cdbd fix(xo-web/XOSTOR): fix approximate SR capacity (#7765)
Introduced by 038d230
2024-06-24 10:53:45 +02:00
b-Nollet
a14c4bbb1c feat(xo-server/rest-api): add rolling pool reboot action (#7761) 2024-06-21 15:46:37 +02:00
Florent BEAUCHAMP
2db21d3086 feat(backups): enable Change Block Tracking Backups (#7750)
Enable CBT for all backups

* enable cbt each time a VM should be backuped by nbd
* if there are no rolling snapshot on a job => the snapshots data are only kept during the transfer. Snapshot data deletion is behing a config flag 'purgeSnapshotData' (default false )

adverse effect:

* rolling back to previous XO version will make full backups
* removed read ahead in nbd , performance gain was low with concurrency and it make code more complex
2024-06-21 11:32:37 +02:00
MlssFrncJrg
6b1036f5ce feat(xo-web): display SCSI id during creation of SR (#7742) 2024-06-21 10:59:31 +02:00
Florent BEAUCHAMP
5cc6b4fe70 feat(xo-web): add CBT to the disk tab (#7732)
show a toggle to enable CBT from the VM disk tab 

---------

Co-authored-by: Mathieu <70369997+MathieuRA@users.noreply.github.com>
2024-06-21 10:36:20 +02:00
Florent BEAUCHAMP
442dd16813 feat(xo-server): disable cbt on vdi/VM/host migration (#7756)
Change Block Tracking (CBT) is blocking VDI migration from one storage to another
The migration errror is computed at the VM level on the xapi, that mean that we need to disable CBT for any disk of the VM that use a VDI
We don't enable CBT after the migration : any client / backup software that need this will need to enable it on demand, and a migration force a full transfer
 host.evacuate don't move the vdi ,it can't generate a VDI_CBT_ENABLED error
2024-06-21 10:23:41 +02:00
Mathieu
6e000af7cb fix(xo-server/XOSTOR): remove callAsync workaround (#7723) 2024-06-21 09:50:17 +02:00
Julien Fontanet
3c13e0ab43 fix(CHANGELOG): move latest badge
Introduced by 7b8a4e720
2024-06-20 17:18:03 +02:00
Julien Fontanet
7b8a4e7209 feat: release 5.95.1 2024-06-20 17:16:26 +02:00
Pierre Donias
c63d2ab00b fix(netbox): force xo-server to use at least http-request-plus@1.0.3 (#7758)
See Zammad#24458

`xo-server-netbox` uses `response.text` which has been added in
http-request-plus@1.0.3. Therefore, the dependency version in `xo-server` needs
to be `^1.0.3` to ensure that `response.text` can be used.
2024-06-20 17:02:20 +02:00
Florent BEAUCHAMP
ba7a29eb5e docs(backups): nbd concurrency and full on migrate (#7741) 2024-06-20 15:36:23 +02:00
Paloma
304b06ca97 feat(xo-core): add card subtitle ui component (#7731) 2024-06-20 15:13:04 +02:00
Julien Fontanet
0661fbae78 feat(xo-server): 5.145.0 2024-06-20 15:08:00 +02:00
Julien Fontanet
16ae1c0d4b fix(lite/stories/card-title): fix imports order
Introduced by 9c29046a8
2024-06-20 14:38:46 +02:00
Paloma
9c29046a88 feat(xo-core): add card title component to web core (#7721) 2024-06-20 11:41:20 +02:00
Julien Fontanet
bb0fd2898e fix(xo-server/rest-api): handle collection without getObject
For instance `/backup/vms` would trigger an *Internal Error*, now it correctly returns 404.
2024-06-20 11:27:14 +02:00
Thierry Goettelmann
276d920cca feat(web-stack): add new i18n guidelines + ESLint + update and fixes (#7753)
- Add new i18n guidelines
- Remove nested keys: `"foo": { "bar": "..." }` ➡️ `"foo.bar": "..."`
  This makes code cleaner (and as a side-effect, it now allows to use `foo` + `foo.bar` keys)
- Lite/6 shared translations are no longer prefixed with `core.`
- Duplicated keys across translation files are no longer accepted.
  This will prevent accidental overwrite of missing common translations
- `fr.json` and `en.json` now required to contains the same keys
- Raw text is no longer accepted in template to prevent forgetting a translation
  Raw text is still accepted in dev-related files (stories, dev pages...)
- Remove useless `alt` on `img`
- Display of percentage now uses `$n(value, 'percent')`
- Removed legacy `lite/src/i18n.ts`
- Add missing translations
- Added visual indicator for missing translations while in dev env: `🌎❗⟨i-am-missing⟩`
2024-06-20 09:35:36 +02:00
Thierry Goettelmann
dd8ad3f274 chore(web stack): add new ESLint Vue rules #6 (#7738)
Enforces import order via the `import/order` ESLint rule
2024-06-20 09:18:15 +02:00
Florent BEAUCHAMP
7ddd579818 fix(backups): EEXIST error when restarting mirror backup (#7694)
feat(backup): remove temporary VHD before backups run

- cleanVm can only delete files when backup job is successful
- an interrupted transfer leave the temp file in place
- the temp file don't change name in a mirror backup since we transfer exactly the same file
- outputStream method use, by default, a flag that create the file if not exists and fail otherwise
    => EEXIST error

- This PR change the behaviour of cleanVM to delete temporary files on the pass before the transfer, thus removing any dot file

---------

Co-authored-by: Julien Fontanet <julien.fontanet@isonoe.net>
2024-06-20 08:24:48 +02:00
Paloma
290f510843 feat(xo-core): add card ui component (#7710)
This component can contain other components and manage their layout.
It can align elements vertically or horizontally.
2024-06-19 11:47:34 +02:00
OlivierFL
c134bbaee3 fix(lite/Dashboard): add missing translations and remove hardcoded values (#7744) 2024-06-18 09:42:27 +02:00
Thierry Goettelmann
e1dd5354a1 chore(web stack): add new ESLint Vue rules #5 (#7736) 2024-06-17 16:56:47 +02:00
Thierry Goettelmann
070258d0e3 chore(web stack): add new ESLint Vue rules #4 (#7734) 2024-06-17 16:48:06 +02:00
Julien Fontanet
f5a49b6da3 fix(xo-server/_xcpUpdate): callAsync result is already parsed
Fixes zammad#26054

Introduced by 08dc33bc9
2024-06-17 10:55:39 +02:00
Florent BEAUCHAMP
a85f44d699 Refactor(backups): move metadata to VDI (#7648)
Preparing for CBT backup that will only keep a part of the VDI snapshot, and remove the VM

* extract most of the other_config operation to a file as constants/methods
* name the constants
* set the relevant metadata values to the vm AND all its attached VDIs
* use the VDI as the base to search and update snapshots/replica/backup
2024-06-17 09:19:46 +02:00
Julien Fontanet
31461118e0 chore: update to turbo@2 2024-06-14 11:43:38 +02:00
Thierry Goettelmann
306a58e412 fix: missing routes in typed-router.d.ts + ignore it from Prettier (#7746) 2024-06-14 10:10:33 +02:00
Julien Fontanet
32448cfa1b feat(task): info, set and warning are now instance methods
Info, properties and warnings should be set on a specific/known task instead of relying on the async context.

For instance, this permits updating the progress of a task from the code on one of its subtasks.
2024-06-13 17:34:34 +02:00
Julien Fontanet
3dde723e31 feat(xo-server/api): no tasks for ignored methods
Follows 33a388193
2024-06-13 16:21:17 +02:00
Julien Fontanet
be5a523b95 feat(xo-server-transport-email): sendEmail() now accepts HTML 2024-06-13 14:56:28 +02:00
Thierry Goettelmann
ad18a860f3 chore(web stack): add new ESLint Vue rules #3 (#7729)
Add `vue/define-macros-order` ESLint rules to ensure consistency across components.

Macros will require to be defined in the following order:
1. `defineOptions`
2. `defineProps`
3. `defineEmits`
4. `defineModel`
5. `defineSlots`

Also ensures `defineExpose` to be the last statement of `<script setup>`
2024-06-13 14:06:26 +02:00
Julien Fontanet
be2efce595 fix(xo-server-transport-email): set text to markdown even if html is defined 2024-06-13 11:23:08 +02:00
Pierre Donias
4295dad3ac fix(web-core): devDependencies → dependencies + peerDependencies (#7707)
web-core is published as a library so its dependencies need to be installed
when web installs its own dependencies

- packages that are (or will be) used by lite and web are declared as
  dependencies in lite and web and as peerDependencies in web-core
- other packages that are only used in web-core are declared as dependencies in
  web-core
2024-06-13 10:42:24 +02:00
Julien Fontanet
cdf902bdf3 chore(.prettierignore): explain why Handlebar is ignored
Introduced by 3f4ca1b32
2024-06-12 15:45:00 +02:00
Thierry Goettelmann
97dd211d0f fix: prettify script must respect .prettierignore (#7745)
Specifying `--ignore-path` option overrides the default value of `[.gitignore, .prettierignore]`.
2024-06-12 15:43:54 +02:00
Paloma
0f11e4c4c4 feat(web-core): add UI separator component (#7722)
This component is a grey line (hr). When its parent is a flexbox with
`flex-direction: column`, it is horizontal, and with `flex-direction: row`,  it
is vertical.
2024-06-12 09:49:55 +02:00
Julien Fontanet
33a388193c feat(xo-server/api): pending/failed calls have an associated tasks 2024-06-11 10:36:29 +02:00
MlssFrncJrg
bed18fc954 feat: open external links in a new tab (#7695) 2024-06-11 10:20:46 +02:00
Thierry Goettelmann
1d8385e1f0 chore(web stack): add new ESLint Vue rules #2 (#7728)
Add `vue/define-emits-declaration` ESLint rule to ensure consistency in `defineEmits` syntax across the components.

It uses the new Vue 3.3+ more succinct syntax.
2024-06-11 09:12:41 +02:00
Thierry Goettelmann
11fbbf5158 chore(web stack): add new ESLint Vue rules (#7727) 2024-06-10 16:54:23 +02:00
Olivier Lambert
209dddda2b docs(roadmap): update our roadmap page (#7709) 2024-06-10 12:54:51 +02:00
Julien Fontanet
7f0984aff0 feat(xo-server/proxy.updateApplianceSettings): support for register and xoaPassword 2024-06-10 10:56:04 +02:00
Julien Fontanet
3f32f9981e chore(xo-server/updateProxyAppliance): simplify code 2024-06-10 10:56:04 +02:00
OlivierFL
faf54e18a8 feat(lite/web-core): add examples for tree-view components stories (#7532) 2024-06-10 09:56:45 +02:00
Pierre Donias
f70f4ea0e6 feat(netbox): support both content_type (Netbox <4) and object_type (Netbox 4) fields
Fixes #7645
See Zammad#24458
See Zammad#25690
See Zammad#24821
See #7733
2024-06-10 09:50:38 +02:00
Pierre Donias
c346cadf24 feat(netbox): explicit supported Netbox versions 2024-06-10 09:50:38 +02:00
Dan Pollak
00c8f37e73 docs: Storage Management (#7674) 2024-06-10 09:02:27 +02:00
Dan Pollak
ee6fa67246 docs: continous → continuous (#7466) 2024-06-07 13:10:25 +02:00
Julien Fontanet
691f36a223 fix(proxy-cli): timeout with long calls
Similar to 29d39a576
2024-06-06 16:38:55 +02:00
Julien Fontanet
3c41d612d0 feat(xo-web/proxies): refresh upgrade status every 30s 2024-06-06 13:58:09 +02:00
Julien Fontanet
0294c3dd68 fix(xo-web/proxies): fetch upgrades even if fetching licenses failed 2024-06-06 13:57:30 +02:00
Dan Pollak
6c25d9e147 docs(incremental_backups): fix XO 5.81 blogpost link (#7725) 2024-06-05 23:07:45 +02:00
Florent BEAUCHAMP
58cc1de804 fix(vmxware-explorer): vsan detection (#7717)
Fixes zammad#25110

Introduced by 2d07fdcd02
2024-06-05 16:37:35 +02:00
b-Nollet
3f4ca1b322 feat(backup-reports): rewrite with Handlebars (#7543) 2024-06-05 15:47:06 +02:00
Julien Fontanet
99e44fe139 chore(xo-web): rename proxyApplianceUpdaterState
The subscription is for a single proxy, not all of them.
2024-06-05 09:22:53 +02:00
Julien Fontanet
b87bc29f0e fix(fuse-vhd): handle readRawData failure (#7718)
The bug may leads to blocked mount.
2024-06-04 16:33:36 +02:00
Mathieu
f6672a643f feat(XO6/core): creation of the interactive UiTable (#7677) 2024-06-03 15:53:47 +02:00
OlivierFL
2a2fe93476 fix(xo-6/tree-view): show halted VMs attached to a pool (#7683)
Introduced by 796bb60
2024-06-03 15:19:00 +02:00
Paloma
93303ca7ac feat(xo-core): add stacked bar UI component (#7676) 2024-06-03 14:28:38 +02:00
Julien Fontanet
3a211524cb docs(installation): request Node LTS
XO is no longer stuck on Node 18.
2024-06-03 10:50:40 +02:00
Paloma
1bba37adb1 feat(xo-core): add card number ui component (#7666) 2024-06-03 09:34:43 +02:00
Paloma
8c4fcc43d2 feat(xo-core): add legend UI component (#7675) 2024-06-03 09:22:56 +02:00
Paloma
e20e3526a5 feat(xo-core): add Legend title UI component (#7678) 2024-06-03 09:17:14 +02:00
Paloma
17fec5876c feat(xo-core): add multi color donut chart ui component (#7661)
SVG based donut chart
2024-06-03 09:16:47 +02:00
Pierre Donias
4cf03cf0a2 feat(lite): 0.2.3 (#7715) 2024-05-31 17:51:06 +02:00
Pierre Donias
57e44734e2 feat: release 5.95.0 (#7714) 2024-05-31 17:39:15 +02:00
Pierre Donias
3075ce9232 feat: technical release (#7713) 2024-05-31 17:22:13 +02:00
Florent BEAUCHAMP
196f3969eb fix(xo-server,vmware-explorer): template is not defined (#7712) 2024-05-31 17:09:49 +02:00
Julien Fontanet
d635de0a96 fix(scripts/gen-deps-list.js): handle dev dependencies 2024-05-31 15:59:52 +02:00
Julien Fontanet
d1e3d79bf8 feat(scripts/gen-deps-list.js): add web & lite
They are applications and must be bumped if any of their deps is bumped.
2024-05-31 15:59:52 +02:00
Pierre Donias
5adec6b2ef feat: technical release (#7708) 2024-05-31 15:59:20 +02:00
MlssFrncJrg
b2b6262aea fix(xo-server,xo-web/pci): allow force reboot host after enabling/disabling PCI passthrough (#7687)
See https://xcp-ng.org/forum/topic/9043/xo-enable-pci-devices-for-pass-through-should-it-work-already-or-not-yet/14
2024-05-31 15:16:47 +02:00
Florent BEAUCHAMP
82d3ec6033 fix(xo-server-perf-alert): improve performances and reliability (#7692)
* make one query per host instead of one per object (VM or host ) per counter ( Memory/CPU)
* fix(xo-server-perf-alert): handle VM without guest tools that does not have memory used metrics
---------

Co-authored-by: b-Nollet <150058367+b-Nollet@users.noreply.github.com>
2024-05-31 14:52:34 +02:00
OlivierFL
3dd5a6ba13 feat(xo-6/vm): add console view (#7693) 2024-05-31 14:23:24 +02:00
Pierre Donias
0215ac2491 fix(yarn.lock): after adding dependency (#7706)
Introduced by 3044a42
2024-05-31 13:43:31 +02:00
Pierre Donias
3044a429cc fix(web-core): missing dependency @fontsource/poppins (#7705) 2024-05-31 11:02:32 +02:00
Florent BEAUCHAMP
53ee6988db feat(xo-server): improve error handling in v2v (#7654)
* use one defer per Vm import
*  improve error lisibility by extracting relevant values
2024-05-31 10:05:01 +02:00
b-Nollet
89950e77f4 fix(xo-server): handle unknown ref in task.other_config.applies_to (#7685) 2024-05-31 09:20:38 +02:00
Julien Fontanet
800655a9a3 fix(xo-server/rest-api): host logs are in tar+gzip
Fixes #7703
2024-05-31 09:12:30 +02:00
Florent BEAUCHAMP
59fbb3ef48 feat(v2v): select template before import (#7566)
Ask user for the base template to use when importing VM
2024-05-31 09:06:17 +02:00
Florent BEAUCHAMP
9133e32dd0 fix(backups/forkStreamUnpipe): unpause stream after unpipe (#7669)
when a backup job does delta replication + backup and the replication
fails to find the base VM, the backup job does not proceed

This commit force the stream to continue to flow after unpiping a
descendant stream
2024-05-31 08:45:33 +02:00
b-Nollet
cb6df6d155 fix(xo-server): don't use stats that were cached exactly one step ago (#7697)
Update stats instead of using cache when cache is exactly one step old. It avoids the stats to be twice the same in a row when fetching it every step.

Introduced by 8347ac6ed8
2024-05-31 08:37:40 +02:00
b-Nollet
20f398398f feat(xo-server-load-balancer): adding option to balance hosts before CPU reaches threshold 2024-05-31 08:36:43 +02:00
b-Nollet
c4d1dee6fd fix(xo-server-load-balancer): execute vCPU-prepositionning separately for each pool 2024-05-31 08:36:43 +02:00
b-Nollet
93627a794c fix(xo-server-load-balancer): don't do vCPU-prepositionning when one host is overloaded 2024-05-31 08:36:43 +02:00
b-Nollet
bfb2442df1 fix(xo-server-load-balancer): some migrations were not awaited 2024-05-31 08:36:43 +02:00
Florent BEAUCHAMP
24d584e905 fix(xo-server): dispose stream properly after v2v import (#7655) 2024-05-31 08:18:10 +02:00
MlssFrncJrg
882facd246 feat: technical release (#7704) 2024-05-30 17:41:04 +02:00
Pierre Donias
4c37be5b31 fix(web): missing prepublishOnly script (#7701) 2024-05-30 16:04:16 +02:00
Pierre Donias
5811a2ede2 chore(web-core): configure package to be published on NPM (#7700) 2024-05-30 14:50:28 +02:00
MlssFrncJrg
8fb918a64d feat: technical release (#7699) 2024-05-30 13:15:38 +02:00
MlssFrncJrg
682198df42 feat(xo-server,xo-web/pool): add migration compression (#7642) 2024-05-29 17:27:00 +02:00
Julien Fontanet
de9288b432 docs(xo-server/REST API): ova extension for VM OVA export
Introduced by acc44f0ce
2024-05-29 17:20:04 +02:00
Julien Fontanet
42d58e9dbf feat(obfuscate): new lib to obfuscate sensitive data
Code was extracted from xo-server and xen-api.
2024-05-29 16:58:54 +02:00
Julien Fontanet
d55f614540 fix(backups): handle migration blocked by a previous run
Fixes https://xcp-ng.org/forum/post/77857
Fixes zammad#25131
Fixes zammad#25443
2024-05-29 16:54:27 +02:00
Julien Fontanet
61ecc85beb feat(xapi): no longer export extractOpaqueRef()
BREAKING CHANGE

It's no longer necessary now that task results are properly parsed.
2024-05-28 11:42:57 +02:00
Julien Fontanet
08dc33bc9d feat(xen-api/watchTask): returned parsed result instead of XML-RPC
BREAKING CHANGE

Also impacts `getResource()` and `putResource()`.
2024-05-28 11:42:57 +02:00
Pierre Donias
e23f649e74 feat(xo-web/tasks): wait 10 seconds before estimating end of task (#7691)
Fixes #7689
2024-05-28 10:27:42 +02:00
OlivierFL
f0cd83e3c4 feat(xo-6/account-menu): add menu items (links to doc and support) (#7662) 2024-05-28 09:13:23 +02:00
OlivierFL
136ea23199 feat(xo-lite): add german translation (#7686)
See #7284
2024-05-28 09:11:17 +02:00
b-Nollet
73ea4fa6b4 fix(fs,xo-server): remote encryption algorithm was private (#7638)
Encryption was not showing up in remote page because the remote mixin was using an attribute that was private. See https://help.vates.tech/#ticket/zoom/24076

Introduced by 86ddb8f2d9
2024-05-27 09:18:51 +02:00
Pierre Donias
990c23b8f5 feat(xo-web/render-xo-item/Vm): yellow icon next to VM name when VM is busy (#7680)
Fixes #7593
2024-05-23 14:24:24 +02:00
Pierre Donias
5fca2f0c74 feat(lite/header): add a link to XOA when detected on the pool (#7679)
When XOA is connected to a pool, it adds information about itself on the pool's
other_config. We can use that in XO Lite to find the XOA's IP address and add a
link to open Xen Orchestra.
2024-05-23 13:32:24 +02:00
Thierry Goettelmann
5cd9e50fa3 feat(web-core/tree): tree filter composable (#7536) 2024-05-23 09:44:00 +02:00
Paloma
18a2b5b549 feat(xo-core): add status pill UI component for backup issues (#7631) 2024-05-23 09:15:30 +02:00
Thierry Goettelmann
da9b9419ae feat(web-core/tree): enhance tree library (#7534)
- Context now stores the selected/expanded/active IDs instead of the `TreeNode` to allow easy manipulation and initial hydration.
- Rename `activable` option to `selectable`
- Rename `labelClasses` to `statuses`
- Add `hasChildren` on `Branch`
- `predicate` now takes the `TreeNode` as argument instead of `TData`
- Add a `meta` option, to pass additional arbitrary data
2024-05-22 17:07:44 +02:00
Olivier Lambert
8c599b0bad docs(advanced): fix old Terraform provider links (#7673) 2024-05-22 15:47:11 +02:00
Julien Fontanet
1ded79169b chore: format with Prettier 2024-05-22 15:42:14 +02:00
Thierry Goettelmann
bea123f347 feat(lite): update stores to use new core subscribable stores (#7602)
Update XO Lite stores to use the new subscribable store system from Web Core
2024-05-22 15:08:55 +02:00
Julien Fontanet
88fb977db9 feat(xo-cli): --url allows bypassing registration
Related to zammad#25158
2024-05-22 12:11:06 +02:00
Julien Fontanet
b48e16c316 feat(xo-cli): use subcommand syntax
This change allows to properly separate options and subcommands,
which opens the door to global options.

Previous syntax is still supported for now but will show a deprecation
message.

```sh
xo-cli --register
xo-cli --createToken

xo-cli register
xo-cli create-token
```
2024-05-22 12:10:37 +02:00
Julien Fontanet
a8ffa91d41 docs(infrastructure): update xo-cli usage 2024-05-22 12:10:16 +02:00
Mathieu
6a67944112 feat(xo-web/XOA/licenses): handle new XOSTOR licenses (#7573) 2024-05-22 11:17:43 +02:00
Mathieu
4908982263 feat(lite/pool/dashboard): remove DVDs from the Storage usage card (#7670) 2024-05-17 14:28:08 +02:00
b-Nollet
6444f88662 chore(xo-server-backup-reports): remove unused legacy handler (#7665) 2024-05-16 16:16:31 +02:00
Pierre Donias
644654c7eb feat(lite/release): add CHANGELOG.md to GitHub release (#7667) 2024-05-16 15:21:00 +02:00
Thierry Goettelmann
5629bbef05 fix(xo-6): rename dynamic pages (#7668)
Fix dynamic pages name: `/:id.vue` => `/[id].vue`
2024-05-16 14:52:52 +02:00
Mathieu
a627fe31e2 fix(XO6/LogoTextOnly): fix color (#7664) 2024-05-16 11:53:54 +02:00
Julien Fontanet
2534d9ff38 chore: update deps 2024-05-16 11:16:18 +02:00
Julien Fontanet
13863beed8 chore: update dev deps 2024-05-16 10:48:44 +02:00
Thierry Goettelmann
796bb60b62 feat(web6): subscribable stores, REST API fetching, infra tree view (#7600)
- Implement subscribable store on XO 6
- Fetch data from REST API
- Display infrastructure tree view
- Updated README
- Added dev pages:
  - **index.vue**: empty page for component development
  - **token.vue**: form to register or clear a token cookie
2024-05-16 09:57:17 +02:00
b-Nollet
7882debe7a feat: release 5.94.2 (#7660) 2024-05-15 11:07:44 +02:00
b-Nollet
548a2e8141 feat: technical release (#7659) 2024-05-15 10:15:11 +02:00
Mathieu
63f89bb6af fix(xo-server/XOSTOR): license.tags can be undefined (#7657)
Co-authored-by: mathieu <mathieura@vates-mathieu.home>
2024-05-15 09:15:46 +02:00
b-Nollet
ae1043a89a feat: technical release (#7656) 2024-05-14 16:58:52 +02:00
Julien Fontanet
c272c9ef2d fix(CHANGELOG.unreleased): add missing backups
Introduced in 7513b90c5
2024-05-14 16:42:02 +02:00
Julien Fontanet
7513b90c5c fix(backups): block migration of whole VM during backup
Related to zammad#16108

Introduced by b00cf1302
2024-05-14 15:57:27 +02:00
Julien Fontanet
ef3ade72e4 fix(xo-server/rest-api): wait for task change when wait is NOT undefined
Introduced by 6c1a1307c
2024-05-14 15:24:28 +02:00
Florent Beauchamp
152c4e69c8 changelog 2024-05-14 15:10:46 +02:00
Florent Beauchamp
60286ba0d8 fix(vmware-explorer): handle new lines in fetchProperty 2024-05-14 15:10:46 +02:00
Thierry Goettelmann
f7664b317e feat(web-core): add helper to create subscribable stores (#7599)
Add a helper which allow to easily create a subscribable store (store that return its context only after calling a subscribe method and handle what happen on subscribe/unsubscribe)

App-agnostic, to be used with XO Lite and XO 6.
2024-05-14 14:42:44 +02:00
Mathieu
89ea175de1 fix(xo-server/XOSTOR): throw clearer error if attempt to create multiple trials (#7649) 2024-05-14 14:18:15 +02:00
OlivierFL
adbae95027 feat(xo-6): handle logout (#7635) 2024-05-14 11:13:43 +02:00
OlivierFL
ffeca2a621 feat(xo-core): add design system version info in components (#7633) 2024-05-14 10:31:46 +02:00
Julien Fontanet
9d122f9e9a fix(CHANGELOG.unreleased): add missing xen-api
Introduced in 859920e6d
2024-05-13 14:58:34 +02:00
Julien Fontanet
859920e6d7 fix(xen-api/{get,put}Resource): avoid unhandled rejection on task result 2024-05-13 14:45:26 +02:00
Julien Fontanet
703d32ce7c chore(xen-api/_sessionCall): remove unused timeout param 2024-05-13 11:05:42 +02:00
Julien Fontanet
acc44f0cea feat(xo-server/rest-api): OVA support for VM export 2024-05-13 09:01:37 +02:00
b-Nollet
9b9c71a80f fix(xo-server): RPU failed to install host patches (#7640) 2024-05-09 13:04:38 +02:00
Julien Fontanet
902f0b1957 fix(xo-server/proxy-console): falling back to legacy is not a debug 2024-05-07 17:12:40 +02:00
Julien Fontanet
29f6f92ad3 fix(xo-server): console support via HTTP proxy
Introduced by e8c9e620c

Fixes https://xcp-ng.org/forum/post/76935
2024-05-07 17:12:01 +02:00
Pierre Donias
771b04acc4 fix(CHANGELOG): packages versions (#7639)
Introduced by 5d5dc9891f
2024-05-06 17:18:07 +02:00
MlssFrncJrg
d3ab7927af feat: release 5.94.1 (#7637) 2024-05-06 15:42:04 +02:00
MlssFrncJrg
5d5dc9891f feat: technical release (#7636) 2024-05-06 14:40:27 +02:00
MlssFrncJrg
1ab62d4762 feat(xo-web/xostor): ability to copy VDI UUID in the resources table (#7629) 2024-05-06 14:10:43 +02:00
MlssFrncJrg
b9a6651f88 feat(xo-web/XOSTOR): warn if replication count is higher than number of hosts with disks (#7625) 2024-05-06 11:39:25 +02:00
OlivierFL
960cf6632d feat(xo6/core): update chip component to v1.1.0 (#7632)
Update the `ChipRemoveIcon` component by replacing the `faCircleXmark` icon with
the `faXmark` icon to match version 1.1.0 from the design system.
2024-05-06 11:23:59 +02:00
Mathieu
da48a396ff fix(xo-server,xo-web/XOSTOR): do not require XCP-ng licenses (#7628) 2024-05-06 10:34:51 +02:00
Mathieu
ed66635b89 fix(xo-web/XOSTOR): handle older versions of linstor plugin (#7630)
Fixes "an error has occurred" in XOSTOR resource list
2024-05-06 10:07:10 +02:00
Pierre Donias
e3d718f1f7 fix(xo-web/pool): only show pool SRs in default SR selector (#7626)
Introduced by #7451
2024-05-06 09:20:51 +02:00
Pierre Donias
3a44b6d777 feat(lite): 0.2.2 (#7624) 2024-04-30 17:05:32 +02:00
Julien Fontanet
6870e067a0 fix(xo-server): use callAsync to install XCP-ng updates
Fixes https://team.vates.fr/vates/pl/bkqyatm7w3yquyw1o9watx3c1o
2024-04-30 16:26:42 +02:00
MlssFrncJrg
507949330f feat: release 5.93.1 (#7623) 2024-04-30 16:17:21 +02:00
OlivierFL
a6a63325cc feat(lite): update tree-view with new components (#7531) 2024-04-30 15:24:56 +02:00
MlssFrncJrg
22bd7ce8d4 feat: technical release (#7620) 2024-04-30 15:07:02 +02:00
Mathieu
dad34e9027 fix(xo-server/XOSTOR): fix install dependencies (#7619) 2024-04-30 14:46:08 +02:00
MlssFrncJrg
c280aecd33 feat: technical release (#7618) 2024-04-30 13:07:18 +02:00
Julien Fontanet
dd0afe3b02 feat(bump-pkg): prevent bump if package.json has modifications 2024-04-30 11:51:30 +02:00
b-Nollet
a8118ec7dd feat(xo-server): implement XO tasks for RPR and RPU (#7578) 2024-04-30 10:33:25 +02:00
MlssFrncJrg
63ebfce3f2 feat(xo-web/new VM): warn if pool is not setup for secure boot (#7562)
Fixes #7500
2024-04-30 10:08:12 +02:00
Pierre Donias
2e9c1d7c11 fix(xo-web/pool/advanced): handle undefined default SR (#7616)
Introduced by #7451
2024-04-30 09:56:17 +02:00
Florent BEAUCHAMP
3a68a61d7e fix(vmware-explorer/esxi): extent chain computation (#7617)
Introduced by bb47dafbf9

This is used to show the UI for the import using  exportVm instead of datastore access
2024-04-30 09:51:05 +02:00
Florent BEAUCHAMP
195a3f6220 fix(xo-server/vm.importFromEsxi): workDirRemote is optional even for single import (#7615)
introduced by 2d07fdcd02
2024-04-30 09:46:03 +02:00
Manon Mercier
8ed24899f5 docs: add example to the Cloud Init section (#7575) 2024-04-29 17:31:23 +02:00
Mathieu
6a31acd28b fix(xo-web/SR/XOSTOR): block access to XOSTOR tab for source users (#7611) 2024-04-29 17:02:50 +02:00
Julien Fontanet
b2e807b309 feat(xo-server/rest-api): actions can be listed without an existing object 2024-04-29 16:50:57 +02:00
Julien Fontanet
d64f5f8ab4 feat(xo-server/rest-api): GET endpoint for single action 2024-04-29 16:50:57 +02:00
Pierre Donias
264eb8166a fix(lite/release): pass "duplex: 'half'" option to tarball request (#7612) 2024-04-29 16:40:35 +02:00
Florent Beauchamp
bb47dafbf9 feat(xo-web): implement vsan import ui 2024-04-29 16:27:55 +02:00
Florent Beauchamp
2d07fdcd02 feat(xo-server): use ExportVM to import disks from vsan
must use a remote to store the temporary vmdks during imports
since they can't be parsed on the fly nor accessed by range
2024-04-29 16:27:55 +02:00
Florent Beauchamp
71e269eeb2 feat(vmware-explorer): implement VHD adapter for stream optimzed vmdk 2024-04-29 16:27:55 +02:00
Florent Beauchamp
5b0105690f feat(vmware-explorer): implement exportVm method
this method internally calls the web service method ExportVM :
 https://developer.vmware.com/apis/196/vim.VirtualMachine.html#exportVm

which can export all the disk of a stopped VM, regardless of the
datastore used,  including VSAN
The disks are exported in stream optimized VMDK , with their
grain directory ( level 1 adressing table) at the end, and
all the data are stored in comrpessed grains with markers
2024-04-29 16:27:55 +02:00
Florent Beauchamp
ed62f738ce refactor(vmware-explorer): extract fetch method 2024-04-29 16:27:55 +02:00
Florent Beauchamp
f0c92c6ed1 fix(vmware-explorer): use fetchProperty instead of search 2024-04-29 16:27:55 +02:00
Mathieu
75d37a52a1 feat(xo-web/XOSTOR): remove beta banner (#7609) 2024-04-29 16:26:57 +02:00
Mathieu
d1318e72c5 fix(xo-server): handle new XOSTOR licenses (#7558) 2024-04-29 15:45:12 +02:00
Mathieu
50938a3c23 feat(xo-web/xo-server/VM): ability to attach PCIs to a VM (#7464)
See #7432
2024-04-29 15:21:52 +02:00
Mathieu
e2a49605f1 feat(xo-web/host/advanced): list PCIs known by the XAPI (#7455)
See #7432
2024-04-29 15:06:15 +02:00
Mathieu
c32ca3d944 feat(xo-server/api): implement PCI API (#7444)
See #7432 
xen-api PR https://github.com/xapi-project/xen-api/pull/5542
2024-04-29 14:52:32 +02:00
Mathieu
08d31051de feat(xo-web/home/SR): display pro support status for XOSTOR SR (#7601) 2024-04-29 14:39:52 +02:00
Julien Fontanet
0c67610cb6 docs(troubleshooting): document stats and backup network 2024-04-29 13:55:06 +02:00
MlssFrncJrg
8946d5c149 fix(xo-server/XOSTOR): fix LVM_ERROR(5) during formatDisks (#7598) 2024-04-29 11:21:58 +02:00
MlssFrncJrg
a321398f43 fix(xo-server/XOSTOR): install or update packages on all pool hosts (#7597) 2024-04-29 11:13:45 +02:00
Florent BEAUCHAMP
da1b452540 fix(CHANGELOG.unreleased): package name (#7610)
Introduced by d64c1b8c3c
2024-04-29 11:10:00 +02:00
MlssFrncJrg
038d230d63 fix(xo-web/xostor/create): don't ignore hosts without disks when checking consistency (#7572) 2024-04-29 10:39:44 +02:00
Florent BEAUCHAMP
d64c1b8c3c fix(vmxware-explorer): handle multiple datacenter (#7553)
When downloading from the http browsing api, we need to pass the datacenter corresponding
to the datastore used
2024-04-29 10:36:38 +02:00
Florent BEAUCHAMP
2427f48618 feat(xva): improve compatibility with xenserver (#7563)
feat(xva): improve import compatibility with xenserver

* from ticket 23636
* fix error (Failure "Field last_boot_CPU_flags not present in rpc")
* fix error (Failure "(Failure "Field suspend_VDI not present in rpc"")
* fix error (Failure "(Failure "Field suspend_SR not present in rpc"")
2024-04-29 10:32:41 +02:00
MlssFrncJrg
81a125846f feat(xo-web, xo-server/XOSTOR): changes before XOSTOR official release (#7568) 2024-04-29 10:29:20 +02:00
Florent BEAUCHAMP
90eccf02e5 fix(vmware-explorer): harden vmx parsing (#7583)
from https://xcp-ng.org/forum/topic/8864/cannot-create-property-ctkenabled-on-string-true-on-trying-to-import-from-esxi/9
2024-04-29 10:27:56 +02:00
Julien Fontanet
7b7a46902e chore(mixins/Tasks): only check clearLogOnSuccess for root tasks 2024-04-26 16:56:00 +02:00
Julien Fontanet
91429406a8 feat(xo-web/subscribeXoTasks): use REST API watch mode 2024-04-26 16:56:00 +02:00
Julien Fontanet
29d39a5764 fix(xo-cli rest): remove HTTP timeout
Default 5s timeout is problematic with new tasks collection watch mode.
2024-04-26 16:56:00 +02:00
Julien Fontanet
6c1a1307c8 feat(xo-server/rest-api): watch mode for tasks
`/rest/v0/tasks?ndjson&watch`:
```
["remove",{"id":"0lv13orww","href":"/rest/v0/tasks/0lv13orww"}]
["update",{"id":"0lv13otzz","properties":{"method":"acl.getCurrentPermissions","params":{},"name":"API call: acl.getCurrentPermissions","userId":"287d34e8-e5ca-48b3-bf32-8499fa167211","type":"api.call"},"start":1713194362080,"status":"pending","updatedAt":1713194362080,"href":"/rest/v0/tasks/0lv13otzz"}]
```
2024-04-26 16:56:00 +02:00
Pierre Donias
1fa9b31076 feat(xo-web): japanese translation (#7605)
See #7582
2024-04-26 16:12:10 +02:00
Pierre Donias
f72de22d44 docs(RPU): document restartHostTimeout config (#7606)
See https://xcp-ng.org/forum/topic/8842
2024-04-26 16:02:02 +02:00
Julien Fontanet
5b41fe0842 chore(xen-api/transports): use @vates/xml{,-rpc}
Fixes #7556
2024-04-26 16:01:28 +02:00
Julien Fontanet
f8fc4aa551 feat(xml-rpc): new lib to parse/format XML-RPC 2024-04-26 16:01:28 +02:00
Julien Fontanet
798049c20e feat(xml): new lib to parse/format XML 2024-04-26 16:01:28 +02:00
MlssFrncJrg
db3c51d55e feat: technical release (#7603) 2024-04-26 11:03:01 +02:00
MlssFrncJrg
2833885638 feat(xo-web/XOSTOR): add confirmation modal informing hosts toolstack may restart (#7570) 2024-04-26 10:35:01 +02:00
Julien Fontanet
dfd58cd2e8 fix(xo-web/vm/backups): handle xo:no-bak tags
Fixes #7527
2024-04-25 15:14:09 +02:00
Mathieu
170537bd16 feat(xo-web/XOSTOR): manage XOSTOR interfaces (#7547) 2024-04-25 13:53:14 +02:00
Mathieu
a15f2ec760 feat(xo-web, xo-server/SR): list resources in a XOSTOR tab (#7542) 2024-04-25 11:23:39 +02:00
MlssFrncJrg
6dc18164d5 fix(xo-web/VM/advanced): fix error when pool is not visible by user (#7580) 2024-04-25 10:12:51 +02:00
Thierry Goettelmann
b3ea140ac0 feat(web6): new default layout (#7591) 2024-04-24 17:04:43 +02:00
OlivierFL
e5b5714f5a feat(web-core): add new ObjectIcon component (#7564) 2024-04-24 17:02:00 +02:00
OlivierFL
2b7b385da6 feat(web-core): add new ComplexIcon component (#7555) 2024-04-24 17:01:25 +02:00
Thierry Goettelmann
8229769f0d feat(web-core): new layout system (#7589)
New layout system with a default `CoreLayout` component which handle header +
resizable sidebar + content header + content + right panel header/content.
2024-04-24 10:23:04 +02:00
Thierry Goettelmann
8eb4dbb683 feat(web-stack): add i18n + keyboard shortcuts (#7590)
- Move i18n configuration from Lite to Core
- Add keyboard shortcuts (toggle lang / theme) to XO 6
- Add locales to Core
- Import Core locales into Lite and XO 6
2024-04-23 16:41:54 +02:00
Thierry Goettelmann
ed128e9b4b feat(web-core/tooltip): update TooltipItem to match design system (#7588) 2024-04-23 15:25:04 +02:00
Thierry Goettelmann
a4f7711554 feat(web-core/if-else): accept array of functions (#7586)
`ifElse` now accepts an array of functions for `onTrue` and `onFalse`
2024-04-23 15:17:25 +02:00
Thierry Goettelmann
7801c61394 feat(web6): new LogoTextOnly component (#7584) 2024-04-23 15:12:18 +02:00
Thierry Goettelmann
694b0389f3 feat(web-core): add MaybeArray and toArray (#7585)
- Add `MaybeArray` utility type
- Add `toArray` utility function
2024-04-23 14:58:15 +02:00
Florent Beauchamp
78fc882c89 fix(xo-server): really stop vm when importing
introduced by acc7b3a5a0
2024-04-23 10:18:42 +02:00
Florent Beauchamp
5e7590852f fix(vmware-explorer): wait for power off to finish
the power off call return a task, this commit wait for this task to end
2024-04-23 10:18:42 +02:00
Florent Beauchamp
a1886b466a fix(xo-server): nfs remote mount while importing from vmware
use the currently mounted and enabled remote instead of mounting them
also this respect the mount point defined in config ( /run/xo-server/mount for xoa)
introduced by bb8c3c18e6
2024-04-23 10:18:42 +02:00
Dan Pollak
3736b699ff docs(airgap): fix deployement→ deployment (#7581)
deployement >> deployment

Signed-off-by: Dan Pollak <danpollak2@gmail.com>
2024-04-23 09:11:29 +02:00
Florent BEAUCHAMP
587da7b133 fix(proxy): xapi watch VM_guest_metrics (#7574)
From zammad#21282
Introduced by 231f09de12

Health check is dependent on the updates on guest metrics to ensure VM is correctly booted
2024-04-22 11:08:00 +02:00
Dan Pollak
7b084fe0fa docs(restapi): fix dislayed → displayed (#7577) 2024-04-20 21:26:23 +02:00
John Impallomeni
b154d09a9c docs(README): use HTTPS for vates.tech link (#7576) 2024-04-20 14:00:06 +02:00
Julien Fontanet
05d975a63a feat(xo-server-audit): expose records in REST API 2024-04-19 11:45:50 +02:00
Julien Fontanet
55b28fdb77 fix(CHANGELOG.unreleased): package order
Introduced by 66915ebc5
2024-04-19 11:29:57 +02:00
Julien Fontanet
6b76a5780b feat(gen-deps-list): better order message
New message says explicitely where the incorrect package should be.
2024-04-19 11:29:57 +02:00
Julien Fontanet
54446cad3f feat(ci): ensure pkgs order in CHANGELOG.unreleased.md 2024-04-19 11:29:28 +02:00
Florent BEAUCHAMP
cc049d336e feat(vmware-explorer): trim hostname (#7560) 2024-04-18 17:23:16 +02:00
Florent BEAUCHAMP
66915ebc54 fix(@vates/node-vsphere-soap): don't use default proxy (#7551) 2024-04-18 11:29:15 +02:00
Florent BEAUCHAMP
aa6b23c06c fix(backups): use the right settings in writers (#7567)
Fixes https://xcp-ng.org/forum/post/75762
Introduced by 42dc75e739

Fix error `Error: val is not a non-empty string or a valid number. val=undefined`
2024-04-18 10:21:44 +02:00
Florent Beauchamp
164985a753 fix(xo-server): use the right limit for vdi size during import 2024-04-17 15:31:37 +02:00
Florent Beauchamp
acc7b3a5a0 refactor(xo-server): extract vmware import method to its own mixin 2024-04-17 15:31:37 +02:00
Florent BEAUCHAMP
2de0a2b32a Update @xen-orchestra/vmware-explorer/esxi.mjs
Co-authored-by: Julien Fontanet <julien.fontanet@isonoe.net>
2024-04-17 15:15:57 +02:00
Florent Beauchamp
0a7ae633e1 fix yarn.lock 2024-04-17 15:15:57 +02:00
Florent BEAUCHAMP
14d1ba5996 Apply suggestions from code review
Co-authored-by: b-Nollet <150058367+b-Nollet@users.noreply.github.com>
2024-04-17 15:15:57 +02:00
Florent Beauchamp
39bbd1a058 feat(vmware-explorer): implement fetchProperty 2024-04-17 15:15:57 +02:00
b-Nollet
4326f50121 docs(load_balancing.md): adding paragraph on vCPU balancing 2024-04-17 14:37:58 +02:00
b-Nollet
15177ace5b fix(xo-server-load-balancer): correct values of CPU and memory thresholds (density plan)
`undefined` CPU and memory thresholds were used in density plan to decide if a host can receive a VM or not, which made the host always accept it.

Introduced by 5a825bd459
2024-04-17 14:37:58 +02:00
Florent BEAUCHAMP
42dc75e739 feat(backups): make health check timeout configurable (#7561) 2024-04-16 18:15:40 +02:00
Julien Fontanet
2b73482f9e docs(airgap): deploy/upgrade processes (#7550) 2024-04-16 14:51:42 +02:00
Florent BEAUCHAMP
d217c7ba61 fix(vmware-explorer): undefined deviceType (#7545)
Introduced by 60488e6f0c

From zammad#23798
2024-04-15 17:32:12 +02:00
Thierry Goettelmann
bd7e566193 feat(web6): add missing deps and config (#7548)
Add missing dependencies and PostCSS configuration
2024-04-15 09:19:47 +02:00
Julien Fontanet
54b473c7b1 feat(xo-proxy/api): log call errors as tasks
This gives more detailed information (start, end, errors) and also adds support for subtasks.

Before:

```
2024-04-14T13:50:16.749Z xo:proxy:api WARN call error {
  method: 'test.throw',
  params: { message: 'foo' },
  error: Error: foo
      at Api.addMethods.test.throw.params.message.type (file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:227:19)
      at m (file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:253:28)
      at Api._call (file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:310:12)
      at file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:90:44
      at Zone.run (<...>/node_modules/node-zone/index.js:80:23)
      at file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:90:29
      at processTicksAndRejections (node:internal/process/task_queues:95:5)
      at compressMiddleware (<...>/node_modules/koa-compress/lib/index.js:56:5)
}
```

After:

```
2024-04-14T13:49:17.102Z xo:proxy:api WARN call error {
  id: 'qxhdytrtmvk',
  properties: [Object: null prototype] {
    method: 'test.throw',
    params: { message: 'foo' }
  },
  start: 1713102557102,
  status: 'failure',
  end: 1713102557102,
  result: Error: foo
      at Api.addMethods.test.throw.params.message.type (file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:227:19)
      at file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:264:39
      at AsyncLocalStorage.run (node:async_hooks:346:14)
      at Task.runInside (<...>/@vates/task/index.js:175:41)
      at Task.run (<...>/@vates/task/index.js:158:31)
      at m (file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:264:29)
      at Api._call (file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:327:12)
      at file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:92:44
      at Zone.run (<...>/node_modules/node-zone/index.js:80:23)
      at file://<...>/@xen-orchestra/proxy/app/mixins/api.mjs:92:29
      at processTicksAndRejections (node:internal/process/task_queues:95:5)
      at compressMiddleware (<...>/node_modules/koa-compress/lib/index.js:56:5)
}
```
2024-04-14 15:55:50 +02:00
Julien Fontanet
5e94c5ad8d feat(proxy/api): add test.throw method 2024-04-14 15:55:50 +02:00
Julien Fontanet
9e2ccc6ea3 feat(task/Task.run): optional opts, context and args can be passed
The goal is to make subtasks creation as easy as possible.
2024-04-14 15:12:05 +02:00
mathieu
2f962dd2e0 feat(xo-web/xostor): possibility to set preferred interface at creation 2024-04-12 10:00:20 +02:00
mathieu
e3f31cc141 feat(xo-server/xostor): possibility to set preferred interface at creation 2024-04-12 10:00:20 +02:00
mathieu
bfbb758341 feat(xapi/xostor): implement network API 2024-04-12 10:00:20 +02:00
mathieu
5f103e631b fix(xo-web): fix Prettier formatting issues
Introduced by c96c253568, a4c775d4ef & 44f6d2f0e3
2024-04-11 11:34:54 +02:00
Thierry Goettelmann
c0eb26fdf0 feat(web-core/utils): new ifElse utility (#7546)
Small utility that mimics VueUse's `whenever` but watches a `boolean` and
accepts a second callback to be run whenever the source is `false`.
2024-04-10 16:53:45 +02:00
Julien Fontanet
8aa421a7a6 feat: release 5.93.1 2024-04-10 12:05:46 +02:00
Julien Fontanet
919e33a4e1 fix(xo-server/upgradeProxyAppliance): clear cache at the end
Previously the cache was not clear in case of quick API upgrade.
2024-04-10 09:43:19 +02:00
Julien Fontanet
5b85a01d83 docs: adapt logs access to new appliance 2024-04-09 12:50:44 +02:00
Julien Fontanet
f8a67744a3 feat(xo-web): 5.142.1 2024-04-08 15:16:42 +02:00
Julien Fontanet
501da8b176 feat(xo-server-perf-alert): 0.3.7 2024-04-08 15:16:21 +02:00
Julien Fontanet
07019d7117 feat(xo-server): 5.140.1 2024-04-08 15:11:12 +02:00
Julien Fontanet
02971173c9 feat(@xen-orchestra/vmware-explorer): 0.6.1 2024-04-08 15:02:03 +02:00
Julien Fontanet
53dadee014 feat(@xen-orchestra/proxy): 0.27.2 2024-04-08 15:00:01 +02:00
Julien Fontanet
9517cda78f feat(@xen-orchestra/immutable-backups): 1.0.4 2024-04-08 14:59:18 +02:00
Julien Fontanet
53626c2d88 feat(@xen-orchestra/backups-cli): 1.0.17 2024-04-08 14:58:01 +02:00
Julien Fontanet
122a7d6a34 feat(@xen-orchestra/backups): 0.47.0 2024-04-08 14:56:24 +02:00
Mathieu
a4c775d4ef feat(xo-web,xo-server/RPU): RPU disabled if XOSTOR in pool (#7540) 2024-04-08 14:38:43 +02:00
Olivier Lambert
45516f7b57 docs(SECURITY.md): update with a new email domain (#7541) 2024-04-08 11:56:22 +02:00
Julien Fontanet
009c0ea6b5 fix(xo-server/vm): expose if VTPM is needed
Introduced by 8834af65f7

Introduced by 2e002de774

Fixes https://github.com/vatesfr/xen-orchestra/pull/7436#discussion_r1534257272
2024-04-08 10:08:28 +02:00
Julien Fontanet
06e9bab0dc fix(CHANGELOG.unreleased): add missing xo-server-perf-alert
Introduced in bfb8d3b29
2024-04-08 09:27:55 +02:00
Julien Fontanet
f8db103074 fix(xo-web): don't ignore case matching tags 2024-04-08 09:25:02 +02:00
Thierry Goettelmann
8013fca7e8 refactor(web-core/collection): rename collection to tree (#7533) 2024-04-08 09:23:23 +02:00
Julien Fontanet
7d849650cf fix(CHANGELOG.unreleased): fix style 2024-04-08 09:21:13 +02:00
b-Nollet
6bf7ffe356 docs: correcting load balancer docs (#7537) 2024-04-08 08:20:41 +02:00
Florent Beauchamp
74fc4dcaca refactor(xo-server/importFromEsxi): don't duplicate code 2024-04-05 16:42:18 +02:00
Florent Beauchamp
ef72eccf3e fix(vmware-explorer): ignore VM without storage
intead of stopping the VM listing if even one VM is broken
2024-04-05 16:42:18 +02:00
Florent Beauchamp
b954e42bd9 fix(xo-server/import): handle VM running without snapshot 2024-04-05 16:42:18 +02:00
Florent Beauchamp
bb8c3c18e6 fix(xo-server/import): use nfs remote also for import multiple
nfs import was only plugged on simple import, used from cli
2024-04-05 16:42:18 +02:00
Mathieu
79c9ef0f09 fix(xo-web/host/advanced): handle getSmartctlHealth error (#7525)
Fixes https://xcp-ng.org/forum/post/74221
2024-04-03 15:32:04 +02:00
Julien Fontanet
3971a9813b feat(backups/worker): log duration/CPU/memory usage 2024-04-03 14:55:24 +02:00
Julien Fontanet
0794a63b51 fix(xo-server/vm.export): body does not have a headers prop
Introduced by bfb8d3b29

Fixes https://xcp-ng.org/forum/post/74554
2024-04-02 16:44:44 +02:00
Florent BEAUCHAMP
f9ca9e7c96 feat(lite): 0.2.1 (#7526) 2024-04-02 15:57:27 +02:00
Julien Fontanet
17ed63fd58 fix(xo-server/rest-api): fix host's logs URL
Introduced by 0815d546a
2024-04-02 14:03:22 +02:00
Julien Fontanet
0815d546a2 fix(xo-server/rest-api): getResource() returns a response not a stream
Introduced by bfb8d3b29

Fixes https://xcp-ng.org/forum/post/74499
2024-04-02 12:02:19 +02:00
Florent BEAUCHAMP
8b7e1b3d7c feat: release 5.93.0 (#7521) 2024-03-29 14:02:38 +01:00
Florent BEAUCHAMP
45d068e89d feat: technical release (#7520) 2024-03-28 14:32:56 +01:00
b-Nollet
a9692cd8bc feat(xo-web, xo-server): HA setup in pool advanced tab (#7503)
Fixes #4731
2024-03-28 13:48:06 +01:00
Florent Beauchamp
88c643f0ff fix(Replication/Healthcheck): use the right xapi to start healthcheck 2024-03-28 13:39:03 +01:00
Florent Beauchamp
be426a3a67 fix(Backup/Mirror): task has already ended when running a health check 2024-03-28 13:39:03 +01:00
Florent Beauchamp
da8e98dd52 fix(backup/healthcheck): handle the case where the baseVM is not in XO cache
this can happens when the xo-server is slow to update a faster XO
xapi.getObject throw a  " no object with UUID or opaque " error during healtcheck phase
2024-03-28 13:39:03 +01:00
Julien Fontanet
7c2735c410 feat(xo-server,xo-web): split XO Proxy upgrade and force upgrade workflow
The normal upgrade is using the API call, the force upgrade is using the reboot workflow.

Also, we should not fallback to the reboot workflow automatically on API call failure
because it can be normal due to xoa-updater and/or XO Proxy being rebooted and rebooting
the VM may break the current upgrade and leave npm in an broken state.
2024-03-28 12:06:10 +01:00
Julien Fontanet
701c51faab fix(xo-server/getProxyApplianceUpdaterState): don't ignore missing API method
This error should be log as well.

It does not break anything because it does prevent from fetching the state.

It should not happen a lot because the version of XO Proxy released alongside
this version of xo-server does provide this method.
2024-03-28 12:06:10 +01:00
Julien Fontanet
62cf60a858 fix(xo-server/getProxyApplianceUpdaterState): don't debounce failures
This permits retrying getting the state ASAP.
2024-03-28 12:06:10 +01:00
MlssFrncJrg
c96c253568 feat(xo-web/templates): allow bypassing blocked operation when deleting protected template (#7493)
See Zammad#21638
2024-03-28 11:13:58 +01:00
Florent BEAUCHAMP
f26cbf4c73 feat(xo-server): s3 and encryption at rest are production ready (#7515) 2024-03-28 10:47:08 +01:00
Mathieu
707f86bbe2 feat(xo-acl-resolver): implement ACLs for PCI and USB (#7478) 2024-03-28 10:40:15 +01:00
Florent BEAUCHAMP
68f5db5804 feat(vmware-explorer): log datastore used 2024-03-28 10:10:31 +01:00
Florent Beauchamp
a1a8b5aad1 fix(xo-server): dispose remotes correctly after VMWare migration, code cleanup 2024-03-28 10:10:31 +01:00
Florent Beauchamp
3939c4f021 feat(vmware-explorer): Use NFS datastore from XO Remote to bypass VMFS6 lock
- Migrate VM to NFS on VMWare side
- mount NFS in XO , named [VMWARE]{datastoreName}
- launch import and it will read through the direct NFS instead of using the soap access layer
2024-03-28 10:10:31 +01:00
Florent Beauchamp
599dc76edb feat(vmware-explorer): one pass XVA for raw and cold deltas
Instead of doing XVA import for raw and one VHD import per delta per disk
2024-03-28 10:10:31 +01:00
Florent Beauchamp
817ae46bc5 fix(vmware-explorer): disk alignment on delta of older ESXi 2024-03-28 10:10:31 +01:00
Florent BEAUCHAMP
5d1800a4ee fix(vhd-lib): clean stream destroying (#7518) 2024-03-28 10:00:12 +01:00
Florent BEAUCHAMP
a548a95e6d feat: technical release (#7517) 2024-03-27 15:35:03 +01:00
Julien Fontanet
88dd987e09 feat(lint-staged): varlidate packages order in CHANGELOG.unreleased.md 2024-03-27 13:38:07 +01:00
Samori Gorse
44f6d2f0e3 fix(xo-web/hub/recipes/kubernetes): remove -00 suffix on version (#7492)
### Description

To merge alongside https://git.vates.tech/vates/xoa/pulls/196

This is a change to remove the addition of `-00` that was used to reference packages installed via APT. 

1.  The [new apt repository](https://kubernetes.io/blog/2023/08/15/pkgs-k8s-io-introduction/) version number do not even match this convention anymore
2. We do not install Kubernetes packages with apt anymore, we install the binaries directly. The package versions number listed in GitHub are an exact match of the various binaries version we want to install
2024-03-27 11:39:15 +01:00
Julien Fontanet
7fb1682347 fix(xen-api): move HTTP timeout to undici dispatcher
This makes all transports respecting it.
2024-03-27 11:34:57 +01:00
OlivierFL
20f06f7ce0 feat(web-core): add UserLink component (#7513) 2024-03-27 11:28:29 +01:00
Julien Fontanet
65be1adadb fix(xo-server/vm.create): fix timeout when copying template
Fixes https://xcp-ng.org/forum/post/74162
2024-03-27 11:24:26 +01:00
MlssFrncJrg
68e99a27a0 feat(xo-web/VM/advanced): add warning when selected firmware is not supported (#7442) 2024-03-27 11:06:42 +01:00
Mathieu
1be7e2acbc fix(xo-server/xostor.create): restart toolstack after installing dependencies (#7437)
Related to zammad#21209
2024-03-27 10:58:17 +01:00
Julien Fontanet
61d167a432 feat(xo-web): add OTP requires validation (#7480)
This prevents users from forgetting to add OTP account in their application and
thus being locked out from XO.
2024-03-27 10:53:07 +01:00
MlssFrncJrg
776d21bd69 feat(xo-web/console): display zoom percentage (#7452)
Fixes zammad#22418

Display zoom percentage in an editable input.
2024-03-27 10:42:51 +01:00
OlivierFL
46d74ad6a0 feat(xo6/core): update tree-view ui (#7385)
- add vertical and horizontal lines between elements
- move dropdown menu components to `web-core`
- update tree-view VM power state icon
2024-03-27 10:29:04 +01:00
Florent BEAUCHAMP
2bc69b5ec7 fix(vwmare-explorer): handle raw cdrom (#7504)
Fixes zammad#23268
2024-03-27 10:25:22 +01:00
Florent BEAUCHAMP
ed4e080b04 fix(xo-server/warmMigration): feature is for sources and premium (#7514)
Introduced by #6549 

See https://team.vates.fr/vates/pl/745krdomtff99rf8951p65rkxh

In the feature matrix of XO, warm migration is for premium (and sources) users
2024-03-27 10:21:46 +01:00
Julien Fontanet
f68e58ad53 feat(xo-web): replace numeric dates by ISO 8601 (#7484)
- international standard intended to be unambiguous and universal
- more compact than previous implementation
- alphabetic order is the same as temporal order
- easier to compare lines in a table
- uncoupled from the language selection: English speaking users are not forced to use US format
2024-03-27 09:58:17 +01:00
OlivierFL
ae780c9bb5 feat(lite): disable navigation to WiP views via tabs (#7482) 2024-03-27 09:42:27 +01:00
OlivierFL
8614add711 feat(lite/web-core): update tab component (#7481)
Update Tab component to match design system:
- use new styles from design system
- move components into `web-core`
2024-03-27 09:17:01 +01:00
Julien Fontanet
1221b23edf fix(xo-server): USB_group has multiple PSUBs/VUSBs (#7483)
Multiple objects, the fields name should be plural.

Introduced by 73ad9c1e7
2024-03-26 14:58:58 +01:00
Julien Fontanet
5da9593ce0 fix(fs/local#getInfo): fix size for huge remote
Fixes zammad#23139

Implement own parsing instead of `@sindresorhus/df` which breaks when headers
and values are not correctly aligned (i.e. big sizes).

This simple parsing should be good enough.

See @sindresorhus/df#13
2024-03-26 11:18:27 +01:00
Julien Fontanet
583c728095 fix(xen-api/putResource): fix proxy and HTTPS support (#7508)
Fixes #7477 #7490 #7496 #7499 #7502

Introduced by 6c16055a1

`putResource()` is not using `undici` for the moment because it needs
work to make it work with the work-around for chunked encoding not
supported in XAPI.
2024-03-25 16:29:36 +01:00
Julien Fontanet
7564bdb402 feat(xo-web/user): show user auth tokens last use
More important than when a token has been created is when and by how it was used last.
2024-03-25 14:50:25 +01:00
Julien Fontanet
1267112272 feat(xo-server/authentication): save token last use info 2024-03-25 14:50:25 +01:00
Pierre Donias
2692a2d3f3 fix(lite/console): prevent duplicate consoles on error (#7505)
When a console connection fails, we wait before trying again. While waiting,
the user may open another console, which creates a new VNC Client, and renders
the console. Therefore, when we're done waiting, we must first check that
`vncClient` is `undefined` so that we don't create 2 instances of `VncClient`.
2024-03-25 14:10:57 +01:00
Julien Fontanet
2fc393b907 fix(xen-api/examples/export-vdi): proxy variable 2024-03-25 13:26:34 +01:00
Julien Fontanet
69060e8af9 feat(xen-api/examples/{ex,im}port-vdi): support HTTP proxy 2024-03-25 10:23:22 +01:00
Julien Fontanet
07a4f5a535 chore(xen-api): make examples deps dev deps 2024-03-25 10:23:22 +01:00
Thierry Goettelmann
a48fc72570 feat(web-core): enhance tooltip directive (#7491) 2024-03-25 10:22:13 +01:00
OlivierFL
7c73e80591 feat(lite/core): update UiButton component (#7450) 2024-03-25 10:14:40 +01:00
OlivierFL
7f2c84d88e feat(web-core): add new chip component (#7485) 2024-03-25 10:04:22 +01:00
OlivierFL
a7f56a125c feat(web-core): move UiStore into web-core (#7486) 2024-03-25 10:03:43 +01:00
OlivierFL
42e3067f7f feat(web-core): add new tag component (#7488) 2024-03-25 09:54:28 +01:00
OlivierFL
f4a1395581 feat(web-core): move menu components to web-core (#7489) 2024-03-25 09:54:02 +01:00
Mathieu
214b8ce19e feat(xo-web/license): ability to rebind licenses (#7390) 2024-03-22 10:02:00 +01:00
b-Nollet
1b5157e9a7 feat(xo-web): edit default SR in pool advanced tab (#7451)
Fixes #7414
2024-03-21 16:30:48 +01:00
Mathieu
0c4275df48 fix(xo-server/xostor): fix group-name for thick provisioning (#7494)
For thick provisioning, group-name must be `linstor_group`

Introduced by 2ff45c6
2024-03-21 15:41:34 +01:00
Florent BEAUCHAMP
98eb9aa8e8 feat(xo-web): implement manifest for XO5 (#7462) 2024-03-21 11:45:38 +01:00
b-Nollet
cf5db2de8f feat(xo-server): replug VIFs and PIFs when MTU is modified (#7443)
Unplug and replug VIFs and PIFs when network MTU is modified, to update their MTU value.

See https://xcp-ng.org/forum/post/72431
2024-03-21 10:02:16 +01:00
b-Nollet
b8177a7bad feat(xo-server-load-balancer): balance vCPUs option (#7333)
Adding an option for performance mode to balance VMs on hosts depending on their number of vCPUs and the number of CPUs of the host. See #5389
2024-03-20 17:11:36 +01:00
Julien Fontanet
a4465c389c fix(xo-server/deployProxy): let consumers remove XenStore entries
This will fix some issues, like xoa-updater registration when the
XO Proxy Appliance do not have Internet access on first boot.
2024-03-20 15:25:44 +01:00
Julien Fontanet
32836cb09e fix(xo-server/getProxyApplianceUpdaterState): ignore configure error
Log the error instead of throwing to keep things working as much as possible.
2024-03-20 10:48:20 +01:00
OlivierFL
e52b18f4e3 feat(lite/web-core): update UiCounter component (#7438)
- move component into `web-core`
- add size `large`
- add `suffix` prop to add text information after the value
2024-03-19 10:04:50 +01:00
OlivierFL
c0a576bc95 feat(lite): use redesigned typography (#7474) 2024-03-19 09:30:07 +01:00
Julien Fontanet
1d912bddab fix(xo-server): pass user IP to authenticateUser 2024-03-18 14:15:34 +01:00
Florent BEAUCHAMP
856961221f test(xo-vmdk-to-vhd): fix reference file path (#7402)
the xsd is not bundled in dist, the tests are run from the dist folder
2024-03-18 10:30:02 +01:00
Julien Fontanet
ac9461be21 fix(xo-server/getProxyApplianceUpdaterState): ignore missing appliance.updater.configure
Introduced by 215577b31
2024-03-18 09:22:03 +01:00
Julien Fontanet
e8c9e620ca fix(xo-server): HTTP proxy support for consoles
Introduced by 6c16055a1

Fixes #7476
2024-03-18 09:13:44 +01:00
Julien Fontanet
215577b313 feat(xo-server/upgradeProxyAppliance): try via API first
It's faster than rebooting the VM.

Attempting this method before falling back to the other one is more robust in
case one of the methods do not work.
2024-03-15 17:40:09 +01:00
Julien Fontanet
8e5d94dd31 fix(xo-web/proxies): can upgrade without vmUuid
Since 572359892
2024-03-15 17:38:15 +01:00
Julien Fontanet
92a3236d59 feat(xo-server/getProxyApplianceUpdaterState): set channel 2024-03-15 17:06:36 +01:00
Julien Fontanet
505aa9278a feat(proxy/api): new method appliance.updater.configure
Allows configuring the updater channel.
2024-03-15 17:06:36 +01:00
Dan Pollak
e8c6f5bad0 docs: add mirror backup to side menu (#7457)
Fixes #7416
2024-03-15 12:55:09 +01:00
Julien Fontanet
6c16055a18 fix(xen-api): various undici fixes (#7472)
* fix(xen-api): various undici fixes

Fixes #7465

Introduced by bfb8d3b29

- don't use single Client as it allows a single parallel request by default
- fix HTTP proxy support (for all methods but `putResource()`
- follow redirects
- `getResource` respect configured protocol
- properly throws on non 2xx responses
2024-03-15 11:06:24 +01:00
Julien Fontanet
c6451cfa35 feat(xo-cli): support OTP 2024-03-14 16:59:46 +01:00
Julien Fontanet
4b59ef5414 fix(xo-server/authenticateUser): check OTP
Fixes zammad#22688
2024-03-14 16:59:46 +01:00
Julien Fontanet
0ccfd4b681 fix(self-signed): do not log private key 2024-03-14 13:51:31 +01:00
MlssFrncJrg
66ee45f52a feat(xo-web/tag): show current tags in Advanced tag creation (#7434)
Fixes #7351
2024-03-14 11:22:29 +01:00
Dan Pollak
54dc3fe9bd fix(xo-web/log-alert-body): fix 'an error has occurred' (#7458) 2024-03-12 16:48:23 +01:00
b-Nollet
bfb8d3b29e chore(xen-api): http-request-plus → undici (except putResource) (#7177) 2024-03-12 11:22:12 +01:00
Thierry Goettelmann
51f95b3c85 feat(web-core): new ButtonIcon component (#7447) 2024-03-12 11:18:20 +01:00
Thierry Goettelmann
8381dba0f0 feat(web-core): new collection management (#7400)
New way to manage collections and sub-collections of items.
It handles different informations (id, label, depth, css classes etc.), status
(active, selected, expanded, etc.), filtering, actions (activate, select, select
children etc.), optional multi-select and generation of selection label (`Foo`,
`Foo, Bar, Baz`, `5 items selected`)
2024-03-12 11:02:25 +01:00
Thierry Goettelmann
a1def028f0 feat(web-core): new dropdown components (#7420)
New `DropdownList`, `DropdownTitle` and `DropdownItem` components, based on Design System.
2024-03-12 10:54:39 +01:00
Thierry Goettelmann
334bad8f27 feat(lite/stories): add web-core + enhancements (#7446)
- Rework story generator to include Web Core + better prop typing
- Replace existing icons with ones more suited to the project (Pool, Host, VM, etc.)
2024-03-12 10:51:07 +01:00
Thierry Goettelmann
acb890c779 docs(web-core): add CSS guidelines (#7454)
Add CSS guidelines for:
- Interactive elements
- Variants
2024-03-12 10:48:04 +01:00
Thierry Goettelmann
c10e3fd48f feat(web-core): redesigned typography (#7460) 2024-03-11 16:34:49 +01:00
Julien Fontanet
6848e34701 fix(CHANGELOG): add missing released packages 2024-03-11 14:54:00 +01:00
Julien Fontanet
58739d7fd2 feat(otp): ignore whitespaces
Some OTP applications (e.g. Authy) add them for nicer display.
2024-03-11 12:39:04 +01:00
Dan Pollak
7e66a4cb06 docs: xoa is default SSH account (#7456)
Fixes zammad#22547
2024-03-11 11:13:25 +01:00
Julien Fontanet
5a63b8dc78 chore: update dev deps 2024-03-11 11:05:06 +01:00
Thierry Goettelmann
6056a618c3 fix(web): add missing vue-router dependency (#7453) 2024-03-08 10:33:07 +01:00
Julien Fontanet
553605343c chore: update dev deps 2024-03-07 15:17:50 +01:00
Julien Fontanet
e14ce2215d docs: XOA default iface is now called enX0 (#7448) 2024-03-07 09:30:31 +01:00
Julien Fontanet
2e002de774 feat(xo-server/createVm): create VTPM if template requests it (#7436)
See https://team.vates.fr/vates/pl/d3mb7wb5fiy57pmp44wnusatce (private page)

Should fix https://www.youtube.com/watch?v=MqHuNu4hjJo
2024-03-06 11:52:51 +01:00
mathieuRA
1ae7b1d500 fix(xo-web/VM): SR no longer required if a migration network is selected 2024-03-06 11:23:50 +01:00
mathieuRA
6625d8f7af fix(xo-web/home/VM): do not migrate_send during VM migration if not needed
Fixes [Forum#8279](https://xcp-ng.org/forum/topic/8279/getting-errors-when-migrating-4-out-5-vmguest/18?_=1707234126297)
2024-03-06 11:23:50 +01:00
mathieuRA
7cfce7b1b6 fix(xo-server/xapi_migrateVm): use correct SR during VDI migration 2024-03-06 11:23:50 +01:00
Mathieu
e52a0bd3df fix(xo-web/SR): destination SRs are ISOs if we are migrating ISOs (#7431)
Fixes #7392
2024-03-06 11:05:55 +01:00
Julien Fontanet
48e6d582a7 feat: release 5.92.1 2024-03-06 10:18:55 +01:00
Mathieu
37c2cfd99e feat(xo-server/XOSTOR): add error case for 'LVM_ERROR(5) (#7428) 2024-03-05 17:50:39 +01:00
Julien Fontanet
50991582ef feat(xo-web): 5.140.0 2024-03-05 17:37:04 +01:00
Julien Fontanet
85a1d539d7 feat(xo-server): 5.138.1 2024-03-05 17:13:18 +01:00
Mathieu
0b00c1a654 fix(xo-server/xostore.create): remove linstor_group on failure (#7426)
Related to zammad#21209
2024-03-04 14:42:06 +01:00
Julien Fontanet
da51b1649c fix(xo-server/robots.txt): must not require authentication
Introduced by a2c36c083

This file is intended for search engines, which, by definition are not signed in.
2024-03-04 11:48:34 +01:00
Dan Pollak
8ad83f0e97 docs(backup): fix link to file level restore (#7421) 2024-03-04 11:15:31 +01:00
Mathieu
54f9be5b2c fix(xo-web/XOSTOR): move 'ignore file sytems' outside of advanced settings (#7429)
Move the toggle outside of advanced settings as its use is very common.
2024-03-04 10:45:53 +01:00
Mathieu
c8778fe2ab feat(xo-web/about): github links opens in a new tab (#7430)
Fixes #7427
2024-03-04 10:40:09 +01:00
Pierre Donias
dcdf2880aa fix(lite): missing color for logo (#7423)
Introduced by 5242affdc1
2024-03-01 11:39:49 +01:00
Julien Fontanet
6e4f79ce33 fix(xo-web/jobs): handle undefined refs.params
Fixes zammad#22196

Introduced by be452a5d6
2024-03-01 11:09:13 +01:00
Julien Fontanet
c2b4033ac3 feat: CONTRIBUTING.md
This file contains documentation to help people contributing to the project,
either by participating on the community forum, reporting issues, or creating
a PR on the documentation or the code.

Most of it was created by https://generator.contributing.md/
2024-03-01 09:45:47 +01:00
b-Nollet
25982cab61 feat: release 5.92.0 (#7425) 2024-02-29 15:50:22 +01:00
b-Nollet
2560b782c1 feat: technical release (#7424) 2024-02-29 14:33:12 +01:00
MlssFrncJrg
649c117461 feat(xo-web/SR): show suspend VDIs VMs (#7391) 2024-02-29 11:09:33 +01:00
Mathieu
aef6e02dc5 feat(xo-web/VUSB): ability to create VUSB (#7412)
See #7409
2024-02-29 11:07:28 +01:00
Pierre Donias
14e59c2d6e feat(lite): 0.2.0 (#7422) 2024-02-29 10:41:04 +01:00
OlivierFL
7cb2f2298d feat(xo6/core): use same-name shorthand syntax in templates (#7419) 2024-02-28 18:04:00 +01:00
MlssFrncJrg
9cb94010fe feat(xo-web/host/advanced): list PUSB devices (#7409) 2024-02-28 16:02:46 +01:00
b-Nollet
5def18902b feat: technical release (#7418) 2024-02-28 15:33:24 +01:00
Mathieu
71991d7172 feat(xo-server/resourceSet): add 'usage' property resourceSet (#7353)
See Zammad#20996

Show used resources even when they are unlimited
2024-02-28 14:40:37 +01:00
Pierre Donias
561e94fee0 fix(xo-web/Text): replace spaces with nbsp so that the text is always clickable (#7411)
See https://xcp-ng.org/forum/topic/8466
2024-02-28 11:39:34 +01:00
Mathieu
be9fd95761 fix(xo-server/sr.createSmb): fix 'SR_BACKEND_FAILURE_111' by correctly passing username (#7407)
Fixes #7356
Introduced by 06d4115
2024-02-28 11:04:41 +01:00
Florent Beauchamp
ccdd6e2b42 docs(backups/mirror): health check only applied on the last transferred 2024-02-28 10:47:44 +01:00
Florent Beauchamp
6f8b7b5051 fix(backups/MixinRemoteWriter): don't run healthcheck if nothing has been transferred 2024-02-28 10:47:44 +01:00
Florent Beauchamp
90ea05ae11 fix(backups/AbstractRemoteVmBackupRunner): correctly store settings 2024-02-28 10:47:44 +01:00
Florent BEAUCHAMP
8bdba77452 fix(vmware-explorer): correctly handle IDE disks (#7394)
Fixes zammad#21585

Previous implementation was only parsing the scsi part of the vm metadata file ( .vmx).

This commit also looks at the ide part and add them to the disk list to be handled ( transferred to XCP) ,while ignoring the cdrom
2024-02-28 10:33:45 +01:00
OlivierFL
493d072d77 feat(xo6/core): improve color context (#7398) 2024-02-28 09:56:49 +01:00
OlivierFL
8fd501c6a2 feat(xo6/core): custom scrollbars (#7364) 2024-02-28 09:54:15 +01:00
Florent BEAUCHAMP
cb843c4432 fix(xo-web/backup): typo (#7415)
Fixes #7413
2024-02-28 09:42:24 +01:00
OlivierFL
60c527ad3c feat(xo6/core): add UI tree-view components in web-core (#7339) 2024-02-28 09:41:29 +01:00
mathieuRA
b85797482f feat(xo-web/backup): add 'nRetriesVmBackupFailures' input 2024-02-27 17:49:40 +01:00
mathieuRA
73edf72ebd feat(@xen-orchestra/backups): retry if VM fails 2024-02-27 17:49:40 +01:00
b-Nollet
611cd81e49 feat(xo-server): minimum MTU value for network creation
Co-authored-by: Mathieu <70369997+MathieuRA@users.noreply.github.com>
2024-02-27 17:36:52 +01:00
b-Nollet
354dd98ec4 feat(xo-web,xo-server): edit MTU 2024-02-27 17:36:52 +01:00
b-Nollet
73ad9c1e73 feat(xo-server): implement VUSB API (#7399) 2024-02-27 16:25:49 +01:00
Mathieu
24a75babef fix(xo-web/SR): fix 'smbiso' SR creation (#7405)
Fixes #7355
See https://xcp-ng.org/forum/topic/8417
Introduced by 06d4115
2024-02-27 10:16:24 +01:00
Julien Fontanet
f722362fd6 fix(xo-server/backupNg.getLogs): fix after handling
Fixes zammad#22192
2024-02-26 16:14:04 +01:00
b-Nollet
a03d165a5f feat: technical release (#7408) 2024-02-26 16:10:52 +01:00
Julien Fontanet
c13b95ee8b feat(xo-server,xo-web): manage auto poweron at pool level (#7401)
Fixes zammad#21691
2024-02-26 11:39:23 +01:00
b-Nollet
b4c39d5b59 fix(backups/VmsRemote): avoid tasks for empty VM directories (#7340)
See zammad#15136

Prevents task creation for empty directories during mirror backup.
2024-02-26 11:27:39 +01:00
Julien Fontanet
41a8a7214f fix(backups/cleanVms): remove incorrect warning (#7406) 2024-02-23 17:09:20 +01:00
Julien Fontanet
5d82f2696e fix(xo-web/new-vm): handle both template_info.disks and VDIs
Related to 3be45c7f4
2024-02-23 16:35:32 +01:00
Julien Fontanet
1c43c9e258 fix(xo-web/new-vm): respect fast clone setting
Fixes zammad#21432
Introduced by 0d4733252
Revealed by 302f7fb85
2024-02-23 16:35:32 +01:00
Julien Fontanet
192326201f fix(xo-web/SelectVmTemplate): fix group by pool
Fixes https://xcp-ng.org/forum/post/72009

Introduced by 1718649e0
2024-02-23 14:42:06 +01:00
Mathieu
089704dbe8 feat(docs): add VM migration spec (#7378)
See #7360
2024-02-23 11:08:44 +01:00
mathieuRA
aefcce45ff feat(xo-server/pusb): implement methods for USB passthrough 2024-02-22 14:58:55 +01:00
mathieuRA
367fb4d8a6 feat(xo-server): implement PUSB in xapi-object-to-xo 2024-02-22 14:58:55 +01:00
Julien Fontanet
e54a0bfc80 fix(xo-web/iso-device): fix SR predicate
Introduced by 1718649e0
2024-02-22 10:58:11 +01:00
Julien Fontanet
9e5541703b fix(xo-web/host): only count memory of running VMs
Introduced by 1718649e0

FIxes https://xcp-ng.org/forum/post/71886
2024-02-22 10:31:06 +01:00
Mathieu
039d5687c0 fix(xo-server/host): fix false positives when restarting host after updates (#7366)
The previous implementation only considered version upgrades and did not take into account the installation of missing patches.

See zammad#21487
Introduced by 85ec261
2024-02-21 15:05:05 +01:00
Florent Beauchamp
b89195eb80 fix(backups/IncrementalRemote): ensure chaining is ok and mutualize code with IncrementalXapi 2024-02-21 10:27:56 +01:00
Florent Beauchamp
822cdc3fb8 refactor(backups/IncrementalRemoteWriter): reuse parent path from checkBaseVdis 2024-02-21 10:27:56 +01:00
Florent Beauchamp
c7b5b715a3 refactor(backups/checkBaseVdi): use uuid, don't check vhd multiple times 2024-02-21 10:27:56 +01:00
Florent Beauchamp
56b427c09c fix(vhd-lib/VhdSynthetic): compression type computation 2024-02-21 10:27:56 +01:00
Mathieu
0e45c52bbc feat(lite/xapi-stats): handle new format (#7383)
Similar to 757a8915d9

Starting from XAPI 23.31, stats are in valid JSON but numbers are encoded as strings.
2024-02-20 17:55:57 +01:00
Mathieu
4fd2b91fc4 feat(xo-web/SizeInput): added 'TiB' and 'PiB' units (#7382) 2024-02-20 17:43:21 +01:00
Florent BEAUCHAMP
7890320a7d fix(xo-server/import): error during import of last snapshot of running VM (#7370)
From zammad#21710

Introduced by 2d047c4fef
2024-02-20 17:39:39 +01:00
Julien Fontanet
1718649e0c feat(xo-server/vm.$container): points to host if VDI on local SR
Fixes https://xcp-ng.org/forum/post/71769
2024-02-20 16:49:53 +01:00
Julien Fontanet
7fc5d62ca9 feat(xo-server/rest-api): export hosts' SMT status
Fixes https://xcp-ng.org/forum/post/71374
2024-02-20 16:33:33 +01:00
Julien Fontanet
eedaca0195 feat(xo-server/remotes): detect, log and fix incorrect params (#7343) 2024-02-16 16:23:06 +01:00
Julien Fontanet
9ffa52cc01 docs(xoa): manual network config 2024-02-16 11:25:34 +01:00
Julien Fontanet
e9a23755b6 test(fs/path/normalizePath): test relative paths handling
Related to 5712f29a5
2024-02-15 10:10:44 +01:00
Julien Fontanet
5712f29a58 fix(vhd-lib/chainVhd): correctly handle relative paths 2024-02-15 09:14:32 +01:00
Julien Fontanet
509ebf900e fix(fs/path/relativeFromFile): correctly handle relative paths 2024-02-15 09:13:10 +01:00
Julien Fontanet
757a8915d9 feat(xo-server/xapi-stats): handle new format
Starting from XAPI 23.31, stats are in valid JSON but numbers are encoded as strings.
2024-02-14 16:14:43 +01:00
Thierry Goettelmann
35c660dbf6 feat(xo-stack): add @core alias to import Core from Web and Lite (#7375) 2024-02-14 14:43:23 +01:00
Julien Fontanet
f23fd69e7e fix(xapi/VIF_create): fetch power_state and MTU in parallel 2024-02-14 11:48:07 +01:00
Julien Fontanet
39c10a7197 fix(xapi/VIF_create): explicit error when no allowed devices
Related to #7380
2024-02-14 11:48:07 +01:00
Julien Fontanet
7a1bc16468 fix: respect logger method signature
This is a minor fix that should not have major impacts.

It's not necessary to release impacted packages.
2024-02-13 17:38:03 +01:00
Julien Fontanet
93dd1a63da docs(log): document method signature 2024-02-13 17:35:58 +01:00
Florent Beauchamp
b4e1064914 fix(backups): _isAlreadyTransferred is async
This leads to a retransfer and a EEXIST error while writing the metadata.

It can happen when a mirror transfer to multiple remotes, fails on one remote and is restarted/resumed.
2024-02-13 16:03:45 +01:00
Florent Beauchamp
810cdc1a77 fix(backups): really skip already transferred backups 2024-02-13 16:03:45 +01:00
Julien Fontanet
1023131828 chore: update dev deps 2024-02-12 20:47:05 +01:00
Smultar
e2d83324ac chore: add name and version to root package.json (#7372)
Fixes #7371
2024-02-12 16:59:50 +01:00
Julien Fontanet
7cea445c21 fix(xo-web/remotes): don't merge all properties into url
Related to #7343

Introduced by fb1bf6a1e7
2024-02-12 14:51:04 +01:00
Julien Fontanet
b5d9d9a9e1 fix(xo-server-audit): ignore tag.getAllConfigured
Introduced by 25e270edb4
2024-02-12 10:58:06 +01:00
Julien Fontanet
3a4e9b8f8e chore(xo-web/config): remove unused computeds
Introduced by 01302d7a60
2024-02-12 10:55:58 +01:00
Julien Fontanet
92efd28b33 fix(xo-web/config): sort backups from newest to oldest
Introduced by 01302d7a60
2024-02-12 10:55:26 +01:00
Julien Fontanet
a2c36c0832 feat(xo-server): add robots.txt
Fixes zammad#21489
2024-02-09 11:25:06 +01:00
Florent BEAUCHAMP
2eb49cfdf1 feat: release 5.91.2 (#7367) 2024-02-09 11:10:59 +01:00
Florent BEAUCHAMP
ba9d4d4bb5 feat: technical release (#7365) 2024-02-09 10:09:09 +01:00
b-Nollet
18dea2f2fe fix(xo-server-load-balancer): create simple plan as in config (#7358)
Previously, a density plan was created when simple plan was selected in load-balancer configuration.
2024-02-08 18:14:08 +01:00
Julien Fontanet
70c51227bf feat(xo-server/rest-api): expose messages
Fixes zammad#21415
2024-02-08 11:25:05 +01:00
Julien Fontanet
e162fd835b feat(xo-server/rest-api): add /groups/:id/users and /users/:id/groups collections
Fixes https://xcp-ng.org/forum/post/70500
2024-02-08 11:23:17 +01:00
Julien Fontanet
bcdcfbf20b feat(xo-server/rest-api): add groups collection
See https://xcp-ng.org/forum/post/70500
2024-02-08 11:23:17 +01:00
Julien Fontanet
a6e93c895c chore(xo-server/rest-api): unify collections handling 2024-02-08 11:23:17 +01:00
Julien Fontanet
5c4f907358 chore(xo-server/rest-api): move :object handling to the collection 2024-02-08 09:56:04 +01:00
Julien Fontanet
e19dbc06fe chore(xo-server/rest-api): uniformize collections creation 2024-02-08 09:06:53 +01:00
Julien Fontanet
287378f9c6 fix(xo-web): changing items per page should select page 1
Fixes #7350
2024-02-07 16:03:53 +01:00
b-Nollet
83a94eefd6 fix(xo-server-load-balancer): error during optimize (#7362)
Introduced by d949112

Fixes #7359
2024-02-07 15:48:10 +01:00
Julien Fontanet
92fc19e2e3 fix(xo-server/api): never log proxy.getApplianceUpdaterState
Due to xo-web's subscription, there can be a lot of errors in case of XO Proxy failures.
2024-02-07 11:42:21 +01:00
Julien Fontanet
521d31ac84 feat(xo-server/logs-cli): display number of deleted entries 2024-02-07 11:42:21 +01:00
Florent BEAUCHAMP
2b3ccb4b0e fix(xo-server/vm.importFromEsxi): userdevice is a string (#7361)
Introduced by 59cc418973

From zammad#82017 and other tickets
2024-02-07 10:24:50 +01:00
Julien Fontanet
2498a4f47c feat: release 5.91.1 2024-02-06 17:18:47 +01:00
Julien Fontanet
dd61feeaf3 feat(xo-server): 5.135.1 2024-02-06 16:41:06 +01:00
Julien Fontanet
7851f8c196 feat(@xen-orchestra/xva): 1.0.2 2024-02-06 16:41:05 +01:00
Julien Fontanet
404a764821 feat(@xen-orchestra/immutable-backups): 1.0.1 2024-02-06 16:40:52 +01:00
Florent Beauchamp
59cc418973 fix(xva): disk order import 2024-02-06 15:00:08 +01:00
Florent Beauchamp
bc00551cb3 fix(xva): off by one
if last block is not already written we must write it, not the next bloxk
2024-02-06 15:00:08 +01:00
Florent Beauchamp
4d24248ea2 fix(xva): missing await 2024-02-06 15:00:08 +01:00
Florent BEAUCHAMP
5c731fd56e fix(xva): align block size 2024-02-06 15:00:08 +01:00
Florent BEAUCHAMP
79abb97b1f fix(xva): last block path
the last block may be added by a different code path where the block counter wasn't padded, leading to trying to allocate a few Ettabytes
2024-02-06 15:00:08 +01:00
Florent BEAUCHAMP
3314ba6e08 fix(xva): write block from time to time to handle timeout
when the source VM is very sparse, a long time can pass between write to xapi, triggering a timeout
2024-02-06 15:00:08 +01:00
Florent Beauchamp
0fe8f8cac3 fix(xo-server/migrate-vms): use thin mode for vhdraw
when importing a snapshot, the parent (raw) does not need to go through a full read
to generate a thin BAT
2024-02-06 15:00:08 +01:00
Julien Fontanet
aaffe8c872 fix(xo-server/rest-api): fix incorrect object's tasks href
Introduced by f3c5e817a

`req.baseUrl` is already handled by `sendObjects`.
2024-02-05 13:38:14 +01:00
Julien Fontanet
cf0e820632 fix(xo-server/rest-api): fix empty object's tasks list
Introduced by 1da05e239

Since task data were moved into properties, the relation between an object and
its tasks was incorrectly checked which led to an abnormal empty tasks list.
2024-02-05 13:38:14 +01:00
Julien Fontanet
82fdfbd18b fix(xo-server/_createProxyVm): {,_}getOrWaitObject
Introduced by 48c3a65cc
2024-02-05 13:38:14 +01:00
OlivierFL
116f2ec47a fix(ISSUE_TEMPLATE/bug_report): verbatim error field (#7357) 2024-02-05 12:42:16 +01:00
Julien Fontanet
5d4723ec06 fix(xo-server/vm.import): fix fetching UUID from ref
Introduced by 70b09839c
Reported by @ydirson

This led to `UUID_INVALID(VM, OpaqueRef:...)` when importing from URL.
2024-02-02 16:47:55 +01:00
Julien Fontanet
092475d57f chore: update to tap@18 2024-02-02 12:52:11 +01:00
Julien Fontanet
e7a97fd175 test(audit-core,predicates,otp,xo-server): move from tap to test
Support of Mocha syntax is much easier than in latest versions of tap
2024-02-02 12:52:11 +01:00
Julien Fontanet
2262ce8814 chore(async-each): remove unused dev dep tap 2024-02-02 12:52:11 +01:00
Julien Fontanet
7ec64476a4 chore: remove unused script ci 2024-02-02 12:52:02 +01:00
Julien Fontanet
bbb43808dd fix(ci): run unit tests 2024-02-02 12:52:02 +01:00
Florent Beauchamp
02d7f0f20b fix(immutable-backups): handle index files too big 2024-02-02 11:28:28 +01:00
Florent Beauchamp
e226db708a fix(immutable-backups): since index is not immutable anymore we should consider an EPERM Error as relevantt 2024-02-02 11:28:28 +01:00
Florent Beauchamp
b7af74e27e fix(immutable-backups): fix variable name errors 2024-02-02 11:28:28 +01:00
Florent Beauchamp
70f2e50274 fix(immutable-backups): index files aren't immutable anymore 2024-02-02 11:28:28 +01:00
Florent Beauchamp
619b3f3c78 test(immutable-backups): add missing tests 2024-02-02 11:28:28 +01:00
Florent Beauchamp
ac182374d6 fix(immutable-backups): tests
introduced by ed7046c1ab
these tests need to be run by root in a linux system => move them to integ.mjs
2024-02-02 11:28:28 +01:00
Florent BEAUCHAMP
8461d68d40 fix(xva): use a buildless dependency for hashes (#7348)
Native dependencies can have some problems on closed systems

Introduced by 2d047c4fef
2024-02-01 16:30:57 +01:00
Julien Fontanet
838f9a42d1 feat: release 5.91.0 2024-01-31 17:59:34 +01:00
Julien Fontanet
a9dbc01b84 feat(xo-web): 5.136.0 2024-01-31 17:34:53 +01:00
Julien Fontanet
5daf269ab3 feat(xo-server): 5.135.0 2024-01-31 17:34:41 +01:00
Julien Fontanet
383677f4dd feat(xo-cli): 0.26.0 2024-01-31 17:33:13 +01:00
Julien Fontanet
28ad2af9b0 feat(@xen-orchestra/proxy): 0.26.45 2024-01-31 17:25:02 +01:00
Julien Fontanet
53b25508af feat(@xen-orchestra/immutable-backups): 1.0.0 2024-01-31 17:22:51 +01:00
Julien Fontanet
54836ec576 fix(immutable-backups): package should be public
Introduced by ed7046c1a
2024-01-31 17:22:04 +01:00
Julien Fontanet
c9897166e0 feat(@xen-orchestra/backups): 0.44.6 2024-01-31 17:18:52 +01:00
Julien Fontanet
1b35fddf3d feat(@xen-orchestra/fs): 4.1.4 2024-01-31 17:16:15 +01:00
MlssFrncJrg
fc2ca1f4d4 fix(xo-web/pool/advanced): added delete button in backup/migration network (#7349)
Introduced by 0f1f459
2024-01-31 17:00:45 +01:00
Florent BEAUCHAMP
ed7046c1ab feat: immutable backups (#6928) 2024-01-31 16:57:28 +01:00
Julien Fontanet
215579ff4d fix(fs/Local#readFile): fix flags handling 2024-01-31 16:50:29 +01:00
Julien Fontanet
4c79a78a05 fix(fs): remove unnecessary/incorrect readFile param 2024-01-31 16:47:33 +01:00
Julien Fontanet
7864c05ee1 fix: race condition between normalize-packages and usage-to-readme
The first was writting `package.json` at the same time the second one was reading it.

The file is now read only once by `normalize-packages`.
2024-01-31 16:22:07 +01:00
Julien Fontanet
1026d18e4b fix(usage-to-readme): remove another debug log
Introduced by a62433081
2024-01-31 16:10:42 +01:00
Julien Fontanet
d62acd3fe7 fix: format with Prettier 2024-01-31 15:54:43 +01:00
Julien Fontanet
cc1b4bc06c feat(xo-server/rest-api): add pool action create_vm
Fixes #6749
2024-01-31 15:07:01 +01:00
Julien Fontanet
d45f843308 feat(xo-cli/rest): dot syntax for deep properties
`foo.bar=baz` can be used instead of `foo=json:'{ "bar": "baz" }'`.
2024-01-31 15:04:34 +01:00
b-Nollet
3c7fa05c43 feat: technical release (#7347) 2024-01-31 13:56:02 +01:00
MlssFrncJrg
05df055552 feat(xo-web/xostor): expose ignoreFileSystems at creation (#7338) 2024-01-31 13:07:24 +01:00
Pierre Donias
755b206901 chore(web): use relative asset URLs (#7337) 2024-01-31 11:51:40 +01:00
Julien Fontanet
3360399b2a fix(usage-to-readme): remove debug logs
Introduced by a62433081
2024-01-31 10:15:37 +01:00
Florent BEAUCHAMP
091bc04ace fix(xo-server): import of @xen-orchestra/xva package (#7346)
Fix https://github.com/vatesfr/xen-orchestra/issues/7344
Introduced by 2d047c4fef

Cause: we did not run the normalize-package command after renaming
2024-01-31 09:58:34 +01:00
Florent Beauchamp
3ab2a8354b feat(xo-web/import): all the import are thin, remove toggle 2024-01-30 18:54:46 +01:00
Florent Beauchamp
2d047c4fef feat(vmware/import): use xva to load base disk 2024-01-30 18:54:46 +01:00
Florent Beauchamp
0f1dcda7db feat(vmware-explorer): don't read from parent when delta block is already complete 2024-01-30 18:54:46 +01:00
Florent Beauchamp
44760668a3 feat(vmware-explorer): batch reading for sesparse delta 2024-01-30 18:54:46 +01:00
Julien Fontanet
a4023dbc54 feat(ci): ensure yarn.lock is up-to-date 2024-01-30 18:33:25 +01:00
Julien Fontanet
3cad7e2467 fix(yarn.lock): refresh
Introduced by 2e6f7d35e
2024-01-30 17:49:46 +01:00
Julien Fontanet
ad5f37436a fix(CHANGELOG.unreleased): add xo-server@minor
Introduced by 06d411543
2024-01-30 17:35:46 +01:00
MlssFrncJrg
8c05eab720 feat(xo-web/pool/patches): disable rolling pool update button (#7294)
Fixes https://github.com/vatesfr/xen-orchestra/issues/6415
2024-01-30 17:30:36 +01:00
Florent BEAUCHAMP
4db605f14a fix(backups/formatVmBackup): handle snapshotless backups (#7342)
Introduced by b48d955c44
2024-01-30 17:25:59 +01:00
MlssFrncJrg
06d411543a feat(xo-web/SR): add possibility to create SMB shared SR (#7330)
Fixes #991
2024-01-30 17:24:31 +01:00
MlssFrncJrg
6084db22a9 fix(xo-web/import/disk): can't change file name when importing from URL (#7332)
Fixes [#7326](https://github.com/vatesfr/xen-orchestra/issues/7326)
2024-01-30 15:04:27 +01:00
Thierry Goettelmann
2e6f7d35ef feat(xo6): setup file based router (#7318) 2024-01-30 11:31:58 +01:00
MlssFrncJrg
0f1f45953c fix(xo-web/pool/advanced): show backup/migration network even when deleted (#7303) 2024-01-30 11:28:43 +01:00
b-Nollet
89a4de5b21 feat: technical release (#7341) 2024-01-30 09:51:24 +01:00
Julien Fontanet
32dd16114e fix(xo-web/backup): smart mode preview should ignore xo:no-bak tags (#7331)
Introduced by 87a9fbe23

Fixes https://xcp-ng.org/forum/post/69797
2024-01-29 15:56:43 +01:00
Julien Fontanet
f5a3cc0cdb fix(xo-server/rest-api): handle empty fields param
Introduced by c18373bb0
2024-01-29 11:57:47 +01:00
Julien Fontanet
5cabf9916a feat(xo-server/rest-api): expose actions' params schema 2024-01-29 11:08:38 +01:00
OlivierFL
8e65ef7dbc feat(xo-web/logs): transform objects UUIDs into clickable links (#7300)
In Settings/Logs modals : transform objects UUIDs into clickable links, leading
to the corresponding object page.
For objects that are not found, UUID can be copied to clipboard.
2024-01-26 17:28:30 +01:00
Mathieu
0c0251082d feat(xo-web/pool): ability to do a rolling pool reboot (#7243)
Fixes #6885
See #7242
2024-01-26 17:08:52 +01:00
Pierre Donias
c250cd9b89 feat(xo-web/VM): ability to add custom notes (#7322)
Fixes #5792
2024-01-26 14:59:32 +01:00
Florent BEAUCHAMP
d6abdb246b feat(xo-server): implement rolling pool reboot (#7242) 2024-01-25 17:50:34 +01:00
Pierre Donias
5769da3ebc feat(xo-web/tags): add tooltips on xo:no-bak and xo:notify-on-snapshot tags (#7335) 2024-01-25 10:27:42 +01:00
Julien Fontanet
4f383635ef feat(xo-server/rest-api): validate params 2024-01-24 11:41:22 +01:00
Julien Fontanet
8a7abc2e54 feat(xo-cli/rest): display error response body 2024-01-24 11:23:43 +01:00
Julien Fontanet
af1650bd14 chore: update dev deps 2024-01-24 10:54:02 +01:00
Julien Fontanet
c6fdef33c4 feat(xo-server): web signin with auth token in query string (#7314)
Potential issue: the token stays in the browser history.
2024-01-24 10:02:38 +01:00
Florent BEAUCHAMP
5f73f09f59 feat(fuse-vhd): implement cli (#7310) 2024-01-23 17:14:18 +01:00
Thierry Goettelmann
1b0fc62e2e feat(xo6): introducing @xen-orchestra/web (#7309)
Introduces `@xen-orchestra/web`, which will be the home for the new incoming
Xen Orchestra v6.

It uses `@xen-orchestra/web-core` as a foundation.

Upgraded common dependencies of Lite/Web/Core.
2024-01-23 11:25:18 +01:00
Julien Fontanet
aa2dc9206d chore: format with Prettier
Introduced by 85ec26194
2024-01-23 11:02:28 +01:00
Mathieu
2b1562da81 fix(xo-server/PIF): IPv4 reconfiguration only worked when mode was updated (#7324) 2024-01-23 10:55:37 +01:00
Mathieu
25e270edb4 feat(xo-web,xo-server/tag): add colored tag (#7262) 2024-01-23 09:46:06 +01:00
MlssFrncJrg
51c11c15a8 feat(xo-web/pool): disable Rolling Pool Update if pool has 1 host (#7286)
See #6415
2024-01-22 11:04:38 +01:00
Julien Fontanet
47922dee56 fix(xo-server/patching): fix typo
Introduced by 901f7b3fe

Fixes #7325
2024-01-20 09:35:48 +01:00
Julien Fontanet
3dda4dbaad chore: format with Prettier 2024-01-19 16:42:10 +01:00
Julien Fontanet
901f7b3fe2 chore(xo-server): remove object properties decorator syntax
This syntax is not supported by Prettier.
2024-01-19 16:42:10 +01:00
Julien Fontanet
774d66512e feat(decorate-with): decorateObject() 2024-01-19 16:42:10 +01:00
b-Nollet
ec1669a32e feat(xo-server-load-balancer): limit concurrent migrations (#7297)
Fixes #7084
2024-01-19 16:37:55 +01:00
Florent BEAUCHAMP
bbcd4184b0 feat(xo-web,backups): fix dynamic disks count with suspend_VDI (#7315)
`suspend_VDI` was counted as a dynamic VHD.

This commit ignores it in the computation, but add a tag if the backup is with memory.
2024-01-19 16:34:06 +01:00
Mathieu
85ec26194b feat(xo-web/host): ask for confirmation to reboot the updated slave host if the master is not (#7293)
Fixes #7059

In case a slave host requires a reboot to apply updates and the master is using
the same version as the slave host, a confirmation modal is triggered.
2024-01-19 11:18:40 +01:00
MlssFrncJrg
f0242380ca feat(xo-web/tab-advanced): allow to update VM creator (#7276)
Related to [forum#7313](https://xcp-ng.org/forum/topic/7313/change-created-by-and-date-information)
2024-01-19 10:40:08 +01:00
Julien Fontanet
a624330818 fix(usage-to-readme): fix multiple .USAGE.md changes 2024-01-19 10:23:52 +01:00
Julien Fontanet
3892efcca2 feat(xo-web/plugins): auto-load follow load/unload (#7317)
Loading, or unloading, will respectively enable, or disable, _Auto-load at server start_,
this should lead to least surprising behaviors.
2024-01-19 09:55:36 +01:00
Mathieu
c1c122d92c feat(xo-web/pool/host): add warning if hosts don't have the same version (#7280)
Fixes #7059
2024-01-18 17:13:57 +01:00
Julien Fontanet
b7a66e9f73 chore(web-core): add missing npmignore
Follow-up of d92d2efc7
2024-01-18 13:29:30 +01:00
Julien Fontanet
d92d2efc78 chore(web-core): normalize package.json 2024-01-18 12:39:39 +01:00
Julien Fontanet
c2cb51a470 feat(lint-staged): add .USAGE.md → README.md
So that it's not needed to manually runs from `normalize-packages.js`.
2024-01-18 12:34:23 +01:00
Thierry Goettelmann
5242affdc1 feat(lite): introducing @xen-orchestra/web-core (#7302)
This PR introduces `@xen-orchestra/web-core`, which will be the common base for
XO Lite and XO 6.

This package is not meant to be distributed and will be used as-is in other
packages thanks to Yarn Workspace. This mean that the files of XO Web Core will
not be built by themselves but by either the package which use it.

Styles have been moved from XO Lite to XO Web Core.

Colors variable have been renamed and updated according to the new Design
System. XO Lite has been updated accordingly.
- `extra-blue` → `purple`
- `green-infra` → `green`
- `orange-world` → `orange`
- `red-vates` → `red`
- `blue-scale` → `grey`

⚠️ A new intermediate shade has been introduced (`--color-grey-400`). So
`--color-blue-scale-400` is now `--color-grey-500` and `--color-blue-scale-500`
is now `--color-grey-600`.

PostCSS color function plugin is used to generate the shades of color like it is
done on the Figma mockup (with blending the base color with black or white at
different degrees).

PostCSS custom media are now loaded globally thanks to a plugin and no longer
require to import `_responsive.pcss` file manually in each file where a custom
media was needed.
2024-01-18 10:04:25 +01:00
Dom Del Nano
71f3be288b feat(xo-server): add xenStoreData to XO VM objects (#7316)
The initial support added in #7055 to support terraform resource support doesn't provide read access to a VM's state.

Since Tterraform's model requires read and write access to the resource it's managing, this PR implements the missing piece for terra-farm/terraform-provider-xenorchestra#261.
2024-01-17 10:24:08 +01:00
OlivierFL
58769815b0 fix(xo-web/modal): close modal when navigating to another url (#7301) 2024-01-16 20:47:21 +01:00
Florent BEAUCHAMP
c81c23c0d0 fix(fuse-vhd): potential race condition in mount/unmount (#7312)
The code was not properly waiting mount/unmount to be done.
2024-01-16 18:27:30 +01:00
Julien Fontanet
f06f89b5b4 feat(self-signed): readCert utility (#7282)
Expired certificates are not automatically detected, which is not a big deal for user certificates because they can still be used and it's their responsibility to update them.

But automatic certificates must be regenerated in that case which was not the case until now.

This commit unifies certificate/key reading, checking and generation for both xo-server and xo-proxy.
2024-01-16 16:58:15 +01:00
Julien Fontanet
fa748ed9de feat(xo-server): load plugins from mono-repo
Contrary to 3e3ce543a8, which was reverted,
this implentation properly handle duplicates.
2024-01-16 15:54:12 +01:00
Julien Fontanet
cd753acff7 feat(xo-server): move plugin lookup paths to config 2024-01-16 15:51:45 +01:00
Julien Fontanet
8ff861e2be feat(xo-server): find plugins sequentially
This provides a deterministic order.

In case of duplicate plugins (with the same name), the first found plugin takes precedence.
2024-01-16 15:31:03 +01:00
Julien Fontanet
95ccb2e0ae feat(gitignore): ignore .tap/ 2024-01-16 14:02:33 +01:00
John P. Cooper
b0e5846ad1 feat(lite): introduce PWA manifest (#7291)
This implements the initial PWA manifest for XO Lite. I requested this several
months to a year ago, so decided to do it myself in the end.
2024-01-16 11:19:10 +01:00
OlivierFL
19fd456ccf fix(xo-web/metadata-restore-modal): add check to hide pool select when restoring xo config backup (#7287)
See https://xcp-ng.org/forum/topic/8130/xo-configbackup-restore
2024-01-16 11:13:01 +01:00
Julien Fontanet
7946a7db68 feat(xo-server/signin): uniformize sign in buttons
Make sign in with password button the same as external providers.
2024-01-16 10:36:04 +01:00
Julien Fontanet
6127e30574 feat(xo-server/signin): remember me with external providers
It works the same as password signin.
2024-01-16 10:36:04 +01:00
Julien Fontanet
4aad9d8e32 fix(xo-server-backup-reports): require Node>=15
Introduced by 2af74008b

Due to using AggregateError.
2024-01-16 10:10:42 +01:00
Julien Fontanet
78d15ddf96 chore: update dev deps 2024-01-16 09:59:16 +01:00
Pierre Donias
302f7fb85e fix(xo-web/new-vm): isDiskTemplate → _isDiskTemplate (#7311) 2024-01-15 16:51:55 +01:00
Thierry Goettelmann
ea19b0851f feat(lite): upgrade deps + root eslint config (#7292) 2024-01-15 11:12:53 +01:00
Julien Fontanet
b0c37df8d7 fix(xo-server/rest-api): /backup/log/<id>
Introduced by 037e1c1df

Fixes https://xcp-ng.org/forum/post/69426
2024-01-11 11:05:00 +01:00
Julien Fontanet
beba6f7e8d chore: format with Prettier 2024-01-11 09:57:28 +01:00
Julien Fontanet
9388b5500c chore(xo-server/signin): remove empty div 2024-01-10 17:17:42 +01:00
Julien Fontanet
bae8ad25e9 feat(xo-web/tasks): hide /rrd_updates by default
After an internal discussion with @Darkbeldin and @olivierlambert.
2024-01-10 16:50:03 +01:00
Julien Fontanet
c96b29fe96 docs(troubleshooting): explicit sudo with xo-server-recover-account 2024-01-10 16:48:34 +01:00
Julien Fontanet
9888013aff feat(xo-server/rest-api): add pool action emergency_shutdown
Fixes #7277
2024-01-10 15:55:14 +01:00
Julien Fontanet
0bbb0c289d feat(xapi/pool_emergencyShutdown): new method
Related to #7277
2024-01-10 15:55:14 +01:00
Julien Fontanet
80097ea777 fix(backups/RestoreMetadataBackup): fix data path resolution
Introduced by ad46bde30

Fixes https://xcp-ng.org/forum/post/68999
2024-01-10 15:39:41 +01:00
Julien Fontanet
be452a5d63 fix(xo-web/jobs/new): reset params on method change
Fixes https://xcp-ng.org/forum/post/69299
2024-01-10 14:05:02 +01:00
Julien Fontanet
bcc0452646 feat(CODE_OF_CONDUCT): update to Contributor Covenant 2.1 2024-01-09 16:36:29 +01:00
Julien Fontanet
9d9691c5a3 fix(xen-api/setFieldEntry): avoid unnecessary MAP_DUPLICATE_KEY error
Fixes https://xcp-ng.org/forum/post/68761
2024-01-09 15:10:37 +01:00
Julien Fontanet
e56edc70d5 feat(xo-cli): 0.24.0 2024-01-09 14:29:24 +01:00
Julien Fontanet
d7f4d0f5e0 feat(xo-cli rest get): support NDJSON responses
Fixes https://xcp-ng.org/forum/post/69326
2024-01-09 14:24:48 +01:00
Julien Fontanet
8c24dd1732 fix(xapi/host_smartReboot): disable the host before fetching resident VMs
Otherwise it might leads to race condition where new VMs appear on the
host but are ignored by this method.
2024-01-08 17:11:21 +01:00
Julien Fontanet
575a423edf fix(xapi/host_smartReboot): resume VMs even if host was originally disabled
The host will always be enabled after this method anyway.
2024-01-08 17:09:53 +01:00
Julien Fontanet
e311860bb5 fix(xapi/host/waitAgentRestart): wait for enabled status 2024-01-08 17:05:30 +01:00
Julien Fontanet
e6289ebc16 docs(rest-api): update TOC 2024-01-08 16:15:32 +01:00
Julien Fontanet
013e20aa0f docs(rest-api): task monitoring 2024-01-08 16:14:20 +01:00
Julien Fontanet
45a0a83fa4 chore(CHANGELOG.unreleased): sort packages 2024-01-08 14:46:17 +01:00
Guillaume de Lafond
ae518399fa docs(configuration): useForwardedHeaders (#7289) 2024-01-08 11:35:24 +01:00
Ronan Abhamon
d949112921 fix(load-balancer): bad comparison to evaluate migration in perf plan (#7288)
Memory is compared to CPU usage to migrate VM in performance plan context.

This condition can cause unwanted migrations.
2024-01-08 11:25:40 +01:00
Manon Mercier
bb19afc45c Update backups.md (#7283)
This change follows a discussion with Marc Pezin and Yannick on Mattermost.

As Yannick pointed out, the doc refers to a remote while there is no such option in XO GUI.
2024-01-06 15:25:51 +01:00
Julien Fontanet
7780cb176a fix(backups/_MixinXapiWriter#healthCheck): add_tag → add_tags
Fixes https://xcp-ng.org/forum/post/69156

Introduced by a5acc7d26
2024-01-06 15:16:51 +01:00
Julien Fontanet
74ff64dfb4 fix(xo-server/collection/redis#_extract): properly ignore missing entries
Introduced by d8280087a

Fixes #7281
2024-01-05 13:53:46 +01:00
Julien Fontanet
9be3c40ead feat(xo-server/collection/redis#_get): return undefined if missing
Related to #7281
2024-01-05 13:52:45 +01:00
OlivierFL
0f00c7e393 fix(lite): typings errors when running yarn type-check (#7278) 2024-01-04 11:33:30 +01:00
OlivierFL
95492f6f89 fix(xo-web/menu): don't subscribe to proxies if not admin (#7249) 2024-01-04 11:05:53 +01:00
Olivier Floch
046fa7282b feat(xo-web): open github issue url with query params when clicking on bug report button 2024-01-04 11:04:27 +01:00
Olivier Floch
6cd99c39f4 feat(github): add github issue form template 2024-01-04 11:04:27 +01:00
Julien Fontanet
48c3a65cc6 fix(xo-server): VM_import() returns ref, not record
Introduced by 70b0983
2024-01-04 09:36:42 +01:00
OlivierFL
8b0b2d7c31 fix(xo-web/menu): don't subscribe to unhealthy vdi chains if not admin (#7265) 2024-01-03 18:11:34 +01:00
Julien Fontanet
d8280087a4 fix(xo-server/collection/redis#_extract): don't ignore empty records 2024-01-03 17:11:50 +01:00
Julien Fontanet
c14261a0bc fix(xo-cli): close connection on sign in error
Otherwise the CLI does not stop.
2024-01-03 17:06:29 +01:00
Julien Fontanet
3d6defca37 fix(xo-server/emergencyShutdownhost): disable host first 2024-01-03 16:26:07 +01:00
Julien Fontanet
d062a5175a chore(xo-server/emergencyShutdownhost): unnecessary var 2024-01-03 16:25:12 +01:00
Julien Fontanet
f218874c4b fix(xo-server/_createProxyVm): {this → _app}.getObject
Fixes zammad#20646

Introduced by 70b0983
2024-01-03 10:58:19 +01:00
Julien Fontanet
b1e879ca2f feat: release 5.90.0 2023-12-29 11:03:07 +01:00
Julien Fontanet
c5010c2caa feat(xo-web): 5.133.0 2023-12-29 10:48:02 +01:00
Julien Fontanet
2c40b99d8b feat(xo-web): scoped tags (#7270)
Based on #7258 developed by @fbeauchamp.

- use inline blocks to respect all paddings/margins
- main settings are in easily modifiable variables
- text color is either black or white based on the background color luminance
- make sure tags and surrounding action buttons are aligned
- always display value in black on white
- delete button use tag color if dark, otherwise black
- Tag component accept color param
2023-12-28 23:10:35 +01:00
Mathieu
0d127f2b92 fix(lite): fix changelog entry (#7269) 2023-12-28 15:56:21 +01:00
Mathieu
0464886e80 feat(lite): 0.1.7 (#7268) 2023-12-28 15:49:09 +01:00
Mathieu
d655a3e222 feat: technical release (#7266) 2023-12-27 16:07:51 +01:00
b-Nollet
579f0b91d5 feat(xo-web,xo-server): restart VM to change memory (#7244)
Fixes #7069

Add modal to restart VM to increase memory.
2023-12-26 23:46:43 +01:00
Florent BEAUCHAMP
72b1878254 fix(vhd-lib/createStreamNbd): skip original table offset before overwriting (#7264)
Introduced by fc1357db93
2023-12-26 22:29:24 +01:00
MlssFrncJrg
74dd4c8db7 feat(lite/nav): display VM count in host when menu is minimized (#7185) 2023-12-26 13:30:23 +01:00
mathieuRA
ef4ecce572 feat(xo-server/PIF): add XO tasks for PIF.reconfigureIp 2023-12-26 11:22:35 +01:00
mathieuRA
1becccffbc feat(xo-web/host/network): display and edit the IPv6 PIF field 2023-12-26 11:22:35 +01:00
mathieuRA
b95b1622b1 feat(xo-server/PIF): PIF.reconfigureIp handle IPv6 2023-12-26 11:22:35 +01:00
Manon Mercier
36d6e3779d docs: XenServer → XCP-ng/XenServer (#7255)
I would like to replace every "XenServer" I find in the doc by "XCP-ng/XenServer".

This follows an internal conversation we had with Olivier and Yann.
2023-12-26 11:21:16 +01:00
Pierre Donias
b0e000328d feat(lite): XOA quick deploy (#7245) 2023-12-22 15:58:54 +01:00
Pierre Donias
cc080ec681 feat: technical release (#7259) 2023-12-22 15:05:17 +01:00
Julien Fontanet
0d4cf48410 feat(xo-cli rest): explicit error if not registered
Fixes https://xcp-ng.org/forum/post/68698
2023-12-22 11:33:08 +01:00
OlivierFL
2ec164c560 feat(xo-web/menu): add warning icon in proxies menu (#7237)
See Zammad#19126

Add a warning icon in Proxies menu if unable to check proxy upgrade
2023-12-22 09:53:00 +01:00
b-Nollet
d38dce9302 feat(xo-server/vm.stop): add forceShutdownDelay (#7247) 2023-12-22 09:13:36 +01:00
Florent BEAUCHAMP
4c775f6b86 fix(xo-server): missing data in mirror backup job (#7254)
Mirror Backup jobs weren't storing all the data necessary for sending the report
2023-12-21 17:39:39 +01:00
b-Nollet
dd6c858737 fix(xo-server): replacing wrong param name (#7256)
Introduced by 1da05e239d

When the `Task` class is imported from `@vates/tasks`, `Task.run()` parameter uses `properties` key instead of `data` . This is corrected in this PR.
2023-12-21 17:38:58 +01:00
Julien Fontanet
194db8d0dd fix(xo-server/Redis#{add,update}): return complete record
Introduced by 32afd5c46

Fixes https://xcp-ng.org/forum/post/68664

The returned record should not be serialized and should contain the identifier.
2023-12-21 15:47:11 +01:00
Julien Fontanet
5b00cf3ecd chore(xo-server/PluginMetadata#merge): don't return record 2023-12-21 15:47:11 +01:00
Julien Fontanet
afa957b305 chore(xo-server/updateCloudConfig): don't return record 2023-12-21 15:47:11 +01:00
Julien Fontanet
ea1921625e chore(xo-server/create{Group,User}): simplify code 2023-12-21 15:47:11 +01:00
Julien Fontanet
65a154a1b3 chore(xo-server/updateJob): don't return record 2023-12-21 15:47:11 +01:00
Julien Fontanet
2249c90be9 chore(xo-server/subjects): update accept array 2023-12-21 15:47:11 +01:00
Julien Fontanet
1b55d5e2b4 chore(xo-server/scheduling): use builtin (un)serialize 2023-12-21 15:47:11 +01:00
Julien Fontanet
9f5be8029a chore(xo-server/remotes): update accept array 2023-12-21 15:47:11 +01:00
Julien Fontanet
16458b953f chore(xo-server/jobs): use builtin (un)serialize 2023-12-21 15:47:11 +01:00
Julien Fontanet
d63a567a1c chore(xo-server/CloudConfigs): remove useless method 2023-12-21 15:47:11 +01:00
Florent Beauchamp
d36e10e73f feat(xo-server/rest-api): expose NBD settings in VDI exports 2023-12-21 11:54:08 +01:00
Florent Beauchamp
5d80a58754 fix(vhd-lib/createStreamNbd): fix off by one block length computation 2023-12-21 11:54:08 +01:00
Florent Beauchamp
77b14a316f feat(xo-server/vdi.exportContent): expose NBD settings 2023-12-21 11:54:08 +01:00
Florent BEAUCHAMP
213eb6a56a feat(xo-web/sr): show coalescing status (#7241) 2023-12-21 11:29:32 +01:00
OlivierFL
2c298ef47a fix(lite): infinite loader when no stats on pool dashboard (#7236) 2023-12-21 11:27:06 +01:00
Pierre Donias
b7b7af8cff fix(CHANGELOG): more specific description for mirror backup report fix (#7253)
Introduced by #7235
See https://github.com/vatesfr/xen-orchestra/pull/7235#discussion_r1431612789
2023-12-21 11:21:06 +01:00
Pierre Donias
5cf5d14449 feat: technical release (#7252) 2023-12-20 16:39:27 +01:00
Julien Fontanet
e0bf9ee9d5 fix(proxy): wait the HTTP server to listen 2023-12-20 15:23:48 +01:00
Florent BEAUCHAMP
54808967f6 fix(backups): handle undefined isVhdDifferencing (#7248)
Introduced by e13d55bfa9

Fixes https://xcp-ng.org/forum/post/68600
2023-12-20 15:23:23 +01:00
Pierre Donias
c63d38dc0f chore(lite): release script (#7219) 2023-12-20 15:07:10 +01:00
Mathieu
41ed5625be fix(xo-server/RPU): correctly migrate VMs to their original host (#7238)
See zamma#19772
Introduced by bea771
2023-12-19 19:22:50 +01:00
Mathieu
e66bcf2a5c feat(xapi/VDI_exportContent): create XAPI task during NBD export (#7228)
See zammad#19003
2023-12-19 19:16:44 +01:00
b-Nollet
c40e71ed49 fix(xo-web/backup): fix reportWhen being reset to undefined (#7235)
See Zammad#17506

Preventing `reportWhen` value from becoming undefined (and resetting to the
default value _Failure_) when a mirror backup is edited without modifying this
field.
2023-12-19 17:08:19 +01:00
Florent BEAUCHAMP
439c721472 refactor(backups/cleanVm) use of merge parameter 2023-12-19 15:42:59 +01:00
Florent Beauchamp
99429edf23 test(backups/cleanVm): fix after changes 2023-12-19 15:42:59 +01:00
Florent Beauchamp
cec8237a47 feat(xo-web): show if an incremental backup is a delta or a full 2023-12-19 15:42:59 +01:00
Florent Beauchamp
e13d55bfa9 feat(backups): store if disks of incremental backups are differencing in metadata 2023-12-19 15:42:59 +01:00
Florent Beauchamp
141c141516 refactor(backups): differentialVhds to isVhdDifferencing 2023-12-19 15:42:59 +01:00
Florent Beauchamp
7a47d23191 feat(backups): show warning message if xva are truncated 2023-12-19 15:42:59 +01:00
Florent Beauchamp
7a8bf671fb feat(backups): put back warning message on truncated vhds 2023-12-19 15:42:59 +01:00
Florent Beauchamp
7f83a3e55e fix(backup, vhd-lib): merge now return the resulting size and merged size 2023-12-19 15:42:59 +01:00
Florent Beauchamp
7f8ab07692 fix(backup): transferred backup size for incremental backup 2023-12-19 15:42:59 +01:00
Julien Fontanet
2634008a6a docs(VM backups): fix settings link 2023-12-19 15:41:39 +01:00
Florent Beauchamp
4c652a457f fixup! refactor(nbd-client): remove unused method and default iterator 2023-12-19 15:28:32 +01:00
Florent Beauchamp
89dc40a1c5 fixup! feat(nbd-client,xapi): implement multiple connections NBD 2023-12-19 15:28:32 +01:00
Florent Beauchamp
04a7982801 test(nbd-client): add test for unaligned export size 2023-12-19 15:28:32 +01:00
Florent Beauchamp
df9b59f980 fix(nbd-client): better handling of multiple disconnection 2023-12-19 15:28:32 +01:00
Florent Beauchamp
fe215a53af feat(nbd-client): expose exportSize on multi nbd client 2023-12-19 15:28:32 +01:00
Florent Beauchamp
0559c843c4 fix(nbd-client): put back retry on block read 2023-12-19 15:28:32 +01:00
Florent Beauchamp
79967e0eec feat(nbd-client/multi): allow partial connexion 2023-12-19 15:28:32 +01:00
Florent Beauchamp
847ad63c09 feat(backups,xo-server,xo-web): make NBD concurrency configurable 2023-12-19 15:28:32 +01:00
Florent Beauchamp
fc1357db93 refactor(nbd-client): remove unused method and default iterator 2023-12-19 15:28:32 +01:00
Florent Beauchamp
b644cbe28d feat(nbd-client,xapi): implement multiple connections NBD 2023-12-19 15:28:32 +01:00
Florent Beauchamp
7ddfb2a684 fix(vhd-lib/createStreamNbd): create binary instead of object stream
This reduces memory consumption.
2023-12-19 15:28:32 +01:00
Julien Fontanet
5a0cfd86c7 fix(xo-server/remote#_unserialize): handle boolean enabled
Introduced by 32afd5c46

Fixes #7246
Fixes https://xcp-ng.org/forum/post/68575
2023-12-19 15:11:34 +01:00
Julien Fontanet
70e3ba17af fix(CHANGELOG.unreleased): metadata → mirror
Introduced by 2b973275c
2023-12-19 15:07:01 +01:00
Julien Fontanet
4784bbfb99 fix(xo-server/vm.migrate): dont hide original error 2023-12-18 15:02:51 +01:00
MlssFrncJrg
ceddddd7f2 fix(xo-server/PIF): call PIF.forget to delete PIF (#7221)
Fixes #7193

Calling `pif.destroy` generates PIF_IS_PHYSICAL error when the PIF has been
physically removed
2023-12-18 14:41:58 +01:00
Julien Fontanet
32afd5c463 feat(xo-server): save JSON records in Redis (#7113)
Better support of non-string properties without having to handle them one by one.

To avoid breaking compatibility with older versions, this should not be merged after reading JSON records has been supported for at least a month.
2023-12-18 14:26:51 +01:00
Julien Fontanet
ac391f6a0f docs(incremental_backups): fix image path
Introduced by a0b50b47e
2023-12-18 11:45:23 +01:00
Manon Mercier
a0b50b47ef docs(incremental_backups): explain NBD (#7240) 2023-12-18 11:41:20 +01:00
b-Nollet
e3618416bf fix(xo-server-transport-slack): slack-node → @slack/webhook (#7220)
Fixes #7130

Changing the package used for Slack webhooks, to ensure compatibility with slack-compatible services like Discord or Mattermost.
2023-12-18 10:56:54 +01:00
Julien Fontanet
37fd6d13db chore(xo-server-transport-email): dont use deprecated nodemailer-markdown
BREAKING CHANGE: now requires Node >=18.'

Permits the use of a more recent `marked`.
2023-12-17 11:56:18 +01:00
Julien Fontanet
eb56666f98 chore(xen-api,xo-server): update to proxy-agent@6.3.1 2023-12-16 16:41:10 +01:00
Julien Fontanet
b7daee81c0 feat(xo-server): http.useForwardedHeaders (#7233)
Fixes https://xcp-ng.org/forum/post/67625

This setting can be enabled when XO is behind a reverse proxy to
fetch clients IP addresses from `X-Forwarded-*` headers.
2023-12-16 12:34:58 +01:00
Julien Fontanet
bee0eb9091 chore: update dev deps 2023-12-15 14:09:18 +01:00
Julien Fontanet
59a9a63971 feat(xo-server/store): ensure leveldb only accessible to current user 2023-12-13 11:36:31 +01:00
Julien Fontanet
a2e8b999da feat(xo-server-auth-saml): forceAuthn setting (#7232)
Fixes https://xcp-ng.org/forum/post/67764
2023-12-13 11:25:16 +01:00
OlivierFL
489ad51b4d feat(lite): add new UiStatusPanel component (#7227) 2023-12-12 11:44:22 +01:00
Julien Fontanet
7db2516a38 chore: update dev deps 2023-12-12 10:30:11 +01:00
Julien Fontanet
1141ef524f fix(xapi/host_smartReboot): retries when HOST_STILL_BOOTING (#7231)
Fixes #7194
2023-12-11 16:04:43 +01:00
OlivierFL
f449258ed3 feat(lite): add indeterminate state on FormToggle component (#7230) 2023-12-11 14:48:24 +01:00
Julien Fontanet
bb3b83c690 fix(xo-server/rest-api): proper 404 in case of missing backup job 2023-12-08 15:19:48 +01:00
Julien Fontanet
2b973275c0 feat(xo-server/rest-api): expose metadata & mirror backup jobs 2023-12-08 15:17:51 +01:00
Julien Fontanet
037e1c1dfa feat(xo-server/rest-api): /backups → /backup 2023-12-08 15:14:06 +01:00
Julien Fontanet
f0da94081b feat(gen-deps-list): detect duplicate packages
Prevents a bug where a second entry would override the previous one and possibly
decrease the release type (e.g. `major + patch → patch`).
2023-12-07 17:15:09 +01:00
Julien Fontanet
cd44a6e28c feat(eslint): enable require-atomic-updates rule 2023-12-07 17:05:21 +01:00
Julien Fontanet
70b09839c7 chore(xo-server): use @xen-orchestra/xapi/VM_import when possible 2023-12-07 16:50:45 +01:00
OlivierFL
12140143d2 feat(lite): added tooltip on CPU provisioning warning icon (#7223) 2023-12-07 09:07:15 +01:00
b-Nollet
e68236c9f2 docs(installation): update Debian & Fedora packages (#7207)
Fixes #7095
2023-12-06 15:39:50 +01:00
Julien Fontanet
8a1a0d76f7 chore: update dev deps 2023-12-06 11:09:54 +01:00
Mathieu
4a5bc5dccc feat(lite): override host address with 'master' query param (#7187) 2023-12-04 11:31:35 +01:00
MlssFrncJrg
0ccdfbd6f4 feat(xo-web/SR): improve forget SR modal message (#7155) 2023-12-04 09:33:50 +01:00
Mathieu
75af7668b5 fix(lite/changelog): fix xolite changelog (#7215) 2023-12-01 10:48:22 +01:00
Thierry Goettelmann
0b454fa670 feat(lite/VM): ability to migrate a VM (#7164) 2023-12-01 10:38:55 +01:00
Pierre Donias
2dcb5cb7cd feat(lite): 0.1.6 (#7213) 2023-11-30 16:01:06 +01:00
Thierry Goettelmann
a5aeeceb7f chore(lite): upgrade dependencies (#7170)
1. Since the project is built-only, all deps have been moved to `devDependencies`
2. TypeScript has been upgraded from 4.9 to 5.2
3. `engines.node` requirement was set to `>=8.10`. It has been updated to `>=18` to be aligned with deps requirements
2023-11-30 15:13:36 +01:00
Florent BEAUCHAMP
b2f2c3cbc4 feat: release 5.89.0 (#7212) 2023-11-30 13:57:49 +01:00
Florent BEAUCHAMP
0f7ac004ad feat: technical release (#7211) 2023-11-30 10:42:54 +01:00
Florent Beauchamp
7faa82a9c8 feat(xo-web): add UX for differential backup 2023-11-30 10:12:20 +01:00
Florent Beauchamp
4b3f60b280 feat(backups): implement differential restore
When restoring a backup, try to reuse data from an existing snapshot.
We use this snasphot and apply a reverse differential of the changes
between the backup and the snapshot

Prerequisite : a uninterrupted delta chain from the backup being
restored to a backup that still have its snapshot on the host
2023-11-30 10:12:20 +01:00
Florent Beauchamp
b29d5ba95c feat(vhd-lib): implement a limit in VhdSynthetic.fromVhdChain 2023-11-30 10:12:20 +01:00
Florent Beauchamp
408fc5af84 feat(vhd-lib): implement VhdNegative
it's a virtual Vhd that contains all the changes to be applied
to reset a child to its parent value
2023-11-30 10:12:20 +01:00
Florent BEAUCHAMP
2748aea4e9 feat: technical release (#7210) 2023-11-29 15:39:25 +01:00
Florent Beauchamp
a5acc7d267 fix(backups,xo-server): don't backup VMs created by Health Check 2023-11-29 14:46:05 +01:00
Florent Beauchamp
87a9fbe237 feat(xo-server,xo-web): don't backup VMs with xo:no-bak tag 2023-11-29 14:46:05 +01:00
Julien Fontanet
9d0b7242f0 fix(xapi-explore-sr): use xen-api@2.0.0 2023-11-29 14:42:50 +01:00
Julien Fontanet
20ec44c3b3 fix(xo-server/registerHttpRequestHandler): match on path
Instead of full URL, so that handlers can manage query string.
2023-11-29 14:42:50 +01:00
Julien Fontanet
6f68456bae feat(xo-server/registerHttpRequestHandler): returns teardown function
`unregisterHttpRequestHandler` is no longer useful and has been removed.
2023-11-29 14:42:50 +01:00
Florent BEAUCHAMP
b856c1a6b4 feat(xo-server,xo-web): show link to the SR for the garbage collector (coalesce) task (#7189)
See https://github.com/vatesfr/xen-orchestra/issues/5379#issuecomment-1765170973
2023-11-29 09:07:05 +01:00
Julien Fontanet
61e1f83a9f feat(xo-server/rest-api): possibility to import a VM 2023-11-28 17:54:10 +01:00
Mathieu
5820e19731 feat(xo-web/VM): display task information on VDI import (#7197) 2023-11-28 15:41:10 +01:00
Pierre Donias
cdb51f8fe3 chore(lite/settings): use FormSelect instead of select (#7206) 2023-11-28 14:46:25 +01:00
Florent BEAUCHAMP
57940e0a52 fix(backups): import on non default SR (#7209) 2023-11-28 14:35:08 +01:00
Florent BEAUCHAMP
6cc95efe51 feat: technical release (#7208) 2023-11-28 09:30:32 +01:00
Pierre Donias
b0ff2342ab chore(netbox): remove null-indexed entries from keyed-by collections (#7156) 2023-11-27 16:26:53 +01:00
Mathieu
0f67692be4 feat(xo-server/xostor): add XO tasks (#7201) 2023-11-27 16:11:53 +01:00
Julien Fontanet
865461bfb9 feat(xo-server/api): backupNg.{,un}mountPartition (#7176)
Manual method to mount a backup partition on the XOA.
2023-11-24 09:47:23 +01:00
Julien Fontanet
e108cb0990 feat(xo-server/rest-api): possibility to import in an existing VDI (#7199) 2023-11-23 17:07:40 +01:00
Florent BEAUCHAMP
c4535c6bae fix(fs/s3): enable md5 if object lock status is unknown (#7195)
From https://xcp-ng.org/forum/topic/7939/unable-to-connect-to-backblaze-b2/7?_=1700572613725
Following 796e2ab674 

User report it fixes the issue https://xcp-ng.org/forum/post/67633
2023-11-23 16:43:25 +01:00
Julien Fontanet
ad8eaaa771 feat(xo-cli): support REST PUT method 2023-11-23 16:30:03 +01:00
Julien Fontanet
9419cade3d feat(xo-server/rest-api): tags property can be updated 2023-11-23 16:30:03 +01:00
Julien Fontanet
272e6422bd chore(xapi/VM_import): typo snapshots → snapshot 2023-11-23 16:28:30 +01:00
Julien Fontanet
547908a8f9 chore(xo-server/proxy.checkHealth): call checkProxyHealth 2023-11-23 16:28:29 +01:00
Mathieu
8abfaa0bd5 feat(lite/VM): ability to export a VM (#7190) 2023-11-23 11:00:38 +01:00
MlssFrncJrg
a9fbcf3962 feat(xo-web/new VM): always show ISO selector (#7166)
Fixes #3464
2023-11-22 11:04:30 +01:00
Michael Bennett
887b49ebbf docs(installation): Fedora & CentOS wrong package libvhd-utils (#7200)
Under Packages the installation of package `libvhdi-utils` is incorrect for Fedora/CentOS. This should be replaced by `libvhdi-tools` instead.
2023-11-21 17:46:17 +01:00
Florent BEAUCHAMP
858ecbc217 fix(xapi/VDI_importContent): other_config entries must be strings (#7198)
Introduced byffd523679de80b36b2eacd30cc98de3c588a2b77
2023-11-21 16:55:53 +01:00
Florent BEAUCHAMP
ffd523679d feat(backups): update VDI importing status its name_label 2023-11-21 14:38:49 +01:00
Florent BEAUCHAMP
bd9db437f1 feat(xapi/VDI_importContent): store task UUID and stream length into other_config 2023-11-21 14:38:49 +01:00
Florent BEAUCHAMP
0365bacfbb feat(backups): show more detail on restored VM (#7186) 2023-11-21 12:28:53 +01:00
MlssFrncJrg
f3e0227c55 feat(xo-web/console): add disabled console message (#7161)
Fixes #6319
2023-11-21 10:39:35 +01:00
Florent BEAUCHAMP
4504141cbf refactor(backups/importIncrementalVm): move base detection to callers (#7165) 2023-11-20 14:52:59 +01:00
b-Nollet
ecbbf878d0 chore(xen-api): convert to ESM (#7181) 2023-11-20 14:32:44 +01:00
MlssFrncJrg
c1faaa3107 fix(xo-server/resource-set): fix error when changing VM resource set (#7144) 2023-11-20 14:19:27 +01:00
Julien Fontanet
59f04b4a6b chore: format with Prettier 2023-11-20 12:34:30 +01:00
Julien Fontanet
781b070e74 fix(xen-api/examples/import-vdi): params handling 2023-11-20 11:45:45 +01:00
Julien Fontanet
1911386aba chore: refresh yarn.lock 2023-11-20 09:55:07 +01:00
MlssFrncJrg
5b0339315f docs(Support): remove Partner Program (#7099) 2023-11-20 09:42:21 +01:00
b-Nollet
5fe53dfa99 refactor(xapi-explore-sr): convert to EM (#7191) 2023-11-17 16:55:11 +01:00
b-Nollet
06068cdcc6 refactor(cr-seed-cli): convert to ESM (#7192) 2023-11-17 16:46:36 +01:00
Julien Fontanet
c88cc2b020 chore(xo-server/token.create): allow 60s for expiresIn
It makes more sense for the min accepted value to be 60s than 60,001ms.
2023-11-17 10:57:48 +01:00
Pierre Donias
03de8ad481 docs(netbox): update steps and screenshots with latest version (#7182) 2023-11-16 16:06:01 +01:00
Julien Fontanet
08ba7e7253 chore: refresh yarn.lock 2023-11-16 10:21:17 +01:00
Florent BEAUCHAMP
9ca3f3df26 fix(xo-vmdk-to-vhd): improve compatibilty of ova with disk bigger than 8.2GB (#7183)
following #7047, from https://xcp-ng.org/forum/topic/7946/ova-export-not-functional?_=1700051758755

ova exported from xo with more than 8.2G data per disk can't be imported in virtual box 

tar-stream@3 pack and entry are now streams
2023-11-15 16:23:33 +01:00
Mathieu
511908bb7d feat(lite/pool/VMs): ability to export selected VMs (#7174) 2023-11-15 15:29:25 +01:00
Thierry Goettelmann
4351aad312 feat(lite): new FormByteSize component (#6741) 2023-11-15 15:28:46 +01:00
Florent BEAUCHAMP
af7aa29c91 feat(nbd-client): various fixes (#6964) 2023-11-15 10:04:09 +01:00
Thierry Goettelmann
315d626055 fix(lite/story): code highlight modal path (#7180) 2023-11-15 09:58:15 +01:00
Pierre Donias
7af0899800 feat(netbox): sync XO users as Netbox tenants (#7158)
See Zammad#11356
See Zammad#17364
See Zammad#18409
2023-11-14 15:25:56 +01:00
Florent BEAUCHAMP
46ec2dfd56 fix(vmware-explorer): better handling of VM import without any storage (#7168) 2023-11-14 15:14:13 +01:00
Thierry Goettelmann
b2348474c3 fix(lite/pool): host patches list is broken if changelog property is empty (#7169) 2023-11-14 15:08:56 +01:00
Julien Fontanet
836300755a feat: release 5.88.2 2023-11-13 15:07:43 +01:00
Julien Fontanet
55c8c8a6e9 feat(xo-server): 5.126.0 2023-11-13 11:41:08 +01:00
Julien Fontanet
38e32cd24c chore: update dev deps 2023-11-13 09:48:54 +01:00
Julien Fontanet
5ceacfaf5a fix(xo-server/redis): fix searching with multiple indexes
Introduced by 36b94f745
2023-11-12 22:18:19 +01:00
Thierry Goettelmann
1ee6b106b9 feat(lite/ui): compact layout (#7159) 2023-11-10 16:26:20 +01:00
Julien Fontanet
eaef4f22d2 fix(xo-web/settings/logs): use template when reporting
Related to #7142
2023-11-10 11:33:01 +01:00
Julien Fontanet
96025df12f feat(xo-server): only create a single token per web client (and user)
Related to e07e2d3cc

Similar to 581b42fa9
2023-11-09 17:13:10 +01:00
Mathieu
a8aac295eb fix(lite/login): correctly handle login from slave (#7110) 2023-11-09 15:00:29 +01:00
Mathieu
83141989f0 feat(xolite/modals): add onClose event (#7167) 2023-11-09 10:57:24 +01:00
Julien Fontanet
9dea52281d docs(installation): explicit Redis should be started 2023-11-07 17:10:35 +01:00
Julien Fontanet
2164c72034 fix(xo-server): log redis errors
Avoid unhandled error events.
2023-11-07 16:08:41 +01:00
Julien Fontanet
0d0c38f3b5 fix(backups): create suspend VDI on correct SR
Introduced by a958fe86d
2023-11-07 15:55:14 +01:00
Pierre Donias
e5be21a590 feat(lite): 0.1.5 (#7162) 2023-11-07 15:49:12 +01:00
Julien Fontanet
bc1a8be862 chore: fix formatting 2023-11-07 14:33:16 +01:00
Mathieu
3df4dbaae7 feat: release 5.88.1 (#7163) 2023-11-07 14:30:53 +01:00
Julien Fontanet
8f2cfebda6 feat(xo-server/rest-api): add users collection 2023-11-07 12:51:12 +01:00
Julien Fontanet
0d00c1c45f chore: update dev deps 2023-11-07 12:32:38 +01:00
Mathieu
9886e06d6a feat: technical release (#7160) 2023-11-07 10:18:09 +01:00
Thierry Goettelmann
478dbdfe41 feat(lite): new modal management (#7134) 2023-11-07 09:49:02 +01:00
Florent BEAUCHAMP
2bfdb60dda fix(fs): handle object storage server not implementing Object lock (#7157) 2023-11-06 17:18:00 +01:00
Pierre Donias
cabd04470d feat(xo-server-netbox): use Netbox version instead of Netbox API version (#7138)
Netbox version is more precise (X.Y.Z instead of X.Y)
2023-11-06 17:10:10 +01:00
Florent BEAUCHAMP
f6819b23f9 fix(xo-web/dashboard): empty VDIs shouldn't be flagged as orphan (#7102)
Fixes zammad#15524
2023-11-06 13:57:44 +01:00
Julien Fontanet
c9dbcf1384 fix(proxy/backup.importVmBackup): only dispose resources at the end (#7152)
Fixes #7052

Fixes zammad#17383

When a stream is returned, the handler immediately returned a stream which disposed the resource.

Due to the disposable having a 5 mins debounce delay, the problem was only apparent after 5 mins.
2023-11-06 10:30:34 +01:00
Pierre Donias
457fec0bc8 fix(xo-server-netbox): properly delete all interfaces that don't have a UUID (#7153)
Fixes zammad#18812

Introduced by 3b1bcc67ae

The first step of synchronizing VIFs with Netbox interfaces is to clean up any interface attached to the Netbox VM that isn't found on the XO VM, *based on their UUID*, including the interfaces that don't have a UUID at all (`uuid` is `null`).

But by looping over the keyed-by-UUID collection, we could only ever delete at most *one* null-UUID interface, the other ones being dropped by `keyBy`.

Using the flat-array collection instead makes sure all the interfaces are handled.
2023-11-06 10:28:43 +01:00
Pierre Donias
db99a22244 fix(xo-web/New network): only hide bond-PIFs when creating a bonded network (#7151)
Fixes #7150
See https://xcp-ng.org/forum/topic/7918
Introduced by dbdc5f3e3b
2023-11-03 11:22:10 +01:00
Pierre Donias
89d8adc6c6 feat(netbox): expose raw HTTP body if it cannot be JSON-parsed (#7146) 2023-11-02 14:28:06 +01:00
Pierre Donias
a3ea70c61c fix(xo-server-netbox): fix site property null/undefined cases (#7145)
Introduced by 1d7559ded2
2023-10-31 16:16:38 +01:00
Mathieu
ae0f3b4fe0 feat: release 5.88.0 (#7143) 2023-10-31 14:58:50 +01:00
Mathieu
2552ef37d2 feat: technical release (#7141) 2023-10-31 10:09:35 +01:00
Pierre Donias
9803e8c6cb feat(xo-web/patches): warning about updating pool master first (#7140) 2023-10-31 09:51:17 +01:00
Florent BEAUCHAMP
3410cbc3b9 fix(backups): use VDI virtual_size instead of physical_size
`physical_size` appears to be broken
2023-10-30 15:55:30 +01:00
Florent BEAUCHAMP
93fce0d4bf fix(backups): pass type to Xapi#getRecord
Fixes #7131

Introduced by 37b211376
2023-10-30 15:55:30 +01:00
MlssFrncJrg
dbdc5f3e3b feat(xo-web/New network): don't show PIFs that belong to a bond (#7136) 2023-10-30 15:47:38 +01:00
Julien Fontanet
581b42fa9d feat(xo-cli): only create a single token per instance (and user) 2023-10-30 15:47:17 +01:00
Julien Fontanet
e07e2d3ccd feat(xo-server/token): client info support 2023-10-30 15:47:17 +01:00
Mathieu
ad928ec23d fix(xo-web/licenses/XOSTOR): various fixes on XOSTOR licenses (#7137)
Introduced by #6983
2023-10-30 14:55:11 +01:00
Pierre Donias
1d7559ded2 fix(xo-server-netbox/VM): explicitly assign site (#7124)
See Zammad#17766
See https://xcp-ng.org/forum/topic/7887
2023-10-30 11:32:12 +01:00
Mathieu
9099b58557 feat: technical release (#7132) 2023-10-27 16:13:04 +02:00
Julien Fontanet
9e70397240 fix(xo-server/redis): fix indexes handling
Introduced by 225a67ae3
2023-10-27 11:27:25 +02:00
Thierry Goettelmann
5f69b0e9a0 feat(lite/console): new console toolbar (#7088) 2023-10-27 10:27:51 +02:00
Julien Fontanet
2a9bff1607 chore(xo-server/importConfig): don't use deptree 2023-10-27 10:14:02 +02:00
Pierre Donias
9e621d7de8 feat(lite/header): replace logo with "XO LITE" (#7118) 2023-10-27 09:16:28 +02:00
Mathieu
3e5c73528d feat(xo-server,xo-web/XOSTOR): XOSTOR implementation (#6983)
See https://xcp-ng.org/forum/topic/5361
2023-10-26 16:58:59 +02:00
Pierre Donias
397b5cd56d fix(xo-server/snapshot): allow self user that is member of a group to snapshot (#7129)
Introduced by a88798cc22
See Zammad#18478
2023-10-26 16:08:43 +02:00
Julien Fontanet
55cb6042e8 chore(yarn.lock): update dev deps 2023-10-26 11:00:14 +02:00
Pierre Donias
339d920b78 feat(xo-web/proxy): ability to open support tunnel on XO Proxy (#7127)
Requires #7126
2023-10-25 17:26:06 +02:00
Julien Fontanet
f14f716f3d feat(xo-server/api): proxy.openSupportTunnel (#7126)
The goal is to provide an easier way for the support team to open a tunnel on a proxy appliance.

This is the server side of this feature.
2023-10-25 17:12:17 +02:00
Julien Fontanet
fb83d1fc98 feat(xo-server/api): ignorable parameters (#7125) 2023-10-25 15:49:41 +02:00
Julien Fontanet
62208e7847 fix(xo-server-transport-xmpp): fix loading (#7082)
Fixes https://xcp-ng.org/forum/post/66402

Introduced by d6fc86b6b
2023-10-25 14:36:40 +02:00
Julien Fontanet
df91772f5c chore(xo-server/server): use builtin (un)serialize 2023-10-25 11:48:53 +02:00
Julien Fontanet
cf8a9d40be chore(xo-server/remote): use builtin (un)serialize 2023-10-25 11:48:53 +02:00
Julien Fontanet
93d1c6c3fc chore(xo-server/plugin-metadata): use builtin (un)serialize 2023-10-25 11:48:53 +02:00
Julien Fontanet
f1fa811e5c chore(xo-server/user): use builtin (un)serialize 2023-10-25 11:48:53 +02:00
Julien Fontanet
5a9812c492 chore(xo-server/group): use builtin (un)serialize 2023-10-25 11:48:53 +02:00
Julien Fontanet
b53d613a64 chore(xo-server/token): use builtin unserialize 2023-10-25 11:48:53 +02:00
Julien Fontanet
225a67ae3b chore(xo-server/redis): proper (un)serialization support 2023-10-25 11:48:53 +02:00
Mathieu
c7eb7db463 feat(xo-web/about): display if XO from source is up to date (#7091)
Fixes #5934
2023-10-24 17:14:01 +02:00
Pierre Donias
edfa729672 chore(lite/assets): remove darkreader properties in SVG files (#7121) 2023-10-24 16:40:37 +02:00
Mathieu
77d9798319 fix(xo-web/vtpm): fix various an error has occured (#7122)
Introduced by 8834af65f7
Introduced by 1a1dd0531d

Fix `an error has occurred` in the VM advanced tab and on the VM creation form
if the user does not have pool permission.
2023-10-24 16:26:36 +02:00
Pierre Donias
680f1e2f07 chore(lite): serve Poppins font internally (#7117) 2023-10-24 15:19:54 +02:00
Julien Fontanet
7c009b0fc0 feat(xo-server): support reading JSON records in Redis
This allows forward compatibility with future versions which will use JSON records in the future.
2023-10-23 15:13:28 +02:00
Pierre Donias
eb7de4f2dd feat(xo-web/self): show # of VMs that belong to each Resource Set (#7114)
See Zammad#17568
2023-10-23 15:03:30 +02:00
Olivier Lambert
2378399981 docs: update project's README (#7116) 2023-10-23 14:25:03 +02:00
Florent BEAUCHAMP
37b2113763 feat(fs/s3): compute sensible chunk size for uploads 2023-10-23 10:23:50 +02:00
Florent BEAUCHAMP
5048485a85 feat(fs/s3): object lock mode need content md5
and the middleware consume addiitionnal memory
2023-10-23 10:23:50 +02:00
Florent BEAUCHAMP
9e667533e9 fix(fs/s3): throw error if upload >50GB 2023-10-23 10:23:50 +02:00
MlssFrncJrg
1fac7922b4 feat(xo-web/dashboard/health): VDIs to coalesce warning contains the number (#7111)
Fixes Zammad#17577
2023-10-20 15:53:24 +02:00
Julien Fontanet
1a0e5eb6fc chore: format with Prettier 2023-10-20 15:52:10 +02:00
Pierre Donias
321e322492 feat(xo-server/clearHost): pass optional batch size arg (#7107)
Fixes #7105
See https://github.com/xapi-project/xen-api/issues/5202
See https://github.com/xapi-project/xen-api/pull/5203

`host.evacuate`: try passing optional batch size argument.
If not supported: remove it and try again.
2023-10-19 17:03:14 +02:00
Mathieu
8834af65f7 feat(xo-server/xo-web/VM/new): VTPM creation (#7077)
See #7066
See #6802
See #7085
2023-10-19 16:48:56 +02:00
Mathieu
1a1dd0531d feat(xo-web/VM/advanced): VTPM management (#7085)
See #7066
See #6802
See #7074
2023-10-19 15:46:03 +02:00
Pierre Donias
8752487280 docs(installation): add nfs-common dependency for Debian/Ubuntu (#7108) 2023-10-18 22:50:29 +02:00
Pierre Donias
4b12a6d31d fix(xo-server-usage-report): handle null and nested stats (#7092)
Introduced by 083483645e

Fixes Zammad#18120
Fixes Zammad#18266

- Always assume that data can be `null`
- Handle edge cases where all values are `null`
- Properly handle nested RRD collections: collections have different depths (`memory`: 1, `cpus[0]`: 2, `pifs.rx[0]`: 3, ...). This PR replaces `getLastDays` which wouldn't handle those depths properly, with `getDeepLastValues` which is run on the whole stat object and doesn't assume the depth of the collections. It finds any Array at any depth and slices it to only keep the last N values.
2023-10-18 22:50:08 +02:00
Julien Fontanet
2924f82754 fix(xo-web): don't sign out on connection error (#7103)
May fix zammad#17717

Introduced by 005ab47d9
2023-10-18 18:07:16 +02:00
Pierre Donias
9b236a6191 fix(netbox/test): test custom fields first (#7104)
More atomic and it makes more sense for users to check that the Netbox
configuration is correct before doing any write operations
2023-10-18 11:56:10 +02:00
Julien Fontanet
a3b8553cec fix(xo-server,xo-web): fix total number of VDIs to coalesce (#7098)
Fixes #7016

Summing all chains does take not common chains into account, the total must be computed on the server side.
2023-10-18 11:52:43 +02:00
Pierre Donias
00a1778a6d feat(lite): set color-scheme CSS property to "dark" in dark mode (#7101) 2023-10-17 16:50:13 +02:00
MlssFrncJrg
3b6bc629bc fix(xo-web/home): fix misaligned descriptions (#7090) 2023-10-16 15:53:35 +02:00
Pierre Donias
04dfd9a02c fix(xo-server-usage-report): use @xen-orchestra/log to log errors (#7096)
Fixes Zammad#14579
Fixes Zammad#18183

Better handles error objects with a circular structure and avoids "Converting
circular structure to JSON" error on stringify
2023-10-16 10:07:57 +02:00
Pierre Donias
fb52868074 fix(xo-server/patching): always check that XS credentials are configured on XS (#7093)
Introduced by a30d962b1d
2023-10-13 16:49:04 +02:00
Pierre Donias
77d53d2abf fix(xo-server/patching): always pass xsCredentials to installPatches on XS (#7089)
Fixes Zammad#18284

Introduced by a30d962b1d
2023-10-13 11:45:17 +02:00
Julien Fontanet
6afb87def1 feat(xo-server/vm.set): support xenStoreData
Fixes #7055
2023-10-13 11:26:48 +02:00
Mathieu
8bfe293414 feat(lite/VM): add copy, snapshot single action (#7087) 2023-10-12 11:09:11 +02:00
Mathieu
2e634a9d1c feat(xapi/VTPM): ability to create, destroy VTPM (#7074) 2023-10-12 09:19:38 +02:00
Pierre Donias
bea771ca90 fix(xo-server/RPU): do not migrate VM back if already on host (#7071)
See https://xcp-ng.org/forum/topic/7802
2023-10-11 16:16:44 +02:00
Pierre Donias
99e3622f31 feat(xo-web/SelectPif): show network name (#7081)
See Zammad#17381
2023-10-10 15:59:24 +02:00
Pizzosaure
a16522241e docs(netbox): remove extra backtick (#7083)
Introduced by 3b3f927e4b
2023-10-10 14:14:15 +02:00
Julien Fontanet
b86cb12649 chore(yarn.lock): update dev deps 2023-10-09 17:06:54 +02:00
Julien Fontanet
2af74008b2 feat(xo-server-backup-reports): errors are logged as XO tasks 2023-10-09 09:35:24 +02:00
Julien Fontanet
2e689592f1 feat(xo-server-backup-reports): error when transports not enabled 2023-10-09 09:35:24 +02:00
Julien Fontanet
3f8436b58b fix(xo-server/authenticateUser): use clearLogOnSuccess
This fixes success logs not deleted due to race conditions.
2023-10-09 09:35:24 +02:00
Julien Fontanet
e3dd59d684 feat(mixins/Tasks#create): clearLogOnSuccess option 2023-10-09 09:35:24 +02:00
mathieuRA
549d9b70a9 feat(xo-web/host): allow to force smartReboot 2023-10-06 16:52:26 +02:00
mathieuRA
3bf6aae103 feat(xapi/host_smartReboot): ability to bypass blocked operations 2023-10-06 16:52:26 +02:00
Julien Fontanet
afb110c473 fix(fs/rmtree): fix huge memory usage (#7073)
Fixes zammad#15258

This adds a sane concurrency limit of 2 per depth level.

Co-authored-by: Florent BEAUCHAMP <florent.beauchamp@vates.fr>
2023-10-06 09:52:11 +02:00
1207 changed files with 70110 additions and 22519 deletions

View File

@@ -15,9 +15,10 @@ module.exports = {
overrides: [
{
files: ['cli.{,c,m}js', '*-cli.{,c,m}js', '**/*cli*/**/*.{,c,m}js'],
files: ['cli.{,c,m}js', '*-cli.{,c,m}js', '**/*cli*/**/*.{,c,m}js', '**/scripts/**.{,c,m}js'],
rules: {
'n/no-process-exit': 'off',
'n/shebang': 'off',
'no-console': 'off',
},
},
@@ -46,6 +47,209 @@ module.exports = {
],
},
},
{
files: ['@xen-orchestra/{web-core,lite,web}/**/*.{vue,ts}'],
parser: 'vue-eslint-parser',
parserOptions: {
sourceType: 'module',
parser: '@typescript-eslint/parser',
},
plugins: ['import'],
extends: [
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-essential',
],
settings: {
'import/resolver': {
typescript: true,
'eslint-import-resolver-custom-alias': {
alias: {
'@core': '../web-core/lib',
'@': './src',
},
extensions: ['.ts'],
packages: ['@xen-orchestra/lite', '@xen-orchestra/web'],
},
},
},
globals: {
XO_LITE_VERSION: true,
XO_LITE_GIT_HEAD: true,
},
rules: {
'import/order': [
'error',
{
groups: ['builtin', 'object', 'internal', ['type', 'external']],
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
pathGroups: [
{
pattern: '@/**',
group: 'internal',
},
{
pattern: '@core/**',
group: 'internal',
},
],
},
],
'no-void': ['error', { allowAsStatement: true }],
'n/no-missing-import': 'off', // using 'import' plugin instead, to support TS aliases
'no-redeclare': 'off', // automatically checked by the TypeScript compiler
'no-dupe-class-members': 'off', // automatically checked by the TypeScript compiler
'no-undef': 'off', // automatically checked by the TypeScript compiler
'@typescript-eslint/no-explicit-any': 'off',
'vue/multi-word-component-names': 'off',
// Vue 3 - Strongly Recommended
'vue/attribute-hyphenation': 'error',
'vue/component-definition-name-casing': 'error',
'vue/first-attribute-linebreak': 'error',
'vue/html-closing-bracket-newline': 'off', // Conflicts with Prettier
'vue/html-closing-bracket-spacing': 'error',
'vue/html-end-tags': 'error',
'vue/html-indent': 'off', // Conflicts with Prettier
'vue/html-quotes': 'error',
'vue/html-self-closing': ['error', { html: { void: 'always', normal: 'always', component: 'always' } }],
'vue/max-attributes-per-line': 'off', // Conflicts with Prettier
'vue/multiline-html-element-content-newline': 'error',
'vue/mustache-interpolation-spacing': 'error',
'vue/no-multi-spaces': 'error',
'vue/no-spaces-around-equal-signs-in-attribute': 'error',
'vue/no-template-shadow': 'error',
'vue/prop-name-casing': 'error',
'vue/require-default-prop': 'off', // https://github.com/vuejs/eslint-plugin-vue/issues/2051
'vue/require-explicit-emits': 'error',
'vue/require-prop-types': 'error',
'vue/singleline-html-element-content-newline': 'off',
'vue/v-bind-style': 'error',
'vue/v-on-event-hyphenation': ['error', 'always', { autofix: true }],
'vue/v-on-style': 'error',
'vue/v-slot-style': 'error',
// Vue 3 - Recommended
'vue/attributes-order': 'error',
'vue/no-lone-template': 'error',
'vue/no-multiple-slot-args': 'error',
'vue/no-v-html': 'error',
'vue/this-in-template': 'error',
// Vue 3 - Uncategorized
'vue/block-order': ['error', { order: ['template', 'script', 'style'] }],
'vue/block-tag-newline': ['error', { singleline: 'consistent', multiline: 'always', maxEmptyLines: 0 }],
'vue/component-api-style': ['error', ['script-setup']],
'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }],
'vue/custom-event-name-casing': ['error', 'camelCase'],
'vue/define-emits-declaration': ['error', 'type-literal'],
'vue/define-macros-order': [
'error',
{
order: ['defineOptions', 'defineProps', 'defineEmits', 'defineModel', 'defineSlots'],
defineExposeLast: true,
},
],
'vue/define-props-declaration': ['error', 'type-based'],
'vue/enforce-style-attribute': ['error', { allow: ['scoped', 'module'] }],
'vue/html-button-has-type': 'error',
'vue/html-comment-content-newline': ['error', { singleline: 'never', multiline: 'always' }],
'vue/html-comment-content-spacing': ['error', 'always'],
'vue/no-duplicate-attr-inheritance': 'error',
'vue/no-empty-component-block': 'error',
'vue/no-multiple-objects-in-class': 'error',
'vue/no-ref-object-reactivity-loss': 'error',
'vue/no-required-prop-with-default': 'error',
'vue/no-static-inline-styles': 'error',
'vue/no-template-target-blank': 'error',
'vue/no-undef-components': ['error', { ignorePatterns: ['RouterLink', 'RouterView', 'I18nT'] }],
'vue/no-undef-properties': 'error',
'vue/no-unused-refs': 'error',
'vue/no-use-v-else-with-v-for': 'error',
'vue/no-useless-mustaches': 'error',
'vue/no-useless-v-bind': 'error',
'vue/no-v-text': 'error',
'vue/padding-line-between-blocks': 'error',
'vue/prefer-define-options': 'error',
'vue/prefer-separate-static-class': 'error',
'vue/prefer-true-attribute-shorthand': 'error',
'vue/require-macro-variable-name': 'error',
'vue/valid-define-options': 'error',
},
},
// Specific rules for XO i18n-related files
{
files: [
'@xen-orchestra/lite/**/*.{vue,ts,json}',
'@xen-orchestra/web/**/*.{vue,ts,json}',
'@xen-orchestra/web-core/**/*.{vue,ts,json}',
],
extends: ['plugin:@intlify/vue-i18n/base'],
settings: {
'vue-i18n': {
localeDir: [
'@xen-orchestra/lite/src/locales/*.json',
'@xen-orchestra/web/src/locales/*.json',
'@xen-orchestra/web-core/lib/locales/*.json',
],
messageSyntaxVersion: '^9.9.0',
},
},
rules: {
strict: 'off',
'no-irregular-whitespace': 'off',
'@intlify/vue-i18n/no-deprecated-i18n-component': 'error',
'@intlify/vue-i18n/no-deprecated-i18n-place-attr': 'error',
'@intlify/vue-i18n/no-deprecated-i18n-places-prop': 'error',
'@intlify/vue-i18n/no-deprecated-modulo-syntax': 'error',
'@intlify/vue-i18n/no-deprecated-tc': 'error',
'@intlify/vue-i18n/no-html-messages': 'error',
'@intlify/vue-i18n/no-i18n-t-path-prop': 'error',
'@intlify/vue-i18n/no-missing-keys': 'error',
'@intlify/vue-i18n/no-raw-text': [
'error',
{
ignoreText: ['Xen Orchestra Lite', 'Xen Orchestra', 'XO Lite', 'XCP-ng', 'XO 5', '404', '⚠️'],
},
],
'@intlify/vue-i18n/no-v-html': 'error',
'@intlify/vue-i18n/valid-message-syntax': 'error',
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
'@intlify/vue-i18n/no-missing-keys-in-other-locales': ['error', { ignoreLocales: ['de', 'fa'] }],
},
},
// Specific rules for XO dev-related files
{
files: [
'@xen-orchestra/lite/src/stories/**/*',
'@xen-orchestra/lite/src/components/component-story/**/*',
'@xen-orchestra/web/src/pages/dev/**/*',
'@xen-orchestra/lite/src/views/story/**/*',
],
rules: {
'@intlify/vue-i18n/no-raw-text': 'off',
},
},
{
files: ['@xen-orchestra/{web-core,lite,web}/src/pages/**/*.vue'],
parserOptions: {
sourceType: 'module',
},
rules: {
'vue/multi-word-component-names': 'off',
},
},
{
files: ['@xen-orchestra/{web-core,lite,web}/typed-router.d.ts'],
parserOptions: {
sourceType: 'module',
},
rules: {
'eslint-comments/disable-enable-pair': 'off',
'eslint-comments/no-unlimited-disable': 'off',
},
},
],
parserOptions: {
@@ -68,6 +272,11 @@ module.exports = {
'no-console': ['error', { allow: ['warn', 'error'] }],
// this rule can prevent race condition bugs like parallel `a += await foo()`
//
// as it has a lots of false positive, it is only enabled as a warning for now
'require-atomic-updates': ['warn', { allowProperties: true }],
strict: 'error',
},
}

View File

@@ -1,48 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'status: triaging :triangular_flag_on_post:, type: bug :bug:'
assignees: ''
---
1. ⚠️ **If you don't follow this template, the issue will be closed**.
2. ⚠️ **If your issue can't be easily reproduced, please report it [on the forum first](https://xcp-ng.org/forum/category/12/xen-orchestra)**.
Are you using XOA or XO from the sources?
If XOA:
- which release channel? (`stable` vs `latest`)
- please consider creating a support ticket in [your dedicated support area](https://xen-orchestra.com/#!/member/support)
If XO from the sources:
- Provide **your commit number**. If it's older than a week, we won't investigate
- Don't forget to [read this first](https://xen-orchestra.com/docs/community.html)
- As well as follow [this guide](https://xen-orchestra.com/docs/community.html#report-a-bug)
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please provide the following information):**
- Node: [e.g. 16.12.1]
- hypervisor: [e.g. XCP-ng 8.2.0]
**Additional context**
Add any other context about the problem here.

119
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,119 @@
name: Bug Report
description: Create a report to help us improve
labels: ['type: bug :bug:', 'status: triaging :triangular_flag_on_post:']
body:
- type: markdown
attributes:
value: |
1. ⚠️ **If you don't follow this template, the issue will be closed**.
2. ⚠️ **If your issue can't be easily reproduced, please report it [on the forum first](https://xcp-ng.org/forum/category/12/xen-orchestra)**.
- type: markdown
attributes:
value: '## Are you using XOA or XO from the sources?'
- type: dropdown
id: xo-origin
attributes:
label: Are you using XOA or XO from the sources?
options:
- XOA
- XO from the sources
- both
validations:
required: false
- type: markdown
attributes:
value: '### If XOA:'
- type: dropdown
id: xoa-channel
attributes:
label: Which release channel?
description: please consider creating a support ticket in [your dedicated support area](https://xen-orchestra.com/#!/member/support)
options:
- stable
- latest
- both
validations:
required: false
- type: markdown
attributes:
value: '### If XO from the sources:'
- type: markdown
attributes:
value: |
- Don't forget to [read this first](https://xen-orchestra.com/docs/community.html)
- As well as follow [this guide](https://xen-orchestra.com/docs/community.html#report-a-bug)
- type: input
id: xo-sources-commit-number
attributes:
label: Provide your commit number
description: If it's older than a week, we won't investigate
placeholder: e.g. 579f0
validations:
required: false
- type: markdown
attributes:
value: '## Bug description:'
- type: textarea
id: bug-description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is
validations:
required: true
- type: textarea
id: error-message
attributes:
label: Error message
render: Text
validations:
required: false
- type: textarea
id: steps
attributes:
label: To reproduce
description: 'Steps to reproduce the behavior:'
value: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
validations:
required: false
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen
validations:
required: false
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem
validations:
required: false
- type: markdown
attributes:
value: '## Environment (please provide the following information):'
- type: input
id: node-version
attributes:
label: Node
placeholder: e.g. 16.12.1
validations:
required: true
- type: input
id: hypervisor-version
attributes:
label: Hypervisor
placeholder: e.g. XCP-ng 8.2.0
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Add any other context about the problem here
validations:
required: false

View File

@@ -24,8 +24,14 @@ jobs:
cache: 'yarn'
- name: Install project dependencies
run: yarn
- name: Ensure packages are sorted in CHANGELOG.unreleased.md
run: ./scripts/gen-deps-list.js --check-order > /dev/null
- name: Ensure yarn.lock is up-to-date
run: git diff --exit-code yarn.lock
- name: Build the project
run: yarn build
- name: Unit tests
run: yarn test-unit
- name: Lint tests
run: yarn test-lint
- name: Integration tests

4
.gitignore vendored
View File

@@ -30,8 +30,12 @@ pnpm-debug.log.*
yarn-error.log
yarn-error.log.*
.env
*.tsbuildinfo
# code coverage
.nyc_output/
coverage/
.turbo/
# https://node-tap.org/dot-tap-folder/
.tap/

6
.prettierignore Normal file
View File

@@ -0,0 +1,6 @@
# Ignore Handlebars files because partials are not currently supported
# https://github.com/prettier/prettier/issues/11834
**/*.hbs
# Generated files
/@xen-orchestra/web/typed-router.d.ts

View File

@@ -33,8 +33,7 @@
"test": "node--test"
},
"devDependencies": {
"sinon": "^16.0.0",
"tap": "^16.3.0",
"sinon": "^18.0.0",
"test": "^3.2.1"
}
}

View File

@@ -62,6 +62,42 @@ decorateClass(Foo, {
})
```
### `decorateObject(object, map)`
Decorates an object the same way `decorateClass()` decorates a class:
```js
import { decorateObject } from '@vates/decorate-with'
const object = {
get bar() {
// body
},
set bar(value) {
// body
},
baz() {
// body
},
}
decorateObject(object, {
// getter and/or setter
bar: {
// without arguments
get: lodash.memoize,
// with arguments
set: [lodash.debounce, 150],
},
// method (with or without arguments)
baz: lodash.curry,
})
```
### `perInstance(fn, ...args)`
Helper to decorate the method by instance instead of for the whole class.

View File

@@ -80,6 +80,42 @@ decorateClass(Foo, {
})
```
### `decorateObject(object, map)`
Decorates an object the same way `decorateClass()` decorates a class:
```js
import { decorateObject } from '@vates/decorate-with'
const object = {
get bar() {
// body
},
set bar(value) {
// body
},
baz() {
// body
},
}
decorateObject(object, {
// getter and/or setter
bar: {
// without arguments
get: lodash.memoize,
// with arguments
set: [lodash.debounce, 150],
},
// method (with or without arguments)
baz: lodash.curry,
})
```
### `perInstance(fn, ...args)`
Helper to decorate the method by instance instead of for the whole class.

View File

@@ -14,10 +14,13 @@ function applyDecorator(decorator, value) {
}
exports.decorateClass = exports.decorateMethodsWith = function decorateClass(klass, map) {
const { prototype } = klass
return decorateObject(klass.prototype, map)
}
function decorateObject(object, map) {
for (const name of Object.keys(map)) {
const decorator = map[name]
const descriptor = getOwnPropertyDescriptor(prototype, name)
const descriptor = getOwnPropertyDescriptor(object, name)
if (typeof decorator === 'function' || Array.isArray(decorator)) {
descriptor.value = applyDecorator(decorator, descriptor.value)
} else {
@@ -30,10 +33,11 @@ exports.decorateClass = exports.decorateMethodsWith = function decorateClass(kla
}
}
defineProperty(prototype, name, descriptor)
defineProperty(object, name, descriptor)
}
return klass
return object
}
exports.decorateObject = decorateObject
exports.perInstance = function perInstance(fn, decorator, ...args) {
const map = new WeakMap()

View File

@@ -20,7 +20,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "2.0.0",
"version": "2.1.0",
"engines": {
"node": ">=8.10"
},

View File

@@ -14,7 +14,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.1.4",
"version": "0.1.5",
"engines": {
"node": ">=8.10"
},
@@ -23,13 +23,13 @@
"test": "node--test"
},
"dependencies": {
"@vates/multi-key-map": "^0.1.0",
"@vates/multi-key-map": "^0.2.0",
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/log": "^0.6.0",
"ensure-array": "^1.0.0"
},
"devDependencies": {
"sinon": "^16.0.0",
"sinon": "^18.0.0",
"test": "^3.2.1"
}
}

View File

@@ -20,6 +20,9 @@ function assertListeners(t, event, listeners) {
}
t.beforeEach(function (t) {
// work around https://github.com/tapjs/tapjs/issues/998
t.context = {}
t.context.ee = new EventEmitter()
t.context.em = new EventListenersManager(t.context.ee)
})

View File

@@ -38,9 +38,9 @@
"version": "1.0.1",
"scripts": {
"postversion": "npm publish --access public",
"test": "tap --branches=72"
"test": "tap --allow-incomplete-coverage"
},
"devDependencies": {
"tap": "^16.2.0"
"tap": "^18.7.0"
}
}

28
@vates/fuse-vhd/.USAGE.md Normal file
View File

@@ -0,0 +1,28 @@
Mount a vhd generated by xen-orchestra to filesystem
### Library
```js
import { mount } from 'fuse-vhd'
// return a disposable, see promise-toolbox/Disposable
// unmount automatically when disposable is disposed
// in case of differencing VHD, it mounts the full chain
await mount(handler, diskId, mountPoint)
```
### cli
From the install folder :
```
cli.mjs <remoteUrl> <vhdPathInRemote> <mountPoint>
```
After installing the package
```
xo-fuse-vhd <remoteUrl> <vhdPathInRemote> <mountPoint>
```
remoteUrl can be found by using cli in `@xen-orchestra/fs` , for example a local remote will have a url like `file:///path/to/remote/root`

59
@vates/fuse-vhd/README.md Normal file
View File

@@ -0,0 +1,59 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/fuse-vhd
[![Package Version](https://badgen.net/npm/v/@vates/fuse-vhd)](https://npmjs.org/package/@vates/fuse-vhd) ![License](https://badgen.net/npm/license/@vates/fuse-vhd) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/fuse-vhd)](https://bundlephobia.com/result?p=@vates/fuse-vhd) [![Node compatibility](https://badgen.net/npm/node/@vates/fuse-vhd)](https://npmjs.org/package/@vates/fuse-vhd)
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/fuse-vhd):
```sh
npm install --save @vates/fuse-vhd
```
## Usage
Mount a vhd generated by xen-orchestra to filesystem
### Library
```js
import { mount } from 'fuse-vhd'
// return a disposable, see promise-toolbox/Disposable
// unmount automatically when disposable is disposed
// in case of differencing VHD, it mounts the full chain
await mount(handler, diskId, mountPoint)
```
### cli
From the install folder :
```
cli.mjs <remoteUrl> <vhdPathInRemote> <mountPoint>
```
After installing the package
```
xo-fuse-vhd <remoteUrl> <vhdPathInRemote> <mountPoint>
```
remoteUrl can be found by using cli in `@xen-orchestra/fs` , for example a local remote will have a url like `file:///path/to/remote/root`
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

26
@vates/fuse-vhd/cli.mjs Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env node
import Disposable from 'promise-toolbox/Disposable'
import { getSyncedHandler } from '@xen-orchestra/fs'
import { mount } from './index.mjs'
async function* main([remoteUrl, vhdPathInRemote, mountPoint]) {
if (mountPoint === undefined) {
throw new TypeError('missing arg: cli <remoteUrl> <vhdPathInRemote> <mountPoint>')
}
const handler = yield getSyncedHandler({ url: remoteUrl })
const mounted = await mount(handler, vhdPathInRemote, mountPoint)
let disposePromise
process.on('SIGINT', async () => {
// ensure single dispose
if (!disposePromise) {
disposePromise = mounted.dispose()
}
await disposePromise
process.exit()
})
}
Disposable.wrap(main)(process.argv.slice(2))

View File

@@ -1,8 +1,11 @@
import LRU from 'lru-cache'
import Fuse from 'fuse-native'
import { createLogger } from '@xen-orchestra/log'
import { VhdSynthetic } from 'vhd-lib'
import { Disposable, fromCallback } from 'promise-toolbox'
const { warn } = createLogger('vates:fuse-vhd')
// build a s stat object from https://github.com/fuse-friends/fuse-native/blob/master/test/fixtures/stat.js
const stat = st => ({
mtime: st.mtime || new Date(),
@@ -52,13 +55,16 @@ export const mount = Disposable.factory(async function* mount(handler, diskPath,
},
read(path, fd, buf, len, pos, cb) {
if (path === '/vhd0') {
return vhd.readRawData(pos, len, cache, buf).then(cb)
return vhd.readRawData(pos, len, cache, buf).then(cb, error => {
warn('read error', { path, len, pos, error })
cb(Fuse.EIO)
})
}
throw new Error(`read file ${path} not exists`)
},
})
return new Disposable(
() => fromCallback(() => fuse.unmount()),
fromCallback(() => fuse.mount())
() => fromCallback(cb => fuse.unmount(cb)),
fromCallback(cb => fuse.mount(cb))
)
})

View File

@@ -1,6 +1,6 @@
{
"name": "@vates/fuse-vhd",
"version": "2.0.0",
"version": "2.1.1",
"license": "ISC",
"private": false,
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/fuse-vhd",
@@ -19,10 +19,15 @@
},
"main": "./index.mjs",
"dependencies": {
"@xen-orchestra/fs": "^4.1.7",
"@xen-orchestra/log": "^0.6.0",
"fuse-native": "^2.2.6",
"lru-cache": "^7.14.0",
"promise-toolbox": "^0.21.0",
"vhd-lib": "^4.6.1"
"vhd-lib": "^4.11.0"
},
"bin": {
"xo-fuse-vhd": "./cli.mjs"
},
"scripts": {
"postversion": "npm publish --access public"

View File

@@ -0,0 +1,18 @@
The use for this librariy is to create a composite key from a JSON value, ignoring objects identity and properties order.
The hash algorithm used is intended to be fast and with low collisions and is not guaranteed to be be secure.
The hash is encoded using [Base64 URL](https://en.wikipedia.org/wiki/Base64#URL_applications) to make it easy to use in URLs and filenames.
```js
import { jsonHash } from '@vates/json-hash'
console.log(jsonHash('foo'))
// → "siEyldVkkW-JpqQkVVZ8h8P0gPzXocFeIg8X1xaaeQs"
// order of properties is ignored
console.log(jsonHash({ foo: 0, bar: 1 }))
// → "JckoRSMIBjNlgEWIXhgpBOuyLQYqABZqvf1ccb3BPg0"
console.log(jsonHash({ bar: 1, foo: 0 }))
// → "JckoRSMIBjNlgEWIXhgpBOuyLQYqABZqvf1ccb3BPg0"
```

1
@vates/json-hash/.npmignore Symbolic link
View File

@@ -0,0 +1 @@
../../scripts/npmignore

View File

@@ -0,0 +1,51 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/json-hash
[![Package Version](https://badgen.net/npm/v/@vates/json-hash)](https://npmjs.org/package/@vates/json-hash) ![License](https://badgen.net/npm/license/@vates/json-hash) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/json-hash)](https://bundlephobia.com/result?p=@vates/json-hash) [![Node compatibility](https://badgen.net/npm/node/@vates/json-hash)](https://npmjs.org/package/@vates/json-hash)
> Compute a stable hash from a JSON-ifiable value
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/json-hash):
```sh
npm install --save @vates/json-hash
```
## Usage
The use for this librariy is to create a composite key from a JSON value, ignoring objects identity and properties order.
The hash algorithm used is intended to be fast and with low collisions and is not guaranteed to be be secure.
The hash is encoded using [Base64 URL](https://en.wikipedia.org/wiki/Base64#URL_applications) to make it easy to use in URLs and filenames.
```js
import { jsonHash } from '@vates/json-hash'
console.log(jsonHash('foo'))
// → "siEyldVkkW-JpqQkVVZ8h8P0gPzXocFeIg8X1xaaeQs"
// order of properties is ignored
console.log(jsonHash({ foo: 0, bar: 1 }))
// → "JckoRSMIBjNlgEWIXhgpBOuyLQYqABZqvf1ccb3BPg0"
console.log(jsonHash({ bar: 1, foo: 0 }))
// → "JckoRSMIBjNlgEWIXhgpBOuyLQYqABZqvf1ccb3BPg0"
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

38
@vates/json-hash/index.js Normal file
View File

@@ -0,0 +1,38 @@
'use strict'
const { createHash } = require('node:crypto')
function updateJsonHash(value, hash) {
if (value !== null && typeof value === 'object') {
if (Array.isArray(value)) {
hash.update('[')
for (const item of value) {
updateJsonHash(item, hash)
// trailing is not a big deal because it does not need to be valid JSON
hash.update(',')
}
hash.update(']')
} else {
hash.update('{')
for (const key of Object.keys(value).sort()) {
updateJsonHash(key, hash)
hash.update(':')
updateJsonHash(value[key], hash)
// trailing is not a big deal because it does not need to be valid JSON
hash.update(',')
}
hash.update('}')
}
} else {
hash.update(JSON.stringify(value))
}
}
exports.jsonHash = function jsonHash(value) {
// this hash does not need to be secure, it just needs to be fast and with low collisions
const hash = createHash('sha256')
updateJsonHash(value, hash)
return hash.digest('base64url')
}

View File

@@ -0,0 +1,21 @@
'use strict'
const { jsonHash } = require('@vates/json-hash')
const assert = require('node:assert/strict')
const test = require('test')
const cases = [
[null, 'dCNOmK_nSY-12vHzasLXiswzlGT5UHA7jAGYkvmCuQs'],
[42, 'c0dctApWjo2ooEXO0RATfhWfiQrE2og7axfcZRs6gEk'],
['foo', 'siEyldVkkW-JpqQkVVZ8h8P0gPzXocFeIg8X1xaaeQs'],
[[], 'T1PNoYwrqgwDVLtfmj7L5e0Sq02OEbqHPC8RFhICuUU'],
[{}, 'RBNvo1WzZ4oRRq0W9-hknpT7T8If536DEMBg9hyq_4o'],
[{ a: 1, b: 2 }, 'LMjVxyj2vtMZ3W1cZ2cJrQij5zn7FBOlPiyBB38OmFE'],
[{ b: 2, a: 1 }, 'LMjVxyj2vtMZ3W1cZ2cJrQij5zn7FBOlPiyBB38OmFE'],
]
for (const [value, hash] of cases) {
test(JSON.stringify(value), function () {
assert.equal(jsonHash(value), hash)
})
}

View File

@@ -0,0 +1,33 @@
{
"private": false,
"name": "@vates/json-hash",
"description": "Compute a stable hash from a JSON-ifiable value",
"keywords": [
"hash",
"json",
"stable"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/json-hash",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/json-hash",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.2.0",
"engines": {
"node": ">=14.18.0"
},
"devDependencies": {
"test": "^3.3.0"
},
"scripts": {
"postversion": "npm publish --access public",
"test": "node--test"
}
}

View File

@@ -17,4 +17,14 @@ map.get(['foo', 'bar']) // 2
map.get(['bar', 'foo']) // 3
map.get([OBJ]) // 4
map.get([{}]) // undefined
map.delete([])
for (const [key, value] of map.entries() {
console.log(key, value)
}
for (const value of map.values()) {
console.log(value)
}
```

View File

@@ -35,6 +35,16 @@ map.get(['foo', 'bar']) // 2
map.get(['bar', 'foo']) // 3
map.get([OBJ]) // 4
map.get([{}]) // undefined
map.delete([])
for (const [key, value] of map.entries() {
console.log(key, value)
}
for (const value of map.values()) {
console.log(value)
}
```
## Contributions

View File

@@ -36,14 +36,31 @@ function del(node, i, keys) {
return node
}
function* entries(node, key) {
if (node !== undefined) {
if (node instanceof Node) {
const { value } = node
if (value !== undefined) {
yield [key, node.value]
}
for (const [childKey, child] of node.children.entries()) {
yield* entries(child, key.concat(childKey))
}
} else {
yield [key, node]
}
}
}
function get(node, i, keys) {
return i === keys.length
? node instanceof Node
? node.value
: node
: node instanceof Node
? get(node.children.get(keys[i]), i + 1, keys)
: undefined
? get(node.children.get(keys[i]), i + 1, keys)
: undefined
}
function set(node, i, keys, value) {
@@ -69,6 +86,22 @@ function set(node, i, keys, value) {
return node
}
function* values(node) {
if (node !== undefined) {
if (node instanceof Node) {
const { value } = node
if (value !== undefined) {
yield node.value
}
for (const child of node.children.values()) {
yield* values(child)
}
} else {
yield node
}
}
}
exports.MultiKeyMap = class MultiKeyMap {
constructor() {
// each node is either a value or a Node if it contains children
@@ -79,6 +112,10 @@ exports.MultiKeyMap = class MultiKeyMap {
this._root = del(this._root, 0, keys)
}
entries() {
return entries(this._root, [])
}
get(keys) {
return get(this._root, 0, keys)
}
@@ -86,4 +123,8 @@ exports.MultiKeyMap = class MultiKeyMap {
set(keys, value) {
this._root = set(this._root, 0, keys, value)
}
values() {
return values(this._root)
}
}

View File

@@ -19,7 +19,7 @@ describe('MultiKeyMap', () => {
// reverse composite key
['bar', 'foo'],
]
const values = keys.map(() => ({}))
const values = keys.map(() => Math.random())
// set all values first to make sure they are all stored and not only the
// last one
@@ -27,6 +27,12 @@ describe('MultiKeyMap', () => {
map.set(key, values[i])
})
assert.deepEqual(
Array.from(map.entries()),
keys.map((key, i) => [key, values[i]])
)
assert.deepEqual(Array.from(map.values()), values)
keys.forEach((key, i) => {
// copy the key to make sure the array itself is not the key
assert.strictEqual(map.get(key.slice()), values[i])

View File

@@ -18,7 +18,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.1.0",
"version": "0.2.0",
"engines": {
"node": ">=8.10"
},

View File

@@ -1,10 +1,9 @@
import assert from 'node:assert'
import { Socket } from 'node:net'
import { connect } from 'node:tls'
import { fromCallback, pRetry, pDelay, pTimeout } from 'promise-toolbox'
import { fromCallback, pRetry, pDelay, pTimeout, pFromCallback } from 'promise-toolbox'
import { readChunkStrict } from '@vates/read-chunk'
import { createLogger } from '@xen-orchestra/log'
import {
INIT_PASSWD,
NBD_CMD_READ,
@@ -21,7 +20,6 @@ import {
OPTS_MAGIC,
NBD_CMD_DISC,
} from './constants.mjs'
const { warn } = createLogger('vates:nbd-client')
// documentation is here : https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md
@@ -36,10 +34,10 @@ export default class NbdClient {
#exportSize
#waitBeforeReconnect
#readAhead
#readBlockRetries
#reconnectRetry
#connectTimeout
#messageTimeout
// AFAIK, there is no guaranty the server answers in the same order as the queries
// so we handle a backlog of command waiting for response and handle concurrency manually
@@ -52,17 +50,23 @@ export default class NbdClient {
#reconnectingPromise
constructor(
{ address, port = NBD_DEFAULT_PORT, exportname, cert },
{ connectTimeout = 6e4, waitBeforeReconnect = 1e3, readAhead = 10, readBlockRetries = 5, reconnectRetry = 5 } = {}
{
connectTimeout = 6e4,
messageTimeout = 6e4,
waitBeforeReconnect = 1e3,
readBlockRetries = 5,
reconnectRetry = 5,
} = {}
) {
this.#serverAddress = address
this.#serverPort = port
this.#exportName = exportname
this.#serverCert = cert
this.#waitBeforeReconnect = waitBeforeReconnect
this.#readAhead = readAhead
this.#readBlockRetries = readBlockRetries
this.#reconnectRetry = reconnectRetry
this.#connectTimeout = connectTimeout
this.#messageTimeout = messageTimeout
}
get exportSize() {
@@ -115,13 +119,27 @@ export default class NbdClient {
if (!this.#connected) {
return
}
this.#connected = false
const socket = this.#serverSocket
const queryId = this.#nextCommandQueryId
this.#nextCommandQueryId++
const buffer = Buffer.alloc(28)
buffer.writeInt32BE(NBD_REQUEST_MAGIC, 0) // it is a nbd request
buffer.writeInt16BE(0, 4) // no command flags for a disconnect
buffer.writeInt16BE(NBD_CMD_DISC, 6) // we want to disconnect from nbd server
await this.#write(buffer)
await this.#serverSocket.destroy()
buffer.writeBigUInt64BE(queryId, 8)
buffer.writeBigUInt64BE(0n, 16)
buffer.writeInt32BE(0, 24)
const promise = pFromCallback(cb => {
socket.end(buffer, 'utf8', cb)
})
try {
await pTimeout.call(promise, this.#messageTimeout)
} catch (error) {
socket.destroy()
}
this.#serverSocket = undefined
this.#connected = false
}
@@ -195,11 +213,13 @@ export default class NbdClient {
}
#read(length) {
return readChunkStrict(this.#serverSocket, length)
const promise = readChunkStrict(this.#serverSocket, length)
return pTimeout.call(promise, this.#messageTimeout)
}
#write(buffer) {
return fromCallback.call(this.#serverSocket, 'write', buffer)
const promise = fromCallback.call(this.#serverSocket, 'write', buffer)
return pTimeout.call(promise, this.#messageTimeout)
}
async #readInt32() {
@@ -232,19 +252,20 @@ export default class NbdClient {
}
try {
this.#waitingForResponse = true
const magic = await this.#readInt32()
const buffer = await this.#read(16)
const magic = buffer.readInt32BE(0)
if (magic !== NBD_REPLY_MAGIC) {
throw new Error(`magic number for block answer is wrong : ${magic} ${NBD_REPLY_MAGIC}`)
}
const error = await this.#readInt32()
const error = buffer.readInt32BE(4)
if (error !== 0) {
// @todo use error code from constants.mjs
throw new Error(`GOT ERROR CODE : ${error}`)
}
const blockQueryId = await this.#readInt64()
const blockQueryId = buffer.readBigUInt64BE(8)
const query = this.#commandQueryBacklog.get(blockQueryId)
if (!query) {
throw new Error(` no query associated with id ${blockQueryId}`)
@@ -265,7 +286,7 @@ export default class NbdClient {
}
}
async readBlock(index, size = NBD_DEFAULT_BLOCK_SIZE) {
async #readBlock(index, size) {
// we don't want to add anything in backlog while reconnecting
if (this.#reconnectingPromise) {
await this.#reconnectingPromise
@@ -281,7 +302,13 @@ export default class NbdClient {
buffer.writeInt16BE(NBD_CMD_READ, 6) // we want to read a data block
buffer.writeBigUInt64BE(queryId, 8)
// byte offset in the raw disk
buffer.writeBigUInt64BE(BigInt(index) * BigInt(size), 16)
const offset = BigInt(index) * BigInt(size)
const remaining = this.#exportSize - offset
if (remaining < BigInt(size)) {
size = Number(remaining)
}
buffer.writeBigUInt64BE(offset, 16)
buffer.writeInt32BE(size, 24)
return new Promise((resolve, reject) => {
@@ -307,45 +334,13 @@ export default class NbdClient {
})
}
async *readBlocks(indexGenerator) {
// default : read all blocks
if (indexGenerator === undefined) {
const exportSize = this.#exportSize
const chunkSize = 2 * 1024 * 1024
indexGenerator = function* () {
const nbBlocks = Math.ceil(Number(exportSize / BigInt(chunkSize)))
for (let index = 0; BigInt(index) < nbBlocks; index++) {
yield { index, size: chunkSize }
}
}
}
const readAhead = []
const readAheadMaxLength = this.#readAhead
const makeReadBlockPromise = (index, size) => {
const promise = pRetry(() => this.readBlock(index, size), {
tries: this.#readBlockRetries,
onRetry: async err => {
warn('will retry reading block ', index, err)
await this.reconnect()
},
})
// error is handled during unshift
promise.catch(() => {})
return promise
}
// read all blocks, but try to keep readAheadMaxLength promise waiting ahead
for (const { index, size } of indexGenerator()) {
// stack readAheadMaxLength promises before starting to handle the results
if (readAhead.length === readAheadMaxLength) {
// any error will stop reading blocks
yield readAhead.shift()
}
readAhead.push(makeReadBlockPromise(index, size))
}
while (readAhead.length > 0) {
yield readAhead.shift()
}
async readBlock(index, size = NBD_DEFAULT_BLOCK_SIZE) {
return pRetry(() => this.#readBlock(index, size), {
tries: this.#readBlockRetries,
onRetry: async err => {
warn('will retry reading block ', index, err)
await this.reconnect()
},
})
}
}

116
@vates/nbd-client/multi.mjs Normal file
View File

@@ -0,0 +1,116 @@
import { asyncEach } from '@vates/async-each'
import { NBD_DEFAULT_BLOCK_SIZE } from './constants.mjs'
import NbdClient from './index.mjs'
import { createLogger } from '@xen-orchestra/log'
const { warn } = createLogger('vates:nbd-client:multi')
export default class MultiNbdClient {
#clients = []
#nbdConcurrency
#options
#readAhead
#settings
get exportSize() {
return this.#clients[0].exportSize
}
constructor(settings, { nbdConcurrency = 8, readAhead = 16, ...options } = {}) {
this.#readAhead = readAhead
this.#options = options
this.#nbdConcurrency = nbdConcurrency
if (!Array.isArray(settings)) {
settings = [settings]
}
this.#settings = settings
}
/**
*
* open nbdConcurrency connections to NBD servers
* it must obtain at least one connection to succeed
* it tries to spread connections on multiple host
*/
async connect() {
const candidates = [...this.#settings]
const promises = []
const baseOptions = this.#options
const _connect = async () => {
if (candidates.length === 0) {
return
}
// a little bit of randomization to spread the load
const nbdInfo = candidates[Math.floor(Math.random() * candidates.length)]
const client = new NbdClient(nbdInfo, {
...baseOptions,
readAhead: Math.ceil(this.#readAhead / this.#nbdConcurrency),
})
try {
await client.connect()
this.#clients.push(client)
} catch (err) {
client.disconnect().catch(() => {})
// do not hammer unreachable hosts, once failed, remove from the list
const candidateIndex = candidates.findIndex(({ address }) => address === nbdInfo.address)
if (candidateIndex >= 0) {
// this candidate may have already been deleted by another parallel promise
candidates.splice(candidateIndex, 1)
}
warn(`can't connect to one nbd client`, { err })
// retry with another candidate (if available)
return _connect()
}
}
for (let i = 0; i < this.#nbdConcurrency; i++) {
promises.push(_connect())
}
await Promise.all(promises)
if (this.#clients.length === 0) {
throw new Error(`Fail to connect to any Nbd client`)
}
if (this.#clients.length < this.#nbdConcurrency) {
warn(
`incomplete connection by multi Nbd, only ${this.#clients.length} over ${this.#nbdConcurrency} expected clients`
)
}
}
async disconnect() {
await asyncEach(this.#clients, client => client.disconnect(), {
stopOnError: false,
})
}
async readBlock(index, size = NBD_DEFAULT_BLOCK_SIZE) {
const clientId = index % this.#clients.length
return this.#clients[clientId].readBlock(index, size)
}
async *readBlocks(indexGenerator) {
// default : read all blocks
const readAhead = []
const makeReadBlockPromise = (index, size) => {
const promise = this.readBlock(index, size)
// error is handled during unshift
promise.catch(() => {})
return promise
}
// read all blocks, but try to keep readAheadMaxLength promise waiting ahead
for (const { index, size } of indexGenerator()) {
// stack readAheadMaxLength promises before starting to handle the results
if (readAhead.length === this.#readAhead) {
// any error will stop reading blocks
yield readAhead.shift()
}
readAhead.push(makeReadBlockPromise(index, size))
}
while (readAhead.length > 0) {
yield readAhead.shift()
}
}
}

View File

@@ -13,7 +13,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "2.0.0",
"version": "3.1.0",
"engines": {
"node": ">=14.0"
},
@@ -24,14 +24,14 @@
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/log": "^0.6.0",
"promise-toolbox": "^0.21.0",
"xen-api": "^1.3.6"
"xen-api": "^4.2.0"
},
"devDependencies": {
"tap": "^16.3.0",
"tap": "^18.7.0",
"tmp": "^0.2.1"
},
"scripts": {
"postversion": "npm publish --access public",
"test-integration": "tap --lines 97 --functions 95 --branches 74 --statements 97 tests/*.integ.mjs"
"test-integration": "tap --allow-incomplete-coverage"
}
}

View File

@@ -1,4 +1,3 @@
import NbdClient from '../index.mjs'
import { spawn, exec } from 'node:child_process'
import fs from 'node:fs/promises'
import { test } from 'tap'
@@ -7,8 +6,10 @@ import { pFromCallback } from 'promise-toolbox'
import { Socket } from 'node:net'
import { NBD_DEFAULT_PORT } from '../constants.mjs'
import assert from 'node:assert'
import MultiNbdClient from '../multi.mjs'
const FILE_SIZE = 10 * 1024 * 1024
const CHUNK_SIZE = 1024 * 1024 // non default size
const FILE_SIZE = 1024 * 1024 * 9.5 // non aligned file size
async function createTempFile(size) {
const tmpPath = await pFromCallback(cb => tmp.file(cb))
@@ -77,15 +78,14 @@ async function killNbdKit() {
)
}
test('it works with unsecured network', async tap => {
test('it works secured network', async tap => {
const path = await createTempFile(FILE_SIZE)
let nbdServer = await spawnNbdKit(path)
const client = new NbdClient(
{
address: '127.0.0.1',
exportname: 'MY_SECRET_EXPORT',
cert: `-----BEGIN CERTIFICATE-----
const connectionSettings = {
address: '127.0.0.1',
exportname: 'MY_SECRET_EXPORT',
cert: `-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUeHpQ0IeD6BmP2zgsv3LV3J4BI/EwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzA1MTcxMzU1MzBaFw0yNDA1
@@ -107,15 +107,21 @@ CYu1Xn/FVPx1HoRgWc7E8wFhDcA/P3SJtfIQWHB9FzSaBflKGR4t8WCE2eE8+cTB
57ABhfYpMlZ4aHjuN1bL
-----END CERTIFICATE-----
`,
},
{
readAhead: 2,
}
)
}
const invalid = {
address: '500.500.500.500',
port: 0,
exportname: 'nop',
}
// it should work even with some broken servers
const nbdInfos = [connectionSettings, connectionSettings, invalid]
const client = new MultiNbdClient(nbdInfos, {
nbdConcurrency: 4,
readAhead: 2,
})
await client.connect()
tap.equal(client.exportSize, BigInt(FILE_SIZE))
const CHUNK_SIZE = 1024 * 1024 // non default size
const indexes = []
for (let i = 0; i < FILE_SIZE / CHUNK_SIZE; i++) {
indexes.push(i)
@@ -127,9 +133,9 @@ CYu1Xn/FVPx1HoRgWc7E8wFhDcA/P3SJtfIQWHB9FzSaBflKGR4t8WCE2eE8+cTB
})
let i = 0
for await (const block of nbdIterator) {
let blockOk = true
let blockOk = block.length === Math.min(CHUNK_SIZE, FILE_SIZE - CHUNK_SIZE * i)
let firstFail
for (let j = 0; j < CHUNK_SIZE; j += 4) {
for (let j = 0; j < block.length; j += 4) {
const wanted = i * CHUNK_SIZE + j
const found = block.readUInt32BE(j)
blockOk = blockOk && found === wanted
@@ -137,7 +143,7 @@ CYu1Xn/FVPx1HoRgWc7E8wFhDcA/P3SJtfIQWHB9FzSaBflKGR4t8WCE2eE8+cTB
firstFail = j
}
}
tap.ok(blockOk, `check block ${i} content`)
tap.ok(blockOk, `check block ${i} content ${block.length}`)
i++
// flaky server is flaky
@@ -147,17 +153,6 @@ CYu1Xn/FVPx1HoRgWc7E8wFhDcA/P3SJtfIQWHB9FzSaBflKGR4t8WCE2eE8+cTB
nbdServer = await spawnNbdKit(path)
}
}
// we can reuse the conneciton to read other blocks
// default iterator
const nbdIteratorWithDefaultBlockIterator = client.readBlocks()
let nb = 0
for await (const block of nbdIteratorWithDefaultBlockIterator) {
nb++
tap.equal(block.length, 2 * 1024 * 1024)
}
tap.equal(nb, 5)
assert.rejects(() => client.readBlock(100, CHUNK_SIZE))
await client.disconnect()
@@ -166,3 +161,18 @@ CYu1Xn/FVPx1HoRgWc7E8wFhDcA/P3SJtfIQWHB9FzSaBflKGR4t8WCE2eE8+cTB
nbdServer.kill()
await fs.unlink(path)
})
test('fails if server does not answer', async () => {
const client = new MultiNbdClient(
{
address: '500.500.500.500',
port: 0,
exportname: 'nop',
},
{
nbdConcurrency: 1,
readAhead: 2,
}
)
await assert.rejects(client.connect())
})

View File

@@ -17,7 +17,20 @@ import https from 'node:https'
import util from 'util'
import soap from 'soap'
import Cookie from 'soap-cookie' // required for session persistence
import { createLogger } from '@xen-orchestra/log'
const { warn } = createLogger('xo:node-vsphere-soap:client')
class VmwareError extends Error {
constructor(rawError) {
super(rawError.body)
const matches = rawError.body?.match(/<faultstring[^>]*>(.*)<\/faultstring>/im)
this.message = matches?.[1] ?? rawError.message
this.name = 'VmwareError'
this.stack = rawError.stack
// not putting the cause since the error failed to be stringified correctly down the road
warn(rawError)
}
}
// Client class
// inherits from EventEmitter
// possible events: connect, error, ready
@@ -31,14 +44,20 @@ export function Client(vCenterHostname, username, password, sslVerify) {
EventEmitter.call(this)
// sslVerify argument handling
// don't use any proxy to connect to the esxi
if (sslVerify) {
this.clientopts = {}
this.clientopts = {
request: axios.create({
proxy: false,
}),
}
} else {
this.clientopts = {
request: axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false,
}),
proxy: false,
}),
}
}
@@ -134,8 +153,9 @@ Client.prototype._connect = function () {
self.clientopts,
function (err, client) {
if (err) {
self.emit('error', err)
throw err
const vErr = new VmwareError(err)
self.emit('error', vErr)
throw vErr
}
self.client = client // save client for later use
@@ -144,6 +164,7 @@ Client.prototype._connect = function () {
.runCommand('RetrieveServiceContent', { _this: 'ServiceInstance' })
.once('result', function (result, raw, soapHeader) {
if (!result.returnval) {
// this is a domain/server error return it as is
self.status = 'disconnected'
self.emit('error', raw)
return
@@ -202,7 +223,7 @@ Client.prototype._close = function () {
function _soapErrorHandler(self, emitter, command, args, err) {
err = err || { body: 'general error' }
if (err.body.match(/session is not authenticated/)) {
if (err.body?.match(/session is not authenticated/)) {
self.status = 'disconnected'
process.removeAllListeners('beforeExit')
@@ -214,16 +235,19 @@ function _soapErrorHandler(self, emitter, command, args, err) {
emitter.emit('result', result, raw, soapHeader)
})
.once('error', function (err) {
emitter.emit('error', err.body)
throw err
const vErr = new VmwareError(err)
emitter.emit('error', vErr)
throw vErr
})
} else {
emitter.emit('error', err.body)
throw err
const vErr = new VmwareError(err)
emitter.emit('error', vErr)
throw vErr
}
} else {
emitter.emit('error', err.body)
throw err
const vErr = new VmwareError(err)
emitter.emit('error', vErr)
throw vErr
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vates/node-vsphere-soap",
"version": "2.0.0",
"version": "2.1.0",
"description": "interface to vSphere SOAP/WSDL from node for interfacing with vCenter or ESXi, forked from node-vsphere-soap",
"main": "lib/client.mjs",
"author": "reedog117",
@@ -11,6 +11,7 @@
},
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"dependencies": {
"@xen-orchestra/log": "^0.6.0",
"axios": "^1.4.0",
"soap": "^1.0.0",
"soap-cookie": "^0.10.1"

View File

@@ -0,0 +1,46 @@
### `replace(value, replacement)`
> Recursively replace sensitive entries with a replacement value.
```js
import { replace } from '@vates/obfuscate'
replace({ username: 'foo', password: 'bar' }, 'obfuscated')
// → { username: 'foo', password: 'obfuscated'}
```
### `obfuscate(value)`
> Recursively replace sensitive entries with a special tag.
```js
import { obfuscate } from '@vates/obfuscate'
obfuscate({ username: 'foo', password: 'bar' })
// → {
// username: 'foo',
// password: 'obfuscated-q3oi6d9X8uenGvdLnHk2',
// }
```
### `merge(newValue, oldValue)`
> Replace entries obfuscated by `obfuscate()` in the new value by entries from the old one.
>
> This is especially useful to give an obfuscated value to a client, let them modify it, and then merge modified and unmodified values.
```js
import { obfuscate, merge } from '@vates/obfuscate'
const original = { username: 'foo', password: 'bar', token: 'baz' }
// compute an obfuscated value to send to a client
const obfuscated = obfuscate(original)
// let the client update any entries
obfuscated.token = 'qux'
// merge the updated value with the original one
merge(obfuscated, original)
// → { username: 'foo', password: 'bar', token: 'qux' }
```

1
@vates/obfuscate/.npmignore Symbolic link
View File

@@ -0,0 +1 @@
../../scripts/npmignore

View File

@@ -0,0 +1,77 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/obfuscate
[![Package Version](https://badgen.net/npm/v/@vates/obfuscate)](https://npmjs.org/package/@vates/obfuscate) ![License](https://badgen.net/npm/license/@vates/obfuscate) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/obfuscate)](https://bundlephobia.com/result?p=@vates/obfuscate) [![Node compatibility](https://badgen.net/npm/node/@vates/obfuscate)](https://npmjs.org/package/@vates/obfuscate)
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/obfuscate):
```sh
npm install --save @vates/obfuscate
```
## Usage
### `replace(value, replacement)`
> Recursively replace sensitive entries with a replacement value.
```js
import { replace } from '@vates/obfuscate'
replace({ username: 'foo', password: 'bar' }, 'obfuscated')
// → { username: 'foo', password: 'obfuscated'}
```
### `obfuscate(value)`
> Recursively replace sensitive entries with a special tag.
```js
import { obfuscate } from '@vates/obfuscate'
obfuscate({ username: 'foo', password: 'bar' })
// → {
// username: 'foo',
// password: 'obfuscated-q3oi6d9X8uenGvdLnHk2',
// }
```
### `merge(newValue, oldValue)`
> Replace entries obfuscated by `obfuscate()` in the new value by entries from the old one.
>
> This is especially useful to give an obfuscated value to a client, let them modify it, and then merge modified and unmodified values.
```js
import { obfuscate, merge } from '@vates/obfuscate'
const original = { username: 'foo', password: 'bar', token: 'baz' }
// compute an obfuscated value to send to a client
const obfuscated = obfuscate(original)
// let the client update any entries
obfuscated.token = 'qux'
// merge the updated value with the original one
merge(obfuscated, original)
// → { username: 'foo', password: 'bar', token: 'qux' }
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

View File

@@ -1,9 +1,18 @@
import mapValues from 'lodash/mapValues.js'
'use strict'
function mapValues(object, iteratee) {
const result = {}
for (const key of Object.keys(object)) {
result[key] = iteratee(object[key], key, object)
}
return result
}
// this random value is used to obfuscate real data
const OBFUSCATED_VALUE = 'obfuscated-q3oi6d9X8uenGvdLnHk2'
exports.OBFUSCATED_VALUE = OBFUSCATED_VALUE
export const merge = (newValue, oldValue) => {
function merge(newValue, oldValue) {
if (newValue === OBFUSCATED_VALUE) {
return oldValue
}
@@ -23,14 +32,16 @@ export const merge = (newValue, oldValue) => {
const iteratee = (v, k) => merge(v, oldValue[k])
return isArray ? newValue.map(iteratee) : mapValues(newValue, iteratee)
}
exports.merge = merge
export const obfuscate = value => replace(value, OBFUSCATED_VALUE)
const obfuscate = value => replace(value, OBFUSCATED_VALUE)
exports.obfuscate = obfuscate
const SENSITIVE_PARAMS = ['token', 'passphrase', /password/i, 'encryptionKey']
const isSensitiveParam = name =>
SENSITIVE_PARAMS.some(pattern => (typeof pattern === 'string' ? pattern === name : pattern.test(name)))
export function replace(value, replacement) {
function replace(value, replacement) {
function helper(value, name) {
if (typeof value === 'string' && isSensitiveParam(name)) {
return replacement
@@ -45,3 +56,4 @@ export function replace(value, replacement) {
return helper(value)
}
exports.replace = replace

View File

@@ -0,0 +1,23 @@
{
"private": false,
"name": "@vates/obfuscate",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/obfuscate",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/obfuscate",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.1.0",
"engines": {
"node": ">=8.10"
},
"scripts": {
"postversion": "npm publish --access public"
}
}

View File

@@ -9,6 +9,15 @@ function assert(name, value) {
}
}
const WHITESPACES = /\s+/g
function cleanToken(otp) {
// remove whitespaces
//
// some OTP clients add whitespaces for better readability (e.g. `XXX XXX`)
// and users might be tempted to write them
return otp && otp.replace(WHITESPACES, '')
}
// https://github.com/google/google-authenticator/wiki/Key-Uri-Format
function generateUri(protocol, label, params) {
assert('label', typeof label === 'string')
@@ -64,7 +73,7 @@ export function generateHotpUri({ counter, digits, issuer, label, secret }) {
}
export async function verifyHotp(token, opts) {
return token === (await generateHotp(opts))
return cleanToken(token) === (await generateHotp(opts))
}
function totpCounter(period = 30, timestamp = Math.floor(Date.now() / 1e3)) {
@@ -85,6 +94,7 @@ export async function verifyTotp(token, { period, timestamp, window = 1, ...opts
const counter = totpCounter(period, timestamp)
const end = counter + window
opts.counter = counter - window
token = cleanToken(token)
while (opts.counter <= end) {
if (token === (await generateHotp(opts))) {
return true

View File

@@ -1,5 +1,5 @@
import { strict as assert } from 'node:assert'
import { describe, it } from 'tap/mocha'
import test from 'test'
import {
generateHotp,
@@ -11,6 +11,8 @@ import {
verifyTotp,
} from './index.mjs'
const { describe, it } = test
describe('generateSecret', function () {
it('generates a string of 32 chars', async function () {
const secret = generateSecret()

View File

@@ -22,7 +22,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "1.0.0",
"version": "1.1.0",
"engines": {
"node": ">=15"
},
@@ -31,9 +31,9 @@
},
"scripts": {
"postversion": "npm publish --access public",
"test": "tap"
"test": "node--test"
},
"devDependencies": {
"tap": "^16.3.0"
"test": "^3.3.0"
}
}

View File

@@ -1,7 +1,7 @@
'use strict'
const assert = require('assert/strict')
const { describe, it } = require('tap').mocha
const { describe, it } = require('test')
const { every, not, some } = require('./')

View File

@@ -32,9 +32,9 @@
},
"scripts": {
"postversion": "npm publish --access public",
"test": "tap"
"test": "node--test"
},
"devDependencies": {
"tap": "^16.0.1"
"test": "^3.3.0"
}
}

View File

@@ -22,41 +22,41 @@ const readChunk = (stream, size) =>
stream.errored != null
? Promise.reject(stream.errored)
: stream.closed || stream.readableEnded
? Promise.resolve(null)
: new Promise((resolve, reject) => {
if (size !== undefined) {
assert(size > 0)
? Promise.resolve(null)
: new Promise((resolve, reject) => {
if (size !== undefined) {
assert(size > 0)
// per Node documentation:
// > The size argument must be less than or equal to 1 GiB.
assert(size < 1073741824)
}
// per Node documentation:
// > The size argument must be less than or equal to 1 GiB.
assert(size < 1073741824)
}
function onEnd() {
resolve(null)
removeListeners()
}
function onError(error) {
reject(error)
removeListeners()
}
function onReadable() {
const data = stream.read(size)
if (data !== null) {
resolve(data)
function onEnd() {
resolve(null)
removeListeners()
}
}
function removeListeners() {
stream.removeListener('end', onEnd)
stream.removeListener('error', onError)
stream.removeListener('readable', onReadable)
}
stream.on('end', onEnd)
stream.on('error', onError)
stream.on('readable', onReadable)
onReadable()
})
function onError(error) {
reject(error)
removeListeners()
}
function onReadable() {
const data = stream.read(size)
if (data !== null) {
resolve(data)
removeListeners()
}
}
function removeListeners() {
stream.removeListener('end', onEnd)
stream.removeListener('error', onError)
stream.removeListener('readable', onReadable)
}
stream.on('end', onEnd)
stream.on('error', onError)
stream.on('readable', onReadable)
onReadable()
})
exports.readChunk = readChunk
/**
@@ -111,42 +111,42 @@ async function skip(stream, size) {
return stream.errored != null
? Promise.reject(stream.errored)
: size === 0 || stream.closed || stream.readableEnded
? Promise.resolve(0)
: new Promise((resolve, reject) => {
let left = size
function onEnd() {
resolve(size - left)
removeListeners()
}
function onError(error) {
reject(error)
removeListeners()
}
function onReadable() {
const data = stream.read()
left -= data === null ? 0 : data.length
if (left > 0) {
// continue to read
} else {
// if more than wanted has been read, push back the rest
if (left < 0) {
stream.unshift(data.slice(left))
}
resolve(size)
? Promise.resolve(0)
: new Promise((resolve, reject) => {
let left = size
function onEnd() {
resolve(size - left)
removeListeners()
}
}
function removeListeners() {
stream.removeListener('end', onEnd)
stream.removeListener('error', onError)
stream.removeListener('readable', onReadable)
}
stream.on('end', onEnd)
stream.on('error', onError)
stream.on('readable', onReadable)
onReadable()
})
function onError(error) {
reject(error)
removeListeners()
}
function onReadable() {
const data = stream.read()
left -= data === null ? 0 : data.length
if (left > 0) {
// continue to read
} else {
// if more than wanted has been read, push back the rest
if (left < 0) {
stream.unshift(data.slice(left))
}
resolve(size)
removeListeners()
}
}
function removeListeners() {
stream.removeListener('end', onEnd)
stream.removeListener('error', onError)
stream.removeListener('readable', onReadable)
}
stream.on('end', onEnd)
stream.on('error', onError)
stream.on('readable', onReadable)
onReadable()
})
}
exports.skip = skip

View File

@@ -72,6 +72,27 @@ Task.warning(message, data)
Task.set(property, value)
```
### `Task.run([opts], fn, ...args)`
This is a convenient shortcut to `new Task(opts).run(() => fn(...args))`.
```js
// options can be passed
await Task.run({ properties: { name: 'foo' } }, fn)
// arguments can be passed
await Task.run(fn, 'foo', 'bar')
// context (this) is forwarded
await Task.run.call(object, object.method)
// as a convenience, you can pass a method name directly
await Task.run.call(object, 'method')
// everything together
await Task.run.call(object, 'method', 'foo', 'bar')
```
### `combineEvents`
Create a consolidated log from individual events.
@@ -123,7 +144,7 @@ const onProgress = makeOnProgress({
onTaskUpdate(taskLog) {},
})
Task.run({ data: { name: 'my task' }, onProgress }, asyncFn)
Task.run({ properties: { name: 'my task' }, onProgress }, asyncFn)
```
It can also be fed event logs directly:

View File

@@ -88,6 +88,27 @@ Task.warning(message, data)
Task.set(property, value)
```
### `Task.run([opts], fn, ...args)`
This is a convenient shortcut to `new Task(opts).run(() => fn(...args))`.
```js
// options can be passed
await Task.run({ properties: { name: 'foo' } }, fn)
// arguments can be passed
await Task.run(fn, 'foo', 'bar')
// context (this) is forwarded
await Task.run.call(object, object.method)
// as a convenience, you can pass a method name directly
await Task.run.call(object, 'method')
// everything together
await Task.run.call(object, 'method', 'foo', 'bar')
```
### `combineEvents`
Create a consolidated log from individual events.
@@ -111,7 +132,7 @@ const onProgress = makeOnProgress({
// current status of the task as described in the previous section
taskLog.status
// undefined or a dictionnary of properties attached to the task
// undefined or a dictionary of properties attached to the task
taskLog.properties
// timestamp at which the abortion was requested, undefined otherwise
@@ -139,7 +160,7 @@ const onProgress = makeOnProgress({
onTaskUpdate(taskLog) {},
})
Task.run({ data: { name: 'my task' }, onProgress }, asyncFn)
Task.run({ properties: { name: 'my task' }, onProgress }, asyncFn)
```
It can also be fed event logs directly:

View File

@@ -3,6 +3,10 @@
const assert = require('node:assert').strict
const { AsyncLocalStorage } = require('node:async_hooks')
function alreadyEnded() {
throw new Error('task has already ended')
}
// define a read-only, non-enumerable, non-configurable property
function define(object, property, value) {
Object.defineProperty(object, property, { value })
@@ -21,7 +25,7 @@ const asyncStorage = global[asyncStorageKey] ?? (global[asyncStorageKey] = new A
const getTask = () => asyncStorage.getStore()
exports.Task = class Task {
class Task {
static get abortSignal() {
const task = getTask()
if (task !== undefined) {
@@ -29,29 +33,25 @@ exports.Task = class Task {
}
}
static info(message, data) {
const task = getTask()
if (task !== undefined) {
task.#emit('info', { data, message })
static run(...args) {
let opts = args[0]
let fn
if (typeof opts === 'object') {
args.shift()
fn = args[0]
} else {
fn = opts
opts = undefined
}
}
args.shift()
static run(opts, fn) {
return new this(opts).run(fn)
}
const thisArg = this !== Task ? this : undefined
static set(name, value) {
const task = getTask()
if (task !== undefined) {
task.#emit('property', { name, value })
if (typeof fn !== 'function') {
fn = this[fn]
}
}
static warning(message, data) {
const task = getTask()
if (task !== undefined) {
task.#emit('warning', { data, message })
}
return new Task(opts).run(() => fn.apply(thisArg, args))
}
static wrap(opts, fn) {
@@ -110,9 +110,7 @@ exports.Task = class Task {
this.#emit('abortionRequested', { reason: signal.reason })
if (!this.#running) {
const status = FAILURE
this.#status = status
this.#emit('end', { result: signal.reason, status })
this.#end(FAILURE, signal.reason)
}
}
})
@@ -123,12 +121,26 @@ exports.Task = class Task {
}
#emit(type, data) {
assert.equal(this.#startData, undefined, 'task has not started yet')
data.id = this.id
data.timestamp = Date.now()
data.type = type
this.#onProgress(data)
}
#end(status, result) {
assert.equal(this.#status, PENDING)
this.#status = status
this.#emit('end', { status, result })
this.#onProgress = alreadyEnded
}
info(message, data) {
this.#emit('info', { data, message })
}
#maybeStart() {
const startData = this.#startData
if (startData !== undefined) {
@@ -140,8 +152,7 @@ exports.Task = class Task {
async run(fn) {
const result = await this.runInside(fn)
if (this.status === PENDING) {
this.#status = SUCCESS
this.#emit('end', { status: SUCCESS, result })
this.#end(SUCCESS, result)
}
return result
}
@@ -155,18 +166,27 @@ exports.Task = class Task {
this.#maybeStart()
try {
const result = await asyncStorage.run(this, fn)
const result = await asyncStorage.run(this, fn, this)
this.#running = false
return result
} catch (result) {
const status = FAILURE
this.#status = status
this.#emit('end', { status, result })
this.#end(FAILURE, result)
throw result
}
}
set(name, value) {
assert.equal(this.status, PENDING)
this.#emit('property', { name, value })
}
warning(message, data) {
assert.equal(this.status, PENDING)
this.#emit('warning', { data, message })
}
wrap(fn) {
const task = this
return function taskRun() {
@@ -181,3 +201,15 @@ exports.Task = class Task {
}
}
}
exports.Task = Task
// setup static aliases
for (const name of ['info', 'set', 'warning']) {
const method = Task.prototype[name]
Task[name] = function () {
const task = getTask()
if (task !== undefined) {
return method.apply(task, arguments)
}
}
}

View File

@@ -3,6 +3,7 @@
const assert = require('node:assert').strict
const { describe, it } = require('test')
const { makeOnProgress } = require('./combineEvents.js')
const { Task } = require('./index.js')
const noop = Function.prototype
@@ -162,6 +163,44 @@ describe('Task', function () {
})
})
describe('.run()', function () {
function fn(...args) {
Task.set('baz', 'qux')
return [this, ...args]
}
const obj = { fn }
it('accepts optional options', async function () {
assert.deepEqual(
await Task.run(
{
properties: { foo: 'bar' },
onProgress: makeOnProgress({
onRootTaskEnd(log) {
assert.deepEqual(log.properties, { __proto__: null, foo: 'bar', baz: 'qux' })
},
}),
},
fn
),
[undefined]
)
})
it('accepts optional arguments', async function () {
assert.deepEqual(await Task.run(fn, 'foo', 'bar'), [undefined, 'foo', 'bar'])
})
it('accepts optional context', async function () {
assert.deepEqual(await Task.run.call('foo', fn), ['foo'])
})
it('accepts a method name instead of a function', async function () {
assert.deepEqual(await Task.run.call(obj, 'fn'), [obj])
})
})
describe('.set()', function () {
it('does nothing when run outside a task', function () {
Task.set('progress', 10)
@@ -180,6 +219,31 @@ describe('Task', function () {
})
})
describe('#set()', function () {
const name = 'progress'
const value = 10
it('throws when the task is not started', function () {
const task = createTask()
assert.throws(() => task.set(name, value), { message: 'task has not started yet' })
})
it(`emits an property message`, async function () {
const task = createTask()
await task.run(async () => {
await Task.run(() => {
task.set(name, value)
assertEvent(task, {
name,
type: 'property',
value,
})
})
})
})
})
describe('.warning()', function () {
it('does nothing when run outside a task', function () {
Task.warning('foo')
@@ -198,6 +262,30 @@ describe('Task', function () {
})
})
for (const type of ['info', 'warning']) {
describe(`#${type}()`, function () {
it('throws when the task is not started', function () {
const task = createTask()
assert.throws(() => task[type]('foo'), { message: 'task has not started yet' })
})
it(`emits an ${type} message`, async function () {
const task = createTask()
await task.run(async () => {
await Task.run(() => {
task[type]('foo')
assertEvent(task, {
data: undefined,
message: 'foo',
type,
})
})
})
})
})
}
describe('#id', function () {
it('can be set', function () {
const task = createTask()

View File

@@ -13,7 +13,7 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "0.2.0",
"version": "0.4.0",
"engines": {
"node": ">=14"
},

128
@vates/xml-rpc/.USAGE.md Normal file
View File

@@ -0,0 +1,128 @@
### Formatting
```js
import { xmlRpcFormatter } from '@vates/xml-rpc/formatter'
// There is a dedicated method per XML-RPC tag (`array`, `string`, etc.)
xmlRpcFormatter.format_methodCall({
methodName: 'examples.getStateName',
params: [40],
})
// → {
// name: 'methodCall',
// children: [
// {
// name: 'methodName',
// children: [ 'examples.getStateName' ]
// },
// {
// name: 'params',
// children: [
// {
// name: 'param',
// children: [ { name: 'value', children: [ '40' ] } ]
// }
// ]
// }
// ]
// }
// There is a dedicated method to format a JS value
xmlRpcFormatter.format_js(new Date(900684535000))
// → {
// name: 'dateTime.iso8601',
// children: [ '1998-07-17T14:08:55.000Z' ]
// }
```
Formatting XML is out of this library's scope, but you can achieve it easily with
`@vates/xml`:
```js
import { formatXml } from '@vates/xml/format'
// it can now be passed to the XML-RPC parser
const tree = xmlRpcFormatter.format_methodCall({
methodName: 'examples.getStateName',
params: [40],
})
// avoid any indentation or new lines as it will confuse many XML-RPC parsers.
const xml = formatXml(tree, { indent: 0 })
```
### Parsing
```js
import { xmlRpcParser } from '@vates/xml-rpc/parser'
xmlRpcParser.parse({
name: 'methodResponse',
children: [
{
name: 'params',
children: [
{
name: 'param',
children: [
{
name: 'value',
children: [
{
name: 'string',
children: ['South Dakota'],
},
],
},
],
},
],
},
],
})
// { params: [ 'South Dakota' ] }
// There is a dedicated method per XML-RPC tag (`array`, `string`, etc.)
xmlRpcParser.parse_array({
name: 'array',
children: [
{
name: 'data',
children: [
{
name: 'value',
children: [{ name: 'boolean', children: ['1'] }],
},
{
name: 'value',
children: [{ name: 'int', children: ['42'] }],
},
],
},
],
})
// [ true, 42 ]
```
Parsing XML is out of this library's scope, but you can achieve it easily with
`@vates/xml`:
```js
import { parseXml } from '@vates/xml/parse'
const xml = `
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value><string>South Dakota</string></value>
</param>
</params>
</methodResponse>
`
const tree = parseXml(xml)
// it can now be passed to the XML-RPC parser
xmlRpcParser.parse(tree)
```

1
@vates/xml-rpc/.npmignore Symbolic link
View File

@@ -0,0 +1 @@
../../scripts/npmignore

161
@vates/xml-rpc/README.md Normal file
View File

@@ -0,0 +1,161 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/xml-rpc
[![Package Version](https://badgen.net/npm/v/@vates/xml-rpc)](https://npmjs.org/package/@vates/xml-rpc) ![License](https://badgen.net/npm/license/@vates/xml-rpc) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/xml-rpc)](https://bundlephobia.com/result?p=@vates/xml-rpc) [![Node compatibility](https://badgen.net/npm/node/@vates/xml-rpc)](https://npmjs.org/package/@vates/xml-rpc)
> Parse and format XML-RPC messages, do not handle transport or XML
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/xml-rpc):
```sh
npm install --save @vates/xml-rpc
```
## Usage
### Formatting
```js
import { xmlRpcFormatter } from '@vates/xml-rpc/formatter'
// There is a dedicated method per XML-RPC tag (`array`, `string`, etc.)
xmlRpcFormatter.format_methodCall({
methodName: 'examples.getStateName',
params: [40],
})
// → {
// name: 'methodCall',
// children: [
// {
// name: 'methodName',
// children: [ 'examples.getStateName' ]
// },
// {
// name: 'params',
// children: [
// {
// name: 'param',
// children: [ { name: 'value', children: [ '40' ] } ]
// }
// ]
// }
// ]
// }
// There is a dedicated method to format a JS value
xmlRpcFormatter.format_js(new Date(900684535000))
// → {
// name: 'dateTime.iso8601',
// children: [ '1998-07-17T14:08:55.000Z' ]
// }
```
Formatting XML is out of this library's scope, but you can achieve it easily with
`@vates/xml`:
```js
import { formatXml } from '@vates/xml/format'
// it can now be passed to the XML-RPC parser
const tree = xmlRpcFormatter.format_methodCall({
methodName: 'examples.getStateName',
params: [40],
})
// avoid any indentation or new lines as it will confuse many XML-RPC parsers.
const xml = formatXml(tree, { indent: 0 })
```
### Parsing
```js
import { xmlRpcParser } from '@vates/xml-rpc/parser'
xmlRpcParser.parse({
name: 'methodResponse',
children: [
{
name: 'params',
children: [
{
name: 'param',
children: [
{
name: 'value',
children: [
{
name: 'string',
children: ['South Dakota'],
},
],
},
],
},
],
},
],
})
// { params: [ 'South Dakota' ] }
// There is a dedicated method per XML-RPC tag (`array`, `string`, etc.)
xmlRpcParser.parse_array({
name: 'array',
children: [
{
name: 'data',
children: [
{
name: 'value',
children: [{ name: 'boolean', children: ['1'] }],
},
{
name: 'value',
children: [{ name: 'int', children: ['42'] }],
},
],
},
],
})
// [ true, 42 ]
```
Parsing XML is out of this library's scope, but you can achieve it easily with
`@vates/xml`:
```js
import { parseXml } from '@vates/xml/parse'
const xml = `
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value><string>South Dakota</string></value>
</param>
</params>
</methodResponse>
`
const tree = parseXml(xml)
// it can now be passed to the XML-RPC parser
xmlRpcParser.parse(tree)
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

104
@vates/xml-rpc/formatter.js Normal file
View File

@@ -0,0 +1,104 @@
'use strict'
function node(name, attributes, children) {
if (Array.isArray(attributes)) {
children = attributes
attributes = undefined
}
return { name, attributes, children }
}
class XmlRpcFormatter {
format_js(value) {
const type = typeof value
if (type === 'object') {
return value === null
? this.format_nil()
: value instanceof Date
? this['format_dateTime.iso8601'](value)
: Array.isArray(value)
? this.format_array(value)
: Buffer.isBuffer(value)
? this.format_base64(value)
: this.format_struct(value)
}
return this['format_' + type](value)
}
format_array(values) {
return node('array', [node('data', values.map(this.format_value, this))])
}
format_base64(buf) {
return node('base64', [buf.toString('base64')])
}
format_boolean(bool) {
return node('boolean', [bool ? '1' : '0'])
}
'format_dateTime.iso8601'(date) {
return node('dateTime.iso8601', [date.toISOString()])
}
format_double(str) {
return node('double', [String(str)])
}
format_fault(val) {
return node('fault', [this.format_value(val)])
}
format_i4(str) {
return node('i4', [String(str)])
}
format_int(str) {
return node('int', [String(str)])
}
format_member([name, value]) {
return node('member', [node('name', [name]), this.format_value(value)])
}
format_methodCall({ methodName, params }) {
return node('methodCall', [node('methodName', [methodName]), this.format_params(params)])
}
format_methodResponse({ fault, params }) {
return node('methodResponse', [fault !== undefined ? this.format_fault(fault) : this.format_params(params)])
}
format_nil() {
return node('nil')
}
format_number(num) {
return Number.isInteger(num) ? this.format_int(num) : this.format_double(num)
}
format_param(value) {
return node('param', [this.format_value(value)])
}
format_params(params) {
return node('params', params.map(this.format_param, this))
}
format_string(str) {
return str
}
format_struct(struct) {
return node('struct', Object.entries(struct).map(this.format_member, this))
}
format_value(value) {
return node('value', [this.format_js(value)])
}
}
exports.Formatter = XmlRpcFormatter
exports.xmlRpcFormatter = new XmlRpcFormatter()

View File

@@ -0,0 +1,33 @@
{
"private": false,
"name": "@vates/xml-rpc",
"description": "Parse and format XML-RPC messages, do not handle transport or XML",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/xml-rpc",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/xml-rpc",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "ISC",
"version": "1.0.0",
"engines": {
"node": ">=12"
},
"devDependencies": {
"@vates/xml": "^2.0.0",
"test": "^3.3.0"
},
"exports": {
"./formatter": "./formatter.js",
"./parser": "./parser.js"
},
"scripts": {
"postversion": "npm publish --access public",
"test": "node--test"
}
}

133
@vates/xml-rpc/parser.js Normal file
View File

@@ -0,0 +1,133 @@
'use strict'
function toValue(node) {
const obj = { __proto__: null }
for (const child of node.children) {
if (typeof child === 'string') {
return child
}
const { name } = child
if (Object.hasOwn(obj, name)) {
const prev = obj[name]
if (Array.isArray(obj, name)) {
prev.push(child)
} else {
obj[name] = [prev, child]
}
} else {
obj[name] = child
}
}
return obj
}
// https://en.wikipedia.org/wiki/XML-RPC#Data_types
class XmlRpcParser {
parse(node) {
return this['parse_' + node.name](node)
}
parse_array({ children: [data] }) {
return data.children.map(this.parse_value, this)
}
parse_base64({ children: [str] }) {
return Buffer.from(str, 'base64')
}
parse_boolean({ children: [str] }) {
return Boolean(Number(str))
}
'parse_dateTime.iso8601'({ children: [str] }) {
const date = new Date(str)
// date successfully parsed
if (!Number.isNaN(+date)) {
return date
}
// XML-RPC dates may use YYYYMMDD instead of YYYY-MM-DD which ECMAScript
// does not parse
return new Date(str.slice(0, 4) + '-' + str.slice(4, 6) + '-' + str.slice(6))
}
parse_double({ children: [str] }) {
return Number(str)
}
parse_fault({ children: [value] }) {
return this.parse_value(value)
}
parse_i4({ children: [str] }) {
return Number(str)
}
parse_int({ children: [str] }) {
return Number(str)
}
parse_member(member) {
member = toValue(member)
return [member.name.children[0], this.parse_value(member.value)]
}
parse_methodCall(methodCall) {
methodCall = toValue(methodCall)
return {
methodName: methodCall.methodName.children[0],
params: this.parse_params(methodCall.params),
}
}
parse_methodResponse(methodResponse) {
methodResponse = toValue(methodResponse)
const { fault } = methodResponse
if (fault !== undefined) {
return { fault: this.parse_fault(fault) }
}
return { params: this.parse_params(methodResponse.params) }
}
parse_nil() {
return null
}
parse_param(param) {
return this.parse_value(param.children[0])
}
parse_params(params) {
return params.children.map(this.parse_param, this)
}
parse_string({ children: [str] }) {
return str
}
parse_struct(struct) {
return Object.fromEntries(struct.children.map(this.parse_member, this))
}
parse_value({ children }) {
if (children.length === 0) {
return ''
}
const child = children[0]
// can be a plain string
if (typeof child === 'string') {
return child
}
return this.parse(child)
}
}
exports.XmlRpcParser = XmlRpcParser
exports.xmlRpcParser = new XmlRpcParser()

View File

@@ -0,0 +1,19 @@
'use strict'
const { formatXml } = require('@vates/xml/format')
const { join } = require('node:path')
const { parseXml } = require('@vates/xml/parse')
const { readFileSync } = require('node:fs')
const { xmlRpcFormatter } = require('@vates/xml-rpc/formatter')
const assert = require('node:assert/strict')
const test = require('test')
const methodCall = require('./methodCall.js')
test(function () {
const xmlTree = parseXml(readFileSync(join(__dirname, 'methodCall.xml')))
const xmlRpc = xmlRpcFormatter.format_methodCall(methodCall)
assert.deepEqual(parseXml(formatXml(xmlRpc)), xmlTree)
})

View File

@@ -0,0 +1,16 @@
'use strict'
module.exports = {
methodName: 'examples.getStateName',
params: [
[1404, 'Something here'],
Buffer.from([121, 111, 117, 32, 99, 97, 110, 39, 116, 32, 114, 101, 97, 100, 32, 116, 104, 105, 115, 33]),
true,
new Date('1998-07-17T14:08:55.000Z'),
-12.53,
42,
'Hello world!',
{ foo: 1, bar: 2 },
null,
],
}

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value>
<array>
<data>
<value>
<int>1404</int>
</value>
<value>
Something here
</value>
</data>
</array>
</value>
</param>
<param>
<value>
<base64>eW91IGNhbid0IHJlYWQgdGhpcyE=</base64>
</value>
</param>
<param>
<value>
<boolean>1</boolean>
</value>
</param>
<param>
<value>
<dateTime.iso8601>1998-07-17T14:08:55.000Z</dateTime.iso8601>
</value>
</param>
<param>
<value>
<double>-12.53</double>
</value>
</param>
<param>
<value>
<int>42</int>
</value>
</param>
<param>
<value>
Hello world!
</value>
</param>
<param>
<value>
<struct>
<member>
<name>foo</name>
<value>
<int>1</int>
</value>
</member>
<member>
<name>bar</name>
<value>
<int>2</int>
</value>
</member>
</struct>
</value>
</param>
<param>
<value>
<nil />
</value>
</param>
</params>
</methodCall>

View File

@@ -0,0 +1,32 @@
'use strict'
const { join } = require('node:path')
const { parseXml } = require('@vates/xml/parse')
const { readFileSync } = require('node:fs')
const { xmlRpcParser } = require('@vates/xml-rpc/parser')
const assert = require('node:assert/strict')
const test = require('test')
const methodCall = require('./methodCall.js')
test('parser.parse()', function () {
const xml = String(readFileSync(join(__dirname, 'methodCall.xml')))
const xmlRpc = parseXml(xml)
assert.deepEqual(xmlRpcParser.parse(xmlRpc), methodCall)
})
test('datetime without dashes nor milliseconds', function () {
assert.deepEqual(
xmlRpcParser.parse({ name: 'dateTime.iso8601', children: ['19980717T14:08:55Z'] }),
new Date('1998-07-17T14:08:55Z')
)
})
test('i4 support', function () {
assert.equal(xmlRpcParser.parse({ name: 'i4', children: ['42'] }), 42)
})
test('string support', function () {
assert.equal(xmlRpcParser.parse({ name: 'string', children: ['foo'] }), 'foo')
})

56
@vates/xml/.USAGE.md Normal file
View File

@@ -0,0 +1,56 @@
### `parseXml(xml, [opts])`
> Based on [`sax`](https://www.npmjs.com/package/sax)
`opts` is an optional object which can contain the following options:
- `normalize = true`: if true, then turn any whitespace into a single space
- `strict = true`: whether or not to be a jerk
- `trim = true`: whether or not to trim text nodes
```js
import { parseXml } from '@xen-orchestra/xml/parse'
const tree = parseXml(`<?xml version="1.0" encoding="UTF-8"?>
<tag1 attr1="value1" attr2="value2">
Text &amp; entities
<tag2 />
<tag2 />
<ns:tag3 />
</tag1>
`)
// → {
// name: 'tag1',
// attributes: { attr1: 'value1', attr2: 'value2' },
// children: [
// 'Text & entities',
// { name: 'tag2', attributes: {}, children: [] },
// { name: 'tag2', attributes: {}, children: [] },
// { name: 'ns:tag3', attributes: {}, children: [] }
// ]
// }
```
### `formatXml(tree, [opts])`
`opts` is an optional object which can contain the following options:
- `includeDeclaration = true`: whether to include an XML declaration
- `indent = 2`: string or number of spaces to use to indent; if an empty string or `0`, no indent or new lines will be used
```js
import { formatXml } from '@xen-orchestra/xml/format'
formatXml({
name: 'tag1',
attributes: { attr1: 'value1', attr2: 'value2' },
children: ['Text & entities', { name: 'tag2' }, { name: 'tag2' }, { name: 'ns:tag3' }],
})
// → <?xml version="1.0" encoding="UTF-8"?>
// <tag1 attr1="value1" attr2="value2">
// Text &amp; entities
// <tag2 />
// <tag2 />
// <ns:tag3 />
// </tag1>
```

1
@vates/xml/.npmignore Symbolic link
View File

@@ -0,0 +1 @@
../../scripts/npmignore

89
@vates/xml/README.md Normal file
View File

@@ -0,0 +1,89 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/xml
[![Package Version](https://badgen.net/npm/v/@vates/xml)](https://npmjs.org/package/@vates/xml) ![License](https://badgen.net/npm/license/@vates/xml) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/xml)](https://bundlephobia.com/result?p=@vates/xml) [![Node compatibility](https://badgen.net/npm/node/@vates/xml)](https://npmjs.org/package/@vates/xml)
> Simple XML formatting/parsing
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/xml):
```sh
npm install --save @vates/xml
```
## Usage
### `parseXml(xml, [opts])`
> Based on [`sax`](https://www.npmjs.com/package/sax)
`opts` is an optional object which can contain the following options:
- `normalize = true`: if true, then turn any whitespace into a single space
- `strict = true`: whether or not to be a jerk
- `trim = true`: whether or not to trim text nodes
```js
import { parseXml } from '@xen-orchestra/xml/parse'
const tree = parseXml(`<?xml version="1.0" encoding="UTF-8"?>
<tag1 attr1="value1" attr2="value2">
Text &amp; entities
<tag2 />
<tag2 />
<ns:tag3 />
</tag1>
`)
// → {
// name: 'tag1',
// attributes: { attr1: 'value1', attr2: 'value2' },
// children: [
// 'Text & entities',
// { name: 'tag2', attributes: {}, children: [] },
// { name: 'tag2', attributes: {}, children: [] },
// { name: 'ns:tag3', attributes: {}, children: [] }
// ]
// }
```
### `formatXml(tree, [opts])`
`opts` is an optional object which can contain the following options:
- `includeDeclaration = true`: whether to include an XML declaration
- `indent = 2`: string or number of spaces to use to indent; if an empty string or `0`, no indent or new lines will be used
```js
import { formatXml } from '@xen-orchestra/xml/format'
formatXml({
name: 'tag1',
attributes: { attr1: 'value1', attr2: 'value2' },
children: ['Text & entities', { name: 'tag2' }, { name: 'tag2' }, { name: 'ns:tag3' }],
})
// → <?xml version="1.0" encoding="UTF-8"?>
// <tag1 attr1="value1" attr2="value2">
// Text &amp; entities
// <tag2 />
// <tag2 />
// <ns:tag3 />
// </tag1>
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

30
@vates/xml/cli.js Executable file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env node
'use strict'
const { inspect } = require('node:util')
const { formatXml } = require('@vates/xml/format')
const { parseXml } = require('@vates/xml/parse')
const { readFileSync } = require('node:fs')
function log(val) {
process.stdout.write(inspect(val, false, null, true))
process.stdout.write('\n')
}
function main([inputPath = 0]) {
const input = readFileSync(inputPath)
// attempt to parse from JSON
let data
try {
data = JSON.parse(input)
} catch (error) {
// fallback to XML
log(parseXml(input))
return
}
process.stdout.write(formatXml(data))
}
main(process.argv.slice(2))

64
@vates/xml/format.js Normal file
View File

@@ -0,0 +1,64 @@
'use strict'
const ENTITIES = {
'"': '&quot;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&apos;',
}
const RE = new RegExp(Object.keys(ENTITIES).join('|'), 'g')
function replace(match) {
return ENTITIES[match]
}
function escape(str) {
return str.replace(RE, replace)
}
function formatNode(node, depth) {
const indent = this.indent.repeat(depth)
if (typeof node === 'object') {
const line = [indent, '<', node.name]
const { attributes } = node
if (attributes !== undefined) {
for (const name of Object.keys(attributes)) {
line.push(' ', name, '="', escape(attributes[name]), '"')
}
}
const { children } = node
if (children === undefined || children.length === 0) {
line.push(' />')
this.lines.push(line.join(''))
} else {
line.push('>')
this.lines.push(line.join(''))
for (const child of children) {
formatNode.call(this, child, depth + 1)
}
this.lines.push(indent + '</' + node.name + '>')
}
} else {
// string (or scalar)
this.lines.push(indent + escape(String(node)))
}
}
exports.formatXml = function formatXml(tree, { includeDeclaration = true, indent = 2 } = {}) {
const lines = []
if (includeDeclaration) {
lines.push('<?xml version="1.0" encoding="UTF-8"?>')
}
const ctx = {
lines,
indent: typeof indent === 'number' ? ' '.repeat(indent) : indent,
}
formatNode.call(ctx, tree, 0)
return lines.join(indent !== 0 && indent !== '' ? '\n' : '')
}

35
@vates/xml/package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"private": false,
"name": "@vates/xml",
"description": "Simple XML formatting/parsing",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/xml",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/xml",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "ISC",
"version": "2.0.0",
"engines": {
"node": ">=14"
},
"dependencies": {
"sax": "^1.3.0"
},
"devDependencies": {
"test": "^3.3.0"
},
"exports": {
"./format": "./format.js",
"./parse": "./parse.js"
},
"scripts": {
"postversion": "npm publish --access public",
"test": "node--test"
}
}

23
@vates/xml/parse.js Normal file
View File

@@ -0,0 +1,23 @@
'use strict'
const sax = require('sax')
exports.parseXml = function parseXml(xml, { normalize = true, strict = true, trim = true } = {}) {
const stack = [{ children: [] }]
const parser = sax.parser(strict, { normalize, trim })
parser.ontext = text => {
stack[stack.length - 1].children.push(text)
}
parser.onopentag = ({ name, attributes }) => {
stack.push({ name, attributes, children: [] })
}
parser.onclosetag = () => {
const node = stack.pop()
stack[stack.length - 1].children.push(node)
}
parser.write(xml).close()
return stack[0].children[0]
}

25
@vates/xml/test/data.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "tag1",
"attributes": {
"attr1": "value1",
"attr2": "value2"
},
"children": [
"Text & entities",
{
"name": "tag2",
"attributes": {},
"children": []
},
{
"name": "tag2",
"attributes": {},
"children": []
},
{
"name": "ns:tag3",
"attributes": {},
"children": []
}
]
}

7
@vates/xml/test/data.xml Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<tag1 attr1="value1" attr2="value2">
Text &amp; entities
<tag2 />
<tag2 />
<ns:tag3 />
</tag1>

18
@vates/xml/test/format.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict'
const { formatXml } = require('@vates/xml/format')
const { join } = require('node:path')
const { readFileSync } = require('node:fs')
const assert = require('node:assert/strict')
const test = require('test')
test('format()', function () {
const xml = String(readFileSync(join(__dirname, 'data.xml')))
const tree = JSON.parse(readFileSync(join(__dirname, 'data.json')))
assert.equal(formatXml(tree) + '\n', xml)
})
test('supports missing attributes and children', function () {
assert.equal(formatXml({ name: 'foo' }, { includeDeclaration: false }), '<foo />')
})

14
@vates/xml/test/parse.js Normal file
View File

@@ -0,0 +1,14 @@
'use strict'
const { parseXml } = require('@vates/xml/parse')
const { readFileSync } = require('node:fs')
const { join } = require('node:path')
const assert = require('node:assert/strict')
const test = require('test')
test('parse()', function () {
const xml = String(readFileSync(join(__dirname, 'data.xml')))
const tree = JSON.parse(readFileSync(join(__dirname, 'data.json')))
assert.deepEqual(parseXml(xml), tree)
})

View File

@@ -35,7 +35,7 @@
"test": "node--test"
},
"devDependencies": {
"sinon": "^16.0.0",
"sinon": "^18.0.0",
"test": "^3.2.1"
}
}

View File

@@ -130,6 +130,10 @@ class AuditCore {
return nValid
}
get(id) {
return this._storage.get(id)
}
async *getFrom(newest) {
const storage = this._storage

View File

@@ -1,7 +1,7 @@
'use strict'
const assert = require('assert/strict')
const { afterEach, describe, it } = require('tap').mocha
const { afterEach, describe, it } = require('test')
const { AlteredRecordError, AuditCore, MissingRecordError, NULL_ID, Storage } = require('.')

View File

@@ -7,16 +7,16 @@
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"version": "0.2.3",
"version": "0.3.0",
"engines": {
"node": ">=14"
},
"scripts": {
"postversion": "npm publish --access public",
"test": "tap --lines 67 --functions 92 --branches 52 --statements 67"
"test": "node--test"
},
"dependencies": {
"@vates/decorate-with": "^2.0.0",
"@vates/decorate-with": "^2.1.0",
"@xen-orchestra/log": "^0.6.0",
"golike-defer": "^0.5.1",
"object-hash": "^2.0.1"
@@ -28,6 +28,6 @@
"url": "https://vates.fr"
},
"devDependencies": {
"tap": "^16.0.1"
"test": "^3.3.0"
}
}

View File

@@ -7,8 +7,8 @@
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"dependencies": {
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/backups": "^0.43.0",
"@xen-orchestra/fs": "^4.1.0",
"@xen-orchestra/backups": "^0.52.2",
"@xen-orchestra/fs": "^4.1.7",
"filenamify": "^6.0.0",
"getopts": "^2.2.5",
"lodash": "^4.17.15",
@@ -27,7 +27,7 @@
"scripts": {
"postversion": "npm publish --access public"
},
"version": "1.0.13",
"version": "1.0.22",
"license": "AGPL-3.0-or-later",
"author": {
"name": "Vates SAS",

View File

@@ -4,23 +4,229 @@ import { formatFilenameDate } from './_filenameDate.mjs'
import { importIncrementalVm } from './_incrementalVm.mjs'
import { Task } from './Task.mjs'
import { watchStreamSize } from './_watchStreamSize.mjs'
import { VhdNegative, VhdSynthetic } from 'vhd-lib'
import { decorateClass } from '@vates/decorate-with'
import { createLogger } from '@xen-orchestra/log'
import { dirname, join } from 'node:path'
import pickBy from 'lodash/pickBy.js'
import { defer } from 'golike-defer'
const { debug, info, warn } = createLogger('xo:backups:importVmBackup')
async function resolveUuid(xapi, cache, uuid, type) {
if (uuid == null) {
return uuid
}
const ref = cache.get(uuid)
if (ref === undefined) {
cache.set(uuid, xapi.call(`${type}.get_by_uuid`, uuid))
}
return cache.get(uuid)
}
export class ImportVmBackup {
constructor({ adapter, metadata, srUuid, xapi, settings: { newMacAddresses, mapVdisSrs = {} } = {} }) {
constructor({
adapter,
metadata,
srUuid,
xapi,
settings: { additionnalVmTag, newMacAddresses, mapVdisSrs = {}, useDifferentialRestore = false } = {},
}) {
this._adapter = adapter
this._importIncrementalVmSettings = { newMacAddresses, mapVdisSrs }
this._importIncrementalVmSettings = { additionnalVmTag, newMacAddresses, mapVdisSrs, useDifferentialRestore }
this._metadata = metadata
this._srUuid = srUuid
this._xapi = xapi
}
async #getPathOfVdiSnapshot(snapshotUuid) {
const metadata = this._metadata
if (this._pathToVdis === undefined) {
const backups = await this._adapter.listVmBackups(
this._metadata.vm.uuid,
({ mode, timestamp }) => mode === 'delta' && timestamp >= metadata.timestamp
)
const map = new Map()
for (const backup of backups) {
for (const [vdiRef, vdi] of Object.entries(backup.vdis)) {
map.set(vdi.uuid, backup.vhds[vdiRef])
}
}
this._pathToVdis = map
}
return this._pathToVdis.get(snapshotUuid)
}
async _reuseNearestSnapshot($defer, ignoredVdis) {
const metadata = this._metadata
const { mapVdisSrs } = this._importIncrementalVmSettings
const { vbds, vhds, vifs, vm, vmSnapshot } = metadata
const streams = {}
const metdataDir = dirname(metadata._filename)
const vdis = ignoredVdis === undefined ? metadata.vdis : pickBy(metadata.vdis, vdi => !ignoredVdis.has(vdi.uuid))
for (const [vdiRef, vdi] of Object.entries(vdis)) {
const vhdPath = join(metdataDir, vhds[vdiRef])
let xapiDisk
try {
xapiDisk = await this._xapi.getRecordByUuid('VDI', vdi.$snapshot_of$uuid)
} catch (err) {
// if this disk is not present anymore, fall back to default restore
warn(err)
}
let snapshotCandidate, backupCandidate
if (xapiDisk !== undefined) {
debug('found disks, wlll search its snapshots', { snapshots: xapiDisk.snapshots })
for (const snapshotRef of xapiDisk.snapshots) {
const snapshot = await this._xapi.getRecord('VDI', snapshotRef)
debug('handling snapshot', { snapshot })
// take only the first snapshot
if (snapshotCandidate && snapshotCandidate.snapshot_time < snapshot.snapshot_time) {
debug('already got a better candidate')
continue
}
// have a corresponding backup more recent than metadata ?
const pathToSnapshotData = await this.#getPathOfVdiSnapshot(snapshot.uuid)
if (pathToSnapshotData === undefined) {
debug('no backup linked to this snaphot')
continue
}
if (snapshot.$SR.uuid !== (mapVdisSrs[vdi.$snapshot_of$uuid] ?? this._srUuid)) {
debug('not restored on the same SR', { snapshotSr: snapshot.$SR.uuid, mapVdisSrs, srUuid: this._srUuid })
continue
}
debug('got a candidate', pathToSnapshotData)
snapshotCandidate = snapshot
backupCandidate = pathToSnapshotData
}
}
let stream
const backupWithSnapshotPath = join(metdataDir, backupCandidate ?? '')
if (vhdPath === backupWithSnapshotPath) {
// all the data are already on the host
debug('direct reuse of a snapshot')
stream = null
vdis[vdiRef].baseVdi = snapshotCandidate
// go next disk , we won't use this stream
continue
}
let disposableDescendants
const disposableSynthetic = await VhdSynthetic.fromVhdChain(this._adapter._handler, vhdPath)
// this will also clean if another disk of this VM backup fails
// if user really only need to restore non failing disks he can retry with ignoredVdis
let disposed = false
const disposeOnce = async () => {
if (!disposed) {
disposed = true
try {
await disposableDescendants?.dispose()
await disposableSynthetic?.dispose()
} catch (error) {
warn('openVhd: failed to dispose VHDs', { error })
}
}
}
$defer.onFailure(() => disposeOnce())
const parentVhd = disposableSynthetic.value
await parentVhd.readBlockAllocationTable()
debug('got vhd synthetic of parents', parentVhd.length)
if (snapshotCandidate !== undefined) {
try {
debug('will try to use differential restore', {
backupWithSnapshotPath,
vhdPath,
vdiRef,
})
disposableDescendants = await VhdSynthetic.fromVhdChain(this._adapter._handler, backupWithSnapshotPath, {
until: vhdPath,
})
const descendantsVhd = disposableDescendants.value
await descendantsVhd.readBlockAllocationTable()
debug('got vhd synthetic of descendants')
const negativeVhd = new VhdNegative(parentVhd, descendantsVhd)
debug('got vhd negative')
// update the stream with the negative vhd stream
stream = await negativeVhd.stream()
vdis[vdiRef].baseVdi = snapshotCandidate
} catch (error) {
// can be a broken VHD chain, a vhd chain with a key backup, ....
// not an irrecuperable error, don't dispose parentVhd, and fallback to full restore
warn(`can't use differential restore`, { error })
disposableDescendants?.dispose()
}
}
// didn't make a negative stream : fallback to classic stream
if (stream === undefined) {
debug('use legacy restore')
stream = await parentVhd.stream()
}
stream.on('end', disposeOnce)
stream.on('close', disposeOnce)
stream.on('error', disposeOnce)
info('everything is ready, will transfer', stream.length)
streams[`${vdiRef}.vhd`] = stream
}
return {
streams,
vbds,
vdis,
version: '1.0.0',
vifs,
vm: { ...vm, suspend_VDI: vmSnapshot.suspend_VDI },
}
}
async #decorateIncrementalVmMetadata() {
const { additionnalVmTag, mapVdisSrs, useDifferentialRestore } = this._importIncrementalVmSettings
const ignoredVdis = new Set(
Object.entries(mapVdisSrs)
.filter(([_, srUuid]) => srUuid === null)
.map(([vdiUuid]) => vdiUuid)
)
let backup
if (useDifferentialRestore) {
backup = await this._reuseNearestSnapshot(ignoredVdis)
} else {
backup = await this._adapter.readIncrementalVmBackup(this._metadata, ignoredVdis)
}
const xapi = this._xapi
const cache = new Map()
const mapVdisSrRefs = {}
if (additionnalVmTag !== undefined) {
backup.vm.tags.push(additionnalVmTag)
}
for (const [vdiUuid, srUuid] of Object.entries(mapVdisSrs)) {
mapVdisSrRefs[vdiUuid] = await resolveUuid(xapi, cache, srUuid, 'SR')
}
const srRef = await resolveUuid(xapi, cache, this._srUuid, 'SR')
Object.values(backup.vdis).forEach(vdi => {
vdi.SR = mapVdisSrRefs[vdi.uuid] ?? srRef
})
return backup
}
async run() {
const adapter = this._adapter
const metadata = this._metadata
const isFull = metadata.mode === 'full'
const sizeContainer = { size: 0 }
const { newMacAddresses } = this._importIncrementalVmSettings
let backup
if (isFull) {
backup = await adapter.readFullVmBackup(metadata)
@@ -28,12 +234,7 @@ export class ImportVmBackup {
} else {
assert.strictEqual(metadata.mode, 'delta')
const ignoredVdis = new Set(
Object.entries(this._importIncrementalVmSettings.mapVdisSrs)
.filter(([_, srUuid]) => srUuid === null)
.map(([vdiUuid]) => vdiUuid)
)
backup = await adapter.readIncrementalVmBackup(metadata, ignoredVdis)
backup = await this.#decorateIncrementalVmMetadata()
Object.values(backup.streams).forEach(stream => watchStreamSize(stream, sizeContainer))
}
@@ -48,8 +249,7 @@ export class ImportVmBackup {
const vmRef = isFull
? await xapi.VM_import(backup, srRef)
: await importIncrementalVm(backup, await xapi.getRecord('SR', srRef), {
...this._importIncrementalVmSettings,
detectBase: false,
newMacAddresses,
})
await Promise.all([
@@ -59,6 +259,13 @@ export class ImportVmBackup {
vmRef,
`${metadata.vm.name_label} (${formatFilenameDate(metadata.timestamp)})`
),
xapi.call(
'VM.set_name_description',
vmRef,
`Restored on ${formatFilenameDate(+new Date())} from ${adapter._handler._remote.name} -
${metadata.vm.name_description}
`
),
])
return {
@@ -69,3 +276,5 @@ export class ImportVmBackup {
)
}
}
decorateClass(ImportVmBackup, { _reuseNearestSnapshot: defer })

View File

@@ -35,6 +35,8 @@ export const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
export const DIR_XO_POOL_METADATA_BACKUPS = 'xo-pool-metadata-backups'
const IMMUTABILTY_METADATA_FILENAME = '/immutability.json'
const { debug, warn } = createLogger('xo:backups:RemoteAdapter')
const compareTimestamp = (a, b) => a.timestamp - b.timestamp
@@ -189,13 +191,14 @@ export class RemoteAdapter {
// check if we will be allowed to merge a a vhd created in this adapter
// with the vhd at path `path`
async isMergeableParent(packedParentUid, path) {
return await Disposable.use(openVhd(this.handler, path), vhd => {
return await Disposable.use(VhdSynthetic.fromVhdChain(this.handler, path), vhd => {
// this baseUuid is not linked with this vhd
if (!vhd.footer.uuid.equals(packedParentUid)) {
return false
}
const isVhdDirectory = vhd instanceof VhdDirectory
// check if all the chain is composed of vhd directory
const isVhdDirectory = vhd.checkVhdsClass(VhdDirectory)
return isVhdDirectory
? this.useVhdDirectory() && this.#getCompressionType() === vhd.compressionType
: !this.useVhdDirectory()
@@ -681,11 +684,13 @@ export class RemoteAdapter {
}
}
async outputStream(path, input, { checksum = true, validator = noop } = {}) {
async outputStream(path, input, { checksum = true, maxStreamLength, streamLength, validator = noop } = {}) {
const container = watchStreamSize(input)
await this._handler.outputStream(path, input, {
checksum,
dirMode: this._dirMode,
maxStreamLength,
streamLength,
async validator() {
await input.task
return validator.apply(this, arguments)
@@ -747,10 +752,37 @@ export class RemoteAdapter {
}
async readVmBackupMetadata(path) {
let json
let isImmutable = false
let remoteIsImmutable = false
// if the remote is immutable, check if this metadatas are also immutables
try {
// this file is not encrypted
await this._handler._readFile(IMMUTABILTY_METADATA_FILENAME)
remoteIsImmutable = true
} catch (error) {
if (error.code !== 'ENOENT') {
throw error
}
}
try {
// this will trigger an EPERM error if the file is immutable
json = await this.handler.readFile(path, { flag: 'r+' })
// s3 handler don't respect flags
} catch (err) {
// retry without triggerring immutbaility check ,only on immutable remote
if (err.code === 'EPERM' && remoteIsImmutable) {
isImmutable = true
json = await this._handler.readFile(path, { flag: 'r' })
} else {
throw err
}
}
// _filename is a private field used to compute the backup id
//
// it's enumerable to make it cacheable
const metadata = { ...JSON.parse(await this._handler.readFile(path)), _filename: path }
const metadata = { ...JSON.parse(json), _filename: path, isImmutable }
// backups created on XenServer < 7.1 via JSON in XML-RPC transports have boolean values encoded as integers, which make them unusable with more recent XAPIs
if (typeof metadata.vm.is_a_template === 'number') {

View File

@@ -21,7 +21,7 @@ export class RestoreMetadataBackup {
})
} else {
const metadata = JSON.parse(await handler.readFile(join(backupId, 'metadata.json')))
const dataFileName = resolve(backupId, metadata.data ?? 'data.json')
const dataFileName = resolve('/', backupId, metadata.data ?? 'data.json').slice(1)
const data = await handler.readFile(dataFileName)
// if data is JSON, sent it as a plain string, otherwise, consider the data as binary and encode it

View File

@@ -2,7 +2,10 @@ import { createLogger } from '@xen-orchestra/log'
import { catchGlobalErrors } from '@xen-orchestra/log/configure'
import Disposable from 'promise-toolbox/Disposable'
import humanFormat from 'human-format'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import mapValues from 'lodash/mapValues.js'
import ms from 'ms'
import { compose } from '@vates/compose'
import { createCachedLookup } from '@vates/cached-dns.lookup'
import { createDebounceResource } from '@vates/disposable/debounceResource.js'
@@ -20,7 +23,7 @@ createCachedLookup().patchGlobal()
const logger = createLogger('xo:backups:worker')
catchGlobalErrors(logger)
const { debug } = logger
const { debug, info, warn } = logger
class BackupWorker {
#config
@@ -149,6 +152,27 @@ process.on('message', async message => {
debug('message received', { message })
if (message.action === 'run') {
const resourceStart = process.resourceUsage()
const timeStart = process.hrtime.bigint()
info('starting backup')
process.on('exit', exitCode => {
const resourceUsage = mapValues(process.resourceUsage(), (end, key) => end - resourceStart[key])
const cpuTotal = resourceUsage.userCPUTime + resourceUsage.systemCPUTime
const duration = Number((process.hrtime.bigint() - timeStart) / 1000n) // in μs
info('process will exit', {
duration,
exitCode,
resourceUsage,
summary: {
duration: ms(duration / 1000),
cpuUsage: Math.round((100 * cpuTotal) / duration) + '%',
memoryUsage: humanFormat.bytes(resourceUsage.maxRSS * 1024),
},
})
})
const backupWorker = new BackupWorker(message.data)
try {
const result = message.runWithLogs
@@ -177,8 +201,16 @@ process.on('message', async message => {
status: 'failure',
})
} finally {
info('backup has ended')
await ignoreErrors.call(backupWorker.debounceResource.flushAll())
process.disconnect()
setTimeout(() => {
warn('worker process did not exit automatically, forcing...')
// eslint-disable-next-line n/no-process-exit
process.exit()
}, 30e3).unref()
}
}
})

View File

@@ -67,6 +67,11 @@ async function generateVhd(path, opts = {}) {
await VhdAbstract.createAlias(handler, path + '.alias.vhd', dataPath)
}
if (opts.blocks) {
for (const blockId of opts.blocks) {
await vhd.writeEntireBlock({ id: blockId, buffer: Buffer.alloc(2 * 1024 * 1024 + 512, blockId) })
}
}
await vhd.writeBlockAllocationTable()
await vhd.writeHeader()
await vhd.writeFooter()
@@ -230,7 +235,7 @@ test('it merges delta of non destroyed chain', async () => {
const metadata = JSON.parse(await handler.readFile(`${rootPath}/metadata.json`))
// size should be the size of children + grand children after the merge
assert.equal(metadata.size, 209920)
assert.equal(metadata.size, 104960)
// merging is already tested in vhd-lib, don't retest it here (and theses vhd are as empty as my stomach at 12h12)
// only check deletion
@@ -320,6 +325,7 @@ describe('tests multiple combination ', () => {
const ancestor = await generateVhd(`${basePath}/ancestor.vhd`, {
useAlias,
mode: vhdMode,
blocks: [1, 3],
})
const child = await generateVhd(`${basePath}/child.vhd`, {
useAlias,
@@ -328,6 +334,7 @@ describe('tests multiple combination ', () => {
parentUnicodeName: 'ancestor.vhd' + (useAlias ? '.alias.vhd' : ''),
parentUuid: ancestor.footer.uuid,
},
blocks: [1, 2],
})
// a grand child vhd in metadata
await generateVhd(`${basePath}/grandchild.vhd`, {
@@ -337,6 +344,7 @@ describe('tests multiple combination ', () => {
parentUnicodeName: 'child.vhd' + (useAlias ? '.alias.vhd' : ''),
parentUuid: child.footer.uuid,
},
blocks: [2, 3],
})
// an older parent that was merging in clean
@@ -395,7 +403,7 @@ describe('tests multiple combination ', () => {
const metadata = JSON.parse(await handler.readFile(`${rootPath}/metadata.json`))
// size should be the size of children + grand children + clean after the merge
assert.deepEqual(metadata.size, vhdMode === 'file' ? 314880 : undefined)
assert.deepEqual(metadata.size, vhdMode === 'file' ? 6502400 : 6501888)
// broken vhd, non referenced, abandonned should be deleted ( alias and data)
// ancestor and child should be merged

View File

@@ -3,7 +3,7 @@ import sum from 'lodash/sum.js'
import { asyncMap } from '@xen-orchestra/async-map'
import { Constants, openVhd, VhdAbstract, VhdFile } from 'vhd-lib'
import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
import { dirname, resolve } from 'node:path'
import { basename, dirname, resolve } from 'node:path'
import { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } from './_backupType.mjs'
import { limitConcurrency } from 'limit-concurrency-decorator'
import { mergeVhdChain } from 'vhd-lib/merge.js'
@@ -36,34 +36,32 @@ const computeVhdsSize = (handler, vhdPaths) =>
)
// chain is [ ancestor, child_1, ..., child_n ]
async function _mergeVhdChain(handler, chain, { logInfo, remove, merge, mergeBlockConcurrency }) {
if (merge) {
logInfo(`merging VHD chain`, { chain })
async function _mergeVhdChain(handler, chain, { logInfo, remove, mergeBlockConcurrency }) {
logInfo(`merging VHD chain`, { chain })
let done, total
const handle = setInterval(() => {
if (done !== undefined) {
logInfo('merge in progress', {
done,
parent: chain[0],
progress: Math.round((100 * done) / total),
total,
})
}
}, 10e3)
try {
return await mergeVhdChain(handler, chain, {
logInfo,
mergeBlockConcurrency,
onProgress({ done: d, total: t }) {
done = d
total = t
},
removeUnused: remove,
let done, total
const handle = setInterval(() => {
if (done !== undefined) {
logInfo('merge in progress', {
done,
parent: chain[0],
progress: Math.round((100 * done) / total),
total,
})
} finally {
clearInterval(handle)
}
}, 10e3)
try {
return await mergeVhdChain(handler, chain, {
logInfo,
mergeBlockConcurrency,
onProgress({ done: d, total: t }) {
done = d
total = t
},
removeUnused: remove,
})
} finally {
clearInterval(handle)
}
}
@@ -181,7 +179,8 @@ export async function cleanVm(
vmDir,
{
fixMetadata,
remove,
remove = false,
removeTmp = remove,
merge,
mergeBlockConcurrency,
mergeLimiter = defaultMergeLimiter,
@@ -202,6 +201,10 @@ export async function cleanVm(
// remove broken VHDs
await asyncMap(vhds, async path => {
if(removeTmp && basename(path)[0] === '.'){
logInfo('deleting temporary VHD', { path })
return VhdAbstract.unlink(handler, path)
}
try {
await Disposable.use(openVhd(handler, path, { checkSecondFooter: !interruptedVhds.has(path) }), vhd => {
if (vhd.footer.diskType === DISK_TYPES.DIFFERENCING) {
@@ -439,7 +442,8 @@ export async function cleanVm(
}
}
logWarn('unused VHD', { path: vhd })
// no warning because a VHD can be unused for perfectly good reasons,
// e.g. the corresponding backup (metadata file) has been deleted
if (remove) {
logInfo('deleting unused VHD', { path: vhd })
unusedVhdsDeletion.push(VhdAbstract.unlink(handler, vhd))
@@ -471,23 +475,20 @@ export async function cleanVm(
const metadataWithMergedVhd = {}
const doMerge = async () => {
await asyncMap(toMerge, async chain => {
const merged = await limitedMergeVhdChain(handler, chain, {
const { finalVhdSize } = await limitedMergeVhdChain(handler, chain, {
logInfo,
logWarn,
remove,
merge,
mergeBlockConcurrency,
})
if (merged !== undefined) {
const metadataPath = vhdsToJSons[chain[chain.length - 1]] // all the chain should have the same metada file
metadataWithMergedVhd[metadataPath] = true
}
const metadataPath = vhdsToJSons[chain[chain.length - 1]] // all the chain should have the same metada file
metadataWithMergedVhd[metadataPath] = (metadataWithMergedVhd[metadataPath] ?? 0) + finalVhdSize
})
}
await Promise.all([
...unusedVhdsDeletion,
toMerge.length !== 0 && (merge ? Task.run({ name: 'merge' }, doMerge) : doMerge()),
toMerge.length !== 0 && (merge ? Task.run({ name: 'merge' }, doMerge) : () => Promise.resolve()),
asyncMap(unusedXvas, path => {
logWarn('unused XVA', { path })
if (remove) {
@@ -509,12 +510,11 @@ export async function cleanVm(
// update size for delta metadata with merged VHD
// check for the other that the size is the same as the real file size
await asyncMap(jsons, async metadataPath => {
const metadata = backups.get(metadataPath)
let fileSystemSize
const merged = metadataWithMergedVhd[metadataPath] !== undefined
const mergedSize = metadataWithMergedVhd[metadataPath]
const { mode, size, vhds, xva } = metadata
@@ -524,26 +524,29 @@ export async function cleanVm(
const linkedXva = resolve('/', vmDir, xva)
try {
fileSystemSize = await handler.getSize(linkedXva)
if (fileSystemSize !== size && fileSystemSize !== undefined) {
logWarn('cleanVm: incorrect backup size in metadata', {
path: metadataPath,
actual: size ?? 'none',
expected: fileSystemSize,
})
}
} catch (error) {
// can fail with encrypted remote
}
} else if (mode === 'delta') {
const linkedVhds = Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
fileSystemSize = await computeVhdsSize(handler, linkedVhds)
// the size is not computed in some cases (e.g. VhdDirectory)
if (fileSystemSize === undefined) {
return
}
// don't warn if the size has changed after a merge
if (!merged && fileSystemSize !== size) {
// FIXME: figure out why it occurs so often and, once fixed, log the real problems with `logWarn`
console.warn('cleanVm: incorrect backup size in metadata', {
path: metadataPath,
actual: size ?? 'none',
expected: fileSystemSize,
})
if (mergedSize === undefined) {
const linkedVhds = Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
fileSystemSize = await computeVhdsSize(handler, linkedVhds)
// the size is not computed in some cases (e.g. VhdDirectory)
if (fileSystemSize !== undefined && fileSystemSize !== size) {
logWarn('cleanVm: incorrect backup size in metadata', {
path: metadataPath,
actual: size ?? 'none',
expected: fileSystemSize,
})
}
}
}
} catch (error) {
@@ -551,9 +554,19 @@ export async function cleanVm(
return
}
// systematically update size after a merge
if ((merged || fixMetadata) && size !== fileSystemSize) {
metadata.size = fileSystemSize
// systematically update size and differentials after a merge
// @todo : after 2024-04-01 remove the fixmetadata options since the size computation is fixed
if (mergedSize || (fixMetadata && fileSystemSize !== size)) {
metadata.size = mergedSize ?? fileSystemSize ?? size
if (mergedSize) {
// all disks are now key disk
metadata.isVhdDifferencing = {}
for (const id of Object.values(metadata.vdis ?? {})) {
metadata.isVhdDifferencing[`${id}.vhd`] = false
}
}
mustRegenerateCache = true
try {
await handler.writeFile(metadataPath, JSON.stringify(metadata), { flags: 'w' })

View File

@@ -1,7 +1,5 @@
import find from 'lodash/find.js'
import groupBy from 'lodash/groupBy.js'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import omit from 'lodash/omit.js'
import { asyncMap } from '@xen-orchestra/async-map'
import { CancelToken } from 'promise-toolbox'
import { compareVersions } from 'compare-versions'
@@ -11,48 +9,16 @@ import { defer } from 'golike-defer'
import { cancelableMap } from './_cancelableMap.mjs'
import { Task } from './Task.mjs'
import pick from 'lodash/pick.js'
export const TAG_BASE_DELTA = 'xo:base_delta'
export const TAG_COPY_SRC = 'xo:copy_of'
const TAG_BACKUP_SR = 'xo:backup:sr'
import { BASE_DELTA_VDI, COPY_OF, VM_UUID } from './_otherConfig.mjs'
const ensureArray = value => (value === undefined ? [] : Array.isArray(value) ? value : [value])
const resolveUuid = async (xapi, cache, uuid, type) => {
if (uuid == null) {
return uuid
}
let ref = cache.get(uuid)
if (ref === undefined) {
ref = await xapi.call(`${type}.get_by_uuid`, uuid)
cache.set(uuid, ref)
}
return ref
}
export async function exportIncrementalVm(
vm,
baseVm,
{
cancelToken = CancelToken.none,
// Sets of UUIDs of VDIs that must be exported as full.
fullVdisRequired = new Set(),
disableBaseTags = false,
preferNbd,
} = {}
baseVdis = {},
{ cancelToken = CancelToken.none, nbdConcurrency = 1, preferNbd } = {}
) {
// refs of VM's VDIs → base's VDIs.
const baseVdis = {}
baseVm &&
baseVm.$VBDs.forEach(vbd => {
let vdi, snapshotOf
if ((vdi = vbd.$VDI) && (snapshotOf = vdi.$snapshot_of) && !fullVdisRequired.has(snapshotOf.uuid)) {
baseVdis[vdi.snapshot_of] = vdi
}
})
const streams = {}
const vdis = {}
@@ -73,13 +39,16 @@ export async function exportIncrementalVm(
}
// Look for a snapshot of this vdi in the base VM.
const baseVdi = baseVdis[vdi.snapshot_of]
const baseVdi = baseVdis[vdi.$snapshot_of.uuid]
vdis[vdiRef] = {
...vdi,
other_config: {
...vdi.other_config,
[TAG_BASE_DELTA]: baseVdi && !disableBaseTags ? baseVdi.uuid : undefined,
[BASE_DELTA_VDI]: baseVdi?.uuid,
[VM_UUID]:
vm.$snapshot_of?.uuid ?? // vm is a snapshot
vm.uuid, // vm is a not snapshot
},
$snapshot_of$uuid: vdi.$snapshot_of?.uuid,
$SR$uuid: vdi.$SR.uuid,
@@ -89,6 +58,7 @@ export async function exportIncrementalVm(
baseRef: baseVdi?.$ref,
cancelToken,
format: 'vhd',
nbdConcurrency,
preferNbd,
})
})
@@ -125,13 +95,6 @@ export async function exportIncrementalVm(
vifs,
vm: {
...vm,
other_config:
baseVm && !disableBaseTags
? {
...vm.other_config,
[TAG_BASE_DELTA]: baseVm.uuid,
}
: omit(vm.other_config, TAG_BASE_DELTA),
},
},
'streams',
@@ -147,7 +110,7 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
$defer,
incrementalVm,
sr,
{ cancelToken = CancelToken.none, detectBase = true, mapVdisSrs = {}, newMacAddresses = false } = {}
{ cancelToken = CancelToken.none, newMacAddresses = false } = {}
) {
const { version } = incrementalVm
if (compareVersions(version, '1.0.0') < 0) {
@@ -157,35 +120,6 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
const vmRecord = incrementalVm.vm
const xapi = sr.$xapi
let baseVm
if (detectBase) {
const remoteBaseVmUuid = vmRecord.other_config[TAG_BASE_DELTA]
if (remoteBaseVmUuid) {
baseVm = find(
xapi.objects.all,
obj => (obj = obj.other_config) && obj[TAG_COPY_SRC] === remoteBaseVmUuid && obj[TAG_BACKUP_SR] === sr.$id
)
if (!baseVm) {
throw new Error(`could not find the base VM (copy of ${remoteBaseVmUuid})`)
}
}
}
const cache = new Map()
const mapVdisSrRefs = {}
for (const [vdiUuid, srUuid] of Object.entries(mapVdisSrs)) {
mapVdisSrRefs[vdiUuid] = await resolveUuid(xapi, cache, srUuid, 'SR')
}
const baseVdis = {}
baseVm &&
baseVm.$VBDs.forEach(vbd => {
const vdi = vbd.$VDI
if (vdi !== undefined) {
baseVdis[vbd.VDI] = vbd.$VDI
}
})
const vdiRecords = incrementalVm.vdis
// 0. Create suspend_VDI
@@ -197,18 +131,7 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
vm: pick(vmRecord, 'uuid', 'name_label', 'suspend_VDI'),
})
} else {
suspendVdi = await xapi.getRecord(
'VDI',
await xapi.VDI_create({
...vdi,
other_config: {
...vdi.other_config,
[TAG_BASE_DELTA]: undefined,
[TAG_COPY_SRC]: vdi.uuid,
},
sr: mapVdisSrRefs[vdi.uuid] ?? sr.$ref,
})
)
suspendVdi = await xapi.getRecord('VDI', await xapi.VDI_create(vdi))
$defer.onFailure(() => suspendVdi.$destroy())
}
}
@@ -226,10 +149,6 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
ha_always_run: false,
is_a_template: false,
name_label: '[Importing…] ' + vmRecord.name_label,
other_config: {
...vmRecord.other_config,
[TAG_COPY_SRC]: vmRecord.uuid,
},
},
{
bios_strings: vmRecord.bios_strings,
@@ -250,17 +169,11 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
const vdi = vdiRecords[vdiRef]
let newVdi
const remoteBaseVdiUuid = detectBase && vdi.other_config[TAG_BASE_DELTA]
if (remoteBaseVdiUuid) {
const baseVdi = find(baseVdis, vdi => vdi.other_config[TAG_COPY_SRC] === remoteBaseVdiUuid)
if (!baseVdi) {
throw new Error(`missing base VDI (copy of ${remoteBaseVdiUuid})`)
}
newVdi = await xapi.getRecord('VDI', await baseVdi.$clone())
if (vdi.baseVdi !== undefined) {
newVdi = await xapi.getRecord('VDI', await vdi.baseVdi.$clone())
$defer.onFailure(() => newVdi.$destroy())
await newVdi.update_other_config(TAG_COPY_SRC, vdi.uuid)
await newVdi.update_other_config(COPY_OF, vdi.uuid)
if (vdi.virtual_size > newVdi.virtual_size) {
await newVdi.$callAsync('resize', vdi.virtual_size)
}
@@ -268,18 +181,7 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
// suspendVDI has already created
newVdi = suspendVdi
} else {
newVdi = await xapi.getRecord(
'VDI',
await xapi.VDI_create({
...vdi,
other_config: {
...vdi.other_config,
[TAG_BASE_DELTA]: undefined,
[TAG_COPY_SRC]: vdi.uuid,
},
SR: mapVdisSrRefs[vdi.uuid] ?? sr.$ref,
})
)
newVdi = await xapi.getRecord('VDI', await xapi.VDI_create(vdi))
$defer.onFailure(() => newVdi.$destroy())
}
@@ -318,13 +220,19 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
// Import VDI contents.
cancelableMap(cancelToken, Object.entries(newVdis), async (cancelToken, [id, vdi]) => {
for (let stream of ensureArray(streams[`${id}.vhd`])) {
if (stream === null) {
// we restore a backup and reuse completly a local snapshot
continue
}
if (typeof stream === 'function') {
stream = await stream()
}
if (stream.length === undefined) {
stream = await createVhdStreamWithLength(stream)
}
await xapi.setField('VDI', vdi.$ref, 'name_label', `[Importing] ${vdiRecords[id].name_label}`)
await vdi.$importContent(stream, { cancelToken, format: 'vhd' })
await xapi.setField('VDI', vdi.$ref, 'name_label', vdiRecords[id].name_label)
}
}),

View File

@@ -0,0 +1,165 @@
import { formatDateTime } from '@xen-orchestra/xapi'
import assert from 'node:assert/strict'
// in `other_config` of an incrementally replicated VM or VDI
// contains the UUID of the object used as a base for an incremental export
// used to search for the replica of the base before applying a incremental replication
export const BASE_DELTA_VDI = 'xo:base_delta_vdi'
// in `other_config` of an incrementally replicated VM, contains the UUID of the target SR used for replication
//
// added after the complete replication
export const REPLICATED_TO_SR_UUID = 'xo:backup:sr'
// in other_config of VDIs of an incrementally replicated VM
// contains the UUID of the source exported object (snapshot or VM)
export const COPY_OF = 'xo:copy_of'
export const DATETIME = 'xo:backup:datetime'
export const JOB_ID = 'xo:backup:job'
export const SCHEDULE_ID = 'xo:backup:schedule'
// contains the number of delta in a chain, stored as a string
export const DELTA_CHAIN_LENGTH = 'xo:backup:deltaChainLength'
// contains the string true if this vdi has been exported successfully
export const EXPORTED_SUCCESSFULLY = 'xo:backup:exported'
// the VM ( not the snapshot) uuid
export const VM_UUID = 'xo:backup:vm'
async function listVdiRefs(xapi, vmRef) {
return xapi.VM_getDisks(vmRef)
}
async function applyToVmAndVdis(xapi, vmRef, fn) {
const vdiRefs = await listVdiRefs(xapi, vmRef)
return Promise.all([fn('VM', vmRef), ...vdiRefs.map(vdiRef => fn('VDI', vdiRef))])
}
async function getDeltaChainLength(xapi, type, ref) {
const otherConfig = await xapi.getField(type, ref, 'other_config')
return Number(otherConfig[DELTA_CHAIN_LENGTH] ?? 0)
}
/**
* set the delta chain lenght ( number of delta since last base backup) to a VM and its associated VDIs
*
* @param {Xapi} xapi
* @param {String} vmRef
* @param {Number} length
* @returns {Promise}
*/
export async function setVmDeltaChainLength(xapi, vmRef, length) {
return applyToVmAndVdis(xapi, vmRef, async (type, ref) => {
await xapi.setFieldEntry(type, ref, 'other_config', DELTA_CHAIN_LENGTH, String(length))
})
}
/**
* Compute the delta chain length of a VM and its associated VDIs
* if there is a discrependcy, use, the highest value
* @param {Xapi} xapi
* @param {String} vmRef
* @returns {Promise}
*/
export async function getVmDeltaChainLength(xapi, vmRef) {
const lengths = await applyToVmAndVdis(xapi, vmRef, async (type, ref) => getDeltaChainLength(xapi, type, ref))
return Math.max(...lengths)
}
/**
*
* Reset the other_config field of a VM and its VDIs
*
* @param {Xapi} xapi
* @param {String} vmRef
* @returns {Promise}
*/
export function resetVmOtherConfig(xapi, vmRef) {
return applyToVmAndVdis(xapi, vmRef, (type, ref) => {
return xapi.setFieldEntries(type, ref, 'other_config', {
[DATETIME]: null,
[DELTA_CHAIN_LENGTH]: null,
[EXPORTED_SUCCESSFULLY]: null,
[JOB_ID]: null,
[SCHEDULE_ID]: null,
[VM_UUID]: null,
// REPLICATED_TO_SR_UUID is not reset since we can replicate a replication
})
})
}
/**
*
* used to ensure compatibiliy with the previous snapshots that were having the config stored only into VM
*
* @param {Xapi} xapi
* @param {String} vmRef
* @returns {Promise}
*/
export async function populateVdisOtherConfig(xapi, vmRef) {
const otherConfig = await xapi.getField('VM', vmRef, 'other_config')
const {
[DATETIME]: datetime,
[DELTA_CHAIN_LENGTH]: chainLength,
[EXPORTED_SUCCESSFULLY]: successfully,
[JOB_ID]: jobId,
[REPLICATED_TO_SR_UUID]: replicatedTo,
[SCHEDULE_ID]: scheduleId,
[VM_UUID]: vmUuid,
} = otherConfig
return applyToVmAndVdis(xapi, vmRef, (type, ref) =>
xapi.setFieldEntries(type, ref, 'other_config', {
[DATETIME]: datetime,
[DELTA_CHAIN_LENGTH]: chainLength,
[EXPORTED_SUCCESSFULLY]: successfully,
[JOB_ID]: jobId,
[REPLICATED_TO_SR_UUID]: replicatedTo,
[SCHEDULE_ID]: scheduleId,
[VM_UUID]: vmUuid,
})
)
}
/**
*
* set the other_config key related to a backup of a VM and its associated VDIs
*
* @param {Xapi} xapi
* @param {String} vmRef
* @param {*} settings
* @returns {PRomise}
*/
export async function setVmOtherConfig(xapi, vmRef, { timestamp, jobId, scheduleId, vmUuid, srUuid = null, ...other }) {
assert.notEqual(timestamp, undefined)
assert.notEqual(jobId, undefined)
assert.notEqual(scheduleId, undefined)
assert.notEqual(vmUuid, undefined)
// srUuid is nullish for backup
assert.equal(Object.keys(other).length, 0)
return applyToVmAndVdis(xapi, vmRef, (type, ref) =>
xapi.setFieldEntries(type, ref, 'other_config', {
[REPLICATED_TO_SR_UUID]: srUuid,
[DATETIME]: formatDateTime(timestamp),
[JOB_ID]: jobId,
[SCHEDULE_ID]: scheduleId,
[VM_UUID]: vmUuid,
})
)
}
/**
*
* mark the export of he VM and its VDIs as successfull
*
* @param {Xapi} xapi
* @param {String} vmRef
* @returns {Promise}
*/
export async function markExportSuccessfull(xapi, vmRef) {
return applyToVmAndVdis(xapi, vmRef, (type, ref) =>
xapi.setFieldEntry(type, ref, 'other_config', EXPORTED_SUCCESSFULLY, 'true')
)
}

View File

@@ -6,11 +6,12 @@ import { extractIdsFromSimplePattern } from '../extractIdsFromSimplePattern.mjs'
import { Task } from '../Task.mjs'
import createStreamThrottle from './_createStreamThrottle.mjs'
import { DEFAULT_SETTINGS, Abstract } from './_Abstract.mjs'
import { runTask } from './_runTask.mjs'
import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
import { FullRemote } from './_vmRunners/FullRemote.mjs'
import { IncrementalRemote } from './_vmRunners/IncrementalRemote.mjs'
const noop = Function.prototype
const DEFAULT_REMOTE_VM_SETTINGS = {
concurrency: 2,
copyRetention: 0,
@@ -18,8 +19,10 @@ const DEFAULT_REMOTE_VM_SETTINGS = {
exportRetention: 0,
healthCheckSr: undefined,
healthCheckVmsWithTags: [],
healthCheckTimeout: '10m',
maxExportRate: 0,
maxMergedDeltasPerRun: Infinity,
nRetriesVmBackupFailures: 0,
timeout: 0,
validateVhdStreams: false,
vmTimeout: 0,
@@ -41,6 +44,7 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
const throttleStream = createStreamThrottle(settings.maxExportRate)
const config = this._config
await Disposable.use(
() => this._getAdapter(job.sourceRemote),
() => (settings.healthCheckSr !== undefined ? this._getRecord('SR', settings.healthCheckSr) : undefined),
@@ -62,8 +66,19 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
const allSettings = this._job.settings
const baseSettings = this._baseSettings
const queue = new Set(vmsUuids)
const taskByVmId = {}
const nTriesByVmId = {}
const handleVm = vmUuid => {
if (nTriesByVmId[vmUuid] === undefined) {
nTriesByVmId[vmUuid] = 0
}
nTriesByVmId[vmUuid]++
const taskStart = { name: 'backup VM', data: { type: 'VM', id: vmUuid } }
const vmSettings = { ...settings, ...allSettings[vmUuid] }
const isLastRun = nTriesByVmId[vmUuid] === vmSettings.nRetriesVmBackupFailures + 1
const opts = {
baseSettings,
@@ -72,7 +87,7 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
healthCheckSr,
remoteAdapters,
schedule,
settings: { ...settings, ...allSettings[vmUuid] },
settings: vmSettings,
sourceRemoteAdapter,
throttleStream,
vmUuid,
@@ -86,10 +101,46 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
throw new Error(`Job mode ${job.mode} not implemented for mirror backup`)
}
return runTask(taskStart, () => vmBackup.run())
return sourceRemoteAdapter
.listVmBackups(vmUuid, ({ mode }) => mode === job.mode)
.then(vmBackups => {
// avoiding to create tasks for empty directories
if (vmBackups.length > 0) {
if (taskByVmId[vmUuid] === undefined) {
taskByVmId[vmUuid] = new Task(taskStart)
}
const task = taskByVmId[vmUuid]
return task
.run(async () => {
try {
const result = await vmBackup.run()
task.success(result)
return result
} catch (error) {
if (isLastRun) {
throw error
} else {
Task.warning(`Retry the VM mirror backup due to an error`, {
attempt: nTriesByVmId[vmUuid],
error: error.message,
})
queue.add(vmUuid)
}
}
})
.catch(noop)
}
})
}
const { concurrency } = settings
await asyncMapSettled(vmsUuids, !concurrency ? handleVm : limitConcurrency(concurrency)(handleVm))
const _handleVm = !concurrency ? handleVm : limitConcurrency(concurrency)(handleVm)
while (queue.size > 0) {
const vmIds = Array.from(queue)
queue.clear()
await asyncMapSettled(vmIds, _handleVm)
}
}
)
}

View File

@@ -11,8 +11,11 @@ import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
import { IncrementalXapi } from './_vmRunners/IncrementalXapi.mjs'
import { FullXapi } from './_vmRunners/FullXapi.mjs'
const noop = Function.prototype
const DEFAULT_XAPI_VM_SETTINGS = {
bypassVdiChainsCheck: false,
cbtDestroySnapshotData: false,
checkpointSnapshot: false,
concurrency: 2,
copyRetention: 0,
@@ -22,8 +25,10 @@ const DEFAULT_XAPI_VM_SETTINGS = {
fullInterval: 0,
healthCheckSr: undefined,
healthCheckVmsWithTags: [],
healthCheckTimeout: '10m',
maxExportRate: 0,
maxMergedDeltasPerRun: Infinity,
nRetriesVmBackupFailures: 0,
offlineBackup: false,
offlineSnapshot: false,
snapshotRetention: 0,
@@ -53,6 +58,7 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
const throttleStream = createStreamThrottle(settings.maxExportRate)
const config = this._config
await Disposable.use(
Disposable.all(
extractIdsFromSimplePattern(job.srs).map(id =>
@@ -89,48 +95,98 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
const allSettings = this._job.settings
const baseSettings = this._baseSettings
const queue = new Set(vmIds)
const taskByVmId = {}
const nTriesByVmId = {}
const handleVm = vmUuid => {
const getVmTask = () => {
if (taskByVmId[vmUuid] === undefined) {
taskByVmId[vmUuid] = new Task(taskStart)
}
return taskByVmId[vmUuid]
}
const vmBackupFailed = error => {
if (isLastRun) {
throw error
} else {
Task.warning(`Retry the VM backup due to an error`, {
attempt: nTriesByVmId[vmUuid],
error: error.message,
})
queue.add(vmUuid)
}
}
if (nTriesByVmId[vmUuid] === undefined) {
nTriesByVmId[vmUuid] = 0
}
nTriesByVmId[vmUuid]++
const vmSettings = { ...settings, ...allSettings[vmUuid] }
const taskStart = { name: 'backup VM', data: { type: 'VM', id: vmUuid } }
const isLastRun = nTriesByVmId[vmUuid] === vmSettings.nRetriesVmBackupFailures + 1
return this._getRecord('VM', vmUuid).then(
disposableVm =>
Disposable.use(disposableVm, vm => {
taskStart.data.name_label = vm.name_label
return runTask(taskStart, () => {
const opts = {
baseSettings,
config,
getSnapshotNameLabel,
healthCheckSr,
job,
remoteAdapters,
schedule,
settings: { ...settings, ...allSettings[vm.uuid] },
srs,
throttleStream,
vm,
}
let vmBackup
if (job.mode === 'delta') {
vmBackup = new IncrementalXapi(opts)
} else {
if (job.mode === 'full') {
vmBackup = new FullXapi(opts)
} else {
throw new Error(`Job mode ${job.mode} not implemented`)
Disposable.use(disposableVm, async vm => {
if (taskStart.data.name_label === undefined) {
taskStart.data.name_label = vm.name_label
}
const task = getVmTask()
return task
.run(async () => {
const opts = {
baseSettings,
config,
getSnapshotNameLabel,
healthCheckSr,
job,
remoteAdapters,
schedule,
settings: vmSettings,
srs,
throttleStream,
vm,
}
}
return vmBackup.run()
})
let vmBackup
if (job.mode === 'delta') {
vmBackup = new IncrementalXapi(opts)
} else {
if (job.mode === 'full') {
vmBackup = new FullXapi(opts)
} else {
throw new Error(`Job mode ${job.mode} not implemented`)
}
}
try {
const result = await vmBackup.run()
task.success(result)
return result
} catch (error) {
vmBackupFailed(error)
}
})
.catch(noop) // errors are handled by logs
}),
error =>
runTask(taskStart, () => {
throw error
getVmTask().run(() => {
vmBackupFailed(error)
})
)
}
const { concurrency } = settings
await asyncMapSettled(vmIds, concurrency === 0 ? handleVm : limitConcurrency(concurrency)(handleVm))
const _handleVm = concurrency === 0 ? handleVm : limitConcurrency(concurrency)(handleVm)
while (queue.size > 0) {
const vmIds = Array.from(queue)
queue.clear()
await asyncMapSettled(vmIds, _handleVm)
}
}
)
}

View File

@@ -31,7 +31,7 @@ export class PoolMetadataBackup {
const poolDir = `${DIR_XO_POOL_METADATA_BACKUPS}/${schedule.id}/${pool.$id}`
const dir = `${poolDir}/${formatFilenameDate(timestamp)}`
const stream = await this._exportPoolMetadata()
const stream = (await this._exportPoolMetadata()).body
const fileName = `${dir}/data`
const metadata = JSON.stringify(

View File

@@ -29,6 +29,10 @@ export function forkStreamUnpipe(source) {
if (source.forks === 0) {
debug('no more forks, destroying original stream')
source.destroy(new Error('no more consumers for this stream'))
} else {
// a combination of stream.unpipe, onReadable and onData may stall stream here
// force it to flow again since we're piping it
source.resume()
}
})
return fork

View File

@@ -1,5 +1,3 @@
import { decorateMethodsWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import { AbstractRemote } from './_AbstractRemote.mjs'
import { FullRemoteWriter } from '../_writers/FullRemoteWriter.mjs'
import { forkStreamUnpipe } from '../_forkStreamUnpipe.mjs'
@@ -10,15 +8,9 @@ export const FullRemote = class FullRemoteVmBackupRunner extends AbstractRemote
_getRemoteWriter() {
return FullRemoteWriter
}
async _run($defer) {
async _run() {
const transferList = await this._computeTransferList(({ mode }) => mode === 'full')
await this._callWriters(async writer => {
await writer.beforeBackup()
$defer(async () => {
await writer.afterBackup()
})
}, 'writer.beforeBackup()')
if (transferList.length > 0) {
for (const metadata of transferList) {
const stream = await this._sourceRemoteAdapter.readFullVmBackup(metadata)
@@ -29,6 +21,8 @@ export const FullRemote = class FullRemoteVmBackupRunner extends AbstractRemote
writer =>
writer.run({
stream: forkStreamUnpipe(stream),
// stream will be forked and transformed, it's not safe to attach additionnal properties to it
streamLength: stream.length,
timestamp: metadata.timestamp,
vm: metadata.vm,
vmSnapshot: metadata.vmSnapshot,
@@ -44,7 +38,3 @@ export const FullRemote = class FullRemoteVmBackupRunner extends AbstractRemote
}
}
}
decorateMethodsWith(FullRemote, {
_run: defer,
})

View File

@@ -30,18 +30,32 @@ export const FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
const vm = this._vm
const exportedVm = this._exportedVm
const stream = this._throttleStream(
await this._xapi.VM_export(exportedVm.$ref, {
compress: Boolean(compression) && (compression === 'native' ? 'gzip' : 'zstd'),
useSnapshot: false,
})
(
await this._xapi.VM_export(exportedVm.$ref, {
compress: Boolean(compression) && (compression === 'native' ? 'gzip' : 'zstd'),
useSnapshot: false,
})
).body
)
const vdis = await exportedVm.$getDisks()
let maxStreamLength = 1024 * 1024 // Ovf file and tar headers are a few KB, let's stay safe
for (const vdiRef of vdis) {
const vdi = await this._xapi.getRecord('VDI', vdiRef)
// the size a of fully allocated vdi will be virtual_size exaclty, it's a gross over evaluation
// of the real stream size in general, since a disk is never completly full
// vdi.physical_size seems to underevaluate a lot the real disk usage of a VDI, as of 2023-10-30
maxStreamLength += vdi.virtual_size
}
const sizeContainer = watchStreamSize(stream)
const timestamp = Date.now()
await this._callWriters(
writer =>
writer.run({
maxStreamLength,
sizeContainer,
stream: forkStreamUnpipe(stream),
timestamp,

View File

@@ -1,7 +1,6 @@
import { asyncEach } from '@vates/async-each'
import { decorateMethodsWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import assert from 'node:assert'
import * as UUID from 'uuid'
import isVhdDifferencingDisk from 'vhd-lib/isVhdDifferencingDisk.js'
import mapValues from 'lodash/mapValues.js'
@@ -9,33 +8,64 @@ import { AbstractRemote } from './_AbstractRemote.mjs'
import { forkDeltaExport } from './_forkDeltaExport.mjs'
import { IncrementalRemoteWriter } from '../_writers/IncrementalRemoteWriter.mjs'
import { Task } from '../../Task.mjs'
import { Disposable } from 'promise-toolbox'
import { openVhd } from 'vhd-lib'
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
class IncrementalRemoteVmBackupRunner extends AbstractRemote {
_getRemoteWriter() {
return IncrementalRemoteWriter
}
async _run($defer) {
async _selectBaseVm(metadata) {
// for each disk , get the parent
const baseUuidToSrcVdi = new Map()
// no previous backup for a base( =key) backup
if (metadata.isBase) {
return
}
await asyncEach(Object.entries(metadata.vdis), async ([id, vdi]) => {
const isDifferencing = metadata.isVhdDifferencing[`${id}.vhd`]
if (isDifferencing) {
const vmDir = getVmBackupDir(metadata.vm.uuid)
const path = `${vmDir}/${metadata.vhds[id]}`
// don't catch error : we can't recover if the source vhd are missing
await Disposable.use(openVhd(this._sourceRemoteAdapter._handler, path), vhd => {
baseUuidToSrcVdi.set(UUID.stringify(vhd.header.parentUuid), vdi.$snapshot_of$uuid)
})
}
})
const presentBaseVdis = new Map(baseUuidToSrcVdi)
await this._callWriters(
writer => presentBaseVdis.size !== 0 && writer.checkBaseVdis(presentBaseVdis),
'writer.checkBaseVdis()',
false
)
// check if the parent vdi are present in all the remotes
baseUuidToSrcVdi.forEach((srcVdiUuid, baseUuid) => {
if (!presentBaseVdis.has(baseUuid)) {
throw new Error(`Missing vdi ${baseUuid} which is a base for a delta`)
}
})
// yeah , let's go
}
async _run() {
const transferList = await this._computeTransferList(({ mode }) => mode === 'delta')
await this._callWriters(async writer => {
await writer.beforeBackup()
$defer(async () => {
await writer.afterBackup()
})
}, 'writer.beforeBackup()')
if (transferList.length > 0) {
for (const metadata of transferList) {
assert.strictEqual(metadata.mode, 'delta')
await this._selectBaseVm(metadata)
await this._callWriters(writer => writer.prepare({ isBase: metadata.isBase }), 'writer.prepare()')
const incrementalExport = await this._sourceRemoteAdapter.readIncrementalVmBackup(metadata, undefined, {
useChain: false,
})
const differentialVhds = {}
const isVhdDifferencing = {}
await asyncEach(Object.entries(incrementalExport.streams), async ([key, stream]) => {
differentialVhds[key] = await isVhdDifferencingDisk(stream)
isVhdDifferencing[key] = await isVhdDifferencingDisk(stream)
})
incrementalExport.streams = mapValues(incrementalExport.streams, this._throttleStream)
@@ -43,13 +73,24 @@ class IncrementalRemoteVmBackupRunner extends AbstractRemote {
writer =>
writer.transfer({
deltaExport: forkDeltaExport(incrementalExport),
differentialVhds,
isVhdDifferencing,
timestamp: metadata.timestamp,
vm: metadata.vm,
vmSnapshot: metadata.vmSnapshot,
}),
'writer.transfer()'
)
// this will update parent name with the needed alias
await this._callWriters(
writer =>
writer.updateUuidAndChain({
isVhdDifferencing,
timestamp: metadata.timestamp,
vdis: incrementalExport.vdis,
}),
'writer.updateUuidAndChain()'
)
await this._callWriters(writer => writer.cleanup(), 'writer.cleanup()')
// for healthcheck
this._tags = metadata.vm.tags
@@ -61,6 +102,3 @@ class IncrementalRemoteVmBackupRunner extends AbstractRemote {
}
export const IncrementalRemote = IncrementalRemoteVmBackupRunner
decorateMethodsWith(IncrementalRemoteVmBackupRunner, {
_run: defer,
})

View File

@@ -1,8 +1,6 @@
import { asyncEach } from '@vates/async-each'
import { asyncMap } from '@xen-orchestra/async-map'
import { createLogger } from '@xen-orchestra/log'
import { pipeline } from 'node:stream'
import findLast from 'lodash/findLast.js'
import isVhdDifferencingDisk from 'vhd-lib/isVhdDifferencingDisk.js'
import keyBy from 'lodash/keyBy.js'
import mapValues from 'lodash/mapValues.js'
@@ -15,9 +13,15 @@ import { IncrementalRemoteWriter } from '../_writers/IncrementalRemoteWriter.mjs
import { IncrementalXapiWriter } from '../_writers/IncrementalXapiWriter.mjs'
import { Task } from '../../Task.mjs'
import { watchStreamSize } from '../../_watchStreamSize.mjs'
import {
DATETIME,
DELTA_CHAIN_LENGTH,
EXPORTED_SUCCESSFULLY,
setVmDeltaChainLength,
markExportSuccessfull,
} from '../../_otherConfig.mjs'
const { debug } = createLogger('xo:backups:IncrementalXapiVmBackup')
const noop = Function.prototype
export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends AbstractXapi {
@@ -30,17 +34,15 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
}
async _copy() {
const baseVm = this._baseVm
const baseVdis = this._baseVdis
const vm = this._vm
const exportedVm = this._exportedVm
const fullVdisRequired = this._fullVdisRequired
const isFull = fullVdisRequired === undefined || fullVdisRequired.size !== 0
const isFull = Object.values(baseVdis).length === 0 || Object.values(baseVdis).some(_ => !_)
await this._callWriters(writer => writer.prepare({ isFull }), 'writer.prepare()')
const deltaExport = await exportIncrementalVm(exportedVm, baseVm, {
fullVdisRequired,
const deltaExport = await exportIncrementalVm(exportedVm, baseVdis, {
nbdConcurrency: this._settings.nbdConcurrency,
preferNbd: this._settings.preferNbd,
})
// since NBD is network based, if one disk use nbd , all the disk use them
@@ -49,11 +51,11 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
Task.info('Transfer data using NBD')
}
const differentialVhds = {}
const isVhdDifferencing = {}
// since isVhdDifferencingDisk is reading and unshifting data in stream
// it should be done BEFORE any other stream transform
await asyncEach(Object.entries(deltaExport.streams), async ([key, stream]) => {
differentialVhds[key] = await isVhdDifferencingDisk(stream)
isVhdDifferencing[key] = await isVhdDifferencingDisk(stream)
})
const sizeContainers = mapValues(deltaExport.streams, stream => watchStreamSize(stream))
@@ -68,7 +70,7 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
writer =>
writer.transfer({
deltaExport: forkDeltaExport(deltaExport),
differentialVhds,
isVhdDifferencing,
sizeContainers,
timestamp,
vm,
@@ -77,18 +79,25 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
'writer.transfer()'
)
this._baseVm = exportedVm
// we want to control the uuid of the vhd in the chain
// and ensure they are correctly chained
await this._callWriters(
writer =>
writer.updateUuidAndChain({
isVhdDifferencing,
timestamp,
vdis: deltaExport.vdis,
}),
'writer.updateUuidAndChain()'
)
if (baseVm !== undefined) {
await exportedVm.update_other_config(
'xo:backup:deltaChainLength',
String(+(baseVm.other_config['xo:backup:deltaChainLength'] ?? 0) + 1)
)
}
if (!isFull) {
await setVmDeltaChainLength(this._xapi, exportedVm.$ref, (this._deltaChainLength ?? 0) + 1)
} // on a full the delta chain will be null
// not the case if offlineBackup
if (exportedVm.is_a_snapshot) {
await exportedVm.update_other_config('xo:backup:exported', 'true')
await markExportSuccessfull(this._xapi, exportedVm.$ref)
}
const size = Object.values(sizeContainers).reduce((sum, { size }) => sum + size, 0)
@@ -106,43 +115,67 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
async _selectBaseVm() {
const xapi = this._xapi
let baseVm = findLast(this._jobSnapshots, _ => 'xo:backup:exported' in _.other_config)
if (baseVm === undefined) {
debug('no base VM found')
return
// filter _jobSnapshotVdis to have only the last successfully exported vdi
// compute delta chain length
// fill baseUuidToSrcVdi with this data
const exportedVdis = this._jobSnapshotVdis.filter(
_ => EXPORTED_SUCCESSFULLY in _.other_config && DATETIME in _.other_config
)
let lastSuccessfullBackup
let lastExportedVdis = []
for (const exportedVdi of exportedVdis) {
if (lastSuccessfullBackup === undefined || lastSuccessfullBackup < exportedVdi.other_config[DATETIME]) {
lastExportedVdis = []
lastSuccessfullBackup = exportedVdi.other_config[DATETIME]
}
if (lastSuccessfullBackup === exportedVdi.other_config[DATETIME]) {
lastExportedVdis.push(exportedVdi)
}
}
this._baseVdis = {}
if (lastExportedVdis.length === 0) {
debug('no base VDIS found', {
jobLength: this._jobSnapshotVdis.length,
lastSuccessfullBackup,
exportedLength: exportedVdis.length,
})
return
}
const deltaChainLength = Math.max(
...lastExportedVdis.map(({ other_config }) => Number(other_config[DELTA_CHAIN_LENGTH] ?? 0))
)
const fullInterval = this._settings.fullInterval
const deltaChainLength = +(baseVm.other_config['xo:backup:deltaChainLength'] ?? 0) + 1
if (!(fullInterval === 0 || fullInterval > deltaChainLength)) {
debug('not using base VM becaust fullInterval reached')
if (fullInterval !== 0 && fullInterval <= deltaChainLength + 1) {
debug('not using base VM because fullInterval reached', {
fullInterval,
deltaChainLength,
eq: fullInterval < deltaChainLength + 1,
dc1: deltaChainLength + 1,
})
return
}
const srcVdis = keyBy(await xapi.getRecords('VDI', await this._vm.$getDisks()), '$ref')
// resolve full record
baseVm = await xapi.getRecord('VM', baseVm.$ref)
const baseUuidToSrcVdi = new Map()
await asyncMap(await baseVm.$getDisks(), async baseRef => {
const [baseUuid, snapshotOf] = await Promise.all([
xapi.getField('VDI', baseRef, 'uuid'),
xapi.getField('VDI', baseRef, 'snapshot_of'),
])
const baseUuidToSrcVdiUuid = new Map()
for (const lastExportedVdi of lastExportedVdis) {
const baseUuid = lastExportedVdi.uuid
const snapshotOf = lastExportedVdi.snapshot_of
const srcVdi = srcVdis[snapshotOf]
if (srcVdi !== undefined) {
baseUuidToSrcVdi.set(baseUuid, srcVdi)
baseUuidToSrcVdiUuid.set(baseUuid, srcVdi.uuid)
} else {
debug('ignore snapshot VDI because no longer present on VM', {
vdi: baseUuid,
})
}
})
}
const presentBaseVdis = new Map(baseUuidToSrcVdi)
const presentBaseVdis = new Map(baseUuidToSrcVdiUuid)
await this._callWriters(
writer => presentBaseVdis.size !== 0 && writer.checkBaseVdis(presentBaseVdis, baseVm),
writer => presentBaseVdis.size !== 0 && writer.checkBaseVdis(presentBaseVdis),
'writer.checkBaseVdis()',
false
)
@@ -151,24 +184,22 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
debug('no base VM found')
return
}
const fullVdisRequired = new Set()
baseUuidToSrcVdi.forEach((srcVdi, baseUuid) => {
baseUuidToSrcVdiUuid.forEach((srcVdiUuid, baseUuid) => {
if (presentBaseVdis.has(baseUuid)) {
debug('found base VDI', {
base: baseUuid,
vdi: srcVdi.uuid,
vdi: srcVdiUuid,
})
this._baseVdis[srcVdiUuid] = lastExportedVdis.find(vdi => vdi.uuid === baseUuid)
} else {
debug('missing base VDI', {
base: baseUuid,
vdi: srcVdi.uuid,
vdi: srcVdiUuid,
})
fullVdisRequired.add(srcVdi.uuid)
// this will force a full for this vdi
this._baseVdis[srcVdiUuid] = undefined
}
})
this._baseVm = baseVm
this._fullVdisRequired = fullVdisRequired
this._deltaChainLength = deltaChainLength
}
}

View File

@@ -1,4 +1,6 @@
import { asyncEach } from '@vates/async-each'
import { decorateMethodsWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import { Disposable } from 'promise-toolbox'
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
@@ -22,6 +24,7 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
this.config = config
this.job = job
this.remoteAdapters = remoteAdapters
this._settings = settings
this.scheduleId = schedule.id
this.timestamp = undefined
@@ -89,11 +92,21 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
return chain
}
async run() {
async run($defer) {
const handler = this._sourceRemoteAdapter._handler
await Disposable.use(await handler.lock(getVmBackupDir(this._vmUuid)), async () => {
await this._callWriters(async writer => {
await writer.beforeBackup()
$defer(async () => {
await writer.afterBackup()
})
}, 'writer.beforeBackup()')
await this._run()
await this._healthCheck()
})
}
}
decorateMethodsWith(AbstractRemote, {
run: defer,
})

View File

@@ -4,11 +4,18 @@ import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { asyncMap } from '@xen-orchestra/async-map'
import { decorateMethodsWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import { formatDateTime } from '@xen-orchestra/xapi'
import { getOldEntries } from '../../_getOldEntries.mjs'
import { Task } from '../../Task.mjs'
import { Abstract } from './_Abstract.mjs'
import {
DATETIME,
JOB_ID,
SCHEDULE_ID,
populateVdisOtherConfig,
resetVmOtherConfig,
setVmOtherConfig,
} from '../../_otherConfig.mjs'
export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
constructor({
@@ -25,7 +32,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
vm,
}) {
super()
if (vm.other_config['xo:backup:job'] === job.id && 'start' in vm.blocked_operations) {
if (vm.other_config[JOB_ID] === job.id && 'start' in vm.blocked_operations) {
// don't match replicated VMs created by this very job otherwise they
// will be replicated again and again
throw new Error('cannot backup a VM created by this very job')
@@ -49,17 +56,17 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
this._exportedVm = undefined
this._vm = vm
this._fullVdisRequired = undefined
this._baseVdis = undefined
this._getSnapshotNameLabel = getSnapshotNameLabel
this._isIncremental = job.mode === 'delta'
this._healthCheckSr = healthCheckSr
this._jobId = job.id
this._jobSnapshots = undefined
this._jobSnapshotVdis = undefined
this._throttleStream = throttleStream
this._xapi = vm.$xapi
// Base VM for the export
this._baseVm = undefined
this._baseVdis = undefined
// Settings for this specific run (job, schedule, VM)
if (tags.includes('xo-memory-backup')) {
@@ -123,15 +130,8 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
// copied on manual snapshots and interfere with the backup jobs
async _cleanMetadata() {
const vm = this._vm
if ('xo:backup:job' in vm.other_config) {
await vm.update_other_config({
'xo:backup:datetime': null,
'xo:backup:deltaChainLength': null,
'xo:backup:exported': null,
'xo:backup:job': null,
'xo:backup:schedule': null,
'xo:backup:vm': null,
})
if (JOB_ID in vm.other_config) {
await resetVmOtherConfig(this._xapi, vm.$ref)
}
}
@@ -146,23 +146,29 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
if (!settings.bypassVdiChainsCheck) {
await vm.$assertHealthyVdiChains()
}
if (settings.preferNbd) {
try {
// enable CBT on all disks if possible
const diskRefs = await xapi.VM_getDisks(vm.$ref)
await Promise.all(diskRefs.map(diskRef => xapi.call('VDI.enable_cbt', diskRef)))
} catch (error) {
Task.info(`couldn't enable CBT`, error)
}
}
const snapshotRef = await vm[settings.checkpointSnapshot ? '$checkpoint' : '$snapshot']({
ignoreNobakVdis: true,
ignoredVdisTag: '[NOBAK]',
name_label: this._getSnapshotNameLabel(vm),
unplugVusbs: true,
})
this.timestamp = Date.now()
await xapi.setFieldEntries('VM', snapshotRef, 'other_config', {
'xo:backup:datetime': formatDateTime(this.timestamp),
'xo:backup:job': this._jobId,
'xo:backup:schedule': this.scheduleId,
'xo:backup:vm': vm.uuid,
await setVmOtherConfig(xapi, snapshotRef, {
timestamp: this.timestamp,
jobId: this._jobId,
scheduleId: this.scheduleId,
vmUuid: vm.uuid,
})
this._exportedVm = await xapi.getRecord('VM', snapshotRef)
return this._exportedVm.uuid
})
} else {
@@ -176,40 +182,120 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
const vmRef = this._vm.$ref
const xapi = this._xapi
const snapshotsRef = await xapi.getField('VM', vmRef, 'snapshots')
const snapshotsOtherConfig = await asyncMap(snapshotsRef, ref => xapi.getField('VM', ref, 'other_config'))
// to ensure compatibility with snapshots older than CBT implementation
// update vdi data to ensure the vdi are correctly fetched in _jobSnapshotVdis
// remove by then end of 2024
const vmSnapshotsRef = await xapi.getField('VM', vmRef, 'snapshots')
const vmSnapshotsOtherConfig = await asyncMap(vmSnapshotsRef, ref => xapi.getField('VM', ref, 'other_config'))
const snapshots = []
snapshotsOtherConfig.forEach((other_config, i) => {
if (other_config['xo:backup:job'] === jobId) {
snapshots.push({ other_config, $ref: snapshotsRef[i] })
const vmSnapshots = []
vmSnapshotsOtherConfig.forEach((other_config, i) => {
if (other_config[JOB_ID] === jobId) {
vmSnapshots.push({ other_config, $ref: vmSnapshotsRef[i] })
}
})
snapshots.sort((a, b) => (a.other_config['xo:backup:datetime'] < b.other_config['xo:backup:datetime'] ? -1 : 1))
this._jobSnapshots = snapshots
await Promise.all(vmSnapshots.map(snapshot => populateVdisOtherConfig(xapi, snapshot.$ref)))
// end of compatibiliy handling
// handle snapshot by VDI
this._jobSnapshotVdis = []
const srcVdis = await xapi.getRecords('VDI', await this._vm.$getDisks())
for (const srcVdi of srcVdis) {
const snapshots = await xapi.getRecords('VDI', srcVdi.snapshots)
for (const snapshot of snapshots) {
if (snapshot.other_config[JOB_ID] === jobId) {
this._jobSnapshotVdis.push(snapshot)
}
}
}
}
async _removeUnusedSnapshots() {
const allSettings = this.job.settings
const baseSettings = this._baseSettings
const baseVmRef = this._baseVm?.$ref
const snapshotsPerSchedule = groupBy(this._jobSnapshots, _ => _.other_config['xo:backup:schedule'])
const snapshotsPerSchedule = groupBy(this._jobSnapshotVdis, _ => _.other_config[SCHEDULE_ID])
const xapi = this._xapi
await asyncMap(Object.entries(snapshotsPerSchedule), ([scheduleId, snapshots]) => {
await asyncMap(Object.entries(snapshotsPerSchedule), async ([scheduleId, snapshots]) => {
const snapshotPerDatetime = groupBy(snapshots, _ => _.other_config[DATETIME])
const datetimes = Object.keys(snapshotPerDatetime)
datetimes.sort()
const settings = {
...baseSettings,
...allSettings[scheduleId],
...allSettings[this._vm.uuid],
}
return asyncMap(getOldEntries(settings.snapshotRetention, snapshots), ({ $ref }) => {
if ($ref !== baseVmRef) {
return xapi.VM_destroy($ref)
// ensure we never delete the last one for delta
const minRetention = this.job.mode === 'delta' ? 1 : 0
const retention = Math.max(settings.snapshotRetention ?? 0, minRetention)
await asyncMap(getOldEntries(retention, datetimes), async datetime => {
const vdis = snapshotPerDatetime[datetime]
let vmRef
// if there is an attached VM => destroy the VM (Non CBT backups)
for (const vdi of vdis) {
if (vdi.$VBDs.length > 0) {
const vbds = vdi.$VBDs
// only one VM linked to this vdi
// this will throw error for VDI still attached to control domain
assert.strictEqual(vbds.length, 1, 'VDI must be free or attached to exactly one VM')
const vm = vbds[0].$VM
assert.strictEqual(vm.is_control_domain, false, `Disk is still attached to DOM0 VM`) // don't delete a VM (especially a control domain)
assert.strictEqual(vm.is_a_snapshot, true, `VM must be a snapshot`) // don't delete a VM (especially a control domain)
const vmRefVdi = vm.$ref
// same vm than other vdi of the same batch
assert.ok(
vmRef === undefined || vmRef === vmRefVdi,
'_removeUnusedSnapshots don t handle vdi related to multiple VMs '
)
vmRef = vmRefVdi
}
}
if (vmRef !== undefined) {
return xapi.VM_destroy(vmRef)
} else {
return asyncMap(
vdis.map(async ({ $ref }) => {
await xapi.VDI_destroy($ref)
})
)
}
})
})
}
async _removeSnapshotData() {
// now that we use CBT, we can destroy the data of the snapshot used for this backup
// going back to a previous version of XO not supporting CBT will create a full backup
// this will only do something after snapshot and transfer
if (
// don't modify the VM
this._exportedVm?.is_a_snapshot &&
// user don't want to keep the snapshot data
this._settings.snapshotRetention === 0 &&
// preferNbd is not a guarantee that the backup used NBD, depending on the network configuration,
// in that case next runs will be full, but there is not an easy way to prevent that
this._settings.preferNbd &&
// only delete snapshost data if the config allows it
this._settings.cbtDestroySnapshotData
) {
Task.info('will delete snapshot data')
const vdiRefs = await this._xapi.VM_getDisks(this._exportedVm.$ref)
await this._xapi.call('VM.destroy', this._exportedVm.$ref)
for (const vdiRef of vdiRefs) {
try {
// data_destroy will fail with a VDI_NO_CBT_METADATA error if CBT is not enabled on this VDI
await this._xapi.call('VDI.data_destroy', vdiRef)
Task.info(`Snapshot data has been deleted`, { vdiRef })
} catch (error) {
Task.warning(`Couldn't deleted snapshot data`, { error, vdiRef })
}
}
}
}
async copy() {
throw new Error('Not implemented')
}
@@ -240,6 +326,26 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
})
}, 'writer.beforeBackup()')
const vm = this._vm
// block migration during the backup on the VM itself, not the latest snapshot
{
const { pool_migrate, migrate_send } = vm.blocked_operations
const reason = 'VM migration is blocked during backup'
await vm.update_blocked_operations({ pool_migrate: reason, migrate_send: reason })
$defer(() =>
// delete the entries if they did not exist previously or if they were
// equal to reason (which happen if a previous backup was interrupted
// before resetting them)
vm.update_blocked_operations({
migrate_send: migrate_send === undefined || migrate_send === reason ? null : migrate_send,
pool_migrate: pool_migrate === undefined || pool_migrate === reason ? null : pool_migrate,
})
)
}
await this._fetchJobSnapshots()
await this._selectBaseVm()
@@ -247,7 +353,6 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
await this._cleanMetadata()
await this._removeUnusedSnapshots()
const vm = this._vm
const isRunning = vm.power_state === 'Running'
const startAfter = isRunning && (settings.offlineBackup ? 'backup' : settings.offlineSnapshot && 'snapshot')
if (startAfter) {
@@ -261,15 +366,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
}
if (this._writers.size !== 0) {
const { pool_migrate = null, migrate_send = null } = this._exportedVm.blocked_operations
const reason = 'VM migration is blocked during backup'
await this._exportedVm.update_blocked_operations({ pool_migrate: reason, migrate_send: reason })
try {
await this._copy()
} finally {
await this._exportedVm.update_blocked_operations({ pool_migrate, migrate_send })
}
await this._copy()
}
} finally {
if (startAfter) {
@@ -278,6 +375,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
await this._fetchJobSnapshots()
await this._removeUnusedSnapshots()
await this._removeSnapshotData()
}
await this._healthCheck()
}

View File

@@ -1,11 +1,11 @@
import cloneDeep from 'lodash/cloneDeep.js'
import mapValues from 'lodash/mapValues.js'
import { forkStreamUnpipe } from '../_forkStreamUnpipe.mjs'
export function forkDeltaExport(deltaExport) {
return Object.create(deltaExport, {
streams: {
value: mapValues(deltaExport.streams, forkStreamUnpipe),
},
})
const { streams, ...rest } = deltaExport
const newMetadata = cloneDeep(rest)
newMetadata.streams = mapValues(streams, forkStreamUnpipe)
return newMetadata
}

View File

@@ -24,7 +24,7 @@ export class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
)
}
async _run({ timestamp, sizeContainer, stream, vm, vmSnapshot }) {
async _run({ maxStreamLength, timestamp, sizeContainer, stream, streamLength, vm, vmSnapshot }) {
const settings = this._settings
const job = this._job
const scheduleId = this._scheduleId
@@ -65,6 +65,8 @@ export class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
await Task.run({ name: 'transfer' }, async () => {
await adapter.outputStream(dataFilename, stream, {
maxStreamLength,
streamLength,
validator: tmpPath => adapter.isValidXva(tmpPath),
})
return { size: sizeContainer.size }

View File

@@ -1,6 +1,5 @@
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map'
import { formatDateTime } from '@xen-orchestra/xapi'
import { formatFilenameDate } from '../../_filenameDate.mjs'
import { getOldEntries } from '../../_getOldEntries.mjs'
@@ -9,6 +8,7 @@ import { Task } from '../../Task.mjs'
import { AbstractFullWriter } from './_AbstractFullWriter.mjs'
import { MixinXapiWriter } from './_MixinXapiWriter.mjs'
import { listReplicatedVms } from './_listReplicatedVms.mjs'
import { setVmOtherConfig } from '../../_otherConfig.mjs'
export class FullXapiWriter extends MixinXapiWriter(AbstractFullWriter) {
constructor(props) {
@@ -76,14 +76,12 @@ export class FullXapiWriter extends MixinXapiWriter(AbstractFullWriter) {
'Start operation for this vm is blocked, clone it if you want to use it.'
)
),
targetVm.update_other_config({
'xo:backup:sr': srUuid,
// these entries need to be added in case of offline backup
'xo:backup:datetime': formatDateTime(timestamp),
'xo:backup:job': job.id,
'xo:backup:schedule': scheduleId,
'xo:backup:vm': vm.uuid,
setVmOtherConfig(xapi, targetVmRef, {
timestamp,
jobId: job.id,
scheduleId,
srUuid,
vmUuid: vm.uuid,
}),
])

View File

@@ -1,13 +1,12 @@
import assert from 'node:assert'
import mapValues from 'lodash/mapValues.js'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { asyncEach } from '@vates/async-each'
import { asyncMap } from '@xen-orchestra/async-map'
import { chainVhd, checkVhdChain, openVhd, VhdAbstract } from 'vhd-lib'
import { chainVhd, openVhd } from 'vhd-lib'
import { createLogger } from '@xen-orchestra/log'
import { decorateClass } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import { dirname } from 'node:path'
import { dirname, basename } from 'node:path'
import { formatFilenameDate } from '../../_filenameDate.mjs'
import { getOldEntries } from '../../_getOldEntries.mjs'
@@ -22,42 +21,45 @@ import { Disposable } from 'promise-toolbox'
const { warn } = createLogger('xo:backups:DeltaBackupWriter')
export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrementalWriter) {
#parentVdiPaths
#vhds
async checkBaseVdis(baseUuidToSrcVdi) {
this.#parentVdiPaths = {}
const { handler } = this._adapter
const adapter = this._adapter
const vdisDir = `${this._vmBackupDir}/vdis/${this._job.id}`
await asyncMap(baseUuidToSrcVdi, async ([baseUuid, srcVdi]) => {
let found = false
await asyncMap(baseUuidToSrcVdi, async ([baseUuid, srcVdiUuid]) => {
let parentDestPath
const vhdDir = `${vdisDir}/${srcVdiUuid}`
try {
const vhds = await handler.list(`${vdisDir}/${srcVdi.uuid}`, {
const vhds = await handler.list(vhdDir, {
filter: _ => _[0] !== '.' && _.endsWith('.vhd'),
ignoreMissing: true,
prependDir: true,
})
const packedBaseUuid = packUuid(baseUuid)
await asyncMap(vhds, async path => {
try {
await checkVhdChain(handler, path)
// Warning, this should not be written as found = found || await adapter.isMergeableParent(packedBaseUuid, path)
//
// since all the checks of a path are done in parallel, found would be containing
// only the last answer of isMergeableParent which is probably not the right one
// this led to the support tickets https://help.vates.fr/#ticket/zoom/4751 , 4729, 4665 and 4300
// the last one is probably the right one
const isMergeable = await adapter.isMergeableParent(packedBaseUuid, path)
found = found || isMergeable
for (let i = vhds.length - 1; i >= 0 && parentDestPath === undefined; i--) {
const path = vhds[i]
try {
if (await adapter.isMergeableParent(packedBaseUuid, path)) {
parentDestPath = path
}
} catch (error) {
warn('checkBaseVdis', { error })
await ignoreErrors.call(VhdAbstract.unlink(handler, path))
}
})
}
} catch (error) {
warn('checkBaseVdis', { error })
}
if (!found) {
// no usable parent => the runner will have to decide to fall back to a full or stop backup
if (parentDestPath === undefined) {
baseUuidToSrcVdi.delete(baseUuid)
} else {
this.#parentVdiPaths[vhdDir] = parentDestPath
}
})
}
@@ -122,6 +124,45 @@ export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrement
}
}
async updateUuidAndChain({ isVhdDifferencing, vdis }) {
assert.notStrictEqual(
this.#vhds,
undefined,
'_transfer must be called before updateUuidAndChain for incremental backups'
)
const parentVdiPaths = this.#parentVdiPaths
const { handler } = this._adapter
const vhds = this.#vhds
await asyncEach(Object.entries(vdis), async ([id, vdi]) => {
const isDifferencing = isVhdDifferencing[`${id}.vhd`]
const path = `${this._vmBackupDir}/${vhds[id]}`
if (isDifferencing) {
assert.notStrictEqual(
parentVdiPaths,
undefined,
'checkbasevdi must be called before updateUuidAndChain for incremental backups'
)
const parentPath = parentVdiPaths[dirname(path)]
// we are in a incremental backup
// we already computed the chain in checkBaseVdis
assert.notStrictEqual(parentPath, undefined, 'A differential VHD must have a parent')
// forbid any kind of loop
assert.ok(basename(parentPath) < basename(path), `vhd must be sorted to be chained`)
await chainVhd(handler, parentPath, handler, path)
}
// set the correct UUID in the VHD if needed
await Disposable.use(openVhd(handler, path), async vhd => {
if (!vhd.footer.uuid.equals(packUuid(vdi.uuid))) {
vhd.footer.uuid = packUuid(vdi.uuid)
await vhd.readBlockAllocationTable() // required by writeFooter()
await vhd.writeFooter()
}
})
})
}
async _deleteOldEntries() {
const adapter = this._adapter
const oldEntries = this._oldEntries
@@ -132,7 +173,7 @@ export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrement
}
}
async _transfer($defer, { differentialVhds, timestamp, deltaExport, vm, vmSnapshot }) {
async _transfer($defer, { isVhdDifferencing, timestamp, deltaExport, vm, vmSnapshot }) {
const adapter = this._adapter
const job = this._job
const scheduleId = this._scheduleId
@@ -140,14 +181,10 @@ export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrement
const jobId = job.id
const handler = adapter.handler
let metadataContent = await this._isAlreadyTransferred(timestamp)
if (metadataContent !== undefined) {
// @todo : should skip backup while being vigilant to not stuck the forked stream
Task.info('This backup has already been transfered')
}
const basename = formatFilenameDate(timestamp)
const vhds = mapValues(
// update this.#vhds before eventually skipping transfer, so that
// updateUuidAndChain has all the mandatory data
const vhds = (this.#vhds = mapValues(
deltaExport.vdis,
vdi =>
`vdis/${jobId}/${
@@ -157,9 +194,18 @@ export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrement
vdi.uuid
: vdi.$snapshot_of$uuid
}/${adapter.getVhdFileName(basename)}`
)
))
let metadataContent = await this._isAlreadyTransferred(timestamp)
if (metadataContent !== undefined) {
// skip backup while being vigilant to not stuck the forked stream
Task.info('This backup has already been transfered')
Object.values(deltaExport.streams).forEach(stream => stream.destroy())
return { size: 0 }
}
metadataContent = {
isVhdDifferencing,
jobId,
mode: job.mode,
scheduleId,
@@ -172,56 +218,24 @@ export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrement
vm,
vmSnapshot,
}
const { size } = await Task.run({ name: 'transfer' }, async () => {
let transferSize = 0
await asyncEach(
Object.entries(deltaExport.vdis),
async ([id, vdi]) => {
Object.keys(deltaExport.vdis),
async id => {
const path = `${this._vmBackupDir}/${vhds[id]}`
const isDelta = differentialVhds[`${id}.vhd`]
let parentPath
if (isDelta) {
const vdiDir = dirname(path)
parentPath = (
await handler.list(vdiDir, {
filter: filename => filename[0] !== '.' && filename.endsWith('.vhd'),
prependDir: true,
})
)
.sort()
.pop()
assert.notStrictEqual(
parentPath,
undefined,
`missing parent of ${id} in ${dirname(path)}, looking for ${vdi.other_config['xo:base_delta']}`
)
parentPath = parentPath.slice(1) // remove leading slash
// TODO remove when this has been done before the export
await checkVhd(handler, parentPath)
}
transferSize += await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
// don't write it as transferSize += await async function
// since i += await asyncFun lead to race condition
// as explained : https://eslint.org/docs/latest/rules/require-atomic-updates
const transferSizeOneDisk = await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
// no checksum for VHDs, because they will be invalidated by
// merges and chainings
checksum: false,
validator: tmpPath => checkVhd(handler, tmpPath),
writeBlockConcurrency: this._config.writeBlockConcurrency,
})
if (isDelta) {
await chainVhd(handler, parentPath, handler, path)
}
// set the correct UUID in the VHD
await Disposable.use(openVhd(handler, path), async vhd => {
vhd.footer.uuid = packUuid(vdi.uuid)
await vhd.readBlockAllocationTable() // required by writeFooter()
await vhd.writeFooter()
})
transferSize += transferSizeOneDisk
},
{
concurrency: settings.diskPerVmConcurrency,

Some files were not shown because too many files have changed in this diff Show More