Compare commits

..

661 Commits

Author SHA1 Message Date
Florent Beauchamp
dd0b71bca0 fix: rework optionnal depency 2022-09-22 14:07:52 +02:00
Florent Beauchamp
dc571a5811 fix(Backup/file restore): don't load fuse-vhd on platform not supported by fuse-native 2022-09-21 16:59:58 +02:00
Mathieu
f1ab62524c fix(xo-web/SR): fix "VDIs to coalesce" in SR advanced tab (#6429)
See https://xcp-ng.org/forum/topic/6334/coalesce-not-showing-anymore/3
Introduced by a9c1239149
2022-09-21 16:21:23 +02:00
rajaa-b
ce78d22bb8 fix(xo-web/tasks): fix tasks being displayed to all users (#6422)
See zammad#9509
Introduced by e246c8ee47
2022-09-21 11:25:14 +02:00
rajaa-b
99a1dbeae1 fix(xo-web/tasks): fix tasks filter (#6424)
See zammad#9423
2022-09-21 11:02:03 +02:00
Julien Fontanet
2a71e28253 docs(backups): add cache for a VM 2022-09-20 14:54:47 +01:00
Florent BEAUCHAMP
46fe3be322 feat: implement file restore on top of FUSE instead of vhdimount (#6409)
It brings file restore to VhdDirectory (and related features like encryption and compression).
2022-09-20 11:04:24 +02:00
Florent BEAUCHAMP
9da65b6c7c feat(backups): write and merge block concurrency are now configurable (#6416) 2022-09-16 14:54:33 +02:00
Julien Fontanet
ad02700b51 fix(backups/RemoteAdapter#_getPartition): mount with norecovery option 2022-09-15 11:06:59 +02:00
Julien Fontanet
8fd10bace7 chore: update deps 2022-09-15 00:06:07 +02:00
Julien Fontanet
9d09a3adf5 feat(backups/deleteVmBackups): run cleanVm in parallel 2022-09-12 11:23:46 +02:00
Julien Fontanet
4350f0cd1a fix(backups/deleteVmBackups): don't fail on cleanVm error
Seen when investigating zammad#8842
2022-09-12 11:23:44 +02:00
Julien Fontanet
5dc993255c fix(backups/DeltaBackupWriter#checkBaseVdis): don't warn on missing dir 2022-09-11 14:04:24 +02:00
Julien Fontanet
e9188a9864 feat(proxy/api): more raw errors
Follow up of ae373c3e7
2022-09-11 13:40:27 +02:00
Julien Fontanet
42dd70c2f7 chore(backups/RemoteAdapter): add more cache related debug 2022-09-10 14:16:54 +02:00
Julien Fontanet
191c124130 feat(backups): update VM backups cache (#6411) 2022-09-10 14:16:29 +02:00
Julien Fontanet
2742f948c2 feat(CHANGELOG): move releases before 2022 in other file
Because the main CHANGELOG is getting too big to be displayed on GitHub.
2022-09-10 13:21:15 +02:00
Julien Fontanet
455a3ba677 fix(CHANGELOG): fix version of 5.74.2 and 5.74.3 2022-09-10 13:14:57 +02:00
Julien Fontanet
1961da9aed feat(xo-server): 5.102.3 2022-09-09 18:29:48 +02:00
Julien Fontanet
e82d9d7a74 fix(xo-server/isValidAuthenticationToken): call _getAuthenticationToken
Introduced by d52dcd070
2022-09-09 17:37:44 +02:00
Florent BEAUCHAMP
dfb3166bed fix(backups): add healthcheck to full backup (#6401) 2022-09-09 16:35:38 +02:00
Florent BEAUCHAMP
5a54f7f302 feat(backups/cleanVm): invalidate cache on backup deletion (#6402) 2022-09-09 16:27:12 +02:00
Julien Fontanet
6002a497fe feat(xo-server): 5.102.2 2022-09-09 15:50:37 +02:00
Julien Fontanet
3fa8b6332e chore: update to app-conf@2.3.0 2022-09-09 12:59:59 +02:00
Manon Mercier
1b521b87c5 docs(configuration): add certificates in title to ease search (#6212) 2022-09-09 10:44:32 +02:00
rajaa-b
8b7d2aab6b feat(xo-server#_startVm): add a message for 'NO_HOSTS_AVAILABLE' error (#6408) 2022-09-09 10:43:22 +02:00
Julien Fontanet
b0006f91f4 fix(xo-server/vm.import): allow additional props for data param
Fixes zammad#9521

Introduced by d7f29e736
2022-09-08 11:10:51 +02:00
Julien Fontanet
31aaa968ec docs(users/SAML): remove confusing warning
See zammad#9420
2022-09-07 09:59:11 +02:00
Mathieu
facb4593f0 feat: release 7.74.2 (#6407) 2022-09-06 15:04:27 +02:00
Mathieu
d1a30363b4 feat: patch release (#6406) 2022-09-06 14:12:46 +02:00
Julien Fontanet
eac5347f32 fix(CHANGELOG): remove CHANGELOG.unreleased comments 2022-09-06 11:56:00 +02:00
Julien Fontanet
2006665fe8 feat(CHANGELOG): release 5.74.1 2022-09-06 11:55:06 +02:00
Julien Fontanet
26a3862d61 chore(CHANGELOG.unreleased): clearer error message
Introduced by 2371109b6
2022-09-06 11:53:53 +02:00
Mathieu
2371109b6f fix(xo-server): handle unfetched VDIs in pool.$ha_statefiles (#6404)
Introduced by 4dc7575d5

Fixes zammad#9498
2022-09-06 11:37:13 +02:00
Mathieu
243bffebbd feat(xo-server-auth-saml): support multiline cert (#6403)
Fixes https://xcp-ng.org/forum/topic/6174/saml-auth-with-azure-ad/10
2022-09-06 10:55:02 +02:00
Julien Fontanet
e69ae7b0db chore(CHANGELOG): integrate released changes 2022-09-03 11:37:43 +02:00
Julien Fontanet
5aff7b94d8 feat(xo-web): 5.103.0 2022-09-03 11:37:10 +02:00
Julien Fontanet
a65058ddd5 feat(xo-server): 5.102.0 2022-09-03 11:36:28 +02:00
Julien Fontanet
b1e81d84c6 feat(@xen-orchestra/proxy): 0.26.1 2022-09-03 11:35:27 +02:00
Julien Fontanet
96e60f7e4f feat(@xen-orchestra/mixins): 0.8.0 2022-09-03 11:34:58 +02:00
Julien Fontanet
5e59c617e8 feat(vhd-lib): 4.0.1 2022-09-03 11:33:54 +02:00
Julien Fontanet
69ad0ade6e feat(@xen-orchestra/fs): 3.1.0 2022-09-03 11:32:23 +02:00
Julien Fontanet
37cdbc19ef fix(xo-web): fix signout
Introduced by 281a1cc54
2022-09-02 18:45:10 +02:00
Julien Fontanet
6cbce81faa feat(xo-server): respect disabled setting for VM console
See #6319
2022-09-02 11:32:57 +02:00
Julien Fontanet
8c14906a60 fix(xo-server-recover-account): connect Redis client (#6398) 2022-09-02 11:01:42 +02:00
Florent BEAUCHAMP
62591e1f6f fix(vhd-lib/merge): reduce concurrency to protect slower backends (#6400) 2022-09-02 11:00:53 +02:00
Julien Fontanet
ea4a888c5e fix(xo-server/vm.create): allow additional props for VDIs and existingDisks
Fixes https://xcp-ng.org/forum/post/52561
2022-09-02 10:26:38 +02:00
Julien Fontanet
281a1cc549 feat(xo-server): validate auth token on HTTP request 2022-09-01 17:19:30 +02:00
Julien Fontanet
d52dcd0708 feat(xo-server): validate auth token on HTTP request 2022-09-01 17:15:39 +02:00
Florent BEAUCHAMP
d8e01b2867 fix(fs/s3#copy): normalize error: no such key → ENOENT (#6388) 2022-09-01 12:51:44 +02:00
Florent BEAUCHAMP
dca3f39156 feat(xo-web): remote level encryption (#6321)
Co-authored-by: mathieuRA <contact@mathieu-raisin.fr>
2022-09-01 11:34:48 +02:00
Julien Fontanet
31e964fe0f fix(xo-server/backupNg.{create,edit,run}Job): allow settings other than concurrency
Introduced by d7f29e736
2022-09-01 00:25:12 +02:00
Julien Fontanet
39d973c43f fix(xo-server/api): allow additional props on objects without prop definitions
Fixes #6395

Introduced by d7f29e736
2022-09-01 00:24:12 +02:00
Julien Fontanet
55f921959d fix(xo-server/api/adaptJsonSchema): fix additionalProperties test
Introduced by d7f29e736
2022-09-01 00:22:49 +02:00
Julien Fontanet
6598090662 fix(xo-server/api): keep previous params format
Introduced by d7f29e736

Avoid breaking `xo-cli --list-commands`.
2022-09-01 00:21:59 +02:00
Julien Fontanet
d7f29e7363 chore(xo-server/api): use Ajv instead of schema-inspector
- standard JSON schema
- faster
- maintained

New implementation also pre-compile schemas which means that params validation for each call is faster and incorrect schemas are detected at startup.
2022-08-31 16:46:17 +02:00
Julien Fontanet
82df6089c3 chore: refresh yarn.lock 2022-08-31 15:59:04 +02:00
rajaa-b
80cc66964e feat(xo-web/proxies): ability to bind licence to existing proxy (#6348)
See Zammad#7457
2022-08-31 15:40:34 +02:00
Florent BEAUCHAMP
7883d38622 fix(vhd-lib/VhdDirectory/mergeBlock): write BAT on block creation (#6300) 2022-08-31 15:35:10 +02:00
Julien Fontanet
2cb5169b6d feat(fs/Local): stack traces v2 (#6363)
- better support of lock/release
- handle sync exceptions as well
- save stacks in `syncStack` instead of replacing existing ones
2022-08-31 15:30:08 +02:00
Julien Fontanet
9ad2c07984 feat: release 5.74.0 2022-08-31 15:09:17 +02:00
Mathieu
a9c1239149 feat(xo-server/xo-web/health): detect invalid vhd-parent VDIs (#6356) 2022-08-31 11:35:35 +02:00
Mathieu
cb1223f72e feat: technical release (#6387) 2022-08-30 15:36:30 +02:00
Mathieu
4dc7575d5b feat(xo-web/storage): display SR used for the HA state files (#6384)
Fixes #6339
2022-08-29 17:02:50 +02:00
Julien Fontanet
276d1ce60a feat(backups/Task): add original log to *log after end* error 2022-08-29 10:05:25 +02:00
Julien Fontanet
58ab32a623 feat(backups/_forkStreamUnpipe): add more debug 2022-08-26 10:49:52 +02:00
Julien Fontanet
c1846e6ff3 fix(xen-api/{get,put}Resource): add sync stack traces support
Follows 857a9f3ef
2022-08-25 17:06:37 +02:00
Julien Fontanet
826de17111 feat(backups/VmBackup#_callWriters): add more debug 2022-08-25 16:43:45 +02:00
Julien Fontanet
8a09ea8bc1 feat(backups/VmBackup#_callWriters): unify single/multiple code
The behavior should be the same even if there is a single writer
2022-08-25 16:43:18 +02:00
Florent Beauchamp
1297c925ad feat: server side of backup encryption 2022-08-23 12:04:16 +02:00
Julien Fontanet
74d15e1a92 chore: format with Prettier 2022-08-23 11:56:26 +02:00
Julien Fontanet
ae373c3e77 feat(proxy/api): returns raw errors
Similar to dd5e11e83
2022-08-23 11:08:25 +02:00
Pierre Donias
e9b90caa3a fix(complex-matcher): properly alias RegExp export as RegExpNode (#6375)
Fixes #6365
Introduced by 9ef2c7da4c
2022-08-22 10:44:07 +02:00
Florent BEAUCHAMP
b89e77a6a4 fix: various VHD related test fixes (#6302) 2022-08-12 16:23:59 +02:00
Florent Beauchamp
61691ac46b fix(vhd-lib/VhdDirectory#mergeBlock): fix rename condition
Introduced by fd752fee8
2022-08-12 16:18:25 +02:00
Florent Beauchamp
512b96af24 fix(backups/cleanVm): fix path of merge state
Introduced by ad149740b1
2022-08-12 16:07:44 +02:00
Mathieu
d369593979 fix(xo-web): from ignoreBackup to bypassBackupCheck (#6362)
Introduced by 837b06ef2b
2022-08-12 15:01:22 +02:00
Julien Fontanet
2f38e0564b fix(fs/Local#lock): correctly assign release
Introduced by 4bed4195a
2022-08-11 17:13:40 +02:00
Julien Fontanet
5e8dd4e4bc fix(vhd-lib/mergeVhdChain): inverse condition to use VhdSynthetic
Introduced by 76813737e
2022-08-11 17:04:23 +02:00
Julien Fontanet
8f9f1f566d fix(proxy/api): typo in Array#includes
Introduced by 08cdcf411
2022-08-11 14:20:57 +02:00
olegur
d7870b8860 small docs typo fix 2022-08-11 14:16:12 +02:00
Julien Fontanet
97fa23f890 chore(xo-web): use vm.convertToTemplate instead of alias vm.convert 2022-08-11 11:22:54 +02:00
Julien Fontanet
f839887da8 chore(xo-server/api): remove unused alias vdi.delete_ 2022-08-11 11:22:54 +02:00
Julien Fontanet
15bfaa15ca chore(xo-server/api): remove unused alias network.delete_ 2022-08-11 11:22:54 +02:00
Julien Fontanet
4a3183ffa0 chore(xo-server/api): remove unused method 2022-08-11 11:22:54 +02:00
Pierre Donias
18d03a076b fix(xo-web/backup/restore): don't use UNSAFE_componentWillReceiveProps (#6364)
Introduced by 7d6e832226

`UNSAFE_componentWillReceiveProps` method was only introduced in React 16 but we
are using React 15
`.eslintrc`: ask eslint to check the React version so that it doesn't suggest to
change it to `UNSAFE_componentWillReceiveProps`
2022-08-11 11:19:07 +02:00
Julien Fontanet
4bed4195ac feat(fs/Local#lock): attempt to reacquire in case of compromission
Related to zammad#8826
2022-08-10 17:41:16 +02:00
Julien Fontanet
a963878af5 fix(fs/Local#lock): never fail on release
Related to zammad#8826

Also, log properly if the log is compromised.
2022-08-10 17:32:36 +02:00
Julien Fontanet
d6c3dc87e0 feat(xo-server): avoid warning if client WS has been closed
Fixes part of zammad#8826
2022-08-10 16:53:38 +02:00
Julien Fontanet
5391a9a5ad chore(CHANGELOG.unreleased): bump fs in major
Introduced by b50e95802
2022-08-10 16:52:39 +02:00
Julien Fontanet
b50e95802c feat(fs): remove JS based SMB handler
It's not well tested nor maintained.
2022-08-10 16:28:05 +02:00
Julien Fontanet
75a9799e96 feat(xo-server): make http.listen config an object
It remains compatible with previous configurations.

It now aligns with xo-proxy's config and is easier to overload with other config files.
2022-08-10 14:05:16 +02:00
Julien Fontanet
dbb9e4d60f feat(fs/Local): add stack traces to native fs methods 2022-08-09 11:25:49 +02:00
Julien Fontanet
d27b6bd49d fix(xo-server/collection/redis#{add,update}): cast to string before inserting in db
Fixes https://xcp-ng.org/forum/post/51933
Fixes #6359

Introduced by 36b94f745
2022-08-07 13:27:25 +02:00
Julien Fontanet
c5d2726faa chore(xo-server/collection/redis): remove unused constructor param
Introduced by 36b94f745
2022-08-07 13:01:51 +02:00
Julien Fontanet
a2a98c490f feat(xo-server/db-cli): improve help message 2022-08-07 12:48:34 +02:00
Julien Fontanet
e2dc1d98f1 feat(xo-server/db-cli repl): now has a collection ready for each namespace 2022-08-07 12:46:52 +02:00
Julien Fontanet
658c26d3c9 fix(xo-server/collection/redis#{add,update}): fix ignore id field 2022-08-06 13:23:46 +02:00
Julien Fontanet
612095789a feat(xo-server/db-cli): repl command 2022-08-06 12:31:56 +02:00
Julien Fontanet
7418d9f670 fix(xo-server/collection/redis#{add,update}): save all fields
Fixes https://xcp-ng.org/forum/post/51916

Introduced by 36b94f745

`Redis#hSet` accepts an object instead of a sequence of key/value.

The previous commit corrupted the database by deleting all but one fields per added/updated objects.
2022-08-06 12:30:08 +02:00
Julien Fontanet
f344c58a62 feat(xo-server/db-cli): ensure errors are printed 2022-08-06 11:30:00 +02:00
Julien Fontanet
36b94f745d fix(xo-server): redis@4 usage
Introduced by 9fab15537
2022-08-05 16:53:22 +02:00
Julien Fontanet
08cdcf4112 feat(proxy/api): method results can be documented/validated 2022-08-05 16:15:21 +02:00
Julien Fontanet
76813737ef feat(vhd-cli/merge): replace mergeVhd by mergeVhdChain 2022-08-05 15:04:44 +02:00
Julien Fontanet
53d15d6a77 chore(vhd-lib/merge): remove unnecessary concat 2022-08-05 15:04:44 +02:00
Julien Fontanet
dd01b62b87 feat(vhd-lib/mergeVhd): no longer exported from the index
BREAKING CHANGE
2022-08-05 15:04:44 +02:00
Julien Fontanet
9fab15537b chore: update deps 2022-08-05 14:25:09 +02:00
Florent BEAUCHAMP
d87db05b2b feat: release 5.73.1 (#6352) 2022-08-04 17:47:50 +02:00
Florent BEAUCHAMP
f1f32c962c feat: technical release (#6351) 2022-08-04 16:05:29 +02:00
Florent Beauchamp
ad149740b1 feat(backups/cleanVm,vhd-lib): support resuming merge of VHD chains
The whole chain is now stored in the merge state.
2022-08-04 15:25:31 +02:00
Florent Beauchamp
9a4e938b91 fix(backups/cleanVm): fix parent/child order when resuming merge 2022-08-04 15:25:31 +02:00
Julien Fontanet
a226760b07 fix(xo-web/css): fix double slash in Font Awesome import
Fixes #6350
2022-08-04 10:23:06 +02:00
Yannick Achy
a11450c3a7 docs(xoa): NTP configuration (#6342)
Co-authored-by: yannick Achy <yannick.achy@vates.fr>
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-08-04 09:24:00 +02:00
Julien Fontanet
e0cab4f937 feat(vhd-lib/merge): augment errors with VHD paths 2022-08-03 16:41:59 +02:00
Julien Fontanet
468250f291 fix(vhd-lib/merge): fix mergeState.currentBlock
Fixes zammad#8794 and zammad#8168

Introduced by 97d94b795

When the concurrency is one (or race condition), `Math.min(...merging)` could be called with `merging` being empty.

This lead to a `NaN` value which, was stored as `null` in the JSON merge state.
2022-08-03 14:56:49 +02:00
Julien Fontanet
d04b93c17e feat(vhd-cli info): explicit header/footer objects 2022-08-03 14:49:14 +02:00
Florent BEAUCHAMP
911556a1aa fix(backups/cleanVm): the child, not the parent is linked to a backup (#6331) 2022-08-03 14:06:35 +02:00
Per-Ole
c7d3230eef feat(xo-server-auth-saml): allow customizing callback URL (#6278)
This will allow you to enter the full path of the callback URL. As stated in issue #6108 the SAML plugin wont work with Azure AD as the callback URL needs to be HTTPS. This was solved by @jens-rabe
2022-08-03 10:12:32 +02:00
Julien Fontanet
b63086bf09 fix(xo-web): use complex-matcher classes to build filters
Using strings directly breaks with special characters.
2022-08-02 21:36:41 +02:00
Florent BEAUCHAMP
a4118a5676 docs(backups): describes file hierarchy with VHD directories (#6337) 2022-08-02 11:12:24 +02:00
Julien Fontanet
26e7e6467c fix(xo-server,xo-web): prevent backup concurrency from being <=0
Fixes #6338
2022-07-31 20:04:47 +02:00
Julien Fontanet
1c9552fa58 docs(mixins/SslCertificate): don't recommends using staging
This confuses users and XO is not currently able to detect that this valid has changed and generate a new certificate.
2022-07-29 19:40:29 +02:00
Julien Fontanet
9875cb5575 docs(mixins/SslCertificate): explicits that cert/key are required 2022-07-29 19:26:33 +02:00
Julien Fontanet
d1c6bb8829 fix(mixins/SslCertificate): remove unnecessary warnings 2022-07-29 19:24:49 +02:00
Julien Fontanet
ef7005a291 fix(backups/cleanVms): remove useless log 2022-07-29 15:42:46 +02:00
Pierre Donias
8068b83ffe feat: release 5.73.0 (#6336) 2022-07-29 10:48:59 +02:00
Pierre Donias
f01a89710c feat: technical release (#6335) 2022-07-29 10:08:26 +02:00
Julien Fontanet
38ced81ada fix(backups,backups-cli): correctly pass loggers to cleanVm
Introduced by c0b0ba433
2022-07-29 08:54:31 +02:00
Julien Fontanet
9834632d59 fix(vhd-lib/merge): delete merge state after cleanup
Related to https://xcp-ng.org/forum/post/51529

In case the clean-up fails, the merge will be retried in the future.
2022-07-28 21:39:36 +02:00
Julien Fontanet
bb4504dd50 fix(vhd-lib/merge): don't delete children if renaming parent failed
Related to https://xcp-ng.org/forum/post/51529
2022-07-28 21:39:36 +02:00
Julien Fontanet
8864c2f2db fix(backups/cleanVm): wait for unreferenced VHDs to be deleted
Introduced by 249f63849
2022-07-28 21:26:22 +02:00
Julien Fontanet
19208472e6 feat(backups/cleanVm): improve log messages
Introduced byc0b0ba433
2022-07-28 21:26:22 +02:00
Florent BEAUCHAMP
10c77ba3cc feat(mixins/SslCertificate): Let's Encrypt support (#6320) 2022-07-28 18:13:12 +02:00
Pierre Donias
cd28fd4945 feat: technical release (#6332) 2022-07-28 15:57:40 +02:00
Mathieu
6778d6aa4a fix(xo-web/VM): display a confirmation modal to bypass blockedOperation (#6295) 2022-07-28 15:01:22 +02:00
Pierre Donias
433851d771 fix(xo-server/xapi-object-to-xo): handle guest metrics reporting empty IP field (#6328)
See https://xcp-ng.org/forum/topic/4810/netbox-plugin-error-ipaddr-the-address-has-neither-ipv6-nor-ipv4-format/27?_=1658735770330
2022-07-27 12:03:22 +02:00
Julien Fontanet
d157fd3528 feat(mixins/HttpProxy): enable by default 2022-07-26 10:59:37 +02:00
Julien Fontanet
9150823c37 fix(xo-server/unregisterProxy): don't try to unbind license if method unavailable 2022-07-25 18:05:08 +02:00
Julien Fontanet
07c3a44441 fix(xo-server/registerProxy): db.add does not return a model
Introduced by 8a71f8473
2022-07-25 18:01:30 +02:00
Julien Fontanet
051bbf9449 fix(xo-server/callProxyMethod): use stored address before XAPI ones 2022-07-25 17:58:40 +02:00
Julien Fontanet
22ea1c0e2a fix(xo-server/proxy.update): return proxy with its URL 2022-07-25 17:54:31 +02:00
Julien Fontanet
6432a44860 chore(fs/createOutputStream): remove deprecated method
Deprecated since 407586e2d
2022-07-25 16:00:42 +02:00
Julien Fontanet
493d861de3 chore(xo-server,proxy): fix linting errors 2022-07-25 13:26:26 +02:00
Julien Fontanet
82452e9616 feat(xo-server/RestApi): add raw VDI import 2022-07-21 16:28:22 +02:00
Julien Fontanet
2fbeaa618a fix(xapi/SR_importVdi): import as VDH, not raw
Fixes #6327
2022-07-21 15:18:48 +02:00
Julien Fontanet
6c08afaa0e fix(xapi/VDI_importContent): format is not optional 2022-07-21 15:18:48 +02:00
Julien Fontanet
af4cc1f574 fix(xo-cli): extract ws error message
Fixes #6022
2022-07-21 12:36:58 +02:00
Julien Fontanet
2fb27b26cd feat(xo-server): refresh HTTP proxy on config change 2022-07-21 10:42:30 +02:00
Pierre Donias
11e09e1f87 fix(xo-web/home/vm): show error toaster when deleting VMs failed (#6323) 2022-07-21 09:42:16 +02:00
Julien Fontanet
9ccb5f8aa9 feat(xo-server): inject proxy in env (#6322)
Fixes zammad#8073

Related to #6320

- brings `no_proxy` supports
- implicit supports for other libs
2022-07-20 15:27:57 +02:00
Pierre Donias
af87d6a0ea docs(contributing): update contribution steps (#6318)
See https://xcp-ng.org/forum/topic/6070/netbox-plugin-enhancements
2022-07-11 17:01:57 +02:00
Julien Fontanet
d847f45cb3 feat: release 5.72.1 2022-07-11 10:37:01 +02:00
Julien Fontanet
38c615609a feat(xo-web): 5.100.0 2022-07-11 10:36:03 +02:00
Julien Fontanet
144cc4b82f feat(xo-server-audit): 0.10.0 2022-07-11 10:36:03 +02:00
Julien Fontanet
d24ab141e9 feat(xo-server): 5.98.1 2022-07-11 10:36:03 +02:00
Julien Fontanet
8505374fcf feat(@xen-orchestra/proxy): 0.23.5 2022-07-11 10:36:03 +02:00
Julien Fontanet
e53d961fc3 feat(@xen-orchestra/backups-cli): 0.7.5 2022-07-11 10:36:03 +02:00
Julien Fontanet
dc8ca7a8ee feat(vhd-lib): 3.3.2 2022-07-11 10:36:03 +02:00
Julien Fontanet
3d1b87d9dc feat(@xen-orchestra/backups): 0.27.0 2022-07-11 10:35:58 +02:00
Julien Fontanet
01fa2af5cd chore: refresh yarn.lock 2022-07-11 10:11:00 +02:00
Julien Fontanet
20a89ca45a feat(xo-server-audit): ignore more methods 2022-07-09 10:41:36 +02:00
Julien Fontanet
16ca2f8da9 fix(xo-web/vm/console): SSH/RDP URLs
Introduced by 2b0f1b6aa and e9f82558e.
2022-07-07 17:06:49 +02:00
Florent BEAUCHAMP
30fe9764ad fix(backups,vhd-lib): merge with VhdSynthetic (#6317) 2022-07-07 16:57:15 +02:00
rajaa-b
e246c8ee47 fix(xo-web/tasks): tasks with no bound objects not displayed (#6315)
See https://xcp-ng.org/forum/topic/6038/not-seeing-tasks-any-more-as-admin
Introduced by dae37c6a50
2022-07-06 10:52:37 +02:00
Julien Fontanet
ba03a48498 chore(xo-server): update to hashy@0.11.1
Fixes https://xcp-ng.org/forum/post/50866

Introduced by 49890a09b7

Fixes argon2id support which is the new default algorithm since argon2@0.28.7
2022-07-06 09:44:48 +02:00
Julien Fontanet
b96dd0160a feat(async-each): change default concurrency to 10
BREAKING CHANGE
2022-07-05 12:00:07 +02:00
Julien Fontanet
49890a09b7 chore: update dev deps 2022-07-05 11:13:50 +02:00
Julien Fontanet
dfce56cee8 feat(async-each): add basic JsDoc typing 2022-07-04 17:37:52 +02:00
Julien Fontanet
a6fee2946a feat(async-each): concurrency 0 means no limit
It's identical to `Infinity` but has broader support (e.g. in JSON).
2022-07-04 17:22:47 +02:00
Julien Fontanet
34c849ee89 fix(vhd-lib/VhdAbstract#readBlock): return type 2022-07-04 10:57:44 +02:00
Mathieu
c7192ed3bf feat(xo-web): display maintenance mode badge next to the SR name (#6313) 2022-07-01 16:22:45 +02:00
Julien Fontanet
4d3dc0c5f7 feat: release 5.72.0 2022-06-30 16:47:32 +02:00
Julien Fontanet
9ba4afa073 chore(CHANGELOG): integrate released changes 2022-06-30 15:49:25 +02:00
Julien Fontanet
3ea4422d13 feat(xo-web): 5.99.0 2022-06-30 15:47:22 +02:00
Julien Fontanet
de2e314f7d feat(xo-server): 5.98.0 2022-06-30 15:46:58 +02:00
Julien Fontanet
2380fb42fe feat(@xen-orchestra/proxy): 0.23.4 2022-06-30 15:46:14 +02:00
Julien Fontanet
95b76076a3 feat(xo-remote-parser): 0.9.1 2022-06-30 15:45:29 +02:00
Julien Fontanet
b415d4c34c feat(vhd-lib): 3.3.1 2022-06-30 15:44:21 +02:00
Julien Fontanet
2d82b6dd6e feat(@xen-orchestra/xapi): 1.4.0 2022-06-30 15:38:10 +02:00
Mathieu
16b1935f12 feat(xo-server,xo-web/SR): display maintenance mode button (#6308)
Fixes #6215
2022-06-30 15:31:28 +02:00
Florent BEAUCHAMP
50ec614b2a feat(xo-web/remotes): ability to set useVhdDirectory in remote params (#6273) 2022-06-30 15:28:42 +02:00
rajaa-b
9e11a0af6e feat(xapi/VM_import): translate checksum error (#6304) 2022-06-30 12:08:36 +02:00
Florent BEAUCHAMP
0c3e42e0b9 fix(vhd-lib): fix VhdDirectory merge on non-S3 remote (#6310) 2022-06-30 11:40:21 +02:00
Julien Fontanet
36b31bb0b3 chore(vhd-lib/merge): minor comment improvement 2022-06-29 15:29:20 +02:00
Mathieu
c03c41450b feat: technical release (#6311) 2022-06-29 15:27:14 +02:00
Florent BEAUCHAMP
dfc2b5d88b feat(Backup): use vhd directory setting of remote (#6303) 2022-06-29 10:51:13 +02:00
Florent BEAUCHAMP
87e3e3ffe3 fix(xo-remote-parser): properly handle undefined options (#6309) 2022-06-29 10:26:50 +02:00
Rajaa.BARHTAOUI
dae37c6a50 feat(xo-web/tasks): show tasks for Self Service users (#6217)
See zammad#5436
2022-06-28 18:35:58 +02:00
Mathieu
c7df11cc6f feat(xo-web/user): user tokens management through XO interface (#6276) 2022-06-28 17:57:59 +02:00
Julien Fontanet
87f1f208c3 feat(vhd-cli): 0.8.0 2022-06-28 16:52:27 +02:00
Julien Fontanet
ba8c5d740e feat(vhd-cli info): list method with multiple VHDs 2022-06-27 16:24:43 +02:00
Julien Fontanet
c275d5d999 chore(vhd-cli): remove build step 2022-06-27 16:24:43 +02:00
Mathieu
cfc53c9c94 feat(xo-web/proxies): copy proxy URL (#6287) 2022-06-27 15:41:32 +02:00
Julien Fontanet
87df917157 feat(vhd-lib/merge): human readable UUID check
Introduced by a1bcd35e2
2022-06-27 14:10:15 +02:00
Julien Fontanet
395d87d290 chore(xo-common): remove build step 2022-06-23 17:24:54 +02:00
Julien Fontanet
aff8ec08ad chore(template): remove build step 2022-06-23 17:24:54 +02:00
Julien Fontanet
4d40b56d85 fix(xo-server/file restore): ignore non-regular files/dirs (#6305)
Fixes zammad#7648

This also ignore (broken and valid) symlinks.
2022-06-23 16:37:56 +02:00
Julien Fontanet
667d0724c3 docs(configuration/custom ca): fix systemd path
Introduced by 03a66e469
2022-06-22 11:32:24 +02:00
Julien Fontanet
a49395553a docs(configuration/custom ca): fix systemd path
Introduced by 03a66e469
2022-06-22 11:30:09 +02:00
Julien Fontanet
cce09bd9cc docs(configuration/custom ca): add note regarding XO Proxy 2022-06-22 10:44:25 +02:00
Julien Fontanet
03a66e4690 docs(configuration/custom ca): use separate systemd file
This is better as it avoids conflicts with existing config and is compatible with the way XO Proxy service is handled.
2022-06-22 10:44:25 +02:00
Florent BEAUCHAMP
fd752fee80 feat(backups,vhd-lib): implement copyless merge (#6271) 2022-06-22 10:36:57 +02:00
Julien Fontanet
8a71f84733 chore(xo-server): remove Model wrapping 2022-06-22 10:10:39 +02:00
Julien Fontanet
9ef2c7da4c chore(complex-matcher): remove build step 2022-06-22 09:55:59 +02:00
Julien Fontanet
8975073416 fix(xapi): add missing file
Introduced by b12c17947

Thanks @Danp2.
2022-06-22 00:07:32 +02:00
Julien Fontanet
d1c1378c9d feat(xo-server-db): minimal CLI to browser the DB 2022-06-21 18:11:44 +02:00
Julien Fontanet
7941284a1d feat(xo-server/collection/Redis): set of all indexes 2022-06-21 17:47:56 +02:00
Julien Fontanet
af2d17b7a5 feat(xo-server/collection/Redis): set of all namespaces 2022-06-21 17:29:19 +02:00
Julien Fontanet
3ca2b01d9a feat(xo-server/collection/Redis): assert namespace doesnt contain _ or : 2022-06-21 17:24:10 +02:00
Julien Fontanet
67193a2ab7 chore(xo-server/collection/Redis): replace prefix by namespace 2022-06-21 17:23:25 +02:00
Julien Fontanet
9757aa36de chore(xo-server/collection/Redis): _id field was never used 2022-06-21 17:23:18 +02:00
Julien Fontanet
29854a9f87 feat(xo-server): new sr.{enable,disable}MaintenanceMode methods 2022-06-21 15:07:09 +02:00
Julien Fontanet
b12c179470 feat(xapi): new SR_{enable,disable}MaintenanceMode methods 2022-06-21 15:07:09 +02:00
Julien Fontanet
bbef15e4e4 feat(xo-server/proxy.get{,All}); return associated URL(s) (#6291) 2022-06-21 11:33:25 +02:00
Florent BEAUCHAMP
c483929a0d fix(ova import): drain disk entry completly (#6284) 2022-06-20 16:09:20 +02:00
Julien Fontanet
1741f395dd chore(xo-server/deleteAuthenticationTokens): optimization
Don't use xo-server/deleteAuthenticationToken to avoid fetching the records twice.
2022-06-19 11:37:42 +02:00
Julien Fontanet
0f29262797 chore(value-matcher): remove build step 2022-06-19 11:28:11 +02:00
Julien Fontanet
31ed477b96 feat(xo-server/token.delete): available for non-admins 2022-06-17 11:59:29 +02:00
Julien Fontanet
9e5de5413d feat(xo-server/Collection#remove): accept a pattern 2022-06-17 11:59:29 +02:00
Florent BEAUCHAMP
0f297a81a4 feat(xo-remote-parser): additional parameters in URL (#6270) 2022-06-16 23:14:34 +02:00
Mathieu
89313def99 fix(xapi/vm): throw forbiddenOperation on blockedOperation (#6290) 2022-06-16 14:39:20 +02:00
Julien Fontanet
8e0be4edaf feat(xo-server/vm.set): blockedOperations now accepts string reasons and null
Related to #6290
2022-06-16 10:16:43 +02:00
Julien Fontanet
a8dfdfb922 fix(event-listeners-manager/add): _listeners is a Map 2022-06-15 14:37:38 +02:00
Julien Fontanet
f096024248 chore(event-listeners-manager): add tests 2022-06-15 14:37:31 +02:00
Julien Fontanet
4f50f90213 feat(xo-server/token.create): minimum duration is now one minute
This change also handles negative or zero invalid durations.
2022-06-15 11:26:32 +02:00
Julien Fontanet
4501902331 feat(xo-server): XO Proxy channel based on current channel (#6277) 2022-06-15 10:42:57 +02:00
Julien Fontanet
df19679dba fix(xo-cli): close connection when finished 2022-06-15 10:25:06 +02:00
Julien Fontanet
9f5a2f67f9 fix(xo-cli): xdg-basedir import
Introduced by 2d5c40632
2022-06-15 10:22:39 +02:00
Julien Fontanet
2d5c406325 chore: update dev deps 2022-06-13 19:33:09 +02:00
Julien Fontanet
151b8a8940 feat(read-chunk): add readChunkStrict 2022-06-13 12:01:02 +02:00
Julien Fontanet
cda027b94a docs(read-chunk): behavior when stream has ended 2022-06-13 11:22:42 +02:00
Julien Fontanet
ee2117abf6 chore(CHANGELOG.unreleased): pkgs list should be ordered
See https://team.vates.fr/vates/pl/1q6or14b9jffjfxk9qyebfg6sh
2022-06-13 11:22:08 +02:00
Thierry Goettelmann
6e7294d49f feat: release 5.71.1 (#6285) 2022-06-13 11:06:36 +02:00
Manon Mercier
062e45f697 docs(backup/troubleshooting): add no XAPI associated error (#6279)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-06-13 10:07:20 +02:00
Julien Fontanet
d18b39990d feat(xo-server/api): introduce a global async API context (#6274)
This allows access to contextual data deep inside the call stack.

Example use cases:
- current user
- specific permission (e.g. read only token)
- current authentication token
2022-06-13 09:43:39 +02:00
Julien Fontanet
7387ac2411 fix(xo-server/disk.import): fix xapi._getOrWaitObject call
Maybe related to #6282

Introduced by 5063a6982
2022-06-10 17:34:33 +02:00
Thierry Goettelmann
4186592f9f feat: technical release (#6281) 2022-06-10 17:05:04 +02:00
Thierry Goettelmann
6c9d5a72a6 feat(xo-web/backup): show cleanVm logs only in case of warnings (#6280) 2022-06-09 22:07:29 +02:00
Julien Fontanet
83690a4dd4 fix(xo-server/_importOvaVm): fix VM creation
Fixes https://xcp-ng.org/forum/post/49920

Introduced by 2af5328a0f
2022-06-09 18:51:35 +02:00
Florent BEAUCHAMP
c11e03ab26 fix(xo-vmdk-to-vhd/generateVmdkData): don't use VM name as OVF filename
It might break the OVA depending on present characters.
2022-06-09 17:18:30 +02:00
Florent BEAUCHAMP
c7d8709267 fix(xo-vmdk-to-vhd/generateVmdkData): reduce compression level
The max value (9) is very slow and should be avoided.
2022-06-09 17:18:30 +02:00
Florent BEAUCHAMP
6579deffad fix(xo-server): don't create zombie task on OVA export
Introduced by 4b9db257f
2022-06-09 17:18:30 +02:00
Julien Fontanet
e2739e7a4b fix(xo-server): make auth tokens created_at/expiration numbers 2022-06-09 16:15:14 +02:00
Florent BEAUCHAMP
c0d587f541 fix(backups): task warning if beforeBackup or checkBaseVdis steps fail (#6266) 2022-06-09 14:39:25 +02:00
Florent BEAUCHAMP
05a96ffc14 fix(xo-web): handle missing result of broken merge tasks in backup logs (#6275) 2022-06-09 14:14:26 +02:00
Julien Fontanet
32a47444d7 feat(proxy-cli): new --url flag
Which can be used instead of `--host` and `--token`.
2022-06-09 13:38:06 +02:00
Julien Fontanet
9ff5de5f33 feat(xo-server): expose _xapiRef to the API
Fixes zammad#7439

This makes objects searchable by their opaque ref in the UI.
2022-06-09 09:52:17 +02:00
Julien Fontanet
09badf33d0 feat(docs/configuration): use NODE_EXTRA_CA_CERTS instead of --use-openssl-ca (#6226)
Fixes zammad#6310

Easier to use and compatible with more distributions.
2022-06-09 09:08:16 +02:00
Julien Fontanet
1643d3637f chore(xo-server/api): remove unused api from context 2022-06-08 22:52:24 +02:00
Julien Fontanet
b962e9ebe8 fix(xo-server/system.methodSignature): declare expected params 2022-06-08 22:52:03 +02:00
Julien Fontanet
66f3528e10 fix(xapi/VM_snapshot): handle undefined VM.VUSBs
Fixes zammad#7401
2022-06-08 16:29:27 +02:00
Julien Fontanet
a5e9f051a2 docs(REST API): content-type is no longer necessary with -T
Because it is no longer set by default to `application/x-www-form-urlencoded` like it was with `--data-binary`.
2022-06-07 23:46:14 +02:00
Julien Fontanet
63bfb76516 docs(REST API): use -T instead of --data-binary for cURL
Because `--data-binary` loads the whole data in memory which isn't compatible with big data like a VHD file, whereas `-T` streams the data to the server.
2022-06-07 23:38:05 +02:00
tkrafael
f88f7d41aa fix(xen-api/putResource): use agent for both requests (#6261)
Fixes #6260
2022-06-07 19:33:33 +02:00
Julien Fontanet
877383ac85 fix(xo-server/sr.createExt): fix SR_create call
Introduced by 052126613
2022-06-07 18:59:30 +02:00
Julien Fontanet
dd5e11e835 feat(xo-server/api): don't filters error sent to admin users (#6262)
Previous behavior was hiding all errors not explicitly dedicated to be sent to API users and replacing them with an *unknown error from the peer*.

This was done to avoid leaking sensitive information, but it often hides important info.

Administrators can already see the raw errors in Settings/Logs, therefore it makes sense to not hide them for these users.
2022-06-07 13:34:34 +02:00
Julien Fontanet
3d43550ffe feat(xo-cli): provide authentication token description 2022-06-07 10:57:28 +02:00
Julien Fontanet
115bc8fa0a feat(xo-server): authentication tokens can have a description 2022-06-07 10:57:26 +02:00
Julien Fontanet
15c46e324c feat(xo-server/api): new user.getAuthenticationTokens 2022-06-07 10:04:45 +02:00
Julien Fontanet
df38366066 fix(xo-server/collection/redis#get): correctly filter on properties when id is provided 2022-06-07 10:04:14 +02:00
Julien Fontanet
28b13ccfff fix(xo-server/collection/redis#get): don't mutate properties param 2022-06-07 09:57:25 +02:00
Julien Fontanet
26a433ebbe feat(xo-server/createAuthenticationToken): add created_at field 2022-06-07 09:20:34 +02:00
Julien Fontanet
1902595190 feat(xo-server/getAuthenticationTokensForUser): filter and remove expired tokens 2022-06-07 09:15:30 +02:00
Julien Fontanet
80146cfb58 feat(xo-server/proxies): expose auth tokens
First step to show expose them in the UI, to make XO Proxies easier to use as HTTP proxies.
2022-06-07 09:02:46 +02:00
Yannick Achy
03d2d6fc94 docs(backups): explain HTTP timeout error and auto power on behavior (#6263)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-06-05 12:21:39 +02:00
Julien Fontanet
379e4d7596 chore(xo-server): use @xen-orchestra/xapi/VBD_unplug 2022-06-02 17:08:22 +02:00
Julien Fontanet
9860bd770b chore(xo-server): use @xen-orchestra/xapi/VBD_destroy 2022-06-02 17:07:18 +02:00
Julien Fontanet
2af5328a0f chore(xo-server): use @xen-orchestra/xapi/VM_create 2022-06-02 17:02:10 +02:00
Julien Fontanet
4084a44f83 chore(xo-server): use @xen-orchestra/xapi/VDI_exportContent 2022-06-02 16:57:21 +02:00
Julien Fontanet
ba7c7ddb23 chore(xo-server): use @xen-orchestra/xapi/VDI_importContent 2022-06-02 16:54:23 +02:00
Julien Fontanet
2351e7b98c chore(xo-server): use @xen-orchestra/xapi/VBD_create 2022-06-02 16:37:49 +02:00
Julien Fontanet
d353dc622c fix(xapi/VBD_create): don't fail if the VBD could not be plugged
Otherwise, the creation method would have failed but the VBD would still exist, violating the principle of least surprise.
2022-06-02 16:26:29 +02:00
Julien Fontanet
3ef6adfd02 feat(xapi/VBD_create): returns the new VBD's ref 2022-06-02 16:25:19 +02:00
Julien Fontanet
5063a6982a chore(xo-server): use @xen-orchestra/xapi/VDI_create 2022-06-02 16:10:16 +02:00
Julien Fontanet
0008f2845c feat(xapi/VDI_create): move sm_config in second param
Similarly to other creation methods, properties that must be explicited are passed in second param.
2022-06-02 14:45:57 +02:00
Julien Fontanet
a0994bc428 fix(scripts/gen-deps-list.js): add missing await
Introduced by a0836ebdd
2022-06-01 16:51:31 +02:00
Julien Fontanet
8fe0d97aec fix(scripts/gen-deps-list.js): fix packages order (#6259)
`deptree` nodes should be added only once with the full list of their dependencies.

For better display, packages are sorted by names before resolving the graph for nicer display.
2022-06-01 16:07:36 +02:00
Julien Fontanet
a8b3c02780 chore(CHANGELOG): integrate released changes 2022-06-01 15:56:01 +02:00
Julien Fontanet
f3489fb57c feat(xo-web): 5.97.1 2022-06-01 15:51:16 +02:00
Julien Fontanet
434b5b375d feat(xo-server): 5.95.0 2022-06-01 15:51:16 +02:00
Julien Fontanet
445120f9f5 feat(@xen-orchestra/proxy): 0.23.1 2022-06-01 15:51:16 +02:00
Julien Fontanet
71b11f0d9c feat(@xen-orchestra/xapi): 1.1.0 2022-06-01 15:51:16 +02:00
Julien Fontanet
8297a9e0e7 feat(@xen-orchestra/fs): 1.0.3 2022-06-01 15:51:16 +02:00
Florent BEAUCHAMP
4999672f2d fix(xo-web/backups): scheduled health check is available to enterprise (#6257)
Introduced by cae3555ca
2022-06-01 15:36:36 +02:00
Thierry Goettelmann
70608ed7e9 fix(scripts/gen-deps-lists.js): various fixes 2022-06-01 14:04:41 +02:00
Julien Fontanet
a0836ebdd7 feat(scripts/gen-deps-list.js): test mode (#6258) 2022-06-01 13:53:56 +02:00
Florent BEAUCHAMP
2b1edd1d4c feat: always log and display full remote errors (#6216)
Co-authored-by: Julien Fontanet <julien.fontanet@isonoe.net>
2022-05-31 17:30:27 +02:00
Thierry Goettelmann
42bb7cc973 feat: release 5.71.0 (#6256) 2022-05-31 16:20:41 +02:00
Julien Fontanet
8299c37bb7 fix(xo-server/pool.rollingUpdate): duplicate poolId declaration
Introduced by 7a2005c20
2022-05-31 14:32:13 +02:00
Mathieu
7a2005c20c feat(xo-server/pool): disable scheduled job when starting RPU (#6244)
See zammad#5377, zammad#5333
2022-05-31 11:59:52 +02:00
Pierre Donias
ae0eb9e66e fix(xo-web/health): make "Too many snapshots" table sortable by number of snaphots (#6255)
See zammad#6439
2022-05-31 11:45:11 +02:00
Julien Fontanet
052126613a feat(xapi,xo-server): create SRs with other_config.auto-scan=true (#6246)
Fixes https://team.vates.fr/vates/pl/nf18hnr51f8f3f3brcbra57uar
2022-05-31 11:24:15 +02:00
l-austenfeld
7959657bd6 fix(xo-server/xapi): missing shutdownHost default parameter (#6253)
Add a default empty object parameter to enable calls to shutdownHost with only one parameter.
This implicitly fixes the density load-balancer, since it calls shutdownHost with only one parameter.
2022-05-31 10:01:47 +02:00
Thierry Goettelmann
9f8bb376ea feat: technical release (#6254) 2022-05-30 17:45:59 +02:00
Julien Fontanet
ee8e2fa906 docs(REST API): use | cat trick in VDI import example 2022-05-30 16:51:35 +02:00
Julien Fontanet
33a380b173 docs(REST API): add name_label param in VDI import example 2022-05-30 16:50:36 +02:00
Julien Fontanet
6e5b6996fa docs(REST API): add required content-type in VM import 2022-05-30 16:48:01 +02:00
Julien Fontanet
6409dc276c docs(REST API): don't use --progress-bar in VDI import example
This is not necessary and more in line with other examples.
2022-05-30 16:46:54 +02:00
Julien Fontanet
98f7ce43e3 feat(xo-server/RestApi): VDI import now returns the new VDI's UUID 2022-05-30 16:45:41 +02:00
Julien Fontanet
aa076e1d2d chore(xo-server/rest-api): use xen-api shorthand syntax 2022-05-30 16:23:39 +02:00
Julien Fontanet
7a096d1b5c chore(xo-server/rest-api): remove unnecessary awaits 2022-05-30 16:00:43 +02:00
Julien Fontanet
93b17ccddd chore(xo-server/api/vm): format with Prettier
Introduced by d7d81431e
2022-05-30 16:00:43 +02:00
Julien Fontanet
68c118c3e5 fix(xo-server/api/vm): add missing quote
Introduced by d7d81431e
2022-05-30 16:00:43 +02:00
Thierry Goettelmann
c0b0ba433f feat(backups,xo-web): add cleanVm warnings to task (#6225) 2022-05-30 15:39:54 +02:00
Thierry Goettelmann
d7d81431ef feat(xo-server/vm.migrate): call VM.assert_can_migrate before (#6245)
Fixes #5301
2022-05-30 15:04:12 +02:00
Pierre Donias
7451f45885 fix(xo-web/home): don't make VM's resource set name clickable for non-admins (#6252)
See https://xcp-ng.org/forum/topic/5902/permissions-for-users-to-be-able-to-snapshot/5?_=1653902135402

Non-admin users aren't allowed to view the Self Service page so it doesn't make
sense to have a link to that page
2022-05-30 15:02:03 +02:00
Florent BEAUCHAMP
c9882001a9 fix(xo-web,xo-server): store health check settings in job instead of schedule (#6251)
Introduced by cae3555ca7
2022-05-30 14:56:28 +02:00
Mathieu
837b06ef2b feat(xo-server/xo-web/pool): avoid RPU/ host reboot, shutdown / host agent reboot during backup (#6232)
See zammad#5377
2022-05-30 11:13:13 +02:00
Julien Fontanet
0e49150b8e feat(xo-server/RestApi): add VDI import
Related to zammad#7036
2022-05-29 20:48:59 +02:00
Julien Fontanet
0ec5f4bf68 chore(proxy,xo-server): update to http-server-plus@0.11.1
This new version fixes, among others, the support of the `Expect: 100-Continue` HTTP header, which is notably used by cURL during `POST`.
2022-05-29 20:44:00 +02:00
Julien Fontanet
601730d737 feat(xapi): new SR_importVdi()
Creates a new VDI on an SR from a VHD.
2022-05-29 20:44:00 +02:00
Julien Fontanet
28eb4b21bd fix(xo-server/disk.import): VHD import
Introduced by 0706e6f4ff
2022-05-29 14:09:08 +02:00
Julien Fontanet
a5afe0bca1 feat(vhd-lib/peekFooterFromStream): check checksum and content 2022-05-29 14:07:48 +02:00
Julien Fontanet
ad5691dcb2 feat(self-signed): improve compat with OpenSSL 3
`-key -` appears to no longer be supported, generating the key in the same step works better.

It's still compatible with OpenSSL 1.
2022-05-27 12:56:55 +02:00
Julien Fontanet
80974fa1dc fix(xo-server/LevelDbLogger#del): don't use ignoreErrors on undefined
Fixes #6250

Introduced by 98bbd53c28

With a callback, levelup methods don't return a promise.
2022-05-26 23:44:11 +02:00
Thierry Goettelmann
78330a0e11 fix(backups/Backup): healthCheckSr should be undefined instead of false if disabled (#6248)
Introduced by cae3555ca7

Fixes https://xcp-ng.org/forum/topic/5903/s3-backup-failure-after-update
2022-05-26 15:38:56 +02:00
Rajaa.BARHTAOUI
b6cff2d784 fix(xo-web/vm/advanced): fix multiple errors when adding ACLs (#6230)
Fixes #6213

In Reaclette <= 0.9.0, the value returned by an effect is interpreted as a new
state by Reaclette, so an effect that doesn't mutate the state must not return a
value.
2022-05-25 16:38:50 +02:00
Mathieu
cae3555ca7 feat(xo-web/backup): scheduled health check (#6227) 2022-05-25 15:25:07 +02:00
Julien Fontanet
1f9cf458ec feat(mixins/Config): support get/watch entire config
If the path is `undefined` or the empty string.
2022-05-25 08:48:32 +02:00
Florent BEAUCHAMP
d9ead2d9f5 feat(xo-web): display scheduled health check logs (#6238) 2022-05-24 15:06:10 +02:00
Florent BEAUCHAMP
92660fd03e feat(xo-server): limit scheduled backup health check to enterprise (#6240) 2022-05-24 14:26:28 +02:00
Florent BEAUCHAMP
5393d847f0 feat(xo-server-backup-reports): show health check (#6242) 2022-05-24 14:23:14 +02:00
Florent BEAUCHAMP
231f09de12 feat(backups,xo-server): scheduled health check (#6228) 2022-05-24 14:16:48 +02:00
Julien Fontanet
b75ca2700b feat(backups): can snapshot VMs with VUSBs 2022-05-24 11:38:05 +02:00
Julien Fontanet
bae7ef9067 feat(xapi/VM_snapshot): add unplugVusbs option 2022-05-24 11:38:05 +02:00
Thierry Goettelmann
8ec8a3b4d9 feat(scripts/gen-deps-list.js): should now be used only during release (#6234) 2022-05-24 11:01:05 +02:00
Julien Fontanet
5b7228ed69 feat(xo-server/createAuthenticationToken): throw if expiresIn is too high 2022-05-23 18:24:51 +02:00
Julien Fontanet
b02bf90c8a feat(emit-async): pass listener, event and emitter to error handler 2022-05-23 11:31:51 +02:00
Julien Fontanet
7d3546734e feat(mixins/Config#watch{,Duration}): pass previous value and path to cb 2022-05-23 09:39:48 +02:00
Julien Fontanet
030013eb5b chore(backups/merge-worker/cli): fix lint error 2022-05-23 09:39:48 +02:00
Julien Fontanet
da181345a6 feat(xo-server/store): check access to leveldb dir
Avoid getting stuck on inaccessible leveldb directory.
2022-05-23 09:39:48 +02:00
Julien Fontanet
30874b2206 chore(xo-cli): convert to ESM 2022-05-19 10:41:39 +02:00
Thierry Goettelmann
2ed6b2dc87 feat(xo-web): ability to configure a default filter for Home/Storage (#6237)
Fixes #6236
2022-05-19 09:36:11 +02:00
Julien Fontanet
41532f35d1 fix(xo-server/BackupNg#_checkAuthorizations): don't fail on missing backup.vm config section
Fixes the last part of #6243
2022-05-18 19:01:06 +02:00
Julien Fontanet
7a198a44cd chore(xo-server/BackupNg#checkAuthorization): make private
It is not used elsewhere and it makes no sense to expose a method with such a generic name on the app instance.
2022-05-18 19:01:06 +02:00
Julien Fontanet
77d615d15b chore(backups/Backup): pass baseSettings to VmBackup
Fix parts of #6243
2022-05-18 19:01:06 +02:00
Julien Fontanet
c7bc397c85 chore(backups/Backup): group settings generation in constructor 2022-05-18 19:01:06 +02:00
Julien Fontanet
38388cc297 chore(backups/VmBackup): remove unused remotes param 2022-05-18 19:01:06 +02:00
Julien Fontanet
a7b17b2b8c chore(backups/Backup): assign this.run in constructor depending of type 2022-05-18 19:01:06 +02:00
Florent BEAUCHAMP
d93afc4648 fix(xo-web/remotes): form not saving HTTPS and allow unauthorized during S3 creation (#6219) 2022-05-18 12:07:38 +02:00
Julien Fontanet
24449e41bb docs(backups): run description object 2022-05-18 11:22:30 +02:00
Julien Fontanet
df6f3ed165 chore(backups,xo-server,proxy): centralize default settings 2022-05-18 11:14:56 +02:00
Julien Fontanet
ca5914dbfb docs(backups): outline writer API 2022-05-17 14:07:01 +02:00
Julien Fontanet
3c3a1f8981 chore: update dev deps 2022-05-17 11:27:06 +02:00
Florent BEAUCHAMP
01810f35b2 fix(S3#_list): handle remote without base directory (#6218)
Related to zammad#6740
2022-05-17 10:56:45 +02:00
Florent BEAUCHAMP
5db4083414 feat(backups): add settings to force snapshotting during VM backup (#6221)
Fixes zammad#6735
2022-05-17 10:54:28 +02:00
Florent BEAUCHAMP
8bf3a747f0 feat(backups): add cache for backup metadata (#6220)
Fixes zammad#5747

Listing all the backup can be slow. To speed it up, the metadata of all the backups of each VM is cached in a single gzipped JSON file.

The cache is invalidated when a backup is deleted or created.
2022-05-17 10:43:00 +02:00
Julien Fontanet
f0e817a8d9 chore: format with Prettier 2022-05-17 10:35:16 +02:00
Florent Beauchamp
b181c59698 fix(fs/S3#_createReadStream): avoid race condition when checking file exist 2022-05-17 10:34:08 +02:00
Julien Fontanet
cfa094f208 chore(xo-proxy-cli): convert to ESM 2022-05-16 17:27:00 +02:00
Julien Fontanet
9ee5a8d089 fix(xo-proxy-cli): don't try to load xo-proxy vendor config
It's not necessary, not relevant (because it does not belong to this app) and the path was incorrect anyway.
2022-05-16 17:27:00 +02:00
Denis Fondras
819127da57 docs(installation): add OpenBSD instruction (#5762) 2022-05-16 12:05:52 +02:00
Julien Fontanet
6e9659a797 feat: release 5.70.2 2022-05-16 10:22:53 +02:00
Julien Fontanet
07bd9cadd4 fix(xo-server/vm.create): typo during VIF creation
Introduced by ecae554a7

Fixes https://xcp-ng.org/forum/post/49143
2022-05-15 14:32:59 +02:00
Florent BEAUCHAMP
a1bcd35e26 feat(backups/cleanVm): can fully merge VHD chains (#6184)
Before this change, `cleanVm` only knew how to merge a single VHD, now, with the help of `VhdSynthetic`, it can merge the whole chain in a single pass.
2022-05-13 16:46:22 +02:00
Florent BEAUCHAMP
1a741e18fd fix(vhd-lib/VhdDirectory#writeChunkFilters): correctly overwrite chunk-filter.json (#6235) 2022-05-13 13:38:02 +02:00
Olivier Lambert
2e133dd0fb feat: create SECURITY.md (#6176)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-05-13 10:58:55 +02:00
Julien Fontanet
ecae554a78 chore(xo-server): use @xen-orchestra/xapi/VIF_create 2022-05-12 15:36:19 +02:00
Julien Fontanet
4bed50b4ed chore(xo-server): remove unused {export,import}DeltaVm functions 2022-05-12 15:10:48 +02:00
Julien Fontanet
c92b371d9e feat(xo-server): 5.93.1 2022-05-12 11:50:48 +02:00
Julien Fontanet
35e6bb30db feat(@xen-orchestra/proxy): 0.22.1 2022-05-12 11:49:32 +02:00
Julien Fontanet
1aaa123f47 feat(@xen-orchestra/mixins): 0.4.0 2022-05-12 11:47:59 +02:00
Julien Fontanet
a8c507a1df feat(@xen-orchestra/backups): 0.23.0 2022-05-12 11:43:41 +02:00
Julien Fontanet
581e3c358f feat(@xen-orchestra/xapi): 1.0.0 2022-05-12 11:41:09 +02:00
Julien Fontanet
e4f1b8f2e0 fix(xo-server/installPatches): fix pool wide detection (#6231)
Introduced by 3f1c41a4f

Fixes zammad#6819 zammad#6781 zammad#6827

In #6186 the behavior was changed to always pass hosts, which broke the pool wide detection.
2022-05-12 10:56:18 +02:00
Julien Fontanet
29e8a7fd7e docs(xo-server/REST API): Set-Cookie is not implemented 2022-05-10 15:31:40 +02:00
Julien Fontanet
4af289c492 feat(docs/architecture): update xo-cli usage 2022-05-10 11:38:19 +02:00
Julien Fontanet
cd95793054 chore(mixins): convert to ESM 2022-05-09 14:46:25 +02:00
Julien Fontanet
ab71578cf2 chore(xapi): major version
When using major version zero, every increase of the minor version number is breaking.

Which means that each new version of `xapi` required also a new release of `@xen-orchestra/backups`, using a true major version will fix that.
2022-05-09 10:00:36 +02:00
Julien Fontanet
df07d4a393 chore: refresh yarn.lock 2022-05-06 11:42:40 +02:00
Julien Fontanet
2518395c03 feat: release 5.70.1 2022-05-04 15:55:41 +02:00
Thierry Goettelmann
50f3ab7798 feat(scripts/gen-deps-list.js): new util to generate deps list (#6181) 2022-05-04 09:57:21 +02:00
Julien Fontanet
2d01056ea9 chore(CHANGELOG): integrate released changes 2022-05-03 17:52:29 +02:00
Julien Fontanet
f40fb3bab3 feat(xo-server): 5.93.0 2022-05-03 17:36:10 +02:00
Julien Fontanet
fe7c60654d feat(@xen-orchestra/proxy): 0.22.0 2022-05-03 17:35:43 +02:00
Julien Fontanet
728b640ff8 feat(@xen-orchestra/backups): 0.22.0 2022-05-03 17:35:20 +02:00
Julien Fontanet
55c247e5d0 feat(@xen-orchestra/xapi): 0.11.0 2022-05-03 17:34:42 +02:00
Julien Fontanet
6be15b780a feat(@xen-orchestra/mixins): 0.3.1 2022-05-03 17:33:44 +02:00
Florent BEAUCHAMP
150c552ef9 fix(xo-server/checkBackupNg): wait for VM to be running before watching guest metrics (#6214)
Introduced by 7d6e832
2022-05-03 15:11:01 +02:00
Julien Fontanet
7005c1f5e5 feat(xapi/VM_{checkpoint,snapshot}): complete NOBAK support (#6208)
Fixes #2560
2022-05-03 10:28:00 +02:00
Julien Fontanet
a66ae33d5d fix(mixins/HttpProxy): don't fail on missing httpServer
Introduced by 98641631a

Similar to bc7fc750f

Which is the case when instanciated from `xo-server-recover-account`.
2022-05-02 13:34:07 +02:00
Julien Fontanet
8ed8447665 chore(cached-dns.lookup/README): regenerate from .USAGE.md 2022-04-29 15:54:57 +02:00
Florent BEAUCHAMP
e740719732 fix(xo-server/proxy-console): don't close client socket before legacy fallback (#6203) 2022-04-29 15:24:30 +02:00
Mathieu
bfd9238f6d feat: release 5.70.0 (#6211) 2022-04-29 12:18:21 +02:00
Mathieu
cca47a8149 feat: technical release (#6209) 2022-04-28 16:28:24 +02:00
Julien Fontanet
3ecf099fe0 feat(mixins/HttpProxy): HTTP/HTTP CONNECT proxy (#6201) 2022-04-28 15:39:21 +02:00
Julien Fontanet
6f56dc0339 fix(CHANGELOG.unreleased): style & package order 2022-04-28 15:15:10 +02:00
Florent BEAUCHAMP
20108208d0 fix(xo-server/clearHost): warn if host does not support migration network for evacuation (#6206) 2022-04-28 11:55:17 +02:00
Florent BEAUCHAMP
0706e6f4ff feat(xo-web,xo-server): implement ISO import (#6180) 2022-04-28 10:41:20 +02:00
Mathieu
af85df611c feat(xo-web/proxy): disable "deploy proxy" for source users (#6199)
See xcp-ng-forum#5740
2022-04-26 19:28:07 +02:00
Mathieu
3c1239cfb8 feat(xo-web/migrate/vm): allow to select private network for VIFs network (#6200) 2022-04-26 15:31:14 +02:00
Julien Fontanet
50d144bf93 chore: format with Prettier 2022-04-26 14:27:54 +02:00
Julien Fontanet
9a5a03d032 chore: update deps 2022-04-26 14:27:52 +02:00
Thierry Goettelmann
854ae0f65e fix(fs/s3): fix stream writing support with Object Lock enabled (#6190) 2022-04-26 11:37:34 +02:00
Julien Fontanet
4fb34ffee9 feat(xen-api/cli): add proxy support 2022-04-25 16:27:36 +02:00
Julien Fontanet
bbf3dae37f fix(xen-api): fix proxy support (#6204)
Introduced by c99120bd2
2022-04-25 16:26:56 +02:00
Julien Fontanet
e69f58eb86 fix(cached-dns.lookup): don't use assert/strict
Fixes #6202

Only available on Node >=15.
2022-04-24 15:28:30 +02:00
Julien Fontanet
c9475ddc65 feat(@vates/cached-dns.lookup): small DNS cache (#6196)
See https://xcp-ng.org/forum/topic/5775/dns-queries-during-backup-job
2022-04-22 15:27:41 +02:00
Julien Fontanet
31d085b6a1 chore: update to app-conf@2.1.0
This new version does not watch vendor config which fixes (minor) issues during services reinstall/upgrade.
2022-04-21 17:17:36 +02:00
Julien Fontanet
173866236f feat(xo-server/_handleHttpRequest): add fn and data to the error log
This should ease debugging.
2022-04-21 16:56:49 +02:00
Florent BEAUCHAMP
b176780527 fix(xo-server/proxy-console): fallback on TCP if WS not available (#6191)
Introduced by c99120bd2

Co-authored-by: Julien Fontanet <julien.fontanet@isonoe.net>
2022-04-21 14:51:33 +02:00
Julien Fontanet
89c72fdbad feat(event-listeners-manager): easy way to clean up event listeners 2022-04-21 14:02:57 +02:00
Florent BEAUCHAMP
7d6e832226 feat(xo-server,xo-web/backups): restore health check (#6148) 2022-04-21 10:26:36 +02:00
Julien Fontanet
c024346475 chore: update to eslint-config-standard@17.0.0 2022-04-20 17:44:50 +02:00
Florent BEAUCHAMP
95ec5929b4 fix(xo-vmdk-to-vhd): remove depency to xmllint for source users (#6195) 2022-04-20 16:07:28 +02:00
Julien Fontanet
1646c50a94 Revert "feat(xen-api): implement fallback addresses (#6061)" (#6198)
This reverts commit 6b8a345241.
2022-04-20 11:58:15 +02:00
Julien Fontanet
b1429e1df3 chore(xen-api/_wrapRecord): basic documentation 2022-04-20 10:46:40 +02:00
Julien Fontanet
6da0aa376f fix(xo-vmdk-to-vhd/createNicsSection): remove incorrect XML node
Introduced by 4b9db257f

See https://github.com/vatesfr/xen-orchestra/pull/6195#discussion_r853177122
2022-04-20 09:56:04 +02:00
Julien Fontanet
1ab5503558 chore(xo-vmdk-to-vhd): refresh README
Introduced by 4b9db257f
2022-04-19 17:47:43 +02:00
Nicolas Raynaud
4b9db257fd feat: initial support for OVA VM export (#6006)
Co-authored-by: Florent Beauchamp <flo850@free.fr>
2022-04-19 11:01:53 +02:00
Julien Fontanet
96f83d92fc chore: update dev deps 2022-04-19 10:32:16 +02:00
Pierre Donias
7c7ee7fb9b feat: release 5.69.2 (#6189) 2022-04-13 16:52:40 +02:00
Pierre Donias
2bd3d57f8a feat(xo-server/rpu): update hosts one by one on XCP-ng (#6188) 2022-04-13 16:38:23 +02:00
Pierre Donias
3f1c41a4f7 fix(xo-server/Rolling Pool Update/_xcpUpdate): handle undefined hosts (#6186)
Fixes #6170
Introduced by e7f9111ab5
2022-04-13 13:22:30 +02:00
Julien Fontanet
ef819f4d53 fix(xo-server/connectXenServer): typo
Introduced by 6b8a34524
2022-04-08 14:30:35 +02:00
Julien Fontanet
23189ed8f9 chore(scripts): add .js suffix for better Prettier/ESLint integration 2022-04-08 14:14:41 +02:00
Florent BEAUCHAMP
6b8a345241 feat(xen-api): implement fallback addresses (#6061) 2022-04-08 11:11:20 +02:00
Julien Fontanet
b3cc168571 docs(xapi/watchObject): explicit behavior and param 2022-04-08 10:57:44 +02:00
Julien Fontanet
00b740c549 chore(xo-server/docs/rest-api): improve readability 2022-04-08 10:57:44 +02:00
Pierre Donias
472bececb1 feat(xo-server/plugins): unload plugin on configuration purge (#6172) 2022-04-07 14:49:36 +02:00
Olivier Lambert
4fe9a4eb59 feat(docs): add REST API doc and minor fixes (#6178)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-04-07 10:42:10 +02:00
Pierre Donias
d331cd934a fix(xo-server/backup): restore VM from proxied remote (#6179) 2022-04-06 16:32:43 +02:00
Olivier Lambert
f6e1b95711 feat(issue template/bug): explicit XOA and sources differences (#6175)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-04-05 15:02:46 +02:00
Florent BEAUCHAMP
edec412bc7 fix(docs/backups): clarify file level restore for VG based on multiple PV (#6152)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-04-05 10:24:41 +02:00
Florent BEAUCHAMP
e142bacb67 fix(xo-vmdk-to-vhd): ensure secondary grain table is present (#6167) 2022-04-05 10:24:00 +02:00
Florent BEAUCHAMP
915e4b66a3 fix(vmdk): ensure descriptor have enough free space (#6163) 2022-04-05 10:22:13 +02:00
Pierre Donias
ee47a361b1 fix(xo-server/config): configure plugins on config import (#6171)
See https://xcp-ng.org/forum/topic/5720/ztp-restore-xoa-from-backup
2022-04-04 15:44:05 +02:00
l-austenfeld
3ab2dad19b fix(docs/configuration): link to sources install (#6177)
Co-authored-by: Lennart Austenfeld <lennarta@mail.upb.de>
2022-04-04 08:28:12 +02:00
Julien Fontanet
635c6db83a fix(backups/ImportVmBackup): fix optional mapVdisSrs
Introduced by dfa5009a9
2022-04-03 16:34:05 +02:00
Julien Fontanet
d2a13f531a feat(xen-api): support pathname in URL
Fixes #6174
2022-04-02 20:07:43 +02:00
Julien Fontanet
e44857c023 fix(xen-api/transports/xml-rpc): fix agent support
Introduced by c99120bd2
2022-04-02 20:05:17 +02:00
Julien Fontanet
749cdd011b fix(proxy/reverse proxy): don't remove first char of local path
Introduced by b78a946458
2022-04-02 19:57:08 +02:00
Julien Fontanet
4fde005b7f fix(proxy/reverse proxy): fix options support
Introduced by b78a946458
2022-04-02 19:54:28 +02:00
Olivier Lambert
0a975fc0cc feat(docs/updates): add RPU documentation and improve the update section (#6173)
* feat(docs/updates): add RPU documentation and improve the update section

Signed-off-by: Olivier Lambert <olivier.lambert@vates.fr>
2022-04-02 11:23:22 +02:00
Pierre Donias
f5b7c59203 feat: release 5.69.1 (#6169) 2022-03-31 15:54:59 +02:00
Florent BEAUCHAMP
c775559912 fix(xo-server): typo in authorization level enterprise (#6168)
Introduced by 8ce1b4bf71
See zammad#6043
2022-03-31 15:39:05 +02:00
Pierre Donias
4b9116ed72 feat: release 5.69.0 (#6166) 2022-03-31 10:50:32 +02:00
Thierry Goettelmann
178501e252 fix(backups/_backupType): XVA sum files are not detected (#6160) 2022-03-30 23:20:05 +02:00
Pierre Donias
c9391abfd9 feat: technical release (#6165) 2022-03-30 16:06:06 +02:00
Pierre Donias
f7a7d9e52d feat: technical release (#6164) 2022-03-30 15:15:22 +02:00
Mathieu
5a65087aeb fix(xo-web/file restore): mention S3 is not compatible (#6158)
Introduced by 3842f5b16d
2022-03-30 12:10:08 +02:00
Pierre Donias
3cfeb8f492 docs(Netbox): update with Netbox v3 screenshots (#6162) 2022-03-30 09:00:18 +02:00
Julien Fontanet
0498e2d679 chore: update deps 2022-03-29 16:35:00 +02:00
Florent BEAUCHAMP
19563d0b3c fix(web): rework human readable number rounding (#6131) 2022-03-29 14:33:04 +02:00
Pierre Donias
4cc1d98a42 fix(xo-server,xo-web/new ISO SR): take NFS version & options into account (#6161)
See zammad#5995
2022-03-29 14:28:16 +02:00
Mathieu
cd408c1687 feat(xo-web/VM/import): ability to import XVA VM from URL (#6130)
Follow-up of 86e390f70f
2022-03-29 11:43:11 +02:00
Rajaa.BARHTAOUI
546859531b feat(xo-web/install patches): improve confirm message (#6159) 2022-03-29 11:17:13 +02:00
Pierre Donias
857e467672 Revert "fix(xo-web/job): properly handle array arguments (#5944)" (#6156)
This reverts commit e2e453985f.

See #5983
See #5973
See zammad#5844
2022-03-29 11:15:43 +02:00
Florent BEAUCHAMP
5de5a80eee feat(xo-web/VM/Advanced): add link to doc on secure boot (#6146) 2022-03-28 15:46:26 +02:00
Thierry Goettelmann
5f1333b2a0 chore(fs/s3): upgrade AWS SDK (#6144)
https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md
2022-03-28 14:06:23 +02:00
Julien Fontanet
5e9548edbc feat(xo-server/rest-api): add VDI export 2022-03-28 11:12:26 +02:00
Julien Fontanet
e42edf2164 fix(xo-server/rest-api/VM export): 404 error if UUID is not a VM 2022-03-28 11:01:09 +02:00
Julien Fontanet
7e8c524a43 fix(xo-server/rest-api): restrict to admin users (missing change)
Introduced by 24ce40042
2022-03-28 11:00:26 +02:00
Julien Fontanet
8d6ba86118 fix(xo-server): xo-common/api-error.js import
Introduced by dc27317d0
2022-03-28 10:36:10 +02:00
Julien Fontanet
dc27317d0b feat(xo-server/rollingPoolUpdate): fail if some hosts are not running
Fixes zammad#795.
2022-03-28 08:46:00 +02:00
Julien Fontanet
0b8032c7a0 feat(xen-api/cli): expose lodash as L 2022-03-28 08:38:41 +02:00
Julien Fontanet
d3e18a81aa chore(xen-api): don't use babel-plugin-lodash
It prevents importing the whole package when necessary.
2022-03-28 08:37:54 +02:00
Julien Fontanet
4707573c57 feat(xo-server/rest-api): add VM export 2022-03-26 23:36:01 +01:00
Julien Fontanet
218e2deef4 chore(xo-server/docs/rest-api): add ToC 2022-03-26 23:14:00 +01:00
Julien Fontanet
47afff3bab chore(xo-server/docs/rest-api): explicit experimental status 2022-03-26 23:13:09 +01:00
Julien Fontanet
24ce400429 feat(xo-server/rest-api): restrict to admin users 2022-03-26 23:01:45 +01:00
Julien Fontanet
ceac341016 chore(xo-server): use @xen-orchestra/xapi/VM_export 2022-03-26 22:59:20 +01:00
Julien Fontanet
7830c89e66 fix(xo-server/Xapi#exportVm): correctly use getRecord
Introduced by 6aa5ec6eb
2022-03-26 12:41:44 +01:00
Julien Fontanet
fa6ca01de9 chore: remove unused Flow config
Introduced by 78c0f2c7e
2022-03-26 09:20:18 +01:00
Julien Fontanet
746bff55a8 fix(backups): import suspended VM without suspend VDI (#6149)
Fixes #6142
2022-03-25 23:06:32 +01:00
Julien Fontanet
ec81e90153 chore(proxy-cli): remove build step 2022-03-25 19:07:26 +01:00
Julien Fontanet
74c9d06d91 chore(cron): remove build step 2022-03-25 17:10:22 +01:00
Julien Fontanet
3cfbd8b70c fix(jest): don't run audit-core tests
Introduced by 046d9b80b
2022-03-25 17:10:22 +01:00
Julien Fontanet
046d9b80bb test(audit-core): use tap instead of Jest 2022-03-25 16:53:04 +01:00
Julien Fontanet
516fda2a3a chore(audit-core): move spec to docs/
To avoid it from being published.
2022-03-25 15:43:37 +01:00
Julien Fontanet
bc5c103cec chore(audit-core): remove build step 2022-03-25 15:40:52 +01:00
Julien Fontanet
98f0c068ad chore(backups): initial documentation 2022-03-25 11:21:17 +01:00
Julien Fontanet
28e0a5a3a3 fix(xo-server/utils/parseXml): ignore XML declaration
Restore previous behavior and fix tests.
2022-03-25 10:18:41 +01:00
Julien Fontanet
5f9d6db761 feat(xo-server/_pDebounceWithKey): proper support of inifinite delay
No more Node warning.
2022-03-25 10:17:33 +01:00
Julien Fontanet
9bec52074f test(xo-server): use tap instead of Jest
Fixes support of .mjs files
2022-03-25 10:16:49 +01:00
Rajaa.BARHTAOUI
dfa5009a9b feat(xo-web/backup/restore): ability to ignore certain VDIs (#6143)
Fixes #4605
2022-03-24 10:51:26 +01:00
Julien Fontanet
fbb5c47358 chore: update to eslint-plugin-n@15.1.0
It fixes the support for dynamic imports (`import()`).
2022-03-24 10:34:44 +01:00
Thierry Goettelmann
f2ca67a7f4 fix(fs/rmtree): unllink can throw EPERM for dirs on POSIX (#6155)
For instance on macOS.
2022-03-24 10:03:56 +01:00
Rajaa.BARHTAOUI
3a9af92571 feat(xo-web): disable patch installation when HA is enabled (#6145)
See kanban#770
2022-03-23 17:16:03 +01:00
Mathieu
b26148bf62 fix(xo-web/new/SR/NFS): disable "No selected value" path option (#6137)
Fixes #5991
2022-03-23 16:55:45 +01:00
Julien Fontanet
7e27e0bda8 linting: enable eslint-plugin-n recommended rules
And fixes a lot of related issues.
2022-03-23 11:18:37 +01:00
Julien Fontanet
94df05566a chore(xapi): remove xo-common from dev deps
Introduced by 5f1c1278e
2022-03-23 09:49:14 +01:00
Julien Fontanet
efa46414fd chore(xapi): remove build step 2022-03-23 09:48:07 +01:00
Julien Fontanet
311623d71a chore(xapi): don't use decorator syntax 2022-03-23 09:36:35 +01:00
Julien Fontanet
89894b0f08 chore: update yarn.lock 2022-03-23 09:35:57 +01:00
Julien Fontanet
ed590eeb41 fix(xen-api/_getHostBackupAddress): avoid using internal address for master
Fixes #6125
2022-03-22 15:44:21 +01:00
Julien Fontanet
8956902c52 chore: update dev deps 2022-03-22 09:34:46 +01:00
Julien Fontanet
720d9649eb fix(xapi): add missing xo-common dep
Introduced by 5f1c1278e
2022-03-22 09:34:46 +01:00
Olivier Lambert
ce95041821 feat(docs/community): add more details on support (#6151)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-03-22 08:01:38 +01:00
Olivier Lambert
9cd84ac834 feat(docs/roadmap): update (#6150)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-03-22 07:50:13 +01:00
Julien Fontanet
eddd6880f2 feat(xo-server/pool.installPatches): disable when HA enabled
Requested by @stormi https://team.vates.fr/vates/pl/r3y6fmxq9byqixehhm8wiyx9zw
2022-03-21 11:23:41 +01:00
Julien Fontanet
e7f9111ab5 feat(xo-server/pool.installPatches): restart toolstacks after install
Fixes https://kanban.vates.fr/b/jnfjuip4eBARBNuv9/xo-releases/t8QpqKnZ23iYnbRxR
2022-03-21 11:19:11 +01:00
Julien Fontanet
0eb2ee72a4 feat(xapi): host_restartAgent 2022-03-21 10:40:35 +01:00
Julien Fontanet
1aa4fac528 chore(xen-api): better status assertions 2022-03-21 10:38:27 +01:00
Pierre Donias
3f07f7ef41 fix(xo-server): invalid arguments passed to VM_destroy (#6119)
Fixes #6124
See https://xcp-ng.org/forum/topic/5569

Introduced by cb52a8b51
2022-03-17 14:37:45 +01:00
Julien Fontanet
886ff2cd70 fix(xo-server/_runJob): createUserConnection → createApiConnection
Introduced by 244b15038
2022-03-16 16:43:28 +01:00
Julien Fontanet
ed09608952 fix(xapi/VM_destroy): remove useless return 2022-03-16 15:54:36 +01:00
Julien Fontanet
6aa5ec6eb6 chore(xo-server): use @xen-orchestra/xapi/VM_{checkpoint,snapshot} 2022-03-16 14:48:59 +01:00
Julien Fontanet
eee4981d4c fix(xapi/VM_checkpoint): set is_a_template to false
The work-around was already implement for `VM_snapshot`.
2022-03-16 11:49:57 +01:00
Julien Fontanet
ab2c0f905a fix(xo-server): fix patching for XCP-ng>=8.2.1
Fixes zammad#5786

The behavior of the plugin has changed:
- it will throw in case of error
- `exit` is no longer defined in case of success

When defined, `exit` is still checked for compatibility with older versions.
2022-03-15 15:24:11 +01:00
Julien Fontanet
4eca3d169e feat(xo-server/api): add api.{getConnections,closeAllConnections} methods 2022-03-15 15:24:11 +01:00
Julien Fontanet
e69944eaaf chore(xo-server/api): uniformize session → connection 2022-03-15 15:24:11 +01:00
Julien Fontanet
bc987b2dda chore(xo-server): WebSocket connection events debugs 2022-03-15 15:23:52 +01:00
Julien Fontanet
244b150385 chore(xo-server): move Xo#createUserConnection() to Api#createApiConnection() 2022-03-15 15:23:01 +01:00
Julien Fontanet
3acbc08ec5 chore(xo-server): move some API logic to Xo#createUserConnection() 2022-03-15 14:38:16 +01:00
Julien Fontanet
1ce7e5d8a4 fix(proxy): fix incorrect order of decorators application
Introduced by e14c177f3
2022-03-14 18:34:01 +01:00
Julien Fontanet
7813fe232a chore(fs/S3): standardize paths handling 2022-03-14 13:41:01 +01:00
Julien Fontanet
1b273071b2 chore(fs/_path): utilities to handle virtual paths 2022-03-14 13:27:51 +01:00
Julien Fontanet
43236f25ad chore(fs/_normalizePath): comment more behaviors 2022-03-14 13:27:51 +01:00
Julien Fontanet
5f2b1acfea chore(xo-server/authorization): simplify check for source users 2022-03-12 16:06:07 +01:00
Florent BEAUCHAMP
b820dcd73f fix(xo-server/authorizations): add missing smart backups and S3 (#6141)
Introduced by 4e8dc2
2022-03-12 09:57:42 +01:00
Florent BEAUCHAMP
0a412c0ef7 feat(CI): use github action (#5987) 2022-03-11 17:18:21 +01:00
Florent BEAUCHAMP
db75f8046c feat(fs/S3#list): workaround Backblaze issue (#6139)
Workaround for #6138
2022-03-11 16:12:08 +01:00
Florent Beauchamp
7bdd1a4a3a fix(vhd-lib): fix tests 2022-03-11 14:55:55 +01:00
Julien Fontanet
b7d39105e8 feat(docs): delete old (unused) from_the_sources 2022-03-10 19:26:48 +01:00
Julien Fontanet
f8b5dbbba5 feat(docs/installation): integrate old from_the_sources changes 2022-03-10 19:26:13 +01:00
Florent BEAUCHAMP
c99120bd24 feat: support VM/hosts consoles behind HTTP proxy (#6133)
This is a major change in the way xo-server connect to a console, from connecting directly as a TCP socket to using a WebSocket in binary mode.

This was already the case prior c17620e but was changed due to XenServer issues with their WebSocket console implementation, it appears to be working fine now.
2022-03-10 13:54:32 +01:00
Julien Fontanet
b9ff3db9b0 feat(decorate-with): decorateClass() (#6136)
Generalization of `decorateMethodsWith` which also works for accessors.

The suffix `With` is not part of the name because it's not fluent (unlike for `@decorateWith(decorator)`).

`decorateMethodsWith` is now a deprecated alias for this new implementation.
2022-03-10 11:51:57 +01:00
Florent BEAUCHAMP
8ce1b4bf71 feat(xo-server): server side authorization (#6107) 2022-03-10 11:45:04 +01:00
Julien Fontanet
0b41a2b132 feat(fs): add basic CLI 2022-03-10 09:48:45 +01:00
Julien Fontanet
575ed92593 fix(xo-server): add missing dep @vates/async-each
Introduced by 6075a3307
2022-03-09 16:14:58 +01:00
Julien Fontanet
d3fe37b879 fix(fs): add missing dep @vates/async-each
Introduced by 96eb79329
2022-03-09 16:13:27 +01:00
Julien Fontanet
8318cb991b chore(xen-api): remove Babel plugin included in preset-env 2022-03-09 09:03:23 +01:00
Julien Fontanet
9779fb1d29 fix(gitignore): ignore all coverage/ 2022-03-09 08:19:59 +01:00
Julien Fontanet
43b4e8c924 fix(CHANGELOG.unreleased): add @xen-orchestra/xapi
Follow-up of c67b55043
2022-03-09 06:58:17 +01:00
Julien Fontanet
756a206a9e feat(xo-server/RestApi): add networks, VBDs, VDIs and VIFs 2022-03-09 06:56:25 +01:00
Julien Fontanet
324ff44886 chore(proxy): remove unused .eslintrc.js
Follow-up of a870813ac
2022-03-09 06:45:24 +01:00
AtaxyaNetwork
04913cabba fix(docs/from_the_sources): replace python-minimal with python3-minimal (#6134)
python-minimal doesn't exist anymore on latest Debian
2022-03-06 19:36:52 +01:00
Julien Fontanet
c67b550435 chore(xen-api): major version
When using major version zero, every increase of the minor version number is breaking.

Which means that each new version of `xen-api` required also a new release of `@xen-orchestra/xapi`, using a true major version will fix that.
2022-03-04 10:17:12 +01:00
Julien Fontanet
88bf60b8c2 feat(xen-api/objects): add index by type 2022-03-04 10:17:12 +01:00
Barney
08fd2beb52 fix(docs/sdn_controller.md): typo optionnal → optional 2022-03-04 09:26:39 +01:00
Julien Fontanet
a870813ac5 chore(proxy): remove build step 2022-03-03 15:38:55 +01:00
Julien Fontanet
c31f0100cb chore(proxy/backups): no license check bypass in dev mode 2022-03-03 15:23:02 +01:00
Julien Fontanet
bc7fc750f0 fix(xo-server/RestApi): don't fail on missing express
Which is the case when instanciated from `xo-server-recover-account`.
2022-03-03 14:26:26 +01:00
Julien Fontanet
9cd1784667 chore(xo-server): remove Babel plugin included in preset-env 2022-03-03 14:12:29 +01:00
Julien Fontanet
fbc7baa9a4 chore(proxy): remove unused Babel plugin 2022-03-03 14:12:29 +01:00
Julien Fontanet
a4e5cf3914 chore: remove babel plugins included in preset-env 2022-03-03 14:12:25 +01:00
Julien Fontanet
e14c177f39 chore(proxy): don't use decorator syntax 2022-03-03 13:45:26 +01:00
Julien Fontanet
b5b53c636c feat(vhd-cli info): human format UUIDs 2022-03-03 10:59:22 +01:00
Julien Fontanet
33b45d2eda feat(xo-server): better info when failing to start a VM
Fixes https://xcp-ng.org/forum/post/47329
2022-03-02 22:52:59 +01:00
Julien Fontanet
4c9bb71626 chore: missing use strict 2022-03-02 17:49:53 +01:00
Julien Fontanet
e9aa88e637 chore(scripts): fix lint errors 2022-03-02 17:48:26 +01:00
Julien Fontanet
e7c7c2ce3e fix(xo-server/pool.rollingUpdate): don't fail on missing load-balancer
Fixes zammad#5642
2022-03-02 17:35:31 +01:00
Manon Mercier
686e7e9527 feat(docs/backup_troubleshooting): Lock file is already being held (#6123)
Co-authored-by: Jon Sands <fohdeesha@gmail.com>
2022-03-01 16:33:11 +01:00
Julien Fontanet
86e390f70f feat(xo-server/vm.import): support from URL 2022-03-01 11:44:16 +01:00
Julien Fontanet
e04352a577 chore: update dev deps 2022-03-01 09:04:28 +01:00
Julien Fontanet
ee4e7620b5 feat(xo-server/sr.getAllUnhealthyVdiChainsLength): memoize 60 seconds
Introduced by 0975863d9
2022-02-28 23:15:02 +01:00
Julien Fontanet
975dc4f314 feat: release 5.68.0 2022-02-28 23:15:02 +01:00
Julien Fontanet
dfff96ace1 feat(xo-server-audit): 0.9.3 2022-02-28 17:37:20 +01:00
Julien Fontanet
f01171bb8b fix(xo-server-audit): ignore sr.getAllUnhealthyVdiChainsLength
Introduced by 0975863d9
2022-02-28 17:36:27 +01:00
Julien Fontanet
8f248f8d14 feat(CHANGELOG): integrate released changes 2022-02-28 17:24:11 +01:00
Julien Fontanet
ae815a80b1 feat(xo-web): 5.94.0 2022-02-28 17:23:43 +01:00
Julien Fontanet
925514b0a3 feat(xo-server): 5.89.0 2022-02-28 17:23:23 +01:00
Julien Fontanet
45639e5a5c feat(@xen-orchestra/proxy): 0.19.0 2022-02-28 17:22:59 +01:00
Julien Fontanet
17ab78f064 feat(@xen-orchestra/backups): 0.20.0 2022-02-28 17:22:27 +01:00
Rajaa.BARHTAOUI
c43a3343d8 fix(xo-server/sr.getAllUnhealthyVdiChainsLength): don't include empty SRs (#6126) 2022-02-28 15:07:33 +01:00
Julien Fontanet
095e40d1dd feat(xo-server/api): logs number of clients 2022-02-28 11:12:40 +01:00
Rajaa.BARHTAOUI
a400ef823f feat(delta backup/restoration): ability to choose SR for each VDI (#6117)
Fixes #4605
Fixes #4016
2022-02-28 09:29:30 +01:00
Julien Fontanet
7684fbd5ec fix(xo-server): add missing @vates/predicates
Introduced by 636025c1b
2022-02-25 14:43:15 +01:00
Julien Fontanet
cd64e8dc28 feat(CHANGELOG): integrate released changes 2022-02-25 13:43:13 +01:00
Julien Fontanet
dbba918b79 feat(xo-web): 5.93.0 2022-02-25 13:40:52 +01:00
Julien Fontanet
88dbe35c4b feat(xo-server): 5.88.0 2022-02-25 13:40:26 +01:00
Julien Fontanet
4a61fedf97 feat(xo-cli): 0.13.0 2022-02-25 13:39:49 +01:00
Julien Fontanet
110181679b feat(@xen-orchestra/proxy): 0.18.1 2022-02-25 13:38:43 +01:00
Julien Fontanet
b0a0092a73 feat(@xen-orchestra/backups): 0.19.1 2022-02-25 13:38:19 +01:00
Julien Fontanet
a6a975ce40 feat(@xen-orchestra/mixins): 0.2.0 2022-02-25 13:36:23 +01:00
Julien Fontanet
a73eb82ac6 feat(@vates/predicates): 1.0.0 2022-02-25 13:33:55 +01:00
Julien Fontanet
702cade0d7 feat(@xen-orchestra/xapi): 0.9.0 2022-02-25 13:33:12 +01:00
Julien Fontanet
367b86bc7a feat(xen-api): 0.36.0 2022-02-25 13:32:14 +01:00
Julien Fontanet
7c5e823805 chore: update deps 2022-02-25 13:18:16 +01:00
Mathieu
0975863d98 feat(xo-server/api/sr, xo-web/dashboard/health): list coalescing VDIs (#6120)
See zammad#5224
2022-02-25 10:26:47 +01:00
Rajaa.BARHTAOUI
6e6886a6ba feat(xo-web/new/sr): creating local SR requires confirmation (#6121) 2022-02-23 23:04:42 +01:00
Julien Fontanet
8bf11d6f6a chore(decorate-with): add tests 2022-02-22 15:26:10 +01:00
Julien Fontanet
6c97ccafd5 chore(xo-server-test/README): regenerate
Introduced by 2ee153509
2022-02-22 12:47:44 +01:00
Julien Fontanet
72ff66ebde chore: enforce strict mode for CJS files 2022-02-22 12:34:41 +01:00
Julien Fontanet
b3d0da7392 feat(eslint): use correct sourceType for scripts and modules 2022-02-22 12:02:57 +01:00
Julien Fontanet
a1c0d82889 fix(docs/from sources): change Node LTS version in example 2022-02-19 14:06:37 +01:00
Julien Fontanet
fbd708d1b5 chore: update dev deps 2022-02-19 14:02:32 +01:00
Julien Fontanet
710ce965a1 feat(gitignore): ignore .nyc_output 2022-02-18 17:24:26 +01:00
Julien Fontanet
2ee1535095 chore(xo-server-test): rename script teststart
By convention, `test` means running tests for the current package, but it's not the case here, `xo-server-test` is a an app (even if dedicated to test `xo-server`'s API) and should be run as such.
2022-02-18 17:20:55 +01:00
Julien Fontanet
471fbef6ef chore: hide USAGE.md
This highlight the fact that it's not designed for direct consumption and it no longer needs a special handling in `npmignore`.
2022-02-18 17:11:52 +01:00
Julien Fontanet
ab63709104 chore(predicates): add tests 2022-02-18 17:03:21 +01:00
Julien Fontanet
0924184358 fix(predicates): use strict mode 2022-02-18 17:03:21 +01:00
Julien Fontanet
6e38cf2bc1 fix(predicates): typo 2022-02-18 17:03:21 +01:00
Pierre Donias
2183422eb5 docs(users/auth-google): update doc with new screens (#6118)
See zammad#5306
2022-02-18 15:49:33 +01:00
Mathieu
3842f5b16d fix(Backup/FileRestore): hide unsupported S3 backups (#6116)
Fixes https://xcp-ng.org/forum/post/46166
2022-02-18 11:07:30 +01:00
Julien Fontanet
1dad6b37ef feat(xo-server): pass backup network address to proxy
Fixes zammad#4836
2022-02-16 18:38:48 +01:00
Julien Fontanet
7023db2264 chore: update dev deps 2022-02-16 14:55:16 +01:00
Julien Fontanet
bf194f5c85 feat(xo-server): add supportFileRestore prop to remote objects
Related to #6116
2022-02-14 14:23:54 +01:00
Julien Fontanet
bcdaa37f8a feat(backups/VmBackup#_callWriters): integrate sub-errors in logs
Fixes zammad#5264
2022-02-14 11:22:32 +01:00
Julien Fontanet
a5f8900d23 chore(CHANGELOG.unreleased): add changes for 3c684c7f4 2022-02-14 11:09:56 +01:00
Julien Fontanet
3c684c7f45 fix(xo-server): use pool.uuid/template.uuid for default templates instead of refs
Fixes https://help.vates.fr/#ticket/zoom/5221

Opaque refs are not persistent and should not be used as ids (e.g. resource sets in database).
2022-02-14 10:15:30 +01:00
Julien Fontanet
cd0f4baa15 fix(xo-server/docs): fix REST API path 2022-02-12 11:33:02 +01:00
Julien Fontanet
58f3050344 feat(xo-server/rest-api): add root collection 2022-02-12 11:32:38 +01:00
Julien Fontanet
ab110bb0df feat(xo-server/rest-api): add hosts, pools & SRs 2022-02-10 17:09:42 +01:00
Julien Fontanet
636025c1b9 chore(xo-server/rest-api): use @vates/predicates 2022-02-10 17:05:55 +01:00
Julien Fontanet
c28fa78963 feat(@vates/predicates): utils to compose predicates 2022-02-10 16:55:15 +01:00
Julien Fontanet
ecb66fb9f3 feat(xo-server): first endpoints of the beta REST API 2022-02-09 23:43:21 +01:00
Julien Fontanet
f8e4192d34 feat(xo-cli): --createToken command 2022-02-09 17:13:18 +01:00
Julien Fontanet
3d15cd57c4 chore: remove pump dep 2022-02-09 14:52:37 +01:00
Julien Fontanet
2e633f0df4 fix(xo-cli): support allowUnauthorized for $sendTo/$getFrom 2022-02-09 14:49:25 +01:00
Julien Fontanet
833b814fd5 fix(xo-cli): dont use deprecated http.resolve 2022-02-09 14:40:47 +01:00
Julien Fontanet
da52d905b6 chore(xo-cli): remove bluebird dep 2022-02-09 14:39:54 +01:00
Julien Fontanet
a2054c24d8 chore(xo-cli): remove mkdirp dep 2022-02-09 14:13:51 +01:00
Julien Fontanet
599db80467 chore: remove l33teral dep 2022-02-09 14:08:24 +01:00
Julien Fontanet
6075a3307e feat(xo-server): announce appliance in pool.other_config (#6115)
It will be used by XO Lite to list available XOs on the pool.
2022-02-09 11:29:01 +01:00
Julien Fontanet
9af06996c0 fix(backups/merge-worker): handle ENAMETOOLONG errors
Related to https://xcp-ng.org/forum/post/46424
2022-02-09 10:18:05 +01:00
Julien Fontanet
72c386cdce fix(backups/merge-worker): ignore missing VM dir
Fixes https://xcp-ng.org/forum/post/46578
2022-02-09 10:11:07 +01:00
Julien Fontanet
f6d4771603 feat(xen-api/barrier): remove stale entries 2022-02-07 16:21:02 +01:00
Julien Fontanet
15d06c591e chore: remove unnecessary strip-indent dep 2022-02-02 12:36:28 +01:00
Julien Fontanet
0a07765027 chore: update deps 2022-02-02 12:35:06 +01:00
Julien Fontanet
81e0d06604 chore: update to eslint-config-standard@-jsx@11.0.0-0
Follow up of 767762064
2022-02-01 10:03:15 +01:00
Julien Fontanet
809314e516 feat: release 5.67.0 2022-01-31 13:41:24 +01:00
Julien Fontanet
cbe37b2ccd feat(xo-server-audit): 0.9.2 2022-01-28 18:19:56 +01:00
Julien Fontanet
14fcbb6b8b chore: format with Prettier 2022-01-28 15:55:35 +01:00
Julien Fontanet
bac976144e chore: lint fixes 2022-01-28 15:55:35 +01:00
Julien Fontanet
7677620645 chore: update to ESLint@8
Brings the (private) class fields support, which is getting more and more used in XO.
2022-01-28 15:55:35 +01:00
Mathieu
c8597bd363 fix(xo-server-audit/getRecords): limit number of records returned (#6113) 2022-01-28 15:46:56 +01:00
Julien Fontanet
899be12418 feat(CHANGELOG): integrate released changes 2022-01-27 14:44:54 +01:00
Julien Fontanet
31975b6737 feat(xo-web): 5.92.0 2022-01-27 14:27:48 +01:00
Julien Fontanet
315549e588 feat(xo-server): 5.87.0 2022-01-27 14:25:20 +01:00
Julien Fontanet
457886d538 feat(xo-server-audit): 0.9.1 2022-01-27 14:25:00 +01:00
Julien Fontanet
8d02b206e0 feat(@xen-orchestra/proxy): 0.18.0 2022-01-27 14:24:23 +01:00
Julien Fontanet
45e5b94b4f feat(xo-vmdk-to-vhd): 2.1.0 2022-01-27 14:23:53 +01:00
Julien Fontanet
ec866b4a61 fix(xo-server-audit): dont log proxy.getApplianceUpdaterState calls
Fix zammad#4753
2022-01-27 14:17:36 +01:00
Mathieu
2e8e2522e5 feat(xo-web/dashboard/health): show pools with no default SR (#6083)
See zammad#4640
2022-01-26 11:20:56 +01:00
Julien Fontanet
96bd46c3a8 fix(xo-web): fix number of VMs when running a backup
Following aa27b3be6
2022-01-25 15:30:58 +01:00
Julien Fontanet
797ed9ac51 fix(xo-server/backups): fix VMs matching
Introduced by aa27b3be6

Fixes https://xcp-ng.org/forum/topic/5473/backup-broken
2022-01-25 15:29:06 +01:00
Julien Fontanet
7089dcddff chore: update dev deps 2022-01-25 09:38:00 +01:00
Julien Fontanet
8ee93dfe9c feat(@xen-orchestra/backups-cli): 0.7.0 2022-01-24 22:35:08 +01:00
Julien Fontanet
88f160820f feat(@xen-orchestra/backups): 0.19.0 2022-01-24 22:34:02 +01:00
Julien Fontanet
b61b3e75c1 feat(vhd-lib): 3.1.0 2022-01-24 22:33:16 +01:00
Julien Fontanet
a1bac51f7b feat(@xen-orchestra/fs): 0.20.0 2022-01-24 22:31:39 +01:00
Julien Fontanet
fa986fb4cb fix(proxy/ReverseProxy): dont 404 non proxy URLs
Introduced by b78a94645
2022-01-24 22:21:18 +01:00
Julien Fontanet
aa27b3be64 fix(backups): dont ignore restored VMs
Fixes zammad#4794

Introduced by cf9f0da6e

Check that the `start` operation is blocked which is the case for replicated VMs but (should) not for restored backups.
2022-01-24 17:11:48 +01:00
Florent Beauchamp
97d94b7952 feat(vhd-lib): merge blocks in parallel for VhdDirectory, throttle mergestate writes 2022-01-24 10:37:34 +01:00
Florent Beauchamp
96eb793298 feat(fs): s3#rmtree parallelize object deletion 2022-01-24 10:37:34 +01:00
Florent Beauchamp
b4f15de7be feat(fs): s3#copy don't use multi part copy for objects smaller than 5GB 2022-01-24 10:37:34 +01:00
Mathieu
ae5726b836 fix(xo-server-audit/generateFingerprint): handle the case when db is empty (#6071)
Fixes #6040
2022-01-21 12:47:56 +01:00
Florent BEAUCHAMP
692e72a78a fix(vhd-lib): fixes asserts on existing merge state (#6099)
Introduced by 5a933bad9
2022-01-21 12:40:45 +01:00
Pierre Donias
ff24364bb6 fix(CHANGELOG): fix and homogenize changelog (#6102) 2022-01-20 15:40:49 +01:00
Florent BEAUCHAMP
b60a1958b6 fix(fs#S3#{list,rmtree}): fix support of more than 1000 files (#6103)
Bug found when working on #6100
2022-01-20 14:31:13 +01:00
Florent Beauchamp
f6a2b505db feat(backups): execute cleanup on each related vm dir after a backup deletion 2022-01-19 10:46:15 +01:00
Florent Beauchamp
38aacdbd7d feat(xo-web): delete all the backups at once and let xo-server handle the cleanup 2022-01-19 10:46:15 +01:00
Florent Beauchamp
089b877cc5 fix(backups): use handler.unlink for deleting metadat instead of VhdAbstract.unlink 2022-01-19 10:46:15 +01:00
Julien Fontanet
81e55dcf77 fix(backups/listAllVmBackups): ignore empty dirs 2022-01-19 10:43:00 +01:00
Julien Fontanet
58dd44bf5d fix(backups/listAllVmBackups): ignore hidden and lock files 2022-01-19 10:41:20 +01:00
Julien Fontanet
3aa6669fd9 chore(vhd-lib): move alias utils to aliases submodule
Introduced in e15be7ebd
2022-01-18 15:33:31 +01:00
Florent BEAUCHAMP
c10601d905 fix(backups/_MixinBackupWriter#afterBackup): execute merge on S3 (#6095)
Introduced by 47f9da216
2022-01-18 11:05:55 +01:00
Florent BEAUCHAMP
e15be7ebd3 fix(backups/_cleanVm): fixes for aliases cleaning (#6094)
Introduced in 249f638495
2022-01-18 10:07:56 +01:00
Julien Fontanet
b465a91cd3 fix(value-matcher/README): __all → __and 2022-01-18 08:58:24 +01:00
Julien Fontanet
f304a46bea fix(vhd-lib/VhdDirectory): missing readChunkFilters in readHeaderAndFooter
Introduced by 249f63849
2022-01-17 10:18:54 +01:00
Pierre Donias
6756faa1cc feat(xo-server,xo-web): disable Load Balancer during Rolling Pool Update (#6089)
Fixes #5711
Follow-up of #6057
2022-01-17 10:08:32 +01:00
Julien Fontanet
73fd7c7d54 fix(backups/_cleanVm): temporary disable aliases checking 2022-01-17 09:52:42 +01:00
Julien Fontanet
60eda9ec69 chore(vhd-lib): remove contentVhdStreamWithLength export from main module 2022-01-16 22:44:41 +01:00
Julien Fontanet
a979c29a15 chore(vhd-lib): remove createReadableRawStream
Use `VhdAbstract#rawContent()` instead.
2022-01-16 22:34:04 +01:00
Julien Fontanet
8f25082917 fix(xo-vmdk-to-vhd): avoid requiring whole vhd-lib
Introduced by 9375b1c8b

Fixes #6093
2022-01-16 22:31:38 +01:00
Nicolas Raynaud
9375b1c8bd feat: support VDI export in VMDK (#5982)
Co-authored-by: Rajaa.BARHTAOUI <rajaa.barhtaoui@gmail.com>
Co-authored-by: Julien Fontanet <julien.fontanet@isonoe.net>
Co-authored-by: Florent BEAUCHAMP <flo850@free.fr>
2022-01-16 18:40:08 +01:00
Julien Fontanet
422a22a767 chore: update dev deps 2022-01-14 14:51:38 +01:00
Florent BEAUCHAMP
249f638495 feat(backups/_cleanVm): check VHD aliases (#6043) 2022-01-13 16:07:28 +01:00
Florent BEAUCHAMP
6cf5e10195 feat(fs/S3#_writeFile): retry on remote internal error (#6086) 2022-01-13 15:46:12 +01:00
666 changed files with 26125 additions and 13229 deletions

View File

@@ -1,5 +1,7 @@
'use strict'
module.exports = {
extends: ['plugin:eslint-comments/recommended', 'standard', 'standard-jsx', 'prettier'],
extends: ['plugin:eslint-comments/recommended', 'plugin:n/recommended', 'standard', 'standard-jsx', 'prettier'],
globals: {
__DEV__: true,
$Dict: true,
@@ -15,11 +17,40 @@ module.exports = {
{
files: ['cli.{,c,m}js', '*-cli.{,c,m}js', '**/*cli*/**/*.{,c,m}js'],
rules: {
'n/no-process-exit': 'off',
'no-console': 'off',
},
},
{
files: ['*.mjs'],
parserOptions: {
sourceType: 'module',
},
},
{
files: ['*.spec.{,c,m}js'],
rules: {
'n/no-unsupported-features/node-builtins': [
'error',
{
version: '>=16',
},
],
'n/no-unsupported-features/es-syntax': [
'error',
{
version: '>=16',
},
],
},
},
],
parserOptions: {
ecmaVersion: 13,
sourceType: 'script',
},
rules: {
// disabled because XAPI objects are using camel case
camelcase: ['off'],
@@ -34,5 +65,7 @@ module.exports = {
'lines-between-class-members': 'off',
'no-console': ['error', { allow: ['warn', 'error'] }],
strict: 'error',
},
}

View File

@@ -1,16 +0,0 @@
[ignore]
<PROJECT_ROOT>/node_modules/.*
[include]
[libs]
[lints]
[options]
esproposal.decorators=ignore
esproposal.optional_chaining=enable
include_warnings=true
module.use_strict=true
[strict]

View File

@@ -6,6 +6,18 @@ labels: 'status: triaging :triangular_flag_on_post:, type: bug :bug:'
assignees: ''
---
**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:
- 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.
@@ -23,7 +35,7 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
**Environment (please provide the following information):**
- Node: [e.g. 16.12.1]
- xo-server: [e.g. 5.82.3]

View File

@@ -4,7 +4,6 @@ about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**

13
.github/workflows/push.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: CI
on: [push]
jobs:
build:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: satackey/action-docker-layer-caching@v0.0.11
# Ignore the failure of a step and avoid terminating the job.
continue-on-error: true
- run: docker-compose -f docker/docker-compose.dev.yml build
- run: docker-compose -f docker/docker-compose.dev.yml up

9
.gitignore vendored
View File

@@ -1,5 +1,4 @@
/_book/
/coverage/
/node_modules/
/lerna-debug.log
/lerna-debug.log.*
@@ -11,10 +10,6 @@
/packages/*/dist/
/packages/*/node_modules/
/@xen-orchestra/proxy/src/app/mixins/index.mjs
/packages/vhd-cli/src/commands/index.js
/packages/xen-api/examples/node_modules/
/packages/xen-api/plot.dat
@@ -35,3 +30,7 @@ pnpm-debug.log.*
yarn-error.log
yarn-error.log.*
.env
# code coverage
.nyc_output/
coverage/

View File

@@ -1,23 +0,0 @@
language: node_js
node_js:
- 14
# Use containers.
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
sudo: false
addons:
apt:
packages:
- qemu-utils
- blktap-utils
- vmdk-stream-converter
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash
- export PATH="$HOME/.yarn/bin:$PATH"
cache:
yarn: true
script:
- yarn run travis-tests

View File

@@ -14,7 +14,7 @@ Returns a promise wich rejects as soon as a call to `iteratee` throws or a promi
`opts` is an object that can contains the following options:
- `concurrency`: a number which indicates the maximum number of parallel call to `iteratee`, defaults to `1`
- `concurrency`: a number which indicates the maximum number of parallel call to `iteratee`, defaults to `10`. The value `0` means no concurrency limit.
- `signal`: an abort signal to stop the iteration
- `stopOnError`: wether to stop iteration of first error, or wait for all calls to finish and throw an `AggregateError`, defaults to `true`

View File

@@ -32,7 +32,7 @@ Returns a promise wich rejects as soon as a call to `iteratee` throws or a promi
`opts` is an object that can contains the following options:
- `concurrency`: a number which indicates the maximum number of parallel call to `iteratee`, defaults to `1`
- `concurrency`: a number which indicates the maximum number of parallel call to `iteratee`, defaults to `10`. The value `0` means no concurrency limit.
- `signal`: an abort signal to stop the iteration
- `stopOnError`: wether to stop iteration of first error, or wait for all calls to finish and throw an `AggregateError`, defaults to `true`

View File

@@ -9,7 +9,16 @@ class AggregateError extends Error {
}
}
exports.asyncEach = function asyncEach(iterable, iteratee, { concurrency = 1, signal, stopOnError = true } = {}) {
/**
* @template Item
* @param {Iterable<Item>} iterable
* @param {(item: Item, index: number, iterable: Iterable<Item>) => Promise<void>} iteratee
* @returns {Promise<void>}
*/
exports.asyncEach = function asyncEach(iterable, iteratee, { concurrency = 10, signal, stopOnError = true } = {}) {
if (concurrency === 0) {
concurrency = Infinity
}
return new Promise((resolve, reject) => {
const it = (iterable[Symbol.iterator] || iterable[Symbol.asyncIterator]).call(iterable)
const errors = []

View File

@@ -36,7 +36,7 @@ describe('asyncEach', () => {
it('works', async () => {
const iteratee = jest.fn(async () => {})
await asyncEach.call(thisArg, iterable, iteratee)
await asyncEach.call(thisArg, iterable, iteratee, { concurrency: 1 })
expect(iteratee.mock.instances).toEqual(Array.from(values, () => thisArg))
expect(iteratee.mock.calls).toEqual(Array.from(values, (value, index) => [value, index, iterable]))
@@ -66,7 +66,7 @@ describe('asyncEach', () => {
}
})
expect(await rejectionOf(asyncEach(iterable, iteratee, { stopOnError: true }))).toBe(error)
expect(await rejectionOf(asyncEach(iterable, iteratee, { concurrency: 1, stopOnError: true }))).toBe(error)
expect(iteratee).toHaveBeenCalledTimes(2)
})
@@ -91,7 +91,9 @@ describe('asyncEach', () => {
}
})
await expect(asyncEach(iterable, iteratee, { signal: ac.signal })).rejects.toThrow('asyncEach aborted')
await expect(asyncEach(iterable, iteratee, { concurrency: 1, signal: ac.signal })).rejects.toThrow(
'asyncEach aborted'
)
expect(iteratee).toHaveBeenCalledTimes(2)
})
})

View File

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

View File

@@ -0,0 +1,30 @@
Node does not cache queries to `dns.lookup`, which can lead application doing a lot of connections to have perf issues and to saturate Node threads pool.
This library attempts to mitigate these problems by providing a version of this function with a version short cache, applied on both errors and results.
> Limitation: `verbatim: false` option is not supported.
It has exactly the same API as the native method and can be used directly:
```js
import { createCachedLookup } from '@vates/cached-dns.lookup'
const lookup = createCachedLookup()
lookup('example.net', { all: true, family: 0 }, (error, result) => {
if (error != null) {
return console.warn(error)
}
console.log(result)
})
```
Or it can be used to replace the native implementation and speed up the whole app:
```js
// assign our cached implementation to dns.lookup
const restore = createCachedLookup().patchGlobal()
// to restore the previous implementation
restore()
```

View File

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

View File

@@ -0,0 +1,63 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/cached-dns.lookup
[![Package Version](https://badgen.net/npm/v/@vates/cached-dns.lookup)](https://npmjs.org/package/@vates/cached-dns.lookup) ![License](https://badgen.net/npm/license/@vates/cached-dns.lookup) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/cached-dns.lookup)](https://bundlephobia.com/result?p=@vates/cached-dns.lookup) [![Node compatibility](https://badgen.net/npm/node/@vates/cached-dns.lookup)](https://npmjs.org/package/@vates/cached-dns.lookup)
> Cached implementation of dns.lookup
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/cached-dns.lookup):
```
> npm install --save @vates/cached-dns.lookup
```
## Usage
Node does not cache queries to `dns.lookup`, which can lead application doing a lot of connections to have perf issues and to saturate Node threads pool.
This library attempts to mitigate these problems by providing a version of this function with a version short cache, applied on both errors and results.
> Limitation: `verbatim: false` option is not supported.
It has exactly the same API as the native method and can be used directly:
```js
import { createCachedLookup } from '@vates/cached-dns.lookup'
const lookup = createCachedLookup()
lookup('example.net', { all: true, family: 0 }, (error, result) => {
if (error != null) {
return console.warn(error)
}
console.log(result)
})
```
Or it can be used to replace the native implementation and speed up the whole app:
```js
// assign our cached implementation to dns.lookup
const restore = createCachedLookup().patchGlobal()
// to restore the previous implementation
restore()
```
## 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

@@ -0,0 +1,72 @@
'use strict'
const assert = require('assert')
const dns = require('dns')
const LRU = require('lru-cache')
function reportResults(all, results, callback) {
if (all) {
callback(null, results)
} else {
const first = results[0]
callback(null, first.address, first.family)
}
}
exports.createCachedLookup = function createCachedLookup({ lookup = dns.lookup } = {}) {
const cache = new LRU({
max: 500,
// 1 minute: long enough to be effective, short enough so there is no need to bother with DNS TTLs
ttl: 60e3,
})
function cachedLookup(hostname, options, callback) {
let all = false
let family = 0
if (typeof options === 'function') {
callback = options
} else if (typeof options === 'number') {
family = options
} else if (options != null) {
assert.notStrictEqual(options.verbatim, false, 'not supported by this implementation')
;({ all = all, family = family } = options)
}
// cache by family option because there will be an error if there is no
// entries for the requestion family so we cannot easily cache all families
// and filter on reporting back
const key = hostname + '/' + family
const result = cache.get(key)
if (result !== undefined) {
setImmediate(reportResults, all, result, callback)
} else {
lookup(hostname, { all: true, family, verbatim: true }, function onLookup(error, results) {
// errors are not cached because this will delay recovery after DNS/network issues
//
// there are no reliable way to detect if the error is real or simply
// that there are no results for the requested hostname
//
// there should be much fewer errors than success, therefore it should
// not be a big deal to not cache them
if (error != null) {
return callback(error)
}
cache.set(key, results)
reportResults(all, results, callback)
})
}
}
cachedLookup.patchGlobal = function patchGlobal() {
const previous = dns.lookup
dns.lookup = cachedLookup
return function restoreGlobal() {
assert.strictEqual(dns.lookup, cachedLookup)
dns.lookup = previous
}
}
return cachedLookup
}

View File

@@ -0,0 +1,32 @@
{
"engines": {
"node": ">=8"
},
"dependencies": {
"lru-cache": "^7.0.4"
},
"private": false,
"name": "@vates/cached-dns.lookup",
"description": "Cached implementation of dns.lookup",
"keywords": [
"cache",
"dns",
"lookup"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/cached-dns.lookup",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/cached-dns.lookup",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "ISC",
"version": "1.0.0",
"scripts": {
"postversion": "npm publish --access public"
}
}

View File

@@ -1,3 +1,5 @@
'use strict'
exports.coalesceCalls = function (fn) {
let promise
const clean = () => {

View File

@@ -1,3 +1,5 @@
'use strict'
/* eslint-env jest */
const { coalesceCalls } = require('./')

View File

@@ -1,3 +1,5 @@
'use strict'
/* eslint-env jest */
const { compose } = require('./')

View File

@@ -13,15 +13,19 @@ class Foo {
}
```
### `decorateMethodsWith(class, map)`
### `decorateClass(class, map)`
Decorates a number of methods directly, without using the decorator syntax:
Decorates a number of accessors and methods directly, without using the decorator syntax:
```js
import { decorateMethodsWith } from '@vates/decorate-with'
import { decorateClass } from '@vates/decorate-with'
class Foo {
bar() {
get bar() {
// body
}
set bar(value) {
// body
}
@@ -30,22 +34,28 @@ class Foo {
}
}
decorateMethodsWith(Foo, {
// without arguments
bar: lodash.curry,
decorateClass(Foo, {
// getter and/or setter
bar: {
// without arguments
get: lodash.memoize,
// with arguments
baz: [lodash.debounce, 150],
// with arguments
set: [lodash.debounce, 150],
},
// method (with or without arguments)
baz: lodash.curry,
})
```
The decorated class is returned, so you can export it directly.
To apply multiple transforms to a method, you can either call `decorateMethodsWith` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
To apply multiple transforms to an accessor/method, you can either call `decorateClass` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
```js
decorateMethodsWith(Foo, {
bar: compose([
decorateClass(Foo, {
baz: compose([
[lodash.debounce, 150]
lodash.curry,
])
@@ -69,4 +79,8 @@ class Foo {
}
```
Because it's a normal function, it can also be used with `decorateMethodsWith`, with `compose` or even by itself.
Because it's a normal function, it can also be used with `decorateClass`, with `compose` or even by itself.
### `decorateMethodsWith(class, map)`
> Deprecated alias for [`decorateClass(class, map)`](#decorateclassclass-map).

View File

@@ -31,15 +31,19 @@ class Foo {
}
```
### `decorateMethodsWith(class, map)`
### `decorateClass(class, map)`
Decorates a number of methods directly, without using the decorator syntax:
Decorates a number of accessors and methods directly, without using the decorator syntax:
```js
import { decorateMethodsWith } from '@vates/decorate-with'
import { decorateClass } from '@vates/decorate-with'
class Foo {
bar() {
get bar() {
// body
}
set bar(value) {
// body
}
@@ -48,22 +52,28 @@ class Foo {
}
}
decorateMethodsWith(Foo, {
// without arguments
bar: lodash.curry,
decorateClass(Foo, {
// getter and/or setter
bar: {
// without arguments
get: lodash.memoize,
// with arguments
baz: [lodash.debounce, 150],
// with arguments
set: [lodash.debounce, 150],
},
// method (with or without arguments)
baz: lodash.curry,
})
```
The decorated class is returned, so you can export it directly.
To apply multiple transforms to a method, you can either call `decorateMethodsWith` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
To apply multiple transforms to an accessor/method, you can either call `decorateClass` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
```js
decorateMethodsWith(Foo, {
bar: compose([
decorateClass(Foo, {
baz: compose([
[lodash.debounce, 150]
lodash.curry,
])
@@ -87,7 +97,11 @@ class Foo {
}
```
Because it's a normal function, it can also be used with `decorateMethodsWith`, with `compose` or even by itself.
Because it's a normal function, it can also be used with `decorateClass`, with `compose` or even by itself.
### `decorateMethodsWith(class, map)`
> Deprecated alias for [`decorateClass(class, map)`](#decorateclassclass-map).
## Contributions

View File

@@ -1,3 +1,5 @@
'use strict'
exports.decorateWith = function decorateWith(fn, ...args) {
return (target, name, descriptor) => ({
...descriptor,
@@ -7,14 +9,27 @@ exports.decorateWith = function decorateWith(fn, ...args) {
const { getOwnPropertyDescriptor, defineProperty } = Object
exports.decorateMethodsWith = function decorateMethodsWith(klass, map) {
function applyDecorator(decorator, value) {
return typeof decorator === 'function' ? decorator(value) : decorator[0](value, ...decorator.slice(1))
}
exports.decorateClass = exports.decorateMethodsWith = function decorateClass(klass, map) {
const { prototype } = klass
for (const name of Object.keys(map)) {
const descriptor = getOwnPropertyDescriptor(prototype, name)
const { value } = descriptor
const decorator = map[name]
descriptor.value = typeof decorator === 'function' ? decorator(value) : decorator[0](value, ...decorator.slice(1))
const descriptor = getOwnPropertyDescriptor(prototype, name)
if (typeof decorator === 'function' || Array.isArray(decorator)) {
descriptor.value = applyDecorator(decorator, descriptor.value)
} else {
const { get, set } = decorator
if (get !== undefined) {
descriptor.get = applyDecorator(get, descriptor.get)
}
if (set !== undefined) {
descriptor.set = applyDecorator(set, descriptor.set)
}
}
defineProperty(prototype, name, descriptor)
}
return klass

View File

@@ -0,0 +1,152 @@
'use strict'
const assert = require('assert')
const { describe, it } = require('tap').mocha
const { decorateClass, decorateWith, decorateMethodsWith, perInstance } = require('./')
const identity = _ => _
describe('decorateWith', () => {
it('works', () => {
const expectedArgs = [Math.random(), Math.random()]
const expectedFn = Function.prototype
const newFn = () => {}
const decorator = decorateWith(function wrapper(fn, ...args) {
assert.deepStrictEqual(fn, expectedFn)
assert.deepStrictEqual(args, expectedArgs)
return newFn
}, ...expectedArgs)
const descriptor = {
configurable: true,
enumerable: false,
value: expectedFn,
writable: true,
}
assert.deepStrictEqual(decorator({}, 'foo', descriptor), {
...descriptor,
value: newFn,
})
})
})
describe('decorateClass', () => {
it('works', () => {
class C {
foo() {}
bar() {}
get baz() {}
// eslint-disable-next-line accessor-pairs
set qux(_) {}
}
const expectedArgs = [Math.random(), Math.random()]
const P = C.prototype
const descriptors = Object.getOwnPropertyDescriptors(P)
const newFoo = () => {}
const newBar = () => {}
const newGetBaz = () => {}
const newSetQux = _ => {}
decorateClass(C, {
foo(fn) {
assert.strictEqual(arguments.length, 1)
assert.strictEqual(fn, P.foo)
return newFoo
},
bar: [
function (fn, ...args) {
assert.strictEqual(fn, P.bar)
assert.deepStrictEqual(args, expectedArgs)
return newBar
},
...expectedArgs,
],
baz: {
get(fn) {
assert.strictEqual(arguments.length, 1)
assert.strictEqual(fn, descriptors.baz.get)
return newGetBaz
},
},
qux: {
set: [
function (fn, ...args) {
assert.strictEqual(fn, descriptors.qux.set)
assert.deepStrictEqual(args, expectedArgs)
return newSetQux
},
...expectedArgs,
],
},
})
const newDescriptors = Object.getOwnPropertyDescriptors(P)
assert.deepStrictEqual(newDescriptors.foo, { ...descriptors.foo, value: newFoo })
assert.deepStrictEqual(newDescriptors.bar, { ...descriptors.bar, value: newBar })
assert.deepStrictEqual(newDescriptors.baz, { ...descriptors.baz, get: newGetBaz })
assert.deepStrictEqual(newDescriptors.qux, { ...descriptors.qux, set: newSetQux })
})
it('throws if using an accessor decorator for a method', function () {
assert.throws(() =>
decorateClass(
class {
foo() {}
},
{ foo: { get: identity, set: identity } }
)
)
})
it('throws if using a method decorator for an accessor', function () {
assert.throws(() =>
decorateClass(
class {
get foo() {}
},
{ foo: identity }
)
)
})
})
it('decorateMethodsWith is an alias of decorateClass', function () {
assert.strictEqual(decorateMethodsWith, decorateClass)
})
describe('perInstance', () => {
it('works', () => {
let calls = 0
const expectedArgs = [Math.random(), Math.random()]
const expectedFn = Function.prototype
function wrapper(fn, ...args) {
assert.strictEqual(fn, expectedFn)
assert.deepStrictEqual(args, expectedArgs)
const i = ++calls
return () => i
}
const wrapped = perInstance(expectedFn, wrapper, ...expectedArgs)
// decorator is not called before decorated called
assert.strictEqual(calls, 0)
const o1 = {}
const o2 = {}
assert.strictEqual(wrapped.call(o1), 1)
// the same decorated function is returned for the same instance
assert.strictEqual(wrapped.call(o1), 1)
// a new decorated function is returned for another instance
assert.strictEqual(wrapped.call(o2), 2)
})
})

View File

@@ -20,11 +20,15 @@
"url": "https://vates.fr"
},
"license": "ISC",
"version": "1.0.0",
"version": "2.0.0",
"engines": {
"node": ">=8.10"
},
"scripts": {
"postversion": "npm publish --access public"
"postversion": "npm publish --access public",
"test": "tap"
},
"devDependencies": {
"tap": "^16.0.1"
}
}

View File

@@ -1,3 +1,5 @@
'use strict'
const { asyncMap } = require('@xen-orchestra/async-map')
const { createLogger } = require('@xen-orchestra/log')

View File

@@ -1,3 +1,5 @@
'use strict'
/* eslint-env jest */
const { createDebounceResource } = require('./debounceResource')

View File

@@ -1,3 +1,5 @@
'use strict'
const ensureArray = require('ensure-array')
const { MultiKeyMap } = require('@vates/multi-key-map')

View File

@@ -1,3 +1,5 @@
'use strict'
/* eslint-env jest */
const { deduped } = require('./deduped')

View File

@@ -0,0 +1,50 @@
> This library is compatible with Node's `EventEmitter` and web browsers' `EventTarget` APIs.
### API
```js
import { EventListenersManager } from '@vates/event-listeners-manager'
const events = new EventListenersManager(emitter)
// adding listeners
events.add('foo', onFoo).add('bar', onBar).on('baz', onBaz)
// removing a specific listener
events.remove('foo', onFoo)
// removing all listeners for a specific event
events.removeAll('foo')
// removing all listeners
events.removeAll()
```
### Typical use case
> Removing all listeners when no longer necessary.
Manually:
```js
const onFoo = () => {}
const onBar = () => {}
const onBaz = () => {}
emitter.on('foo', onFoo).on('bar', onBar).on('baz', onBaz)
// CODE LOGIC
emitter.off('foo', onFoo).off('bar', onBar).off('baz', onBaz)
```
With this library:
```js
const events = new EventListenersManager(emitter)
events.add('foo', () => {})).add('bar', () => {})).add('baz', () => {}))
// CODE LOGIC
events.removeAll()
```

View File

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

View File

@@ -0,0 +1,81 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/event-listeners-manager
[![Package Version](https://badgen.net/npm/v/@vates/event-listeners-manager)](https://npmjs.org/package/@vates/event-listeners-manager) ![License](https://badgen.net/npm/license/@vates/event-listeners-manager) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/event-listeners-manager)](https://bundlephobia.com/result?p=@vates/event-listeners-manager) [![Node compatibility](https://badgen.net/npm/node/@vates/event-listeners-manager)](https://npmjs.org/package/@vates/event-listeners-manager)
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/event-listeners-manager):
```
> npm install --save @vates/event-listeners-manager
```
## Usage
> This library is compatible with Node's `EventEmitter` and web browsers' `EventTarget` APIs.
### API
```js
import { EventListenersManager } from '@vates/event-listeners-manager'
const events = new EventListenersManager(emitter)
// adding listeners
events.add('foo', onFoo).add('bar', onBar).on('baz', onBaz)
// removing a specific listener
events.remove('foo', onFoo)
// removing all listeners for a specific event
events.removeAll('foo')
// removing all listeners
events.removeAll()
```
### Typical use case
> Removing all listeners when no longer necessary.
Manually:
```js
const onFoo = () => {}
const onBar = () => {}
const onBaz = () => {}
emitter.on('foo', onFoo).on('bar', onBar).on('baz', onBaz)
// CODE LOGIC
emitter.off('foo', onFoo).off('bar', onBar).off('baz', onBaz)
```
With this library:
```js
const events = new EventListenersManager(emitter)
events.add('foo', () => {})).add('bar', () => {})).add('baz', () => {}))
// CODE LOGIC
events.removeAll()
```
## 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

@@ -0,0 +1,56 @@
'use strict'
exports.EventListenersManager = class EventListenersManager {
constructor(emitter) {
this._listeners = new Map()
this._add = (emitter.addListener || emitter.addEventListener).bind(emitter)
this._remove = (emitter.removeListener || emitter.removeEventListener).bind(emitter)
}
add(type, listener) {
let listeners = this._listeners.get(type)
if (listeners === undefined) {
listeners = new Set()
this._listeners.set(type, listeners)
}
// don't add the same listener multiple times (allowed on Node.js)
if (!listeners.has(listener)) {
listeners.add(listener)
this._add(type, listener)
}
return this
}
remove(type, listener) {
const allListeners = this._listeners
const listeners = allListeners.get(type)
if (listeners !== undefined && listeners.delete(listener)) {
this._remove(type, listener)
if (listeners.size === 0) {
allListeners.delete(type)
}
}
return this
}
removeAll(type) {
const allListeners = this._listeners
const remove = this._remove
const types = type !== undefined ? [type] : allListeners.keys()
for (const type of types) {
const listeners = allListeners.get(type)
if (listeners !== undefined) {
allListeners.delete(type)
for (const listener of listeners) {
remove(type, listener)
}
}
}
return this
}
}

View File

@@ -0,0 +1,67 @@
'use strict'
const t = require('tap')
const { EventEmitter } = require('events')
const { EventListenersManager } = require('./')
const noop = Function.prototype
// function spy (impl = Function.prototype) {
// function spy() {
// spy.calls.push([Array.from(arguments), this])
// }
// spy.calls = []
// return spy
// }
function assertListeners(t, event, listeners) {
t.strictSame(t.context.ee.listeners(event), listeners)
}
t.beforeEach(function (t) {
t.context.ee = new EventEmitter()
t.context.em = new EventListenersManager(t.context.ee)
})
t.test('.add adds a listener', function (t) {
t.context.em.add('foo', noop)
assertListeners(t, 'foo', [noop])
t.end()
})
t.test('.add does not add a duplicate listener', function (t) {
t.context.em.add('foo', noop).add('foo', noop)
assertListeners(t, 'foo', [noop])
t.end()
})
t.test('.remove removes a listener', function (t) {
t.context.em.add('foo', noop).remove('foo', noop)
assertListeners(t, 'foo', [])
t.end()
})
t.test('.removeAll removes all listeners of a given type', function (t) {
t.context.em.add('foo', noop).add('bar', noop).removeAll('foo')
assertListeners(t, 'foo', [])
assertListeners(t, 'bar', [noop])
t.end()
})
t.test('.removeAll removes all listeners', function (t) {
t.context.em.add('foo', noop).add('bar', noop).removeAll()
assertListeners(t, 'foo', [])
assertListeners(t, 'bar', [])
t.end()
})

View File

@@ -0,0 +1,46 @@
{
"engines": {
"node": ">=6"
},
"private": false,
"name": "@vates/event-listeners-manager",
"descriptions": "Easy way to clean up event listeners",
"keywords": [
"add",
"addEventListener",
"addListener",
"browser",
"clear",
"DOM",
"emitter",
"event",
"EventEmitter",
"EventTarget",
"management",
"manager",
"node",
"remove",
"removeEventListener",
"removeListener"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/event-listeners-manager",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/event-listeners-manager",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "ISC",
"version": "1.0.1",
"scripts": {
"postversion": "npm publish --access public",
"test": "tap --branches=72"
},
"devDependencies": {
"tap": "^16.2.0"
}
}

1
@vates/fuse-vhd/.npmignore Symbolic link
View File

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

66
@vates/fuse-vhd/index.js Normal file
View File

@@ -0,0 +1,66 @@
'use strict'
const LRU = require('lru-cache')
const { VhdSynthetic } = require('vhd-lib')
const { Disposable, fromCallback } = require('promise-toolbox')
// 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(),
atime: st.atime || new Date(),
ctime: st.ctime || new Date(),
size: st.size !== undefined ? st.size : 0,
mode: st.mode === 'dir' ? 16877 : st.mode === 'file' ? 33188 : st.mode === 'link' ? 41453 : st.mode,
uid: st.uid !== undefined ? st.uid : process.getuid(),
gid: st.gid !== undefined ? st.gid : process.getgid(),
})
exports.mount = Disposable.factory(async function* mount(handler, diskPath, mountDir) {
const Fuse = require('fuse-native')
const vhd = yield VhdSynthetic.fromVhdChain(handler, diskPath)
const cache = new LRU({
max: 16, // each cached block is 2MB in size
})
await vhd.readBlockAllocationTable()
const fuse = new Fuse(mountDir, {
async readdir(path, cb) {
if (path === '/') {
return cb(null, ['vhd0'])
}
cb(Fuse.ENOENT)
},
async getattr(path, cb) {
if (path === '/') {
return cb(
null,
stat({
mode: 'dir',
size: 4096,
})
)
}
if (path === '/vhd0') {
return cb(
null,
stat({
mode: 'file',
size: vhd.footer.currentSize,
})
)
}
cb(Fuse.ENOENT)
},
read(path, fd, buf, len, pos, cb) {
if (path === '/vhd0') {
return vhd.readRawData(pos, len, cache, buf).then(cb)
}
throw new Error(`read file ${path} not exists`)
},
})
return new Disposable(
() => fromCallback(() => fuse.unmount()),
fromCallback(() => fuse.mount())
)
})

View File

@@ -0,0 +1,32 @@
{
"name": "@vates/fuse-vhd",
"version": "0.0.1",
"license": "ISC",
"private": false,
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/fuse-vhd",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/fuse-vhd",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"engines": {
"node": ">=10.0"
},
"dependencies": {
"@xen-orchestra/log": "^0.3.0",
"lru-cache": "^7.14.0",
"promise-toolbox": "^0.21.0",
"vhd-lib": "^4.0.1"
},
"optionalDependencies": {
"fuse-native": "^2.2.6"
},
"scripts": {
"postversion": "npm publish --access public"
}
}

View File

@@ -1,3 +1,5 @@
'use strict'
class Node {
constructor(value) {
this.children = new Map()

View File

@@ -1,3 +1,5 @@
'use strict'
/* eslint-env jest */
const { MultiKeyMap } = require('./')

View File

@@ -1,3 +1,5 @@
'use strict'
const ms = require('ms')
exports.parseDuration = value => {

View File

@@ -0,0 +1,57 @@
`undefined` predicates are ignored and `undefined` is returned if all predicates are `undefined`, this permits the most efficient composition:
```js
const compositePredicate = every(undefined, some(predicate2, undefined))
// ends up as
const compositePredicate = predicate2
```
Predicates can also be passed wrapped in an array:
```js
const compositePredicate = every([predicate1, some([predicate2, predicate3])])
```
`this` and all arguments are passed to the nested predicates.
### `every(predicates)`
> Returns a predicate that returns `true` iff every predicate returns `true`.
```js
const isBetween3And7 = every(
n => n >= 3,
n => n <= 7
)
isBetween3And10(0)
// → false
isBetween3And10(5)
// → true
isBetween3And10(10)
// → false
```
### `some(predicates)`
> Returns a predicate that returns `true` iff some predicate returns `true`.
```js
const isAliceOrBob = some(
name => name === 'Alice',
name => name === 'Bob'
)
isAliceOrBob('Alice')
// → true
isAliceOrBob('Bob')
// → true
isAliceOrBob('Oscar')
// → false
```

View File

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

View File

@@ -0,0 +1,90 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/predicates
[![Package Version](https://badgen.net/npm/v/@vates/predicates)](https://npmjs.org/package/@vates/predicates) ![License](https://badgen.net/npm/license/@vates/predicates) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/predicates)](https://bundlephobia.com/result?p=@vates/predicates) [![Node compatibility](https://badgen.net/npm/node/@vates/predicates)](https://npmjs.org/package/@vates/predicates)
> Utilities to compose predicates
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/predicates):
```
> npm install --save @vates/predicates
```
## Usage
`undefined` predicates are ignored and `undefined` is returned if all predicates are `undefined`, this permits the most efficient composition:
```js
const compositePredicate = every(undefined, some(predicate2, undefined))
// ends up as
const compositePredicate = predicate2
```
Predicates can also be passed wrapped in an array:
```js
const compositePredicate = every([predicate1, some([predicate2, predicate3])])
```
`this` and all arguments are passed to the nested predicates.
### `every(predicates)`
> Returns a predicate that returns `true` iff every predicate returns `true`.
```js
const isBetween3And7 = every(
n => n >= 3,
n => n <= 7
)
isBetween3And10(0)
// → false
isBetween3And10(5)
// → true
isBetween3And10(10)
// → false
```
### `some(predicates)`
> Returns a predicate that returns `true` iff some predicate returns `true`.
```js
const isAliceOrBob = some(
name => name === 'Alice',
name => name === 'Bob'
)
isAliceOrBob('Alice')
// → true
isAliceOrBob('Bob')
// → true
isAliceOrBob('Oscar')
// → false
```
## 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

@@ -0,0 +1,71 @@
'use strict'
const {
isArray,
prototype: { filter },
} = Array
class InvalidPredicate extends TypeError {
constructor(value) {
super('not a valid predicate')
this.value = value
}
}
function isDefinedPredicate(value) {
if (value === undefined) {
return false
}
if (typeof value !== 'function') {
throw new InvalidPredicate(value)
}
return true
}
function handleArgs() {
let predicates
if (!(arguments.length === 1 && isArray((predicates = arguments[0])))) {
predicates = arguments
}
return filter.call(predicates, isDefinedPredicate)
}
exports.every = function every() {
const predicates = handleArgs.apply(this, arguments)
const n = predicates.length
if (n === 0) {
return
}
if (n === 1) {
return predicates[0]
}
return function everyPredicate() {
for (let i = 0; i < n; ++i) {
if (!predicates[i].apply(this, arguments)) {
return false
}
}
return true
}
}
exports.some = function some() {
const predicates = handleArgs.apply(this, arguments)
const n = predicates.length
if (n === 0) {
return
}
if (n === 1) {
return predicates[0]
}
return function somePredicate() {
for (let i = 0; i < n; ++i) {
if (predicates[i].apply(this, arguments)) {
return true
}
}
return false
}
}

View File

@@ -0,0 +1,65 @@
'use strict'
const assert = require('assert/strict')
const { describe, it } = require('tap').mocha
const { every, some } = require('./')
const T = () => true
const F = () => false
const testArgsHandling = fn => {
it('returns undefined if all predicates are undefined', () => {
assert.equal(fn(undefined), undefined)
assert.equal(fn([undefined]), undefined)
})
it('returns the predicate if only a single one is passed', () => {
assert.equal(fn(undefined, T), T)
assert.equal(fn([undefined, T]), T)
})
it('throws if it receives a non-predicate', () => {
const error = new TypeError('not a valid predicate')
error.value = 3
assert.throws(() => fn(3), error)
})
it('forwards this and arguments to predicates', () => {
const thisArg = 'qux'
const args = ['foo', 'bar', 'baz']
const predicate = function () {
assert.equal(this, thisArg)
assert.deepEqual(Array.from(arguments), args)
}
fn(predicate, predicate).apply(thisArg, args)
})
}
const runTests = (fn, truthTable) =>
it('works', () => {
truthTable.forEach(([result, ...predicates]) => {
assert.equal(fn(...predicates)(), result)
assert.equal(fn(predicates)(), result)
})
})
describe('every', () => {
testArgsHandling(every)
runTests(every, [
[true, T, T],
[false, T, F],
[false, F, T],
[false, F, F],
])
})
describe('some', () => {
testArgsHandling(some)
runTests(some, [
[true, T, T],
[true, T, F],
[true, F, T],
[false, F, F],
])
})

View File

@@ -0,0 +1,40 @@
{
"private": false,
"name": "@vates/predicates",
"description": "Utilities to compose predicates",
"keywords": [
"and",
"combine",
"compose",
"every",
"function",
"functions",
"or",
"predicate",
"predicates",
"some"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/predicates",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@vates/predicates",
"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": ">=6"
},
"scripts": {
"postversion": "npm publish --access public",
"test": "tap"
},
"devDependencies": {
"tap": "^16.0.1"
}
}

View File

@@ -0,0 +1,26 @@
### `readChunk(stream, [size])`
- returns the next available chunk of data
- like `stream.read()`, a number of bytes can be specified
- returns with less data than expected if stream has ended
- returns `null` if the stream has ended and no data has been read
```js
import { readChunk } from '@vates/read-chunk'
;(async () => {
let chunk
while ((chunk = await readChunk(stream, 1024)) !== null) {
// do something with chunk
}
})()
```
### `readChunkStrict(stream, [size])`
Similar behavior to `readChunk` but throws if the stream ended before the requested data could be read.
```js
import { readChunkStrict } from '@vates/read-chunk'
const chunk = await readChunkStrict(stream, 1024)
```

View File

@@ -16,9 +16,12 @@ Installation of the [npm package](https://npmjs.org/package/@vates/read-chunk):
## Usage
### `readChunk(stream, [size])`
- returns the next available chunk of data
- like `stream.read()`, a number of bytes can be specified
- returns `null` if the stream has ended
- returns with less data than expected if stream has ended
- returns `null` if the stream has ended and no data has been read
```js
import { readChunk } from '@vates/read-chunk'
@@ -30,6 +33,16 @@ import { readChunk } from '@vates/read-chunk'
})()
```
### `readChunkStrict(stream, [size])`
Similar behavior to `readChunk` but throws if the stream ended before the requested data could be read.
```js
import { readChunkStrict } from '@vates/read-chunk'
const chunk = await readChunkStrict(stream, 1024)
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on

View File

@@ -1,13 +0,0 @@
- returns the next available chunk of data
- like `stream.read()`, a number of bytes can be specified
- returns `null` if the stream has ended
```js
import { readChunk } from '@vates/read-chunk'
;(async () => {
let chunk
while ((chunk = await readChunk(stream, 1024)) !== null) {
// do something with chunk
}
})()
```

View File

@@ -1,3 +1,5 @@
'use strict'
const readChunk = (stream, size) =>
size === 0
? Promise.resolve(Buffer.alloc(0))
@@ -28,3 +30,22 @@ const readChunk = (stream, size) =>
onReadable()
})
exports.readChunk = readChunk
exports.readChunkStrict = async function readChunkStrict(stream, size) {
const chunk = await readChunk(stream, size)
if (chunk === null) {
throw new Error('stream has ended without data')
}
if (size !== undefined && chunk.length !== size) {
const error = new Error('stream has ended with not enough data')
Object.defineProperties(error, {
chunk: {
value: chunk,
},
})
throw error
}
return chunk
}

View File

@@ -1,8 +1,10 @@
'use strict'
/* eslint-env jest */
const { Readable } = require('stream')
const { readChunk } = require('./')
const { readChunk, readChunkStrict } = require('./')
const makeStream = it => Readable.from(it, { objectMode: false })
makeStream.obj = Readable.from
@@ -41,3 +43,27 @@ describe('readChunk', () => {
})
})
})
const rejectionOf = promise =>
promise.then(
value => {
throw value
},
error => error
)
describe('readChunkStrict', function () {
it('throws if stream is empty', async () => {
const error = await rejectionOf(readChunkStrict(makeStream([])))
expect(error).toBeInstanceOf(Error)
expect(error.message).toBe('stream has ended without data')
expect(error.chunk).toEqual(undefined)
})
it('throws if stream ends with not enough data', async () => {
const error = await rejectionOf(readChunkStrict(makeStream(['foo', 'bar']), 10))
expect(error).toBeInstanceOf(Error)
expect(error.message).toBe('stream has ended with not enough data')
expect(error.chunk).toEqual(Buffer.from('foobar'))
})
})

View File

@@ -19,7 +19,7 @@
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"version": "0.1.2",
"version": "1.0.0",
"engines": {
"node": ">=8.10"
},

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env node
'use strict'
const fs = require('fs')
const mapKeys = (object, iteratee) => {

View File

@@ -1,3 +1,5 @@
'use strict'
const wrapCall = (fn, arg, thisArg) => {
try {
return Promise.resolve(fn.call(thisArg, arg))

View File

@@ -1,3 +1,5 @@
'use strict'
/* eslint-env jest */
const { asyncMapSettled } = require('./')

View File

@@ -1,3 +1,5 @@
'use strict'
// type MaybePromise<T> = Promise<T> | T
//
// declare export function asyncMap<T1, T2>(

View File

@@ -1 +0,0 @@
module.exports = require('../../@xen-orchestra/babel-config')(require('./package.json'))

View File

@@ -1 +0,0 @@
../../scripts/babel-eslintrc.js

View File

@@ -1,12 +1,14 @@
import assert from 'assert'
import hash from 'object-hash'
import { createLogger } from '@xen-orchestra/log'
import { decorateWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
'use strict'
const assert = require('assert')
const hash = require('object-hash')
const { createLogger } = require('@xen-orchestra/log')
const { decorateClass } = require('@vates/decorate-with')
const { defer } = require('golike-defer')
const log = createLogger('xo:audit-core')
export class Storage {
exports.Storage = class Storage {
constructor() {
this._lock = Promise.resolve()
}
@@ -29,7 +31,7 @@ const ID_TO_ALGORITHM = {
5: 'sha256',
}
export class AlteredRecordError extends Error {
class AlteredRecordError extends Error {
constructor(id, nValid, record) {
super('altered record')
@@ -38,8 +40,9 @@ export class AlteredRecordError extends Error {
this.record = record
}
}
exports.AlteredRecordError = AlteredRecordError
export class MissingRecordError extends Error {
class MissingRecordError extends Error {
constructor(id, nValid) {
super('missing record')
@@ -47,8 +50,10 @@ export class MissingRecordError extends Error {
this.nValid = nValid
}
}
exports.MissingRecordError = MissingRecordError
export const NULL_ID = 'nullId'
const NULL_ID = 'nullId'
exports.NULL_ID = NULL_ID
const HASH_ALGORITHM_ID = '5'
const createHash = (data, algorithmId = HASH_ALGORITHM_ID) =>
@@ -57,13 +62,12 @@ const createHash = (data, algorithmId = HASH_ALGORITHM_ID) =>
excludeKeys: key => key === 'id',
})}`
export class AuditCore {
class AuditCore {
constructor(storage) {
assert.notStrictEqual(storage, undefined)
this._storage = storage
}
@decorateWith(defer)
async add($defer, subject, event, data) {
const time = Date.now()
$defer(await this._storage.acquireLock())
@@ -148,7 +152,6 @@ export class AuditCore {
}
}
@decorateWith(defer)
async deleteRangeAndRewrite($defer, newest, oldest) {
assert.notStrictEqual(newest, undefined)
assert.notStrictEqual(oldest, undefined)
@@ -189,3 +192,9 @@ export class AuditCore {
}
}
}
exports.AuditCore = AuditCore
decorateClass(AuditCore, {
add: defer,
deleteRangeAndRewrite: defer,
})

View File

@@ -1,6 +1,9 @@
/* eslint-env jest */
'use strict'
import { AlteredRecordError, AuditCore, MissingRecordError, NULL_ID, Storage } from '.'
const assert = require('assert/strict')
const { afterEach, describe, it } = require('tap').mocha
const { AlteredRecordError, AuditCore, MissingRecordError, NULL_ID, Storage } = require('.')
const asyncIteratorToArray = async asyncIterator => {
const array = []
@@ -72,7 +75,7 @@ const auditCore = new AuditCore(db)
const storeAuditRecords = async () => {
await Promise.all(DATA.map(data => auditCore.add(...data)))
const records = await asyncIteratorToArray(auditCore.getFrom())
expect(records.length).toBe(DATA.length)
assert.equal(records.length, DATA.length)
return records
}
@@ -83,10 +86,11 @@ describe('auditCore', () => {
const [newestRecord, deletedRecord] = await storeAuditRecords()
const nValidRecords = await auditCore.checkIntegrity(NULL_ID, newestRecord.id)
expect(nValidRecords).toBe(DATA.length)
assert.equal(nValidRecords, DATA.length)
await db.del(deletedRecord.id)
await expect(auditCore.checkIntegrity(NULL_ID, newestRecord.id)).rejects.toEqual(
await assert.rejects(
auditCore.checkIntegrity(NULL_ID, newestRecord.id),
new MissingRecordError(deletedRecord.id, 1)
)
})
@@ -97,7 +101,8 @@ describe('auditCore', () => {
alteredRecord.event = ''
await db.put(alteredRecord)
await expect(auditCore.checkIntegrity(NULL_ID, newestRecord.id)).rejects.toEqual(
await assert.rejects(
auditCore.checkIntegrity(NULL_ID, newestRecord.id),
new AlteredRecordError(alteredRecord.id, 1, alteredRecord)
)
})
@@ -107,8 +112,8 @@ describe('auditCore', () => {
await auditCore.deleteFrom(secondRecord.id)
expect(await db.get(firstRecord.id)).toBe(undefined)
expect(await db.get(secondRecord.id)).toBe(undefined)
assert.equal(await db.get(firstRecord.id), undefined)
assert.equal(await db.get(secondRecord.id), undefined)
await auditCore.checkIntegrity(secondRecord.id, thirdRecord.id)
})

View File

@@ -9,28 +9,14 @@
},
"version": "0.2.0",
"engines": {
"node": ">=10"
"node": ">=14"
},
"main": "dist/",
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"postversion": "npm publish --access public",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
},
"devDependencies": {
"@babel/cli": "^7.7.4",
"@babel/core": "^7.7.4",
"@babel/plugin-proposal-decorators": "^7.8.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.0",
"@babel/preset-env": "^7.7.4",
"cross-env": "^7.0.2",
"rimraf": "^3.0.0"
"test": "tap --lines 67 --functions 92 --branches 52 --statements 67"
},
"dependencies": {
"@vates/decorate-with": "^1.0.0",
"@vates/decorate-with": "^2.0.0",
"@xen-orchestra/log": "^0.3.0",
"golike-defer": "^0.5.1",
"object-hash": "^2.0.1"
@@ -40,5 +26,8 @@
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"devDependencies": {
"tap": "^16.0.1"
}
}

View File

@@ -10,7 +10,7 @@
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"engines": {
"node": ">=6"
"node": ">=8.3"
},
"license": "AGPL-3.0-or-later",
"author": {

View File

@@ -1,3 +1,5 @@
'use strict'
const getopts = require('getopts')
const { version } = require('./package.json')

View File

@@ -1,3 +1,5 @@
'use strict'
const { dirname } = require('path')
const fs = require('promise-toolbox/promisifyAll')(require('fs'))

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env node
'use strict'
// -----------------------------------------------------------------------------
@@ -26,7 +26,13 @@ module.exports = async function main(args) {
await asyncMap(_, async vmDir => {
vmDir = resolve(vmDir)
try {
await adapter.cleanVm(vmDir, { fixMetadata: fix, remove, merge, onLog: (...args) => console.warn(...args) })
await adapter.cleanVm(vmDir, {
fixMetadata: fix,
remove,
merge,
logInfo: (...args) => console.log(...args),
logWarn: (...args) => console.warn(...args),
})
} catch (error) {
console.error('adapter.cleanVm', vmDir, error)
}

View File

@@ -1,3 +1,5 @@
'use strict'
const filenamify = require('filenamify')
const get = require('lodash/get')
const { asyncMap } = require('@xen-orchestra/async-map')

View File

@@ -1,3 +1,5 @@
'use strict'
const groupBy = require('lodash/groupBy')
const { asyncMap } = require('@xen-orchestra/async-map')
const { createHash } = require('crypto')

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env node
'use strict'
require('./_composeCommands')({
'clean-vms': {
get main() {

View File

@@ -7,12 +7,12 @@
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"dependencies": {
"@xen-orchestra/async-map": "^0.1.2",
"@xen-orchestra/backups": "^0.18.3",
"@xen-orchestra/fs": "^0.19.3",
"@xen-orchestra/backups": "^0.27.4",
"@xen-orchestra/fs": "^3.1.0",
"filenamify": "^4.1.0",
"getopts": "^2.2.5",
"lodash": "^4.17.15",
"promise-toolbox": "^0.20.0"
"promise-toolbox": "^0.21.0"
},
"engines": {
"node": ">=7.10.1"
@@ -27,7 +27,7 @@
"scripts": {
"postversion": "npm publish --access public"
},
"version": "0.6.1",
"version": "0.7.7",
"license": "AGPL-3.0-or-later",
"author": {
"name": "Vates SAS",

View File

@@ -1,10 +1,12 @@
'use strict'
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
const Disposable = require('promise-toolbox/Disposable.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
const Disposable = require('promise-toolbox/Disposable')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { compileTemplate } = require('@xen-orchestra/template')
const { limitConcurrency } = require('limit-concurrency-decorator')
const { extractIdsFromSimplePattern } = require('./_extractIdsFromSimplePattern.js')
const { extractIdsFromSimplePattern } = require('./extractIdsFromSimplePattern.js')
const { PoolMetadataBackup } = require('./_PoolMetadataBackup.js')
const { Task } = require('./Task.js')
const { VmBackup } = require('./_VmBackup.js')
@@ -22,6 +24,34 @@ const getAdaptersByRemote = adapters => {
const runTask = (...args) => Task.run(...args).catch(noop) // errors are handled by logs
const DEFAULT_SETTINGS = {
reportWhen: 'failure',
}
const DEFAULT_VM_SETTINGS = {
bypassVdiChainsCheck: false,
checkpointSnapshot: false,
concurrency: 2,
copyRetention: 0,
deleteFirst: false,
exportRetention: 0,
fullInterval: 0,
healthCheckSr: undefined,
healthCheckVmsWithTags: [],
maxMergedDeltasPerRun: 2,
offlineBackup: false,
offlineSnapshot: false,
snapshotRetention: 0,
timeout: 0,
unconditionalSnapshot: false,
vmTimeout: 0,
}
const DEFAULT_METADATA_SETTINGS = {
retentionPoolMetadata: 0,
retentionXoMetadata: 0,
}
exports.Backup = class Backup {
constructor({ config, getAdapter, getConnectedRecord, job, schedule }) {
this._config = config
@@ -40,17 +70,22 @@ exports.Backup = class Backup {
'{job.name}': job.name,
'{vm.name_label}': vm => vm.name_label,
})
}
run() {
const type = this._job.type
const { type } = job
const baseSettings = { ...DEFAULT_SETTINGS }
if (type === 'backup') {
return this._runVmBackup()
Object.assign(baseSettings, DEFAULT_VM_SETTINGS, config.defaultSettings, config.vm?.defaultSettings)
this.run = this._runVmBackup
} else if (type === 'metadataBackup') {
return this._runMetadataBackup()
Object.assign(baseSettings, DEFAULT_METADATA_SETTINGS, config.defaultSettings, config.metadata?.defaultSettings)
this.run = this._runMetadataBackup
} else {
throw new Error(`No runner for the backup type ${type}`)
}
Object.assign(baseSettings, job.settings[''])
this._baseSettings = baseSettings
this._settings = { ...baseSettings, ...job.settings[schedule.id] }
}
async _runMetadataBackup() {
@@ -62,13 +97,6 @@ exports.Backup = class Backup {
}
const config = this._config
const settings = {
...config.defaultSettings,
...config.metadata.defaultSettings,
...job.settings[''],
...job.settings[schedule.id],
}
const poolIds = extractIdsFromSimplePattern(job.pools)
const isEmptyPools = poolIds.length === 0
const isXoMetadata = job.xoMetadata !== undefined
@@ -76,6 +104,8 @@ exports.Backup = class Backup {
throw new Error('no metadata mode found')
}
const settings = this._settings
const { retentionPoolMetadata, retentionXoMetadata } = settings
if (
@@ -187,14 +217,7 @@ exports.Backup = class Backup {
const schedule = this._schedule
const config = this._config
const { settings } = job
const scheduleSettings = {
...config.defaultSettings,
...config.vm.defaultSettings,
...settings[''],
...settings[schedule.id],
}
const settings = this._settings
await Disposable.use(
Disposable.all(
extractIdsFromSimplePattern(job.srs).map(id =>
@@ -222,14 +245,15 @@ exports.Backup = class Backup {
})
)
),
async (srs, remoteAdapters) => {
() => (settings.healthCheckSr !== undefined ? this._getRecord('SR', settings.healthCheckSr) : undefined),
async (srs, remoteAdapters, healthCheckSr) => {
// remove adapters that failed (already handled)
remoteAdapters = remoteAdapters.filter(_ => _ !== undefined)
// remove srs that failed (already handled)
srs = srs.filter(_ => _ !== undefined)
if (remoteAdapters.length === 0 && srs.length === 0 && scheduleSettings.snapshotRetention === 0) {
if (remoteAdapters.length === 0 && srs.length === 0 && settings.snapshotRetention === 0) {
return
}
@@ -239,23 +263,27 @@ exports.Backup = class Backup {
remoteAdapters = getAdaptersByRemote(remoteAdapters)
const allSettings = this._job.settings
const baseSettings = this._baseSettings
const handleVm = vmUuid =>
runTask({ name: 'backup VM', data: { type: 'VM', id: vmUuid } }, () =>
Disposable.use(this._getRecord('VM', vmUuid), vm =>
new VmBackup({
baseSettings,
config,
getSnapshotNameLabel,
healthCheckSr,
job,
// remotes,
remoteAdapters,
schedule,
settings: { ...scheduleSettings, ...settings[vmUuid] },
settings: { ...settings, ...allSettings[vm.uuid] },
srs,
vm,
}).run()
)
)
const { concurrency } = scheduleSettings
const { concurrency } = settings
await asyncMapSettled(vmIds, concurrency === 0 ? handleVm : limitConcurrency(concurrency)(handleVm))
}
)

View File

@@ -1,3 +1,5 @@
'use strict'
const { asyncMap } = require('@xen-orchestra/async-map')
exports.DurablePartition = class DurablePartition {

View File

@@ -0,0 +1,64 @@
'use strict'
const { Task } = require('./Task')
exports.HealthCheckVmBackup = class HealthCheckVmBackup {
#xapi
#restoredVm
constructor({ restoredVm, xapi }) {
this.#restoredVm = restoredVm
this.#xapi = xapi
}
async run() {
return Task.run(
{
name: 'vmstart',
},
async () => {
let restoredVm = this.#restoredVm
const xapi = this.#xapi
const restoredId = restoredVm.uuid
// remove vifs
await Promise.all(restoredVm.$VIFs.map(vif => xapi.callAsync('VIF.destroy', vif.$ref)))
const start = new Date()
// start Vm
await xapi.callAsync(
'VM.start',
restoredVm.$ref,
false, // Start paused?
false // Skip pre-boot checks?
)
const started = new Date()
const timeout = 10 * 60 * 1000
const startDuration = started - start
let remainingTimeout = timeout - startDuration
if (remainingTimeout < 0) {
throw new Error(`VM ${restoredId} not started after ${timeout / 1000} second`)
}
// wait for the 'Running' event to be really stored in local xapi object cache
restoredVm = await xapi.waitObjectState(restoredVm.$ref, vm => vm.power_state === 'Running', {
timeout: remainingTimeout,
})
const running = new Date()
remainingTimeout -= running - started
if (remainingTimeout < 0) {
throw new Error(`local xapi did not get Runnig state for VM ${restoredId} after ${timeout / 1000} second`)
}
// wait for the guest tool version to be defined
await xapi.waitObjectState(restoredVm.guest_metrics, gm => gm?.PV_drivers_version?.major !== undefined, {
timeout: remainingTimeout,
})
}
)
}
}

View File

@@ -1,3 +1,5 @@
'use strict'
const assert = require('assert')
const { formatFilenameDate } = require('./_filenameDate.js')
@@ -6,9 +8,9 @@ const { Task } = require('./Task.js')
const { watchStreamSize } = require('./_watchStreamSize.js')
exports.ImportVmBackup = class ImportVmBackup {
constructor({ adapter, metadata, srUuid, xapi, settings: { newMacAddresses } = {} }) {
constructor({ adapter, metadata, srUuid, xapi, settings: { newMacAddresses, mapVdisSrs = {} } = {} }) {
this._adapter = adapter
this._importDeltaVmSettings = { newMacAddresses }
this._importDeltaVmSettings = { newMacAddresses, mapVdisSrs }
this._metadata = metadata
this._srUuid = srUuid
this._xapi = xapi
@@ -28,7 +30,12 @@ exports.ImportVmBackup = class ImportVmBackup {
} else {
assert.strictEqual(metadata.mode, 'delta')
backup = await adapter.readDeltaVmBackup(metadata)
const ignoredVdis = new Set(
Object.entries(this._importDeltaVmSettings.mapVdisSrs)
.filter(([_, srUuid]) => srUuid === null)
.map(([vdiUuid]) => vdiUuid)
)
backup = await adapter.readDeltaVmBackup(metadata, ignoredVdis)
Object.values(backup.streams).forEach(stream => watchStreamSize(stream, sizeContainer))
}

View File

@@ -1,33 +1,42 @@
'use strict'
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
const Disposable = require('promise-toolbox/Disposable.js')
const fromCallback = require('promise-toolbox/fromCallback.js')
const fromEvent = require('promise-toolbox/fromEvent.js')
const pDefer = require('promise-toolbox/defer.js')
const { synchronized } = require('decorator-synchronized')
const Disposable = require('promise-toolbox/Disposable')
const fromCallback = require('promise-toolbox/fromCallback')
const fromEvent = require('promise-toolbox/fromEvent')
const pDefer = require('promise-toolbox/defer')
const groupBy = require('lodash/groupBy.js')
const pickBy = require('lodash/pickBy.js')
const { dirname, join, normalize, resolve } = require('path')
const { createLogger } = require('@xen-orchestra/log')
const { Constants, createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } = require('vhd-lib')
const { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } = require('vhd-lib')
const { deduped } = require('@vates/disposable/deduped.js')
const { decorateMethodsWith } = require('@vates/decorate-with')
const { compose } = require('@vates/compose')
const { execFile } = require('child_process')
const { readdir, stat } = require('fs-extra')
const { readdir, lstat } = require('fs-extra')
const { v4: uuidv4 } = require('uuid')
const { ZipFile } = require('yazl')
const zlib = require('zlib')
const { BACKUP_DIR } = require('./_getVmBackupDir.js')
const { cleanVm } = require('./_cleanVm.js')
const { formatFilenameDate } = require('./_filenameDate.js')
const { getTmpDir } = require('./_getTmpDir.js')
const { isMetadataFile } = require('./_backupType.js')
const { isValidXva } = require('./_isValidXva.js')
const { listPartitions, LVM_PARTITION_TYPE } = require('./_listPartitions.js')
const { lvs, pvs } = require('./_lvm.js')
const { asyncEach } = require('@vates/async-each')
const { mount } = require('@vates/fuse-vhd')
const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
exports.DIR_XO_CONFIG_BACKUPS = DIR_XO_CONFIG_BACKUPS
const DIR_XO_POOL_METADATA_BACKUPS = 'xo-pool-metadata-backups'
exports.DIR_XO_POOL_METADATA_BACKUPS = DIR_XO_POOL_METADATA_BACKUPS
const { warn } = createLogger('xo:backups:RemoteAdapter')
const { debug, warn } = createLogger('xo:backups:RemoteAdapter')
const compareTimestamp = (a, b) => a.timestamp - b.timestamp
@@ -37,16 +46,13 @@ const resolveRelativeFromFile = (file, path) => resolve('/', dirname(file), path
const resolveSubpath = (root, path) => resolve(root, `.${resolve('/', path)}`)
const RE_VHDI = /^vhdi(\d+)$/
async function addDirectory(files, realPath, metadataPath) {
try {
const subFiles = await readdir(realPath)
await asyncMap(subFiles, file => addDirectory(files, realPath + '/' + file, metadataPath + '/' + file))
} catch (error) {
if (error == null || error.code !== 'ENOTDIR') {
throw error
}
const stats = await lstat(realPath)
if (stats.isDirectory()) {
await asyncMap(await readdir(realPath), file =>
addDirectory(files, realPath + '/' + file, metadataPath + '/' + file)
)
} else if (stats.isFile()) {
files.push({
realPath,
metadataPath,
@@ -68,11 +74,16 @@ const debounceResourceFactory = factory =>
}
class RemoteAdapter {
constructor(handler, { debounceResource = res => res, dirMode, vhdDirectoryCompression } = {}) {
constructor(
handler,
{ debounceResource = res => res, dirMode, vhdDirectoryCompression, useGetDiskLegacy = false } = {}
) {
this._debounceResource = debounceResource
this._dirMode = dirMode
this._handler = handler
this._vhdDirectoryCompression = vhdDirectoryCompression
this._readCacheListVmBackups = synchronized.withKey()(this._readCacheListVmBackups)
this._useGetDiskLegacy = useGetDiskLegacy
}
get handler() {
@@ -88,9 +99,6 @@ class RemoteAdapter {
return partition
}
_getLvmLogicalVolumes = Disposable.factory(this._getLvmLogicalVolumes)
_getLvmLogicalVolumes = deduped(this._getLvmLogicalVolumes, (devicePath, pvId, vgName) => [devicePath, pvId, vgName])
_getLvmLogicalVolumes = debounceResourceFactory(this._getLvmLogicalVolumes)
async *_getLvmLogicalVolumes(devicePath, pvId, vgName) {
yield this._getLvmPhysicalVolume(devicePath, pvId && (await this._findPartition(devicePath, pvId)))
@@ -102,9 +110,6 @@ class RemoteAdapter {
}
}
_getLvmPhysicalVolume = Disposable.factory(this._getLvmPhysicalVolume)
_getLvmPhysicalVolume = deduped(this._getLvmPhysicalVolume, (devicePath, partition) => [devicePath, partition?.id])
_getLvmPhysicalVolume = debounceResourceFactory(this._getLvmPhysicalVolume)
async *_getLvmPhysicalVolume(devicePath, partition) {
const args = []
if (partition !== undefined) {
@@ -125,11 +130,10 @@ class RemoteAdapter {
}
}
_getPartition = Disposable.factory(this._getPartition)
_getPartition = deduped(this._getPartition, (devicePath, partition) => [devicePath, partition?.id])
_getPartition = debounceResourceFactory(this._getPartition)
async *_getPartition(devicePath, partition) {
const options = ['loop', 'ro']
// the norecovery option is necessary because if the partition is dirty,
// mount will try to fix it which is impossible if because the device is read-only
const options = ['loop', 'ro', 'norecovery']
if (partition !== undefined) {
const { size, start } = partition
@@ -180,7 +184,6 @@ class RemoteAdapter {
})
}
_usePartitionFiles = Disposable.factory(this._usePartitionFiles)
async *_usePartitionFiles(diskId, partitionId, paths) {
const path = yield this.getPartition(diskId, partitionId)
@@ -227,11 +230,30 @@ class RemoteAdapter {
return promise
}
#removeVmBackupsFromCache(backups) {
for (const [dir, filenames] of Object.entries(
groupBy(
backups.map(_ => _._filename),
dirname
)
)) {
// detached async action, will not reject
this._updateCache(dir + '/cache.json.gz', backups => {
for (const filename of filenames) {
debug('removing cache entry', { entry: filename })
delete backups[filename]
}
})
}
}
async deleteDeltaVmBackups(backups) {
const handler = this._handler
// unused VHDs will be detected by `cleanVm`
await asyncMapSettled(backups, ({ _filename }) => VhdAbstract.unlink(handler, _filename))
// this will delete the json, unused VHDs will be detected by `cleanVm`
await asyncMapSettled(backups, ({ _filename }) => handler.unlink(_filename))
this.#removeVmBackupsFromCache(backups)
}
async deleteMetadataBackup(backupId) {
@@ -259,6 +281,8 @@ class RemoteAdapter {
await asyncMapSettled(backups, ({ _filename, xva }) =>
Promise.all([handler.unlink(_filename), handler.unlink(resolveRelativeFromFile(_filename, xva))])
)
this.#removeVmBackupsFromCache(backups)
}
deleteVmBackup(file) {
@@ -266,7 +290,8 @@ class RemoteAdapter {
}
async deleteVmBackups(files) {
const { delta, full, ...others } = groupBy(await asyncMap(files, file => this.readVmBackupMetadata(file)), 'mode')
const metadatas = await asyncMap(files, file => this.readVmBackupMetadata(file))
const { delta, full, ...others } = groupBy(metadatas, 'mode')
const unsupportedModes = Object.keys(others)
if (unsupportedModes.length !== 0) {
@@ -277,6 +302,14 @@ class RemoteAdapter {
delta !== undefined && this.deleteDeltaVmBackups(delta),
full !== undefined && this.deleteFullVmBackups(full),
])
await asyncMap(new Set(files.map(file => dirname(file))), dir =>
// - don't merge in main process, unused VHDs will be merged in the next backup run
// - don't error in case this fails:
// - if lock is already being held, a backup is running and cleanVm will be ran at the end
// - otherwise, there is nothing more we can do, orphan file will be cleaned in the future
this.cleanVm(dir, { remove: true, logWarn: warn }).catch(noop)
)
}
#getCompressionType() {
@@ -284,17 +317,15 @@ class RemoteAdapter {
}
#useVhdDirectory() {
return this.handler.type === 's3'
return this.handler.useVhdDirectory()
}
#useAlias() {
return this.#useVhdDirectory()
}
getDisk = Disposable.factory(this.getDisk)
getDisk = deduped(this.getDisk, diskId => [diskId])
getDisk = debounceResourceFactory(this.getDisk)
async *getDisk(diskId) {
async *#getDiskLegacy(diskId) {
const RE_VHDI = /^vhdi(\d+)$/
const handler = this._handler
const diskPath = handler._getFilePath('/' + diskId)
@@ -324,13 +355,36 @@ class RemoteAdapter {
}
}
async *getDisk(diskId) {
if (this._useGetDiskLegacy) {
yield* this.#getDiskLegacy(diskId)
return
}
const handler = this._handler
// this is a disposable
const mountDir = yield getTmpDir()
try {
// this is also a disposable
yield mount(handler, diskId, mountDir)
} catch (error) {
// fallback in case of missing dependency
if (error.code === 'MODULE_NOT_FOUND') {
yield* this.#getDiskLegacy(diskId)
} else {
throw error
}
}
// this will yield disk path to caller
yield `${mountDir}/vhd0`
}
// partitionId values:
//
// - undefined: raw disk
// - `<partitionId>`: partitioned disk
// - `<pvId>/<vgName>/<lvName>`: LVM on a partitioned disk
// - `/<vgName>/lvName>`: LVM on a raw disk
getPartition = Disposable.factory(this.getPartition)
async *getPartition(diskId, partitionId) {
const devicePath = yield this.getDisk(diskId)
if (partitionId === undefined) {
@@ -359,9 +413,14 @@ class RemoteAdapter {
const handler = this._handler
const backups = { __proto__: null }
await asyncMap(await handler.list(BACKUP_DIR), async vmUuid => {
const vmBackups = await this.listVmBackups(vmUuid)
backups[vmUuid] = vmBackups
await asyncMap(await handler.list(BACKUP_DIR), async entry => {
// ignore hidden and lock files
if (entry[0] !== '.' && !entry.endsWith('.lock')) {
const vmBackups = await this.listVmBackups(entry)
if (vmBackups.length !== 0) {
backups[entry] = vmBackups
}
}
})
return backups
@@ -370,18 +429,25 @@ class RemoteAdapter {
listPartitionFiles(diskId, partitionId, path) {
return Disposable.use(this.getPartition(diskId, partitionId), async rootPath => {
path = resolveSubpath(rootPath, path)
const entriesMap = {}
await asyncMap(await readdir(path), async name => {
try {
const stats = await stat(`${path}/${name}`)
entriesMap[stats.isDirectory() ? `${name}/` : name] = {}
} catch (error) {
if (error == null || error.code !== 'ENOENT') {
throw error
await asyncEach(
await readdir(path),
async name => {
try {
const stats = await lstat(`${path}/${name}`)
if (stats.isDirectory()) {
entriesMap[name + '/'] = {}
} else if (stats.isFile()) {
entriesMap[name] = {}
}
} catch (error) {
if (error == null || error.code !== 'ENOENT') {
throw error
}
}
}
})
},
{ concurrency: 1 }
)
return entriesMap
})
@@ -446,34 +512,114 @@ class RemoteAdapter {
return backupsByPool
}
async listVmBackups(vmUuid, predicate) {
#getVmBackupsCache(vmUuid) {
return `${BACKUP_DIR}/${vmUuid}/cache.json.gz`
}
async #readCache(path) {
try {
return JSON.parse(await fromCallback(zlib.gunzip, await this.handler.readFile(path)))
} catch (error) {
if (error.code !== 'ENOENT') {
warn('#readCache', { error, path })
}
}
}
_updateCache = synchronized.withKey()(this._updateCache)
// eslint-disable-next-line no-dupe-class-members
async _updateCache(path, fn) {
const cache = await this.#readCache(path)
if (cache !== undefined) {
fn(cache)
await this.#writeCache(path, cache)
}
}
async #writeCache(path, data) {
try {
await this.handler.writeFile(path, await fromCallback(zlib.gzip, JSON.stringify(data)), { flags: 'w' })
} catch (error) {
warn('#writeCache', { error, path })
}
}
async invalidateVmBackupListCache(vmUuid) {
await this.handler.unlink(this.#getVmBackupsCache(vmUuid))
}
async #getCachabledDataListVmBackups(dir) {
debug('generating cache', { path: dir })
const handler = this._handler
const backups = []
const backups = {}
try {
const files = await handler.list(`${BACKUP_DIR}/${vmUuid}`, {
const files = await handler.list(dir, {
filter: isMetadataFile,
prependDir: true,
})
await asyncMap(files, async file => {
try {
const metadata = await this.readVmBackupMetadata(file)
if (predicate === undefined || predicate(metadata)) {
// inject an id usable by importVmBackupNg()
metadata.id = metadata._filename
backups.push(metadata)
}
// inject an id usable by importVmBackupNg()
metadata.id = metadata._filename
backups[file] = metadata
} catch (error) {
warn(`listVmBackups ${file}`, { error })
warn(`can't read vm backup metadata`, { error, file, dir })
}
})
return backups
} catch (error) {
let code
if (error == null || ((code = error.code) !== 'ENOENT' && code !== 'ENOTDIR')) {
throw error
}
}
}
// use _ to mark this method as private by convention
// since we decorate it with synchronized.withKey in the constructor
// and # function are not writeable.
//
// read the list of backup of a Vm from cache
// if cache is missing or broken => regenerate it and return
async _readCacheListVmBackups(vmUuid) {
const path = this.#getVmBackupsCache(vmUuid)
const cache = await this.#readCache(path)
if (cache !== undefined) {
debug('found VM backups cache, using it', { path })
return cache
}
// nothing cached, or cache unreadable => regenerate it
const backups = await this.#getCachabledDataListVmBackups(`${BACKUP_DIR}/${vmUuid}`)
if (backups === undefined) {
return
}
// detached async action, will not reject
this.#writeCache(path, backups)
return backups
}
async listVmBackups(vmUuid, predicate) {
const backups = []
const cached = await this._readCacheListVmBackups(vmUuid)
if (cached === undefined) {
return []
}
Object.values(cached).forEach(metadata => {
if (predicate === undefined || predicate(metadata)) {
backups.push(metadata)
}
})
return backups.sort(compareTimestamp)
}
@@ -499,13 +645,35 @@ class RemoteAdapter {
return backups.sort(compareTimestamp)
}
async writeVhd(path, input, { checksum = true, validator = noop } = {}) {
async writeVmBackupMetadata(vmUuid, metadata) {
const path = `/${BACKUP_DIR}/${vmUuid}/${formatFilenameDate(metadata.timestamp)}.json`
await this.handler.outputFile(path, JSON.stringify(metadata), {
dirMode: this._dirMode,
})
// will not throw
this._updateCache(this.#getVmBackupsCache(vmUuid), backups => {
debug('adding cache entry', { entry: path })
backups[path] = {
...metadata,
// these values are required in the cache
_filename: path,
id: path,
}
})
return path
}
async writeVhd(path, input, { checksum = true, validator = noop, writeBlockConcurrency } = {}) {
const handler = this._handler
if (this.#useVhdDirectory()) {
const dataPath = `${dirname(path)}/data/${uuidv4()}.vhd`
await createVhdDirectoryFromStream(handler, dataPath, input, {
concurrency: 16,
concurrency: writeBlockConcurrency,
compression: this.#getCompressionType(),
async validator() {
await input.task
@@ -529,60 +697,42 @@ class RemoteAdapter {
})
}
async _createSyntheticStream(handler, paths) {
let disposableVhds = []
// if it's a path : open all hierarchy of parent
if (typeof paths === 'string') {
let vhd,
vhdPath = paths
do {
const disposable = await openVhd(handler, vhdPath)
vhd = disposable.value
disposableVhds.push(disposable)
vhdPath = resolveRelativeFromFile(vhdPath, vhd.header.parentUnicodeName)
} while (vhd.footer.diskType !== Constants.DISK_TYPES.DYNAMIC)
} else {
// only open the list of path given
disposableVhds = paths.map(path => openVhd(handler, path))
}
// open the hierarchy of ancestors until we find a full one
async _createSyntheticStream(handler, path) {
const disposableSynthetic = await VhdSynthetic.fromVhdChain(handler, path)
// I don't want the vhds to be disposed on return
// but only when the stream is done ( or failed )
const disposables = await Disposable.all(disposableVhds)
const vhds = disposables.value
let disposed = false
const disposeOnce = async () => {
if (!disposed) {
disposed = true
try {
await disposables.dispose()
await disposableSynthetic.dispose()
} catch (error) {
warn('_createSyntheticStream: failed to dispose VHDs', { error })
warn('openVhd: failed to dispose VHDs', { error })
}
}
}
const synthetic = new VhdSynthetic(vhds)
await synthetic.readHeaderAndFooter()
const synthetic = disposableSynthetic.value
await synthetic.readBlockAllocationTable()
const stream = await synthetic.stream()
stream.on('end', disposeOnce)
stream.on('close', disposeOnce)
stream.on('error', disposeOnce)
return stream
}
async readDeltaVmBackup(metadata) {
async readDeltaVmBackup(metadata, ignoredVdis) {
const handler = this._handler
const { vbds, vdis, vhds, vifs, vm } = metadata
const { vbds, vhds, vifs, vm } = metadata
const dir = dirname(metadata._filename)
const vdis = ignoredVdis === undefined ? metadata.vdis : pickBy(metadata.vdis, vdi => !ignoredVdis.has(vdi.uuid))
const streams = {}
await asyncMapSettled(Object.keys(vdis), async id => {
streams[`${id}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[id]))
await asyncMapSettled(Object.keys(vdis), async ref => {
streams[`${ref}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[ref]))
})
return {
@@ -600,7 +750,10 @@ class RemoteAdapter {
}
async readVmBackupMetadata(path) {
return Object.defineProperty(JSON.parse(await this._handler.readFile(path)), '_filename', { value: path })
// _filename is a private field used to compute the backup id
//
// it's enumerable to make it cacheable
return { ...JSON.parse(await this._handler.readFile(path)), _filename: path }
}
}
@@ -615,4 +768,30 @@ Object.assign(RemoteAdapter.prototype, {
isValidXva,
})
decorateMethodsWith(RemoteAdapter, {
_getLvmLogicalVolumes: compose([
Disposable.factory,
[deduped, (devicePath, pvId, vgName) => [devicePath, pvId, vgName]],
debounceResourceFactory,
]),
_getLvmPhysicalVolume: compose([
Disposable.factory,
[deduped, (devicePath, partition) => [devicePath, partition?.id]],
debounceResourceFactory,
]),
_getPartition: compose([
Disposable.factory,
[deduped, (devicePath, partition) => [devicePath, partition?.id]],
debounceResourceFactory,
]),
_usePartitionFiles: Disposable.factory,
getDisk: compose([Disposable.factory, [deduped, diskId => [diskId]], debounceResourceFactory]),
getPartition: Disposable.factory,
})
exports.RemoteAdapter = RemoteAdapter

View File

@@ -1,3 +1,5 @@
'use strict'
const { DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
const { PATH_DB_DUMP } = require('./_PoolMetadataBackup.js')

View File

@@ -1,12 +1,18 @@
const CancelToken = require('promise-toolbox/CancelToken.js')
'use strict'
const CancelToken = require('promise-toolbox/CancelToken')
const Zone = require('node-zone')
const logAfterEnd = () => {
throw new Error('task has already ended')
const logAfterEnd = log => {
const error = new Error('task has already ended')
error.log = log
throw error
}
const noop = Function.prototype
const serializeErrors = errors => (Array.isArray(errors) ? errors.map(serializeError) : errors)
// Create a serializable object from an error.
//
// Otherwise some fields might be non-enumerable and missing from logs.
@@ -15,6 +21,7 @@ const serializeError = error =>
? {
...error, // Copy enumerable properties.
code: error.code,
errors: serializeErrors(error.errors), // supports AggregateError
message: error.message,
name: error.name,
stack: error.stack,

View File

@@ -1,3 +1,5 @@
'use strict'
const { asyncMap } = require('@xen-orchestra/async-map')
const { DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')

View File

@@ -1,11 +1,14 @@
'use strict'
const assert = require('assert')
const findLast = require('lodash/findLast.js')
const groupBy = require('lodash/groupBy.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const keyBy = require('lodash/keyBy.js')
const mapValues = require('lodash/mapValues.js')
const { asyncMap } = require('@xen-orchestra/async-map')
const { createLogger } = require('@xen-orchestra/log')
const { decorateMethodsWith } = require('@vates/decorate-with')
const { defer } = require('golike-defer')
const { formatDateTime } = require('@xen-orchestra/xapi')
@@ -21,6 +24,13 @@ const { watchStreamSize } = require('./_watchStreamSize.js')
const { debug, warn } = createLogger('xo:backups:VmBackup')
class AggregateError extends Error {
constructor(errors, message) {
super(message)
this.errors = errors
}
}
const asyncEach = async (iterable, fn, thisArg = iterable) => {
for (const item of iterable) {
await fn.call(thisArg, item)
@@ -34,17 +44,28 @@ const forkDeltaExport = deltaExport =>
},
})
exports.VmBackup = class VmBackup {
constructor({ config, getSnapshotNameLabel, job, remoteAdapters, remotes, schedule, settings, srs, vm }) {
if (vm.other_config['xo:backup:job'] === job.id) {
// otherwise replicated VMs would be matched and replicated again and again
class VmBackup {
constructor({
config,
getSnapshotNameLabel,
healthCheckSr,
job,
remoteAdapters,
remotes,
schedule,
settings,
srs,
vm,
}) {
if (vm.other_config['xo:backup:job'] === 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')
}
this.config = config
this.job = job
this.remoteAdapters = remoteAdapters
this.remotes = remotes
this.scheduleId = schedule.id
this.timestamp = undefined
@@ -58,6 +79,7 @@ exports.VmBackup = class VmBackup {
this._fullVdisRequired = undefined
this._getSnapshotNameLabel = getSnapshotNameLabel
this._isDelta = job.mode === 'delta'
this._healthCheckSr = healthCheckSr
this._jobId = job.id
this._jobSnapshots = undefined
this._xapi = vm.$xapi
@@ -84,7 +106,6 @@ exports.VmBackup = class VmBackup {
: [FullBackupWriter, FullReplicationWriter]
const allSettings = job.settings
Object.keys(remoteAdapters).forEach(remoteId => {
const targetSettings = {
...settings,
@@ -107,33 +128,49 @@ exports.VmBackup = class VmBackup {
}
// calls fn for each function, warns of any errors, and throws only if there are no writers left
async _callWriters(fn, warnMessage, parallel = true) {
async _callWriters(fn, step, parallel = true) {
const writers = this._writers
const n = writers.size
if (n === 0) {
return
}
if (n === 1) {
const [writer] = writers
async function callWriter(writer) {
const { name } = writer.constructor
try {
debug('writer step starting', { step, writer: name })
await fn(writer)
debug('writer step succeeded', { duration: step, writer: name })
} catch (error) {
writers.delete(writer)
warn('writer step failed', { error, step, writer: name })
// these two steps are the only one that are not already in their own sub tasks
if (step === 'writer.checkBaseVdis()' || step === 'writer.beforeBackup()') {
Task.warning(
`the writer ${name} has failed the step ${step} with error ${error.message}. It won't be used anymore in this job execution.`
)
}
throw error
}
return
}
if (n === 1) {
const [writer] = writers
return callWriter(writer)
}
const errors = []
await (parallel ? asyncMap : asyncEach)(writers, async function (writer) {
try {
await fn(writer)
await callWriter(writer)
} catch (error) {
this.delete(writer)
warn(warnMessage, { error, writer: writer.constructor.name })
errors.push(error)
}
})
if (writers.size === 0) {
throw new Error('all targets have failed, step: ' + warnMessage)
throw new AggregateError(errors, 'all targets have failed, step: ' + step)
}
}
@@ -160,7 +197,10 @@ exports.VmBackup = class VmBackup {
const settings = this._settings
const doSnapshot =
this._isDelta || (!settings.offlineBackup && vm.power_state === 'Running') || settings.snapshotRetention !== 0
settings.unconditionalSnapshot ||
this._isDelta ||
(!settings.offlineBackup && vm.power_state === 'Running') ||
settings.snapshotRetention !== 0
if (doSnapshot) {
await Task.run({ name: 'snapshot' }, async () => {
if (!settings.bypassVdiChainsCheck) {
@@ -168,7 +208,9 @@ exports.VmBackup = class VmBackup {
}
const snapshotRef = await vm[settings.checkpointSnapshot ? '$checkpoint' : '$snapshot']({
ignoreNobakVdis: true,
name_label: this._getSnapshotNameLabel(vm),
unplugVusbs: true,
})
this.timestamp = Date.now()
@@ -290,22 +332,17 @@ exports.VmBackup = class VmBackup {
}
async _removeUnusedSnapshots() {
const jobSettings = this.job.settings
const allSettings = this.job.settings
const baseSettings = this._baseSettings
const baseVmRef = this._baseVm?.$ref
const { config } = this
const baseSettings = {
...config.defaultSettings,
...config.metadata.defaultSettings,
...jobSettings[''],
}
const snapshotsPerSchedule = groupBy(this._jobSnapshots, _ => _.other_config['xo:backup:schedule'])
const xapi = this._xapi
await asyncMap(Object.entries(snapshotsPerSchedule), ([scheduleId, snapshots]) => {
const settings = {
...baseSettings,
...jobSettings[scheduleId],
...jobSettings[this.vm.uuid],
...allSettings[scheduleId],
...allSettings[this.vm.uuid],
}
return asyncMap(getOldEntries(settings.snapshotRetention, snapshots), ({ $ref }) => {
if ($ref !== baseVmRef) {
@@ -384,7 +421,24 @@ exports.VmBackup = class VmBackup {
this._fullVdisRequired = fullVdisRequired
}
run = defer(this.run)
async _healthCheck() {
const settings = this._settings
if (this._healthCheckSr === undefined) {
return
}
// check if current VM has tags
const { tags } = this.vm
const intersect = settings.healthCheckVmsWithTags.some(t => tags.includes(t))
if (settings.healthCheckVmsWithTags.length !== 0 && !intersect) {
return
}
await this._callWriters(writer => writer.healthCheck(this._healthCheckSr), 'writer.healthCheck()')
}
async run($defer) {
const settings = this._settings
assert(
@@ -394,7 +448,9 @@ exports.VmBackup = class VmBackup {
await this._callWriters(async writer => {
await writer.beforeBackup()
$defer(() => writer.afterBackup())
$defer(async () => {
await writer.afterBackup()
})
}, 'writer.beforeBackup()')
await this._fetchJobSnapshots()
@@ -430,5 +486,11 @@ exports.VmBackup = class VmBackup {
await this._fetchJobSnapshots()
await this._removeUnusedSnapshots()
}
await this._healthCheck()
}
}
exports.VmBackup = VmBackup
decorateMethodsWith(VmBackup, {
run: defer,
})

View File

@@ -1,3 +1,5 @@
'use strict'
const { asyncMap } = require('@xen-orchestra/async-map')
const { DIR_XO_CONFIG_BACKUPS } = require('./RemoteAdapter.js')

View File

@@ -1,4 +1,6 @@
'use strict'
exports.isMetadataFile = filename => filename.endsWith('.json')
exports.isVhdFile = filename => filename.endsWith('.vhd')
exports.isXvaFile = filename => filename.endsWith('.xva')
exports.isXvaSumFile = filename => filename.endsWith('.xva.cheksum')
exports.isXvaSumFile = filename => filename.endsWith('.xva.checksum')

View File

@@ -1,11 +1,16 @@
'use strict'
require('@xen-orchestra/log/configure.js').catchGlobalErrors(
require('@xen-orchestra/log').createLogger('xo:backups:worker')
)
const Disposable = require('promise-toolbox/Disposable.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
require('@vates/cached-dns.lookup').createCachedLookup().patchGlobal()
const Disposable = require('promise-toolbox/Disposable')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const { compose } = require('@vates/compose')
const { createDebounceResource } = require('@vates/disposable/debounceResource.js')
const { decorateMethodsWith } = require('@vates/decorate-with')
const { deduped } = require('@vates/disposable/deduped.js')
const { getHandler } = require('@xen-orchestra/fs')
const { parseDuration } = require('@vates/parse-duration')
@@ -58,11 +63,6 @@ class BackupWorker {
}).run()
}
getAdapter = Disposable.factory(this.getAdapter)
getAdapter = deduped(this.getAdapter, remote => [remote.url])
getAdapter = compose(this.getAdapter, function (resource) {
return this.debounceResource(resource)
})
async *getAdapter(remote) {
const handler = getHandler(remote, this.#remoteOptions)
await handler.sync()
@@ -77,11 +77,6 @@ class BackupWorker {
}
}
getXapi = Disposable.factory(this.getXapi)
getXapi = deduped(this.getXapi, ({ url }) => [url])
getXapi = compose(this.getXapi, function (resource) {
return this.debounceResource(resource)
})
async *getXapi({ credentials: { username: user, password }, ...opts }) {
const xapi = new Xapi({
...this.#xapiOptions,
@@ -103,6 +98,30 @@ class BackupWorker {
}
}
decorateMethodsWith(BackupWorker, {
getAdapter: compose([
Disposable.factory,
[deduped, remote => [remote.url]],
[
compose,
function (resource) {
return this.debounceResource(resource)
},
],
]),
getXapi: compose([
Disposable.factory,
[deduped, xapi => [xapi.url]],
[
compose,
function (resource) {
return this.debounceResource(resource)
},
],
]),
})
// Received message:
//
// Message {

View File

@@ -1,5 +1,7 @@
const cancelable = require('promise-toolbox/cancelable.js')
const CancelToken = require('promise-toolbox/CancelToken.js')
'use strict'
const cancelable = require('promise-toolbox/cancelable')
const CancelToken = require('promise-toolbox/CancelToken')
// Similar to `Promise.all` + `map` but pass a cancel token to the callback
//

View File

@@ -1,14 +1,18 @@
'use strict'
/* eslint-env jest */
const rimraf = require('rimraf')
const tmp = require('tmp')
const fs = require('fs-extra')
const uuid = require('uuid')
const { getHandler } = require('@xen-orchestra/fs')
const { pFromCallback } = require('promise-toolbox')
const crypto = require('crypto')
const { RemoteAdapter } = require('./RemoteAdapter')
const { VHDFOOTER, VHDHEADER } = require('./tests.fixtures.js')
const { VhdFile, Constants, VhdDirectory, VhdAbstract } = require('vhd-lib')
const { checkAliases } = require('./_cleanVm')
const { dirname, basename } = require('path')
let tempDir, adapter, handler, jobId, vdiId, basePath
@@ -30,12 +34,17 @@ afterEach(async () => {
await handler.forget()
})
const uniqueId = () => crypto.randomBytes(16).toString('hex')
const uniqueId = () => uuid.v1()
const uniqueIdBuffer = () => uuid.v1({}, Buffer.alloc(16))
async function generateVhd(path, opts = {}) {
let vhd
const dataPath = opts.useAlias ? path + '.data' : path
let dataPath = path
if (opts.useAlias) {
await handler.mkdir(dirname(path) + '/data/')
dataPath = dirname(path) + '/data/' + basename(path)
}
if (opts.mode === 'directory') {
await handler.mkdir(dataPath)
vhd = new VhdDirectory(handler, dataPath)
@@ -45,10 +54,9 @@ async function generateVhd(path, opts = {}) {
}
vhd.header = { ...VHDHEADER, ...opts.header }
vhd.footer = { ...VHDFOOTER, ...opts.footer }
vhd.footer.uuid = Buffer.from(crypto.randomBytes(16))
vhd.footer = { ...VHDFOOTER, ...opts.footer, uuid: uniqueIdBuffer() }
if (vhd.header.parentUnicodeName) {
if (vhd.header.parentUuid) {
vhd.footer.diskType = Constants.DISK_TYPES.DIFFERENCING
} else {
vhd.footer.diskType = Constants.DISK_TYPES.DYNAMIC
@@ -70,48 +78,53 @@ test('It remove broken vhd', async () => {
await handler.writeFile(`${basePath}/notReallyAVhd.vhd`, 'I AM NOT A VHD')
expect((await handler.list(basePath)).length).toEqual(1)
let loggued = ''
const onLog = message => {
const logInfo = message => {
loggued += message
}
await adapter.cleanVm('/', { remove: false, onLog })
expect(loggued).toEqual(`error while checking the VHD with path /${basePath}/notReallyAVhd.vhd`)
await adapter.cleanVm('/', { remove: false, logInfo, logWarn: logInfo, lock: false })
expect(loggued).toEqual(`VHD check error`)
// not removed
expect((await handler.list(basePath)).length).toEqual(1)
// really remove it
await adapter.cleanVm('/', { remove: true, onLog })
await adapter.cleanVm('/', { remove: true, logInfo, logWarn: () => {}, lock: false })
expect((await handler.list(basePath)).length).toEqual(0)
})
test('it remove vhd with missing or multiple ancestors', async () => {
// one with a broken parent
// one with a broken parent, should be deleted
await generateVhd(`${basePath}/abandonned.vhd`, {
header: {
parentUnicodeName: 'gone.vhd',
parentUid: Buffer.from(crypto.randomBytes(16)),
parentUuid: uniqueIdBuffer(),
},
})
// one orphan, which is a full vhd, no parent
// one orphan, which is a full vhd, no parent : should stay
const orphan = await generateVhd(`${basePath}/orphan.vhd`)
// a child to the orphan
// a child to the orphan in the metadata : should stay
await generateVhd(`${basePath}/child.vhd`, {
header: {
parentUnicodeName: 'orphan.vhd',
parentUid: orphan.footer.uuid,
parentUuid: orphan.footer.uuid,
},
})
await handler.writeFile(
`metadata.json`,
JSON.stringify({
mode: 'delta',
vhds: [`${basePath}/child.vhd`, `${basePath}/abandonned.vhd`],
}),
{ flags: 'w' }
)
// clean
let loggued = ''
const onLog = message => {
const logInfo = message => {
loggued += message + '\n'
}
await adapter.cleanVm('/', { remove: true, onLog })
await adapter.cleanVm('/', { remove: true, logInfo, logWarn: logInfo, lock: false })
const deletedOrphanVhd = loggued.match(/deleting orphan VHD/g) || []
expect(deletedOrphanVhd.length).toEqual(1) // only one vhd should have been deleted
const deletedAbandonnedVhd = loggued.match(/abandonned.vhd is missing/g) || []
expect(deletedAbandonnedVhd.length).toEqual(1) // and it must be abandonned.vhd
// we don't test the filew on disk, since they will all be marker as unused and deleted without a metadata.json file
})
@@ -139,19 +152,17 @@ test('it remove backup meta data referencing a missing vhd in delta backup', asy
await generateVhd(`${basePath}/child.vhd`, {
header: {
parentUnicodeName: 'orphan.vhd',
parentUid: orphan.footer.uuid,
parentUuid: orphan.footer.uuid,
},
})
let loggued = ''
const onLog = message => {
const logInfo = message => {
loggued += message + '\n'
}
await adapter.cleanVm('/', { remove: true, onLog })
let matched = loggued.match(/deleting unused VHD /g) || []
await adapter.cleanVm('/', { remove: true, logInfo, logWarn: logInfo, lock: false })
let matched = loggued.match(/deleting unused VHD/g) || []
expect(matched.length).toEqual(1) // only one vhd should have been deleted
matched = loggued.match(/abandonned.vhd is unused/g) || []
expect(matched.length).toEqual(1) // and it must be abandonned.vhd
// a missing vhd cause clean to remove all vhds
await handler.writeFile(
@@ -162,14 +173,14 @@ test('it remove backup meta data referencing a missing vhd in delta backup', asy
`${basePath}/deleted.vhd`, // in metadata but not in vhds
`${basePath}/orphan.vhd`,
`${basePath}/child.vhd`,
// abandonned.json is not here
// abandonned.vhd is not here anymore
],
}),
{ flags: 'w' }
)
loggued = ''
await adapter.cleanVm('/', { remove: true, onLog })
matched = loggued.match(/deleting unused VHD /g) || []
await adapter.cleanVm('/', { remove: true, logInfo, logWarn: () => {}, lock: false })
matched = loggued.match(/deleting unused VHD/g) || []
expect(matched.length).toEqual(2) // all vhds (orphan and child ) should have been deleted
})
@@ -193,30 +204,28 @@ test('it merges delta of non destroyed chain', async () => {
const child = await generateVhd(`${basePath}/child.vhd`, {
header: {
parentUnicodeName: 'orphan.vhd',
parentUid: orphan.footer.uuid,
parentUuid: orphan.footer.uuid,
},
})
// a grand child
await generateVhd(`${basePath}/grandchild.vhd`, {
header: {
parentUnicodeName: 'child.vhd',
parentUid: child.footer.uuid,
parentUuid: child.footer.uuid,
},
})
let loggued = []
const onLog = message => {
const logInfo = message => {
loggued.push(message)
}
await adapter.cleanVm('/', { remove: true, onLog })
expect(loggued[0]).toEqual(`the parent /${basePath}/orphan.vhd of the child /${basePath}/child.vhd is unused`)
expect(loggued[1]).toEqual(`incorrect size in metadata: 12000 instead of 209920`)
await adapter.cleanVm('/', { remove: true, logInfo, logWarn: logInfo, lock: false })
expect(loggued[0]).toEqual(`incorrect backup size in metadata`)
loggued = []
await adapter.cleanVm('/', { remove: true, merge: true, onLog })
const [unused, merging] = loggued
expect(unused).toEqual(`the parent /${basePath}/orphan.vhd of the child /${basePath}/child.vhd is unused`)
expect(merging).toEqual(`merging /${basePath}/child.vhd into /${basePath}/orphan.vhd`)
await adapter.cleanVm('/', { remove: true, merge: true, logInfo, logWarn: () => {}, lock: false })
const [merging] = loggued
expect(merging).toEqual(`merging VHD chain`)
const metadata = JSON.parse(await handler.readFile(`metadata.json`))
// size should be the size of children + grand children after the merge
@@ -235,12 +244,8 @@ test('it finish unterminated merge ', async () => {
`metadata.json`,
JSON.stringify({
mode: 'delta',
size: undefined,
vhds: [
`${basePath}/orphan.vhd`, // grand child should not be merged
`${basePath}/child.vhd`,
// orphan is not here, he should be merged in child
],
size: 209920,
vhds: [`${basePath}/orphan.vhd`, `${basePath}/child.vhd`],
})
)
@@ -250,7 +255,7 @@ test('it finish unterminated merge ', async () => {
const child = await generateVhd(`${basePath}/child.vhd`, {
header: {
parentUnicodeName: 'orphan.vhd',
parentUid: orphan.footer.uuid,
parentUuid: orphan.footer.uuid,
},
})
// a merge in progress file
@@ -266,8 +271,7 @@ test('it finish unterminated merge ', async () => {
})
)
// a unfinished merging
await adapter.cleanVm('/', { remove: true, merge: true })
await adapter.cleanVm('/', { remove: true, merge: true, logWarn: () => {}, lock: false })
// 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
@@ -279,12 +283,17 @@ test('it finish unterminated merge ', async () => {
// each of the vhd can be a file, a directory, an alias to a file or an alias to a directory
// the message an resulting files should be identical to the output with vhd files which is tested independantly
describe('tests mulitple combination ', () => {
describe('tests multiple combination ', () => {
for (const useAlias of [true, false]) {
for (const vhdMode of ['file', 'directory']) {
test(`alias : ${useAlias}, mode: ${vhdMode}`, async () => {
// a broken VHD
const brokenVhdDataPath = basePath + useAlias ? 'broken.data' : 'broken.vhd'
if (useAlias) {
await handler.mkdir(basePath + '/data')
}
const brokenVhdDataPath = basePath + (useAlias ? '/data/broken.vhd' : '/broken.vhd')
if (vhdMode === 'directory') {
await handler.mkdir(brokenVhdDataPath)
} else {
@@ -302,9 +311,10 @@ describe('tests mulitple combination ', () => {
mode: vhdMode,
header: {
parentUnicodeName: 'gone.vhd',
parentUid: crypto.randomBytes(16),
parentUuid: uniqueIdBuffer(),
},
})
// an ancestor of a vhd present in metadata
const ancestor = await generateVhd(`${basePath}/ancestor.vhd`, {
useAlias,
@@ -315,7 +325,7 @@ describe('tests mulitple combination ', () => {
mode: vhdMode,
header: {
parentUnicodeName: 'ancestor.vhd' + (useAlias ? '.alias.vhd' : ''),
parentUid: ancestor.footer.uuid,
parentUuid: ancestor.footer.uuid,
},
})
// a grand child vhd in metadata
@@ -324,7 +334,7 @@ describe('tests mulitple combination ', () => {
mode: vhdMode,
header: {
parentUnicodeName: 'child.vhd' + (useAlias ? '.alias.vhd' : ''),
parentUid: child.footer.uuid,
parentUuid: child.footer.uuid,
},
})
@@ -339,7 +349,7 @@ describe('tests mulitple combination ', () => {
mode: vhdMode,
header: {
parentUnicodeName: 'cleanAncestor.vhd' + (useAlias ? '.alias.vhd' : ''),
parentUid: cleanAncestor.footer.uuid,
parentUuid: cleanAncestor.footer.uuid,
},
})
@@ -367,7 +377,8 @@ describe('tests mulitple combination ', () => {
],
})
)
await adapter.cleanVm('/', { remove: true, merge: true })
await adapter.cleanVm('/', { remove: true, merge: true, logWarn: () => {}, lock: false })
const metadata = JSON.parse(await handler.readFile(`metadata.json`))
// size should be the size of children + grand children + clean after the merge
@@ -379,14 +390,16 @@ describe('tests mulitple combination ', () => {
const survivors = await handler.list(basePath)
// console.log(survivors)
if (useAlias) {
const dataSurvivors = await handler.list(basePath + '/data')
// the goal of the alias : do not move a full folder
expect(survivors).toContain('ancestor.vhd.data')
expect(survivors).toContain('grandchild.vhd.data')
expect(survivors).toContain('cleanAncestor.vhd.data')
expect(dataSurvivors).toContain('ancestor.vhd')
expect(dataSurvivors).toContain('grandchild.vhd')
expect(dataSurvivors).toContain('cleanAncestor.vhd')
expect(survivors).toContain('clean.vhd.alias.vhd')
expect(survivors).toContain('child.vhd.alias.vhd')
expect(survivors).toContain('grandchild.vhd.alias.vhd')
expect(survivors.length).toEqual(6)
expect(survivors.length).toEqual(4) // the 3 ok + data
expect(dataSurvivors.length).toEqual(3) // the 3 ok + data
} else {
expect(survivors).toContain('clean.vhd')
expect(survivors).toContain('child.vhd')
@@ -401,7 +414,31 @@ describe('tests mulitple combination ', () => {
test('it cleans orphan merge states ', async () => {
await handler.writeFile(`${basePath}/.orphan.vhd.merge.json`, '')
await adapter.cleanVm('/', { remove: true })
await adapter.cleanVm('/', { remove: true, logWarn: () => {}, lock: false })
expect(await handler.list(basePath)).toEqual([])
})
test('check Aliases should work alone', async () => {
await handler.mkdir('vhds')
await handler.mkdir('vhds/data')
await generateVhd(`vhds/data/ok.vhd`)
await VhdAbstract.createAlias(handler, 'vhds/ok.alias.vhd', 'vhds/data/ok.vhd')
await VhdAbstract.createAlias(handler, 'vhds/missingData.alias.vhd', 'vhds/data/nonexistent.vhd')
await generateVhd(`vhds/data/missingalias.vhd`)
await checkAliases(['vhds/missingData.alias.vhd', 'vhds/ok.alias.vhd'], 'vhds/data', {
remove: true,
handler,
logWarn: () => {},
})
// only ok have suvived
const alias = (await handler.list('vhds')).filter(f => f.endsWith('.vhd'))
expect(alias.length).toEqual(1)
const data = await handler.list('vhds/data')
expect(data.length).toEqual(1)
})

View File

@@ -1,19 +1,27 @@
const assert = require('assert')
'use strict'
const sum = require('lodash/sum')
const UUID = require('uuid')
const { asyncMap } = require('@xen-orchestra/async-map')
const { Constants, mergeVhd, openVhd, VhdAbstract, VhdFile } = require('vhd-lib')
const { Constants, openVhd, VhdAbstract, VhdFile } = require('vhd-lib')
const { isVhdAlias, resolveVhdAlias } = require('vhd-lib/aliases')
const { dirname, resolve } = require('path')
const { DISK_TYPES } = Constants
const { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } = require('./_backupType.js')
const { limitConcurrency } = require('limit-concurrency-decorator')
const { mergeVhdChain } = require('vhd-lib/merge')
const { Task } = require('./Task.js')
const { Disposable } = require('promise-toolbox')
const handlerPath = require('@xen-orchestra/fs/path')
// checking the size of a vhd directory is costly
// 1 Http Query per 1000 blocks
// we only check size of all the vhd are VhdFiles
function shouldComputeVhdsSize(vhds) {
function shouldComputeVhdsSize(handler, vhds) {
if (handler.isEncrypted) {
return false
}
return vhds.every(vhd => vhd instanceof VhdFile)
}
@@ -21,88 +29,51 @@ const computeVhdsSize = (handler, vhdPaths) =>
Disposable.use(
vhdPaths.map(vhdPath => openVhd(handler, vhdPath)),
async vhds => {
if (shouldComputeVhdsSize(vhds)) {
if (shouldComputeVhdsSize(handler, vhds)) {
const sizes = await asyncMap(vhds, vhd => vhd.getSize())
return sum(sizes)
}
}
)
// chain is an array of VHDs from child to parent
//
// the whole chain will be merged into parent, parent will be renamed to child
// and all the others will deleted
async function mergeVhdChain(chain, { handler, onLog, remove, merge }) {
assert(chain.length >= 2)
let child = chain[0]
const parent = chain[chain.length - 1]
const children = chain.slice(0, -1).reverse()
chain
.slice(1)
.reverse()
.forEach(parent => {
onLog(`the parent ${parent} of the child ${child} is unused`)
})
// chain is [ ancestor, child_1, ..., child_n ]
async function _mergeVhdChain(handler, chain, { logInfo, remove, merge, mergeBlockConcurrency }) {
if (merge) {
// `mergeVhd` does not work with a stream, either
// - make it accept a stream
// - or create synthetic VHD which is not a stream
if (children.length !== 1) {
// TODO: implement merging multiple children
children.length = 1
child = children[0]
}
onLog(`merging ${child} into ${parent}`)
logInfo(`merging VHD chain`, { chain })
let done, total
const handle = setInterval(() => {
if (done !== undefined) {
onLog(`merging ${child}: ${done}/${total}`)
logInfo('merge in progress', {
done,
parent: chain[0],
progress: Math.round((100 * done) / total),
total,
})
}
}, 10e3)
const mergedSize = await mergeVhd(
handler,
parent,
handler,
child,
// children.length === 1
// ? child
// : await createSyntheticStream(handler, children),
{
try {
return await mergeVhdChain(handler, chain, {
logInfo,
mergeBlockConcurrency,
onProgress({ done: d, total: t }) {
done = d
total = t
},
}
)
clearInterval(handle)
await Promise.all([
VhdAbstract.rename(handler, parent, child),
asyncMap(children.slice(0, -1), child => {
onLog(`the VHD ${child} is unused`)
if (remove) {
onLog(`deleting unused VHD ${child}`)
return VhdAbstract.unlink(handler, child)
}
}),
])
return mergedSize
removeUnused: remove,
})
} finally {
clearInterval(handle)
}
}
}
const noop = Function.prototype
const INTERRUPTED_VHDS_REG = /^\.(.+)\.merge.json$/
const listVhds = async (handler, vmDir) => {
const listVhds = async (handler, vmDir, logWarn) => {
const vhds = new Set()
const aliases = {}
const interruptedVhds = new Map()
await asyncMap(
@@ -119,37 +90,118 @@ const listVhds = async (handler, vmDir) => {
const list = await handler.list(vdiDir, {
filter: file => isVhdFile(file) || INTERRUPTED_VHDS_REG.test(file),
})
aliases[vdiDir] = list.filter(vhd => isVhdAlias(vhd)).map(file => `${vdiDir}/${file}`)
list.forEach(file => {
await asyncMap(list, async file => {
const res = INTERRUPTED_VHDS_REG.exec(file)
if (res === null) {
vhds.add(`${vdiDir}/${file}`)
} else {
interruptedVhds.set(`${vdiDir}/${res[1]}`, `${vdiDir}/${file}`)
try {
const mergeState = JSON.parse(await handler.readFile(`${vdiDir}/${file}`))
interruptedVhds.set(`${vdiDir}/${res[1]}`, {
statePath: `${vdiDir}/${file}`,
chain: mergeState.chain,
})
} catch (error) {
// fall back to a non resuming merge
vhds.add(`${vdiDir}/${file}`)
logWarn('failed to read existing merge state', { path: file, error })
}
}
})
}
)
)
return { vhds, interruptedVhds }
return { vhds, interruptedVhds, aliases }
}
async function checkAliases(
aliasPaths,
targetDataRepository,
{ handler, logInfo = noop, logWarn = console.warn, remove = false }
) {
const aliasFound = []
for (const alias of aliasPaths) {
const target = await resolveVhdAlias(handler, alias)
if (!isVhdFile(target)) {
logWarn('alias references non VHD target', { alias, target })
if (remove) {
logInfo('removing alias and non VHD target', { alias, target })
await handler.unlink(target)
await handler.unlink(alias)
}
continue
}
try {
const { dispose } = await openVhd(handler, target)
try {
await dispose()
} catch (e) {
// error during dispose should not trigger a deletion
}
} catch (error) {
logWarn('missing or broken alias target', { alias, target, error })
if (remove) {
try {
await VhdAbstract.unlink(handler, alias)
} catch (error) {
if (error.code !== 'ENOENT') {
logWarn('error deleting alias target', { alias, target, error })
}
}
}
continue
}
aliasFound.push(resolve('/', target))
}
const vhds = await handler.list(targetDataRepository, {
ignoreMissing: true,
prependDir: true,
})
await asyncMap(vhds, async path => {
if (!aliasFound.includes(path)) {
logWarn('no alias references VHD', { path })
if (remove) {
logInfo('deleting unused VHD', { path })
await VhdAbstract.unlink(handler, path)
}
}
})
}
exports.checkAliases = checkAliases
const defaultMergeLimiter = limitConcurrency(1)
exports.cleanVm = async function cleanVm(
vmDir,
{ fixMetadata, remove, merge, mergeLimiter = defaultMergeLimiter, onLog = noop }
{
fixMetadata,
remove,
merge,
mergeBlockConcurrency,
mergeLimiter = defaultMergeLimiter,
logInfo = noop,
logWarn = console.warn,
}
) {
const limitedMergeVhdChain = mergeLimiter(mergeVhdChain)
const limitedMergeVhdChain = mergeLimiter(_mergeVhdChain)
const handler = this._handler
const vhdsToJSons = new Set()
const vhdById = new Map()
const vhdParents = { __proto__: null }
const vhdChildren = { __proto__: null }
const { vhds, interruptedVhds } = await listVhds(handler, vmDir)
const { vhds, interruptedVhds, aliases } = await listVhds(handler, vmDir, logWarn)
// remove broken VHDs
await asyncMap(vhds, async path => {
@@ -167,12 +219,31 @@ exports.cleanVm = async function cleanVm(
}
vhdChildren[parent] = path
}
// Detect VHDs with the same UUIDs
//
// Due to a bug introduced in a1bcd35e2
const duplicate = vhdById.get(UUID.stringify(vhd.footer.uuid))
let vhdKept = vhd
if (duplicate !== undefined) {
logWarn('uuid is duplicated', { uuid: UUID.stringify(vhd.footer.uuid) })
if (duplicate.containsAllDataOf(vhd)) {
logWarn(`should delete ${path}`)
vhdKept = duplicate
vhds.delete(path)
} else if (vhd.containsAllDataOf(duplicate)) {
logWarn(`should delete ${duplicate._path}`)
vhds.delete(duplicate._path)
} else {
logWarn('same ids but different content')
}
}
vhdById.set(UUID.stringify(vhdKept.footer.uuid), vhdKept)
})
} catch (error) {
vhds.delete(path)
onLog(`error while checking the VHD with path ${path}`, { error })
logWarn('VHD check error', { path, error })
if (error?.code === 'ERR_ASSERTION' && remove) {
onLog(`deleting broken ${path}`)
logInfo('deleting broken VHD', { path })
return VhdAbstract.unlink(handler, path)
}
}
@@ -181,21 +252,25 @@ exports.cleanVm = async function cleanVm(
// remove interrupted merge states for missing VHDs
for (const interruptedVhd of interruptedVhds.keys()) {
if (!vhds.has(interruptedVhd)) {
const statePath = interruptedVhds.get(interruptedVhd)
const { statePath } = interruptedVhds.get(interruptedVhd)
interruptedVhds.delete(interruptedVhd)
onLog('orphan merge state', {
logWarn('orphan merge state', {
mergeStatePath: statePath,
missingVhdPath: interruptedVhd,
})
if (remove) {
onLog(`deleting orphan merge state ${statePath}`)
logInfo('deleting orphan merge state', { statePath })
await handler.unlink(statePath)
}
}
}
// @todo : add check for data folder of alias not referenced in a valid alias
// check if alias are correct
// check if all vhd in data subfolder have a corresponding alias
await asyncMap(Object.keys(aliases), async dir => {
await checkAliases(aliases[dir], `${dir}/data`, { handler, logInfo, logWarn, remove })
})
// remove VHDs with missing ancestors
{
@@ -216,9 +291,9 @@ exports.cleanVm = async function cleanVm(
if (!vhds.has(parent)) {
vhds.delete(vhdPath)
onLog(`the parent ${parent} of the VHD ${vhdPath} is missing`)
logWarn('parent VHD is missing', { parent, child: vhdPath })
if (remove) {
onLog(`deleting orphan VHD ${vhdPath}`)
logInfo('deleting orphan VHD', { path: vhdPath })
deletions.push(VhdAbstract.unlink(handler, vhdPath))
}
}
@@ -236,6 +311,7 @@ exports.cleanVm = async function cleanVm(
}
const jsons = new Set()
let mustInvalidateCache = false
const xvas = new Set()
const xvaSums = []
const entries = await handler.list(vmDir, {
@@ -255,7 +331,7 @@ exports.cleanVm = async function cleanVm(
// check is not good enough to delete the file, the best we can do is report
// it
if (!(await this.isValidXva(path))) {
onLog(`the XVA with path ${path} is potentially broken`)
logWarn('XVA might be broken', { path })
}
})
@@ -269,7 +345,7 @@ exports.cleanVm = async function cleanVm(
try {
metadata = JSON.parse(await handler.readFile(json))
} catch (error) {
onLog(`failed to read metadata file ${json}`, { error })
logWarn('failed to read backup metadata', { path: json, error })
jsons.delete(json)
return
}
@@ -280,10 +356,11 @@ exports.cleanVm = async function cleanVm(
if (xvas.has(linkedXva)) {
unusedXvas.delete(linkedXva)
} else {
onLog(`the XVA linked to the metadata ${json} is missing`)
logWarn('the XVA linked to the backup is missing', { backup: json, xva: linkedXva })
if (remove) {
onLog(`deleting incomplete backup ${json}`)
logInfo('deleting incomplete backup', { path: json })
jsons.delete(json)
mustInvalidateCache = true
await handler.unlink(json)
}
}
@@ -303,9 +380,10 @@ exports.cleanVm = async function cleanVm(
vhdsToJSons[path] = json
})
} else {
onLog(`Some VHDs linked to the metadata ${json} are missing`, { missingVhds })
logWarn('some VHDs linked to the backup are missing', { backup: json, missingVhds })
if (remove) {
onLog(`deleting incomplete backup ${json}`)
logInfo('deleting incomplete backup', { path: json })
mustInvalidateCache = true
jsons.delete(json)
await handler.unlink(json)
}
@@ -317,7 +395,7 @@ exports.cleanVm = async function cleanVm(
const unusedVhdsDeletion = []
const toMerge = []
{
// VHD chains (as list from child to ancestor) to merge indexed by last
// VHD chains (as list from oldest to most recent) to merge indexed by most recent
// ancestor
const vhdChainsToMerge = { __proto__: null }
@@ -341,14 +419,14 @@ exports.cleanVm = async function cleanVm(
if (child !== undefined) {
const chain = getUsedChildChainOrDelete(child)
if (chain !== undefined) {
chain.push(vhd)
chain.unshift(vhd)
return chain
}
}
onLog(`the VHD ${vhd} is unused`)
logWarn('unused VHD', { path: vhd })
if (remove) {
onLog(`deleting unused VHD ${vhd}`)
logInfo('deleting unused VHD', { path: vhd })
unusedVhdsDeletion.push(VhdAbstract.unlink(handler, vhd))
}
}
@@ -359,7 +437,13 @@ exports.cleanVm = async function cleanVm(
// merge interrupted VHDs
for (const parent of interruptedVhds.keys()) {
vhdChainsToMerge[parent] = [vhdChildren[parent], parent]
// before #6349 the chain wasn't in the mergeState
const { chain, statePath } = interruptedVhds.get(parent)
if (chain === undefined) {
vhdChainsToMerge[parent] = [parent, vhdChildren[parent]]
} else {
vhdChainsToMerge[parent] = chain.map(vhdPath => handlerPath.resolveFromFile(statePath, vhdPath))
}
}
Object.values(vhdChainsToMerge).forEach(chain => {
@@ -372,9 +456,15 @@ exports.cleanVm = async function cleanVm(
const metadataWithMergedVhd = {}
const doMerge = async () => {
await asyncMap(toMerge, async chain => {
const merged = await limitedMergeVhdChain(chain, { handler, onLog, remove, merge })
const merged = await limitedMergeVhdChain(handler, chain, {
logInfo,
logWarn,
remove,
merge,
mergeBlockConcurrency,
})
if (merged !== undefined) {
const metadataPath = vhdsToJSons[chain[0]] // all the chain should have the same metada file
const metadataPath = vhdsToJSons[chain[chain.length - 1]] // all the chain should have the same metada file
metadataWithMergedVhd[metadataPath] = true
}
})
@@ -384,18 +474,18 @@ exports.cleanVm = async function cleanVm(
...unusedVhdsDeletion,
toMerge.length !== 0 && (merge ? Task.run({ name: 'merge' }, doMerge) : doMerge()),
asyncMap(unusedXvas, path => {
onLog(`the XVA ${path} is unused`)
logWarn('unused XVA', { path })
if (remove) {
onLog(`deleting unused XVA ${path}`)
logInfo('deleting unused XVA', { path })
return handler.unlink(path)
}
}),
asyncMap(xvaSums, path => {
// no need to handle checksums for XVAs deleted by the script, they will be handled by `unlink()`
if (!xvas.has(path.slice(0, -'.checksum'.length))) {
onLog(`the XVA checksum ${path} is unused`)
logInfo('unused XVA checksum', { path })
if (remove) {
onLog(`deleting unused XVA checksum ${path}`)
logInfo('deleting unused XVA checksum', { path })
return handler.unlink(path)
}
}
@@ -417,7 +507,11 @@ exports.cleanVm = async function cleanVm(
if (mode === 'full') {
// a full backup : check size
const linkedXva = resolve('/', vmDir, xva)
fileSystemSize = await handler.getSize(linkedXva)
try {
fileSystemSize = await handler.getSize(linkedXva)
} 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)
@@ -429,11 +523,15 @@ exports.cleanVm = async function cleanVm(
// don't warn if the size has changed after a merge
if (!merged && fileSystemSize !== size) {
onLog(`incorrect size in metadata: ${size ?? 'none'} instead of ${fileSystemSize}`)
logWarn('incorrect backup size in metadata', {
path: metadataPath,
actual: size ?? 'none',
expected: fileSystemSize,
})
}
}
} catch (error) {
onLog(`failed to get size of ${metadataPath}`, { error })
logWarn('failed to get backup size', { backup: metadataPath, error })
return
}
@@ -443,11 +541,16 @@ exports.cleanVm = async function cleanVm(
try {
await handler.writeFile(metadataPath, JSON.stringify(metadata), { flags: 'w' })
} catch (error) {
onLog(`failed to update size in backup metadata ${metadataPath} after merge`, { error })
logWarn('failed to update backup size in metadata', { path: metadataPath, error })
}
}
})
// purge cache if a metadata file has been deleted
if (mustInvalidateCache) {
await handler.unlink(vmDir + '/cache.json.gz')
}
return {
// boolean whether some VHDs were merged (or should be merged)
merge: toMerge.length !== 0,

View File

@@ -1,14 +1,18 @@
const compareVersions = require('compare-versions')
'use strict'
const find = require('lodash/find.js')
const groupBy = require('lodash/groupBy.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors')
const omit = require('lodash/omit.js')
const { asyncMap } = require('@xen-orchestra/async-map')
const { CancelToken } = require('promise-toolbox')
const { compareVersions } = require('compare-versions')
const { createVhdStreamWithLength } = require('vhd-lib')
const { defer } = require('golike-defer')
const { cancelableMap } = require('./_cancelableMap.js')
const { Task } = require('./Task.js')
const { pick } = require('lodash')
const TAG_BASE_DELTA = 'xo:base_delta'
exports.TAG_BASE_DELTA = TAG_BASE_DELTA
@@ -17,6 +21,17 @@ const TAG_COPY_SRC = 'xo:copy_of'
exports.TAG_COPY_SRC = TAG_COPY_SRC
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
}
exports.exportDeltaVm = async function exportDeltaVm(
vm,
@@ -50,17 +65,6 @@ exports.exportDeltaVm = async function exportDeltaVm(
return
}
// If the VDI name start with `[NOBAK]`, do not export it.
if (vdi.name_label.startsWith('[NOBAK]')) {
// FIXME: find a way to not create the VDI snapshot in the
// first time.
//
// The snapshot must not exist otherwise it could break the
// next export.
ignoreErrors.call(vdi.$destroy())
return
}
vbds[vbd.$ref] = vbd
const vdiRef = vdi.$ref
@@ -165,6 +169,12 @@ exports.importDeltaVm = defer(async function importDeltaVm(
}
}
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 => {
@@ -179,19 +189,25 @@ exports.importDeltaVm = defer(async function importDeltaVm(
let suspendVdi
if (vmRecord.power_state === 'Suspended') {
const vdi = vdiRecords[vmRecord.suspend_VDI]
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: mapVdisSrs[vdi.uuid] ?? sr.$ref,
if (vdi === undefined) {
Task.warning('Suspend VDI not available for this suspended VM', {
vm: pick(vmRecord, 'uuid', 'name_label'),
})
)
$defer.onFailure(() => suspendVdi.$destroy())
} 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,
})
)
$defer.onFailure(() => suspendVdi.$destroy())
}
}
// 1. Create the VM.
@@ -255,7 +271,7 @@ exports.importDeltaVm = defer(async function importDeltaVm(
[TAG_BASE_DELTA]: undefined,
[TAG_COPY_SRC]: vdi.uuid,
},
SR: mapVdisSrs[vdi.uuid] ?? sr.$ref,
SR: mapVdisSrRefs[vdi.uuid] ?? sr.$ref,
})
)
$defer.onFailure(() => newVdi.$destroy())

View File

@@ -1,3 +1,5 @@
'use strict'
const { utcFormat, utcParse } = require('d3-time-format')
// Format a date in ISO 8601 in a safe way to be used in filenames

View File

@@ -1,6 +1,10 @@
'use strict'
const eos = require('end-of-stream')
const { PassThrough } = require('stream')
const { debug } = require('@xen-orchestra/log').createLogger('xo:backups:forkStreamUnpipe')
// create a new readable stream from an existing one which may be piped later
//
// in case of error in the new readable stream, it will simply be unpiped
@@ -9,18 +13,23 @@ exports.forkStreamUnpipe = function forkStreamUnpipe(stream) {
const { forks = 0 } = stream
stream.forks = forks + 1
debug('forking', { forks: stream.forks })
const proxy = new PassThrough()
stream.pipe(proxy)
eos(stream, error => {
if (error !== undefined) {
debug('error on original stream, destroying fork', { error })
proxy.destroy(error)
}
})
eos(proxy, _ => {
stream.forks--
eos(proxy, error => {
debug('end of stream, unpiping', { error, forks: --stream.forks })
stream.unpipe(proxy)
if (stream.forks === 0) {
debug('no more forks, destroying original stream')
stream.destroy(new Error('no more consumers for this stream'))
}
})

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