Compare commits

...

147 Commits

Author SHA1 Message Date
Pierre Donias
7603974370 feat(xo-server): 5.35.0 2019-02-08 13:45:04 +01:00
Pierre Donias
6cb5639243 feat(xo-server-auth-saml): 0.5.3 2019-02-08 13:44:11 +01:00
Pierre Donias
0c5a37d8a3 feat(fs): 0.6.1 2019-02-08 13:42:52 +01:00
Pierre Donias
78cc7fe664 feat(xen-api): 0.24.2 2019-02-08 13:39:09 +01:00
Julien Fontanet
2d51bef390 feat(xo-server/snapshotVm): retry when VM_SNAPSHOT_WITH_QUIESCE_FAILED (#3952)
Fixes #3938
2019-02-08 13:16:08 +01:00
Julien Fontanet
bc68fff079 fix(CHANGELOG.unreleased): move items from fixes to enhancement 2019-02-08 11:19:49 +01:00
Nicolas Raynaud
0a63acac73 fix(OVA import): fix tar file size parsing (#3941)
Avoids relying on PAX header, uses a weird encoding in the normal filesize header.

Fixes #3900
2019-02-07 22:51:38 +01:00
Julien Fontanet
e484b073e1 feat(xo-server/moveVdi): retry on TOO_MANY_STORAGE_MIGRATES (#3940)
Fix xoa-support#1222
2019-02-07 17:46:41 +01:00
Julien Fontanet
b2813d7cc0 feat(xo-server/snapshotVm): detect and destroy broken quiesced snapshots (#3937)
Fixes #3936
2019-02-07 17:37:09 +01:00
Julien Fontanet
29b941868d feat(xen-api): work-around empty VBD#VDI XenServer issue (#3950) 2019-02-07 16:44:42 +01:00
Julien Fontanet
37af47ecff fix(xo-server/remote.getAllInfo): reduce timeout to 5s 2019-02-07 14:17:16 +01:00
Julien Fontanet
8eb28d40da feat(vhd-cli): display version in usage 2019-02-07 14:17:15 +01:00
Jon Sands
383dd7b38e feat(docs/cloudinit): various changes (#3942)
- Removed the "CloudInit support is available in the 4.11 release and higher" message - is anyone still using XOA this many years old?  
- Added a note about our change to the configdrive type, and notes for users who have customized their datasources to look for only openstack (inspired by a customer)  
- Updated all screenshots to the modern XOA UI.
2019-02-07 11:52:04 +01:00
Rajaa.BARHTAOUI
b13b3fe9f6 feat(xo-web/vm/disk): display device name (#3946)
Fixes #3902
2019-02-07 09:41:26 +01:00
Enishowk
04a5f55b16 feat(xo-web/VM): expose the creation date of the VM (#3947)
Fixes #3932
2019-02-07 09:19:09 +01:00
Rajaa.BARHTAOUI
4ab1de918e feat(xo-web/home): set description on bulk snapshot (#3933)
Fixes #3925
2019-02-06 10:41:35 +01:00
Julien Fontanet
44fc5699fd chore(xo-server): upgrade jest-worker to 24.0.0
Fixes #3929.

Related to jest#7182.
2019-02-05 18:32:03 +01:00
Julien Fontanet
dd6c3ff434 feat(docs/backups): add link to introduction video 2019-02-05 17:21:12 +01:00
Enishowk
d747b937ee fix(@xen-orchestra/fs): don't ignore mount options (#3931)
Fixes #3935
2019-02-05 17:19:09 +01:00
Julien Fontanet
9aa63d0354 fix(xo-server/backup NG): fix error condition (#3939)
Fix #3875
2019-02-05 16:44:28 +01:00
Julien Fontanet
36220ac1c5 feat(docs/from sources): add cifs-utils dependency 2019-02-05 10:22:40 +01:00
Julien Fontanet
d8eb5d4934 chore(.editorconfig): uniformize indent to 2 spaces 2019-02-04 18:01:09 +01:00
Julien Fontanet
b580ea98a7 fix(xo-server-auth-saml): AssertionConsumerServiceURL matches callback URL
Fixes xoa-support#1235
2019-02-04 16:21:26 +01:00
Julien Fontanet
0ad68c2280 chore(PULL_REQUEST_TEMPLATE): CHANGELOG → CHANGELOG.unreleased.md 2019-02-04 13:47:57 +01:00
Julien Fontanet
b16f1899ac chore(CHANGELOG.unreleased): contains unreleased changes
Inspired by [Prettier](https://github.com/prettier/prettier/blob/master/CHANGELOG.unreleased.md).

Changes should go there instead of CHANGELOG, they will be moved during the release process.

This change should prevent the issue where old updated PRs added changes at incorrect positions in the CHANGELOG.
2019-02-04 13:43:29 +01:00
ETL
7e740a429a feat(docs): add coalescing troubleshooting tip (#3927) 2019-02-04 13:26:34 +01:00
Pierre Donias
61b1bd2533 fix(xo-web/host): show actual host's RAM usage (#3924)
Instead of the sum of each VM's RAM usage
2019-02-01 12:03:52 +01:00
Pierre Donias
d6ddba8e56 feat(xo-server): 5.34.1 2019-02-01 09:31:42 +01:00
Julien Fontanet
d10c7f3898 fix(xo-server/package.files): config.json → config.toml 2019-02-01 09:12:18 +01:00
Pierre Donias
2b2c2c42f1 chore(CHANGELOG): 5.31.0 2019-01-31 15:37:39 +01:00
Pierre Donias
efc65a0669 feat(xo-web): 5.34.0 2019-01-31 15:32:03 +01:00
Pierre Donias
d8e0727d4d feat(xo-server): 5.34.0 2019-01-31 15:31:28 +01:00
Pierre Donias
a46a95b6fa feat(xo-vmdk-to-vhd): 0.1.6 2019-01-31 15:30:17 +01:00
Pierre Donias
ab4c3bc416 feat(xen-api): 0.24.1 2019-01-31 15:28:18 +01:00
Pierre Donias
8a2f012b79 feat(vhd-lib): 0.5.1 2019-01-31 15:25:40 +01:00
Julien Fontanet
5fd9eea3f6 fix(changelog): vhd-lib 0.5.1 2019-01-31 15:23:50 +01:00
Julien Fontanet
1b12aa90de fix(xo-server/backup NG): delete interrupted replications (#3923)
Fixes xoa-support#1215
2019-01-31 15:12:22 +01:00
Julien Fontanet
dfb6d1b58e chore(xo-server/backup NG): document other_config entries 2019-01-31 14:43:52 +01:00
Pierre Donias
53add3bf2d fix(xo-web/otp): enterprise plan (#3922) 2019-01-31 14:18:03 +01:00
Julien Fontanet
63414d5db9 Update CHANGELOG.md 2019-01-31 14:05:03 +01:00
Rajaa.BARHTAOUI
1312df8c88 feat(xo-web/vm/advanced): set VCPUs-params:mask under Advanced (#3254)
Fixes #3241
2019-01-31 11:33:45 +01:00
Julien Fontanet
94d36c3458 fix(CHANGELOG): add missing issue link 2019-01-31 11:20:25 +01:00
Rajaa.BARHTAOUI
0c3623e0f8 feat(xo-web/settings/remotes): NFS: display default options (#3921)
Fixes #3631
2019-01-31 11:14:01 +01:00
Julien Fontanet
ad01fcc880 feat(vhd-lib/Vhd#{_getBatEntry,containsBlock}()): never throw (#3920)
Fix xoa-support#1214

Fix `createSyntheticStream` when the disk has been resize in the chain.
2019-01-31 10:36:51 +01:00
Enishowk
b7f20a963f fix(xo-web): dont call user.getAll to get current user (#3918)
Fixes #3573
2019-01-30 17:50:22 +01:00
Enishowk
c51aad61eb chore(xo-server): improve style of login buttons (#3913)
xoa-support#1138
2019-01-30 17:34:22 +01:00
Julien Fontanet
12bbdba82c feat(xo-server/backupNg.getLogs): extract a subset of logs (#3914)
Related to xoa-support#1024
2019-01-30 14:35:39 +01:00
badrAZ
eb3760ee4a feat(xo-web/SR): display iscsi paths (#3829)
Fixes #3659
2019-01-30 11:02:34 +01:00
Rajaa.BARHTAOUI
af00adcfcc feat(xo-web/host/network): add PIF speed (#3901)
Fixes #3887
2019-01-30 10:11:39 +01:00
badrAZ
93985e1a51 feat(xo-web/exportVm): support zstd compression (#3891)
Fixes #3773
2019-01-28 15:42:39 +01:00
Pierre Donias
36f7af8576 feat(xo-web): www-xo notifications (#3904)
Related to xoa#21
2019-01-28 15:29:52 +01:00
Julien Fontanet
0608cda6d7 fix(yarn.lock): update 2019-01-28 15:03:22 +01:00
Enishowk
9565823900 feat(xo-server,xo-web): add OTP authentication (#3879)
Fixes #2044
2019-01-28 14:50:37 +01:00
Julien Fontanet
48b833c3b3 fix(xo-server/xapiObjectToXo): make dependents param optional
Fixes some legacy backups issues
2019-01-25 16:50:17 +01:00
Julien Fontanet
9990439594 fix(xen-api#putResource): correctly handle redirection for non-stream body
Fixes xoa-support#1179
2019-01-25 16:46:12 +01:00
badrAZ
e9fb37325d feat(xo-web/host): ability to enable/disable multipathing (#3865)
See #3659
2019-01-25 15:46:44 +01:00
Rajaa.BARHTAOUI
810c976d37 fix(xo-web/VM): snapshot with memory: "invalid parameters" (#3903) 2019-01-25 14:56:18 +01:00
Pierre Donias
c1cbc3b5aa feat(xo-web/host,vm): breadcrumb (#3898) 2019-01-25 11:21:58 +01:00
badrAZ
8298db1f2e feat(xo-web/copyVm): support zstd compression (#3889)
See #3773
2019-01-24 11:27:38 +01:00
badrAZ
47844fcf69 feat(xo-web/backup-ng): support zstd compression for full backups (#3883)
See #3773
2019-01-23 15:17:52 +01:00
Pierre Donias
f26f8b2af9 fix(xo-server/_getXenUpdates): call readAll with context (#3897) 2019-01-23 15:16:41 +01:00
Rajaa.BARHTAOUI
b246e84c48 feat(xo-web/render-xo-item): add pool name to templates (#3896)
Fixes #3894
2019-01-23 14:58:05 +01:00
Emerson Kfuri
6545e47193 feat(docs): document mountOptions.useSudo setting
Related to #3419
2019-01-22 16:57:48 +01:00
Julien Fontanet
0a78c2bb94 fix(xo-server/moveVdi): wait deletion before creating new VBD
Fixes #3426
2019-01-22 16:44:41 +01:00
Julien Fontanet
36102e0dff fix(xo-server/createCloudInitConfigDrive): fix label (11 chars) 2019-01-22 16:43:58 +01:00
badrAZ
bce0bf05e5 feat(xo-server): provide zstd compression for VM export (#3878)
See #3773
2019-01-22 15:53:28 +01:00
Julien Fontanet
55b762f490 chore: update Prettier to 1.16.1 2019-01-22 15:52:12 +01:00
Julien Fontanet
ad58f6a147 fix(xo-server/fatfs): fix param default value 2019-01-22 14:36:57 +01:00
Julien Fontanet
d67038c78d chore: update dependencies 2019-01-22 13:46:43 +01:00
Jon Sands
4badf48c45 feat(xo-server/vm.create): change cloud-init configdrive to nocloud type (#3877)
Related to #3872
2019-01-22 11:42:25 +01:00
Rajaa.BARHTAOUI
449dd2998b feat(xo-web/editable): Select supports multiple values (#3623) 2019-01-22 10:03:43 +01:00
Julien Fontanet
c613b4cab3 feat(PR template): add test to checklist 2019-01-21 17:34:24 +01:00
Julien Fontanet
370a0e8851 fix(log/README): transports.console → transportConsole 2019-01-21 17:34:24 +01:00
badrAZ
eb4f9f0b18 chore(xo-web/copyVm): remove unused code (#3888) 2019-01-21 16:25:05 +01:00
Rajaa.BARHTAOUI
bbf5e82c5d feat(xo-web): show type and copiable ID of unknown items (#3856)
Fixes #3833
2019-01-21 16:06:26 +01:00
badrAZ
27835bfbd0 fix(xo-web/backup-ng): smart settings not saved when editing backup (#3886)
Fixes #3885
2019-01-21 15:10:02 +01:00
Julien Fontanet
f663dbe7a7 fix(xo-server/backup NG): properly handle missing VHD in chain
Fixes #3875
2019-01-18 16:49:03 +01:00
Julien Fontanet
02e7eeec51 feat({log,xo-server}/serializeError): handle code property 2019-01-18 16:49:02 +01:00
Julien Fontanet
29a7bd0cb2 fix(xen-api/wrapError): better handling of non-XAPI errors
Fixes #3880
2019-01-18 09:14:33 +01:00
Julien Fontanet
0fd22b9fd8 feat(xo-web): basic plugins filtering (#3871) 2019-01-16 15:25:19 +01:00
Nicolas Raynaud
df809baaaf fix(OVA import): support big files (#3504)
Fixes #3468

- fix bug in VMDK header parsing
- parse extended tar headers
2019-01-16 10:31:52 +01:00
Julien Fontanet
cfd956631b chore: update http-request-plus to 0.7.1 2019-01-16 09:39:32 +01:00
Rajaa.BARHTAOUI
23687f62f0 fix(xo-web/xoa/update): enable downgrade button for ending trial (#3867) 2019-01-16 09:30:36 +01:00
Danp2
5aabea1121 fix(xo-web/messages): backuping → backing up (#3870) 2019-01-16 09:22:23 +01:00
Rajaa.BARHTAOUI
eac07a96de feat(xo-web/render-xo-item): optionally show free memory (#3832)
Fixes #3264

Show free memory in `SelectHost`.
2019-01-15 13:40:53 +01:00
Rajaa.BARHTAOUI
e9d1876699 fix(xo-web/link): middle click opens two tabs in firefox (#3825)
Fixes #3450
2019-01-15 10:35:31 +01:00
Julien Fontanet
f25705d559 fix(plugins/README): link to the documentation (#3866)
They cannot be installed from the npm repository.
2019-01-14 21:07:43 +01:00
badrAZ
ea48136797 feat(xo-server/host): ability to enable/disable multipathing (#3858)
See #3659
2019-01-14 15:01:44 +01:00
Julien Fontanet
270185d9dc feat(xen-api/examples/export-vm): add --gzip and --zstd 2019-01-14 14:57:45 +01:00
Julien Fontanet
308d53dc6b feat(xo-server/config): expose cookie options (#3861)
Fixes #3850
2019-01-14 14:31:39 +01:00
Julien Fontanet
a97c5f4cd9 feat(xo-server/sample.config): use TOML instead of YAML (#3860) 2019-01-14 14:00:16 +01:00
Julien Fontanet
3d7e0df4dd feat(xo-server/api): enable compression by default
Related to #3720
2019-01-14 12:24:31 +01:00
Julien Fontanet
53a0b7eed0 chore: update dependencies 2019-01-14 11:13:18 +01:00
Rajaa.BARHTAOUI
1ed2a6b620 fix(xo-web/new/sr): no redirection if the SR creation failed or canceled (#3853)
Fixes #3843
2019-01-14 10:53:11 +01:00
badrAZ
76f9017482 feat(xo-server/api): emit event on call/resolution of xo method (#3770) 2019-01-14 10:40:50 +01:00
Pierre Donias
86425f5d51 chore(CHANGELOG): staging 2019-01-11 15:27:44 +01:00
Pierre Donias
d77894310f feat(xo-web): 5.33.0 2019-01-11 15:18:50 +01:00
Pierre Donias
cbee05e0c7 feat(xo-server): 5.33.0 2019-01-11 15:18:12 +01:00
Pierre Donias
7f36dddefb feat(fs): 0.6.0 2019-01-11 15:16:53 +01:00
Pierre Donias
56b2dbd4fd feat(xen-api): 0.24.0 2019-01-11 15:14:54 +01:00
Pierre Donias
df67908784 feat(vhd-cli): 0.2.0 2019-01-11 15:13:03 +01:00
Pierre Donias
5dcdb81843 feat(vhd-lib): 0.5.0 2019-01-11 15:11:35 +01:00
Julien Fontanet
7f85935e43 feat(xo-server/backup NG): check complete VHD chain (#3820)
This triggers a full export if the chain appears incomplete.
2019-01-11 15:04:24 +01:00
marcpezin
2ab820d511 New store documentation update (#3837)
* new store documentation

* grammar fix
2019-01-11 15:03:39 +01:00
Rajaa.BARHTAOUI
db19668453 fix(xo-web/new/sr): remove warning modal for already used path (#3851)
Fixes #3844
2019-01-11 14:20:29 +01:00
Rajaa.BARHTAOUI
0f0ad029a6 feat(xo-web/new/sr): add tooltip for reattach action button (#3852)
Fixes #3845
2019-01-11 14:00:44 +01:00
badrAZ
062a98839c fix(xo-web/backup-ng): typo in the backup form (#3855)
Fixes #3854
2019-01-11 13:13:52 +01:00
Julien Fontanet
c38f21b76b feat(xen-api#{get,put}Resource): add 24h timeout on HTTP requests (#3834)
The Xen API does not support longer requests and it's necessary to properly detect broken requests.
2019-01-11 10:19:38 +01:00
Enishowk
e34a0a6e33 feat({fs,xo-server,xo-web}/remotes): show free space and usage (#3767)
Fixes #3055
2019-01-11 10:07:22 +01:00
badrAZ
f3c3889531 fix(xo-server/new-vm): fix creating a VM on a different local SR (#3827)
Fixes #3084
2019-01-10 15:39:40 +01:00
Rajaa.BARHTAOUI
7de22013a4 feat(xo-web/home): grouped VM snapshot (#3787)
Fixes #3778
2019-01-09 14:27:05 +01:00
Julien Fontanet
711d88765b chore(xen-api/examples/export-vdi): various stats 2019-01-09 13:50:36 +01:00
badrAZ
e9a7421be6 fix(xo-server/xen-servers): handle loosing connection case (#3841)
Fixes #3839
2019-01-09 13:46:17 +01:00
badrAZ
83fe490dbb fix(xo-server/xen-servers): fix connecting status (#3838) 2019-01-09 11:51:20 +01:00
Rajaa.BARHTAOUI
20c92c668b fix(xo-web/self): sort Resource Sets in Self view (#3823)
Fixes #3818
2019-01-09 10:01:46 +01:00
Enishowk
5d0f1c9cce feat(xo-web): autofocus username login page (#3836)
Fixes #3835
2019-01-09 09:44:38 +01:00
badrAZ
20317448a1 feat(xo-web/backup-ng/health): add number of lone snapshots to tab title (#3824)
Fixes #3500
2019-01-08 16:53:03 +01:00
badrAZ
b8a3d00343 feat(xo-server,xo-web/snapshot): ability to snapshot the VM memory (#3812)
Fixes #3795
2019-01-08 16:10:00 +01:00
Julien Fontanet
b459f74a8c fix(xen-api/getRecord): pass type and ref to _wrapRecord 2019-01-08 14:52:53 +01:00
Jon Sands
96a966b9ea fix(docs/CR/manual seed): update to Backup NG (#3758)
Fix #3754
2019-01-08 10:07:06 +01:00
Rajaa.BARHTAOUI
1af42617c2 feat(xo-web/vm/advanced): ACL management from VM view (#3774)
Fixes #3040
2019-01-07 15:35:24 +01:00
Julien Fontanet
100dd38c33 fix(fs/smb-mount#_sync): issue if already mounted
`mount.cifs` does not pring `already mounted`.
2019-01-07 14:43:27 +01:00
Julien Fontanet
2bf4950f4f chore: update dependencies 2019-01-07 10:46:12 +01:00
Enishowk
e8a98945f5 feat(@xen-orchestra/fs): implement SMB on top of mount (#3708)
Fixes #2257
2019-01-07 10:05:18 +01:00
Julien Fontanet
6c2e493576 chore(xo-server): use TOML for vendor conf 2019-01-04 17:28:38 +01:00
Julien Fontanet
f4fb0a1c79 feat(xo-web/xoa/update): make pkgs list copiable 2019-01-03 15:33:28 +01:00
Julien Fontanet
3a9b68fd8d fix(xo-server-auth-ldap/test-cli): pforOwn → pForOwn
Introduced in b3004a38aa, never released.
2019-01-02 16:01:44 +01:00
Julien Fontanet
c9f0481efc fix(xo-web/vm/advanced): only show nested virt for HVM
Fixes #3743
2018-12-24 10:49:25 +01:00
Julien Fontanet
93724218b3 chore(xo-server/backup NG): remove work-around
It should no longer be necessary.
2018-12-24 01:15:25 +01:00
Julien Fontanet
74b97e6518 feat(fs): can use sudo for mount/umount (#3819)
Fixes #3419
2018-12-21 17:24:12 +01:00
Julien Fontanet
f096bdc5d8 chore: update dependencies 2018-12-21 14:44:30 +01:00
badrAZ
0c64596a17 fix(xo-server/Xapi#checkpointVm): returns the created snapshot (#3810) 2018-12-21 11:27:54 +01:00
badrAZ
267be8e904 chore(backup-ng/logs): create a dedicated folder (#3816) 2018-12-21 11:07:34 +01:00
badrAZ
841a8ed1a5 feat(xo-web/backup-ng/logs): move restore logs to the restore tab (#3802)
Fixes #3772
2018-12-20 17:17:33 +01:00
Julien Fontanet
c55daae734 Update CHANGELOG.md 2018-12-20 15:31:05 +01:00
Julien Fontanet
9762fb1912 feat(xo-server): 5.32.2 2018-12-20 14:27:21 +01:00
Julien Fontanet
4047d11b2f feat(xo-server/conf): support TOML 2018-12-20 14:27:21 +01:00
badrAZ
d4215eb452 fix(xo-web/backup-ng): fix compression (#3809) 2018-12-20 09:39:31 +01:00
Julien Fontanet
17014c2819 feat(xo-web): 5.32.1 2018-12-19 16:49:52 +01:00
Julien Fontanet
4d24803b72 feat(xo-server): 5.32.1 2018-12-19 16:48:41 +01:00
Julien Fontanet
6b30465ef2 fix(xo-web/backup-ng): handle compression default value (#3796)
Missing from c5a21922d and e0a3b8ace.
2018-12-19 16:47:54 +01:00
Julien Fontanet
eac9ce597b chore(xen-api/_watchEvents): dont return a promise
This removes potential unhandled rejection warnings.
2018-12-19 14:24:49 +01:00
Pierre Donias
5c8c18fbe6 feat: XOA registration notification (#3808)
Fixes #3803
2018-12-19 14:08:28 +01:00
Shayan Ostadhassan
6f35a1a850 fix(xo-server/sr/probeHba): cast size to number (#3806)
Fixes #3805

This fixes an exception thrown when list of discovered HBAs return string
instead of integer as disk size.
2018-12-19 12:02:58 +01:00
Julien Fontanet
917701e2f6 feat(xo-server/vm.snapshot): add saveMemory param (#3807)
Related to #3795
2018-12-19 10:39:53 +01:00
Pierre Donias
4d4e87aa93 feat(xo-web): 5.32.0 2018-12-18 14:02:20 +01:00
Pierre Donias
e3bbfc6b19 feat(xo-server): 5.32.0 2018-12-18 14:01:38 +01:00
159 changed files with 4583 additions and 2261 deletions

View File

@@ -3,63 +3,12 @@
# Julien Fontanet's configuration
# https://gist.github.com/julien-f/8096213
# Top-most EditorConfig file.
root = true
# Common config.
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
# CoffeeScript
#
# https://github.com/polarmobile/coffeescript-style-guide/blob/master/README.md
[*.{,lit}coffee]
indent_size = 2
indent_style = space
# Markdown
[*.{md,mdwn,mdown,markdown}]
indent_size = 4
indent_style = space
# Package.json
#
# This indentation style is the one used by npm.
[package.json]
indent_size = 2
indent_style = space
# Pug (Jade)
[*.{jade,pug}]
indent_size = 2
indent_style = space
# JavaScript
#
# Two spaces seems to be the standard most common style, at least in
# Node.js (http://nodeguide.com/style.html#tabs-vs-spaces).
[*.{js,jsx,ts,tsx}]
indent_size = 2
indent_style = space
# Less
[*.less]
indent_size = 2
indent_style = space
# Sass
#
# Style used for http://libsass.com
[*.s[ac]ss]
indent_size = 2
indent_style = space
# YAML
#
# Only spaces are allowed.
[*.yaml]
indent_size = 2
indent_style = space

View File

@@ -15,6 +15,6 @@
},
"dependencies": {
"golike-defer": "^0.4.1",
"xen-api": "^0.23.0"
"xen-api": "^0.24.2"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/fs",
"version": "0.5.0",
"version": "0.6.1",
"license": "AGPL-3.0",
"description": "The File System for Xen Orchestra backups.",
"keywords": [],
@@ -21,6 +21,7 @@
},
"dependencies": {
"@marsaud/smb2": "^0.13.0",
"@sindresorhus/df": "^2.1.0",
"@xen-orchestra/async-map": "^0.0.0",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",

View File

@@ -0,0 +1,82 @@
import execa from 'execa'
import fs from 'fs-extra'
import { join } from 'path'
import { tmpdir } from 'os'
import LocalHandler from './local'
const sudoExeca = (command, args, opts) =>
execa('sudo', [command, ...args], opts)
export default class MountHandler extends LocalHandler {
constructor(
remote,
{
mountsDir = join(tmpdir(), 'xo-fs-mounts'),
useSudo = false,
...opts
} = {},
params
) {
super(remote, opts)
this._execa = useSudo ? sudoExeca : execa
this._params = {
...params,
options: [params.options, remote.options].filter(
_ => _ !== undefined
).join(','),
}
this._realPath = join(
mountsDir,
remote.id ||
Math.random()
.toString(36)
.slice(2)
)
}
async _forget() {
await this._execa('umount', ['--force', this._getRealPath()], {
env: {
LANG: 'C',
},
}).catch(error => {
if (
error == null ||
typeof error.stderr !== 'string' ||
!error.stderr.includes('not mounted')
) {
throw error
}
})
}
_getRealPath() {
return this._realPath
}
async _sync() {
await fs.ensureDir(this._getRealPath())
const { type, device, options, env } = this._params
return this._execa(
'mount',
['-t', type, device, this._getRealPath(), '-o', options],
{
env: {
LANG: 'C',
...env,
},
}
).catch(error => {
let stderr
if (
error == null ||
typeof (stderr = error.stderr) !== 'string' ||
!(stderr.includes('already mounted') || stderr.includes('busy'))
) {
throw error
}
})
}
}

View File

@@ -0,0 +1,9 @@
import path from 'path'
const { resolve } = path.posix
// normalize the path:
// - does not contains `.` or `..` (cannot escape root dir)
// - always starts with `/`
const normalizePath = path => resolve('/', path)
export { normalizePath as default }

View File

@@ -1,32 +1,30 @@
// @flow
// $FlowFixMe
import asyncMap from '@xen-orchestra/async-map'
import getStream from 'get-stream'
import asyncMap from '@xen-orchestra/async-map'
import path from 'path'
import { fromCallback, fromEvent, ignoreErrors, timeout } from 'promise-toolbox'
import { parse } from 'xo-remote-parser'
import { randomBytes } from 'crypto'
import { type Readable, type Writable } from 'stream'
import normalizePath from './_normalizePath'
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
const { dirname, resolve } = path.posix
const { dirname } = path.posix
type Data = Buffer | Readable | string
type FileDescriptor = {| fd: mixed, path: string |}
type LaxReadable = Readable & Object
type LaxWritable = Writable & Object
type RemoteInfo = { used?: number, size?: number }
type File = FileDescriptor | string
const checksumFile = file => file + '.checksum'
// normalize the path:
// - does not contains `.` or `..` (cannot escape root dir)
// - always starts with `/`
const normalizePath = path => resolve('/', path)
const DEFAULT_TIMEOUT = 6e5 // 10 min
const ignoreEnoent = error => {
@@ -222,6 +220,10 @@ export default class RemoteHandlerAbstract {
await this._forget()
}
async getInfo(): Promise<RemoteInfo> {
return timeout.call(this._getInfo(), this._timeout)
}
async getSize(file: File): Promise<number> {
return timeout.call(
this._getSize(typeof file === 'string' ? normalizePath(file) : file),
@@ -430,6 +432,10 @@ export default class RemoteHandlerAbstract {
// called to finalize the remote
async _forget(): Promise<void> {}
async _getInfo(): Promise<Object> {
return {}
}
async _getSize(file: File): Promise<number> {
throw new Error('Not implemented')
}

View File

@@ -16,6 +16,8 @@ class TestHandler extends AbstractHandler {
}
}
jest.useFakeTimers()
describe('closeFile()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
@@ -52,6 +54,18 @@ describe('createReadStream()', () => {
})
})
describe('getInfo()', () => {
it('throws in case of timeout', async () => {
const testHandler = new TestHandler({
getInfo: () => new Promise(() => {}),
})
const promise = testHandler.getInfo()
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('getSize()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({

View File

@@ -116,6 +116,26 @@ handlers.forEach(url => {
})
})
describe('#getInfo()', () => {
let info
beforeAll(async () => {
info = await handler.getInfo()
})
it('should return an object with info', async () => {
expect(typeof info).toBe('object')
})
it('should return correct type of attribute', async () => {
if (info.size !== undefined) {
expect(typeof info.size).toBe('number')
}
if (info.used !== undefined) {
expect(typeof info.used).toBe('number')
}
})
})
describe('#getSize()', () => {
beforeEach(() => handler.outputFile('file', TEST_DATA))

View File

@@ -1,19 +1,27 @@
// @flow
import execa from 'execa'
import type RemoteHandler from './abstract'
import RemoteHandlerLocal from './local'
import RemoteHandlerNfs from './nfs'
import RemoteHandlerSmb from './smb'
import RemoteHandlerSmbMount from './smb-mount'
export type { default as RemoteHandler } from './abstract'
export type Remote = { url: string }
const HANDLERS = {
file: RemoteHandlerLocal,
smb: RemoteHandlerSmb,
nfs: RemoteHandlerNfs,
}
try {
execa.sync('mount.cifs', ['-V'])
HANDLERS.smb = RemoteHandlerSmbMount
} catch (_) {
HANDLERS.smb = RemoteHandlerSmb
}
export const getHandler = (remote: Remote, ...rest: any): RemoteHandler => {
// FIXME: should be done in xo-remote-parser.
const type = remote.url.split('://')[0]

View File

@@ -1,3 +1,4 @@
import df from '@sindresorhus/df'
import fs from 'fs-extra'
import { fromEvent } from 'promise-toolbox'
@@ -46,6 +47,10 @@ export default class LocalHandler extends RemoteHandlerAbstract {
})
}
_getInfo() {
return df.file(this._getFilePath('/'))
}
async _getSize(file) {
const stats = await fs.stat(
this._getFilePath(typeof file === 'string' ? file : file.path)

View File

@@ -1,92 +1,20 @@
import execa from 'execa'
import fs from 'fs-extra'
import { join } from 'path'
import { tmpdir } from 'os'
import { parse } from 'xo-remote-parser'
import LocalHandler from './local'
import MountHandler from './_mount'
const DEFAULT_NFS_OPTIONS = 'vers=3'
export default class NfsHandler extends LocalHandler {
constructor(
remote,
{ mountsDir = join(tmpdir(), 'xo-fs-mounts'), ...opts } = {}
) {
super(remote, opts)
this._realPath = join(
mountsDir,
remote.id ||
Math.random()
.toString(36)
.slice(2)
)
export default class NfsHandler extends MountHandler {
constructor(remote, opts) {
const { host, port, path } = parse(remote.url)
super(remote, opts, {
type: 'nfs',
device: `${host}${port !== undefined ? ':' + port : ''}:${path}`,
options: DEFAULT_NFS_OPTIONS,
})
}
get type() {
return 'nfs'
}
_getRealPath() {
return this._realPath
}
async _mount() {
await fs.ensureDir(this._getRealPath())
const { host, path, port, options } = this._remote
return execa(
'mount',
[
'-t',
'nfs',
'-o',
DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
`${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(),
],
{
env: {
LANG: 'C',
},
}
).catch(error => {
if (
error == null ||
typeof error.stderr !== 'string' ||
!error.stderr.includes('already mounted')
) {
throw error
}
})
}
async _umount() {
await execa('umount', ['--force', this._getRealPath()], {
env: {
LANG: 'C',
},
}).catch(error => {
if (
error == null ||
typeof error.stderr !== 'string' ||
!error.stderr.includes('not mounted')
) {
throw error
}
})
}
async _forget() {
try {
await this._umount(this._remote)
} catch (_) {
// We have to go on...
}
}
async _sync() {
await this._mount()
return this._remote
}
}

View File

@@ -0,0 +1,25 @@
import { parse } from 'xo-remote-parser'
import MountHandler from './_mount'
import normalizePath from './_normalizePath'
export default class SmbMountHandler extends MountHandler {
constructor(remote, opts) {
const { domain = 'WORKGROUP', host, password, path, username } = parse(
remote.url
)
super(remote, opts, {
type: 'cifs',
device: '//' + host + normalizePath(path),
options: `domain=${domain}`,
env: {
USER: username,
PASSWD: password,
},
})
}
get type() {
return 'smb'
}
}

View File

@@ -75,7 +75,7 @@ catchGlobalErrors(transport)
```js
import transportConsole from '@xen-orchestra/log/transports/console'
configure(transports.console())
configure(transportConsole())
```
#### Email

View File

@@ -55,7 +55,8 @@ export const required = name => {
// -------------------------------------------------------------------
export const serializeError = error => ({
...error,
...error, // Copy enumerable properties.
code: error.code,
message: error.message,
name: error.name,
stack: error.stack,

View File

@@ -1,7 +1,68 @@
# ChangeLog
## *next*
## **5.31.0** (2019-01-31)
### Enhancements
- [Backup NG] Restore logs moved to restore tab [#3772](https://github.com/vatesfr/xen-orchestra/issues/3772) (PR [#3802](https://github.com/vatesfr/xen-orchestra/pull/3802))
- [Remotes] New SMB implementation that provides better stability and performance [#2257](https://github.com/vatesfr/xen-orchestra/issues/2257) (PR [#3708](https://github.com/vatesfr/xen-orchestra/pull/3708))
- [VM/advanced] ACL management from VM view [#3040](https://github.com/vatesfr/xen-orchestra/issues/3040) (PR [#3774](https://github.com/vatesfr/xen-orchestra/pull/3774))
- [VM / snapshots] Ability to save the VM memory [#3795](https://github.com/vatesfr/xen-orchestra/issues/3795) (PR [#3812](https://github.com/vatesfr/xen-orchestra/pull/3812))
- [Backup NG / Health] Show number of lone snapshots in tab label [#3500](https://github.com/vatesfr/xen-orchestra/issues/3500) (PR [#3824](https://github.com/vatesfr/xen-orchestra/pull/3824))
- [Login] Add autofocus on username input on login page [#3835](https://github.com/vatesfr/xen-orchestra/issues/3835) (PR [#3836](https://github.com/vatesfr/xen-orchestra/pull/3836))
- [Home/VM] Bulk snapshot: specify snapshots' names [#3778](https://github.com/vatesfr/xen-orchestra/issues/3778) (PR [#3787](https://github.com/vatesfr/xen-orchestra/pull/3787))
- [Remotes] Show free space and disk usage on remote [#3055](https://github.com/vatesfr/xen-orchestra/issues/3055) (PR [#3767](https://github.com/vatesfr/xen-orchestra/pull/3767))
- [New SR] Add tooltip for reattach action button [#3845](https://github.com/vatesfr/xen-orchestra/issues/3845) (PR [#3852](https://github.com/vatesfr/xen-orchestra/pull/3852))
- [VM migration] Display hosts' free memory [#3264](https://github.com/vatesfr/xen-orchestra/issues/3264) (PR [#3832](https://github.com/vatesfr/xen-orchestra/pull/3832))
- [Plugins] New field to filter displayed plugins (PR [#3832](https://github.com/vatesfr/xen-orchestra/pull/3871))
- Ability to copy ID of "unknown item"s [#3833](https://github.com/vatesfr/xen-orchestra/issues/3833) (PR [#3856](https://github.com/vatesfr/xen-orchestra/pull/3856))
- [Cloud-Init] switch config drive type to `nocloud` to prepare for the passing of network config (PR [#3877](https://github.com/vatesfr/xen-orchestra/pull/3877))
- [UI] Show pool name next to templates' names [#3894](https://github.com/vatesfr/xen-orchestra/issues/3894) (PR [#3896](https://github.com/vatesfr/xen-orchestra/pull/3896))
- [Backup NG] Support zstd compression for full backups [#3773](https://github.com/vatesfr/xen-orchestra/issues/3773) (PR [#3883](https://github.com/vatesfr/xen-orchestra/pull/3883))
- [VM] Ability to copy a VM with zstd compression [#3773](https://github.com/vatesfr/xen-orchestra/issues/3773) (PR [#3889](https://github.com/vatesfr/xen-orchestra/pull/3889))
- [VM & Host] "Pool > Host" breadcrumb at the top of the page (PR [#3898](https://github.com/vatesfr/xen-orchestra/pull/3898))
- [Hosts] Ability to enable/disable host multipathing [#3659](https://github.com/vatesfr/xen-orchestra/issues/3659) (PR [#3865](https://github.com/vatesfr/xen-orchestra/pull/3865))
- [Login] Add OTP authentication [#2044](https://github.com/vatesfr/xen-orchestra/issues/2044) (PR [#3879](https://github.com/vatesfr/xen-orchestra/pull/3879))
- [Notifications] New notification page to provide important information about XOA (PR [#3904](https://github.com/vatesfr/xen-orchestra/pull/3904))
- [VM] Ability to export a VM with zstd compression [#3773](https://github.com/vatesfr/xen-orchestra/issues/3773) (PR [#3891](https://github.com/vatesfr/xen-orchestra/pull/3891))
- [Host/network] Display PIF speed [#3887](https://github.com/vatesfr/xen-orchestra/issues/3887) (PR [#3901](https://github.com/vatesfr/xen-orchestra/pull/3901))
- [SR] Display iscsi paths and mark the SR with a yellow dot if one path is not available. [#3659](https://github.com/vatesfr/xen-orchestra/issues/3659) (PR [#3829](https://github.com/vatesfr/xen-orchestra/pull/3829))
- [UI] Unifies the Signin buttons (PR [#3913](https://github.com/vatesfr/xen-orchestra/pull/3913))
- [Settings/remotes] NFS: display default option on placeholder [#3631](https://github.com/vatesfr/xen-orchestra/issues/3631) (PR [#3921](https://github.com/vatesfr/xen-orchestra/pull/3921))
- [VM/advanced] Ability to pin vCPU to physical cores [#3241](https://github.com/vatesfr/xen-orchestra/issues/3241) (PR [#3254](https://github.com/vatesfr/xen-orchestra/pull/3254))
### Bug fixes
- [Self] Display sorted Resource Sets [#3818](https://github.com/vatesfr/xen-orchestra/issues/3818) (PR [#3823](https://github.com/vatesfr/xen-orchestra/pull/3823))
- [Servers] Correctly report connecting status (PR [#3838](https://github.com/vatesfr/xen-orchestra/pull/3838))
- [Servers] Fix cannot reconnect to a server after connection has been lost [#3839](https://github.com/vatesfr/xen-orchestra/issues/3839) (PR [#3841](https://github.com/vatesfr/xen-orchestra/pull/3841))
- [New VM] Fix `NO_HOSTS_AVAILABLE()` error when creating a VM on a local SR from template on another local SR [#3084](https://github.com/vatesfr/xen-orchestra/issues/3084) (PR [#3827](https://github.com/vatesfr/xen-orchestra/pull/3827))
- [Backup NG] Fix typo in the form [#3854](https://github.com/vatesfr/xen-orchestra/issues/3854) (PR [#3855](https://github.com/vatesfr/xen-orchestra/pull/3855))
- [New SR] No warning when creating a NFS SR on a path that is already used as NFS SR [#3844](https://github.com/vatesfr/xen-orchestra/issues/3844) (PR [#3851](https://github.com/vatesfr/xen-orchestra/pull/3851))
- [New SR] No redirection if the SR creation failed or canceled [#3843](https://github.com/vatesfr/xen-orchestra/issues/3843) (PR [#3853](https://github.com/vatesfr/xen-orchestra/pull/3853))
- [Home] Fix two tabs opened by middle click in Firefox [#3450](https://github.com/vatesfr/xen-orchestra/issues/3450) (PR [#3825](https://github.com/vatesfr/xen-orchestra/pull/3825))
- [XOA] Enable downgrade for ending trial (PR [#3867](https://github.com/vatesfr/xen-orchestra/pull/3867))
- [OVA import] allow import of big files [#3468](https://github.com/vatesfr/xen-orchestra/issues/3468) (PR [#3504](https://github.com/vatesfr/xen-orchestra/pull/3504))
- [Backup NG] Smart settings not saved when editing a backup job [#3885](https://github.com/vatesfr/xen-orchestra/issues/3885) (PR [#3886](https://github.com/vatesfr/xen-orchestra/pull/3886))
- [VM/snapshot] New snapshot with memory: fix "invalid parameters" error (PR [#3903](https://github.com/vatesfr/xen-orchestra/pull/3903))
- [VM creation] Broken CloudInit config drive when VM created on local SR
- [Legacy Backup] Fix error when restoring a backup
- [Home] Fix `user.getAll` error when user is not admin [#3573](https://github.com/vatesfr/xen-orchestra/issues/3573) (PR [#3918](https://github.com/vatesfr/xen-orchestra/pull/3918))
- [Backup NG] Fix restore issue when a disk has grown [#3910](https://github.com/vatesfr/xen-orchestra/issues/3910) (PR [#3920](https://github.com/vatesfr/xen-orchestra/pull/3920))
- [Backup NG] Delete _importing_ VMs due to interrupted CR/DR (PR [#3923](https://github.com/vatesfr/xen-orchestra/pull/3923))
### Released packages
- vhd-cli v0.2.0
- @xen-orchestra/fs v0.6.0
- vhd-lib v0.5.1
- xoa-updater v0.15.0
- xen-api v0.24.1
- xo-vmdk-to-vhd v0.1.6
- xo-server v5.34.0
- xo-web v5.34.0
## **5.30.0** (2018-12-20)
### Enhancements
@@ -17,6 +78,7 @@
- [Backup NG] Add a link to the documentation [#3789](https://github.com/vatesfr/xen-orchestra/issues/3789) (PR [#3790](https://github.com/vatesfr/xen-orchestra/pull/3790))
- [Backup NG] Ability to copy schedule/job id to the clipboard [#3753](https://github.com/vatesfr/xen-orchestra/issues/3753) (PR [#3791](https://github.com/vatesfr/xen-orchestra/pull/3791))
- [Backup NG / logs] Merge the job log status with the display details button [#3797](https://github.com/vatesfr/xen-orchestra/issues/3797) (PR [#3800](https://github.com/vatesfr/xen-orchestra/pull/3800))
- [XOA] Notification banner when XOA is not registered [#3803](https://github.com/vatesfr/xen-orchestra/issues/3803) (PR [#3808](https://github.com/vatesfr/xen-orchestra/pull/3808))
### Bug fixes
@@ -34,6 +96,8 @@
## **5.29.0** (2018-11-29)
### Enhancements
- [Perf alert] Ability to trigger an alarm if a host/VM/SR usage value is below the threshold [#3612](https://github.com/vatesfr/xen-orchestra/issues/3612) (PR [#3675](https://github.com/vatesfr/xen-orchestra/pull/3675))
- [Home/VMs] Display pool's name [#2226](https://github.com/vatesfr/xen-orchestra/issues/2226) (PR [#3709](https://github.com/vatesfr/xen-orchestra/pull/3709))
- [Servers] Prevent new connection if pool is already connected [#2238](https://github.com/vatesfr/xen-orchestra/issues/2238) (PR [#3724](https://github.com/vatesfr/xen-orchestra/pull/3724))

29
CHANGELOG.unreleased.md Normal file
View File

@@ -0,0 +1,29 @@
> This file contains all changes that have not been released yet.
### Enhancements
- [Home] Set description on bulk snapshot [#3925](https://github.com/vatesfr/xen-orchestra/issues/3925) (PR [#3933](https://github.com/vatesfr/xen-orchestra/pull/3933))
- Work-around the XenServer issue when `VBD#VDI` is an empty string instead of an opaque reference (PR [#3950](https://github.com/vatesfr/xen-orchestra/pull/3950))
- [VDI migration] Retry when XenServer fails with `TOO_MANY_STORAGE_MIGRATES` (PR [#3940](https://github.com/vatesfr/xen-orchestra/pull/3940))
- [VM]
- [General] The creation date of the VM is now visible [#3932](https://github.com/vatesfr/xen-orchestra/issues/3932) (PR [#3947](https://github.com/vatesfr/xen-orchestra/pull/3947))
- [Disks] Display device name [#3902](https://github.com/vatesfr/xen-orchestra/issues/3902) (PR [#3946](https://github.com/vatesfr/xen-orchestra/pull/3946))
- [VM Snapshotting]
- Detect and destroy broken quiesced snapshot left by XenServer [#3936](https://github.com/vatesfr/xen-orchestra/issues/3936) (PR [#3937](https://github.com/vatesfr/xen-orchestra/pull/3937))
- Retry twice after a 1 minute delay if quiesce failed [#3938](https://github.com/vatesfr/xen-orchestra/issues/3938) (PR [#3952](https://github.com/vatesfr/xen-orchestra/pull/3952))
### Bug fixes
- [Import] Fix import of big OVA files
- [Host] Show the host's memory usage instead of the sum of the VMs' memory usage (PR [#3924](https://github.com/vatesfr/xen-orchestra/pull/3924))
- [SAML] Make `AssertionConsumerServiceURL` matches the callback URL
- [Backup NG] Correctly delete broken VHD chains [#3875](https://github.com/vatesfr/xen-orchestra/issues/3875) (PR [#3939](https://github.com/vatesfr/xen-orchestra/pull/3939))
- [Remotes] Don't ignore `mount` options [#3935](https://github.com/vatesfr/xen-orchestra/issues/3935) (PR [#3931](https://github.com/vatesfr/xen-orchestra/pull/3931))
### Released packages
- xen-api v0.24.2
- @xen-orchestra/fs v0.6.1
- xo-server-auth-saml v0.5.3
- xo-server v5.35.0
- xo-web v5.35.0

View File

@@ -4,10 +4,11 @@
- [ ] PR reference the relevant issue (e.g. `Fixes #007`)
- [ ] if UI changes, a screenshot has been added to the PR
- [ ] CHANGELOG:
- [ ] `CHANGELOG.unreleased.md`:
- enhancement/bug fix entry added
- list of packages to release updated (`${name} v${new version}`)
- [ ] documentation updated
- [ ] **I have tested added/updated features** (and impacted code)
### Process

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -33,7 +33,7 @@ Just remember this: **coalesce will happen every time a snapshot is removed**.
First check SMlog on the XenServer host for messages relating to VDI corruption or coalesce job failure. For example, by running `cat /var/log/SMlog | grep -i exception` or `cat /var/log/SMlog | grep -i error` on the XenServer host with the affected storage.
Coalesce jobs can also fail to run if the SR does not have enough free space. Check the problematic SR and make sure it has enough free space, generally 30% or more free is recommended depending on VM size.
Coalesce jobs can also fail to run if the SR does not have enough free space. Check the problematic SR and make sure it has enough free space, generally 30% or more free is recommended depending on VM size. You can check if this is the issue by searching `SMlog` with `grep -i coales /var/log/SMlog` (you may have to look at previous logs such as `SMlog.1`).
You can check if a coalesce job is currently active by running `ps axf | grep vhd` on the XenServer host and looking for a VHD process in the results (one of the resulting processes will be the grep command you just ran, ignore that one).

View File

@@ -1,5 +1,7 @@
# Backups
> Watch our [introduction video](https://www.youtube.com/watch?v=FfUqIwT8KzI) (45m) to Backup in Xen Orchestra!
This section is dedicated to all existing methods of rolling back or backing up your VMs in Xen Orchestra.
There are several ways to protect your VMs:

View File

@@ -1,7 +1,5 @@
# CloudInit
> CloudInit support is available in the 4.11 release and higher
Cloud-init is a program "that handles the early initialization of a cloud instance"[^n]. In other words, you can, on a "cloud-init"-ready template VM, pass a lot of data at first boot:
* setting the hostname
@@ -18,25 +16,27 @@ So it means very easily customizing your VM when you create it from a compatible
You only need to use a template of a VM with CloudInit installed inside it. [Check this blog post to learn how to install CloudInit](https://xen-orchestra.com/blog/centos-cloud-template-for-xenserver/).
**Note:** In XOA 5.31, we changed the cloud-init config drive type from [OpenStack](https://cloudinit.readthedocs.io/en/latest/topics/datasources/configdrive.html) to the [NoCloud](https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html) type. This will allow us to pass network configuration to VMs in the future. For 99% of users, including default cloud-init installs, this change will have no effect. However if you have previously modified your cloud-init installation in a VM template to only look for `openstack` drive types (for instance with the `datasource_list` setting in `/etc/cloud/cloud.cfg`) you need to modify it to also look for `nocloud`.
## Usage
First, select your compatible template (CloudInit ready) and name it:
![](https://xen-orchestra.com/blog/content/images/2015/12/template_choice.png)
![](./assets/cloud-init-1.png)
Then, activate the config drive and insert your SSH key. Or you can also use a custom CloudInit configuration:
![](https://xen-orchestra.com/blog/content/images/2016/02/CloudInit.png)
![](./assets/cloud-init-2.png)
> CloudInit configuration examples are [available here](http://cloudinit.readthedocs.org/en/latest/topics/examples.html).
You can extend the disk size (**in this case, the template disk was 8 GiB originally**):
You can extend the disk size (**in this case, the template disk was 8 GiB originally**). We'll extend it to 20GiB:
![](https://xen-orchestra.com/blog/content/images/2015/12/diskedition.png)
![](./assets/cloud-init-3.png)
Finally, create the VM:
![](https://xen-orchestra.com/blog/content/images/2015/12/recap.png)
![](./assets/cloud-init-4.png)
Now start the VM and SSH to its IP:

View File

@@ -4,42 +4,37 @@ Once Xen Orchestra is installed, you can configure some parameters in the config
## Configuration
The configuration file is located at `/etc/xo-server/config.yaml`.
**WARNING: YAML is very strict with indentation: use spaces, not tabs.**
The configuration file is located at `/etc/xo-server/config.toml`.
### User to run XO-server as
By default, XO-server runs as 'root'. You can change that by uncommenting these lines and choose whatever user/group you want:
```yaml
user: 'nobody'
group: 'nogroup'
```toml
user = 'nobody'
group = 'nogroup'
```
**Warning!** A non-privileged user:
* can't bind to a port < 1024
* can't mount NFS shares
**Warning!** A non-privileged user requires the use of ``sudo`` to mount NFS shares. See [installation from the sources](from_the_sources.md).
### HTTP listen address and port
By default, XO-server listens on all addresses (0.0.0.0) and runs on port 80. If you need to, you can change this in the `# Basic HTTP` section:
```yaml
host: '0.0.0.0'
port: 80
```toml
host = '0.0.0.0'
port = 80
```
### HTTPS
XO-server can also run in HTTPS (you can run HTTP and HTTPS at the same time) - just modify what's needed in the `# Basic HTTPS` section, this time with the certificates/keys you need and their path:
```yaml
host: '0.0.0.0'
port: 443
certificate: './certificate.pem'
key: './key.pem'
```toml
host = '0.0.0.0'
port = 443
certificate = './certificate.pem'
key = './key.pem'
```
> If a chain of certificates authorities is needed, you may bundle them directly in the certificate. Note: the order of certificates does matter, your certificate should come first followed by the certificate of the above certificate authority up to the root.
@@ -60,10 +55,9 @@ This should be written just before the `mount` option, inside the `http:` block.
You shouldn't have to change this. It's the path where `xo-web` files are served by `xo-server`.
```yaml
mounts:
'/':
- '../xo-web/dist/'
```toml
[http.mounts]
'/' = '../xo-web/dist/'
```
### Custom certificate authority
@@ -87,8 +81,8 @@ Don't forget to reload `systemd` conf and restart `xo-server`:
By default, XO-server will try to contact Redis server on `localhost`, with the port `6379`. But you can define whatever you want:
```yaml
uri: 'tcp://db:password@hostname:port'
```toml
uri = 'tcp://db:password@hostname:port'
```
### Proxy for XenServer updates and patches
@@ -101,12 +95,12 @@ To do that behind a corporate proxy, just add the `httpProxy` variable to match
You can add this at the end of your config file:
```yaml
```toml
# HTTP proxy configuration used by xo-server to fetch resources on the Internet.
#
# See: https://github.com/TooTallNate/node-proxy-agent#maps-proxy-protocols-to-httpagent-implementations
httpProxy: 'http://username:password@proxyAddress:port'
httpProxy = 'http://username:password@proxyAddress:port'
```
### Log file

View File

@@ -36,38 +36,52 @@ To protect the replication, we removed the possibility to boot your copied VM di
## Manual initial seed
> This is **only** if you need to make the initial copy without making the whole transfer through your network. Otherwise, **you don't need this**.
**If you can't transfer the first backup through your network because it's too large**, you can make a seed locally. In order to do this, follow this procedure (until we make it accessible directly in XO).
**If you can't transfer the first backup through your network**, you can make a seed locally. In order to do this, follow this procedure (until we make it accessible directly in XO):
> This is **only** if you need to make the initial copy without making the whole transfer through your network. Otherwise, **you don't need this**. These instructions are for Backup-NG jobs, and will not work to seed a legacy backup job. Please migrate any legacy jobs to Backup-NG!
### Preparation
1. create a cont. rep job to a non-distant SR (even the SR where the VM currently is). Do NOT enable the job during creation.
1. manually start the first replication (only the first)
1. when finished, export the replicated VM (via XOA or any other means, doesn't matter how you get your XVA file)
1. import the replicated VM on your distant destination
1. you can now remove your local replicated copy
### Job creation
### Modifications
Create the Continuous Replication backup job, and leave it disabled for now. On the main Backup-NG page, note its identifiers, the main `backupJobId` and the ID of one on the schedules for the job, `backupScheduleId`.
In your source host:
### Seed creation
1. Get the UUID of the remote destination SR where your VM was imported
1. On the source host: `xe vm-param-list uuid=<SourceVM_UUID> | grep other-config`.
* You should see somewhere in other-config: `xo:base_delta:<SR_UUID>: <VM_snapshot_UUID>;`
* Remove this entry with `xe vm-param-remove uuid=<OriginalVM_UUID> param-name=other-config param-key=xo:base_delta:<SR_UUID>`
* Recreate the correct param: `xe vm-param-set uuid=<OriginalVM_UUID> other-config:xo:base_delta:<destination_SR_UUID>=<VM_snapshot_UUID>`
Manually create a snapshot on the VM to backup, and note its UUID as `snapshotUuid` from the snapshot panel for the VM.
In XO:
> DO NOT ever delete or alter this snapshot, feel free to rename it to make that clear.
1. Edit the replication job and select the new destination SR
### Seed copy
On the destination host; to avoid data corruption, you need to avoid any VM start:
Export this snapshot to a file, then import it on the target SR.
Note the UUID of this newly created VM as `targetVmUuid`.
> DO not start this VM or it will break the Continuous Replication job! You can rename this VM to more easily remember this.
### Set up metadata
The XOA backup system requires metadata to correctly associate the source snapshot and the target VM to the backup job. We're going to use the `xo-cr-seed` utility to help us set them up.
First install the tool (all the following is done from the XOA VM CLI):
```
xe vm-param-set blocked-operations:start uuid=<DestinationVM_UUID>
npm i -g xo-cr-seed
```
### Enable
Here is an example of how the utility expects the UUIDs and info passed to it:
Manually run the job the first time to check if everything is OK. Then, enable the job. **Now, only the deltas are sent, your initial seed saved you a LOT of time if you have a slow network.**
```
xo-cr-seed
Usage: xo-cr-seed <source XAPI URL> <source snapshot UUID> <target XAPI URL> <target VM UUID> <backup job id> <backup schedule id>
xo-cr-seed v0.2.0
```
Putting it altogether and putting our values and UUID's into the command, it will look like this (it is a long command):
```
xo-cr-seed https://root:password@xen1.company.tld 4a21c1cd-e8bd-4466-910a-f7524ecc07b1 https://root:password@xen2.company.tld 5aaf86ca-ae06-4a4e-b6e1-d04f0609e64d 90d11a94-a88f-4a84-b7c1-ed207d3de2f9 369a26f0-da77-41ab-a998-fa6b02c69b9a
```
### Finished
Your backup job should now be working correctly! Manually run the job the first time to check if everything is OK. Then, enable the job. **Now, only the deltas are sent, your initial seed saved you a LOT of time if you have a slow network.**

View File

@@ -24,10 +24,11 @@ to create a [GitHub pull request](https://help.github.com/articles/using-pull-re
1. Create a branch for your work
2. Create a pull request for this branch against the `master` branch
3. Push into the branch until the pull request is ready to merge
4. Avoid unnecessary merges: keep you branch up to date by regularly rebasing `git rebase origin/master`
5. When ready to merge, clean up the history (reorder commits, squash some of them together, rephrase messages): `git rebase -i origin/master`
2. Add a summary of your changes to `CHANGELOG.md` under the `next` section, if your changes do not relate to an existing changelog item
3. Create a pull request for this branch against the `master` branch
4. Push into the branch until the pull request is ready to merge
5. Avoid unnecessary merges: keep you branch up to date by regularly rebasing `git rebase origin/master`
6. When ready to merge, clean up the history (reorder commits, squash some of them together, rephrase messages): `git rebase -i origin/master`
### Issue triage

View File

@@ -2,26 +2,50 @@
This is the easiest purchase option: you can buy XOA with your registered email account on `xen-orchestra.com`.
## Choose your edition
You can choose the edition you want in two places:
* [the pricing page](https://xen-orchestra.com/#!/pricing)
* [your account/member page](https://xen-orchestra.com/#!/member)
* [your account/purchases page](https://xen-orchestra.com/#!/purchases)
> You need to be logged to make a purchase. If you don't have an account, please [register here](https://xen-orchestra.com/#!/signup).
> You need to be logged in to make a purchase. If you don't have an account, please [register here](https://xen-orchestra.com/#!/signup).
From your account page, click on the purchase menu, then select the edition you need:
![](./assets/directpurchase.png)
Then you need to fill in your information and select **"buy it for my own use"**:
## Purchase options
The second step is to select your purchase option:
- Subscription: only available with a credit card payment. Choose this option for a monthly payment or a yearly payment **renewed automatically** each year.
- Paid period: **check or wire transfer only**. This purchase allows you to subscribe for a one, two or three year period
> A 2 year subscription period grants you 1 month discounted
> A 3 year subscription period grants you 2 months discounted
Then you need to fill in your information and select **"Buy for my own use"** (direct purchase)
![](./assets/member_purchase_2.png)
The default payment method is by **credit card**. But you can also choose the "wire transfer" tab (with the "bank" icon):
## Billing information
You need to complete all the required information on this page in order to move forward.
![](./assets/member_purchase3.png)
> Note: If you are part of the Eurozone, you will need to provide a valid EU VAT number in order to proceed to payment. Transactions between companies inside the Eurozone are VAT free.
Transactions outside the Eurozone are VAT free.
## Wire transfer process
![](./assets/billing_info.png)
If you select wire transfer, you need to upload a proof of transfer before we can unlock your XOA. If you don't, you'll have to wait for funds to actually be transferred into our account.
## Select your payment mode
Credit Card, Wire transfer or Bank check are the three payment methods available on our store. Some methods can be unavailable regarding the purchase option you have selected during step one.
> Wire transfer is not available for monthly and yearly subscription - Credit Card is not available for paid period.
![](./assets/payment_mode.png)
> All required information for wire transfer and Check payment will be available in the last step of the payment AND on your proforma invoice.
> ⚠ Please, use an explicit reference for your wire transfer in order for us to easily identify your payment.

View File

@@ -38,12 +38,12 @@ XO needs the following packages to be installed. Redis is used as a database by
For example, on Debian:
```
apt-get install build-essential redis-server libpng-dev git python-minimal libvhdi-utils lvm2
apt-get install build-essential redis-server libpng-dev git python-minimal libvhdi-utils lvm2 cifs-utils
```
## Fetching the Code
You need to use the `git` source code manager to fetch the code. Ideally you should run XO as a non-root user, however if you don't run as root you will not be able to mount NFS remotes. As your chosen non-root (or root) user, run the following:
You need to use the `git` source code manager to fetch the code. Ideally, you should run XO as a non-root user, and if you choose to, you need to set up `sudo` to be able to mount NFS remotes. As your chosen non-root (or root) user, run the following:
```
git clone -b master http://github.com/vatesfr/xen-orchestra
@@ -64,17 +64,15 @@ Now you have to create a config file for `xo-server`:
```
$ cd packages/xo-server
$ cp sample.config.yaml .xo-server.yaml
$ cp sample.config.toml .xo-server.toml
```
Edit and uncomment it to have the right path to serve `xo-web`, because `xo-server` embeds an HTTP server (we assume that `xen-orchestra` and `xo-web` are in the same directory). It's near the end of the file:
Edit and uncomment it to have the right path to serve `xo-web`, because `xo-server` embeds an HTTP server (we assume that `xen-orchestra` and `xo-web` are in the same directory):
```yaml
mounts: '/': '../xo-web/dist/'
```toml
[http.mounts]
'/' = '../xo-web/dist/'
```
> Note this `dist` folder will be created in the next step.
**WARNING: YAML is very strict with indentation: use spaces for it, not tabs**.
In this config file, you can also change default ports (80 and 443) for xo-server. If you are running the server as a non-root user, you will need to set the port to 1024 or higher.
@@ -143,9 +141,6 @@ If you need to delete the service:
forever-service delete orchestra
```
## Troubleshooting
If you have problems during the building phase, follow these steps in your `xen-orchestra` directory:
@@ -187,3 +182,17 @@ Don't forget to start redis if you don't reboot now:
```
service redis start
```
## SUDO
If you are running `xo-server` as a non-root user, you need to use `sudo` to be able to mount NFS remotes. You can do this by editing `xo-server/.xo-server.toml` and setting `useSudo = true`. It's near the end of the file:
```
useSudo = true
```
You need to configure `sudo` to allow the user of your choice to run mount/umount commands without asking for a password. Depending on your operating system / sudo version, the location of this configuration may change. Regardless, you can use:
```
username ALL=(root)NOPASSWD: /bin/mount, /bin/umount
```

View File

@@ -4,7 +4,7 @@ If you can't purchase using your own account, usually because you need to go thr
Typically, you will provide two contacts:
* The "billing contact" (in general, the purchaser email). This account will have access to invoices. This is the account which makes the purchase and then binds the XO plan to the second contact account, the technical contact.
* The "billing contact" (in general, the purchaser email). This account will have access to invoices. This is the account doing the purchase. Once purchased, the license needs to be bound to the second contact account, the technical contact.
* The "technical contact", the email of the system administrator using the solution and making support requests.
## As "billing contact"
@@ -17,10 +17,10 @@ Typically, you will provide two contacts:
Now, you just have to pick the edition of Xen Orchestra you want to purchase for your IT team.
2. You will then see the payment screen. If your are not purchasing the edition for yourself, you have to pick the **buy for another account** option.
2. On the first payment screen, after you choose the plan and the subscription method. You can select the option "Buy for another account"
![](./assets/purchase-for-another.png)
![](./assets/member_purchase_2.png)
3. Once the payment is completed, you will have to bind the plan with the end-user account (technical contact). If the end-user doesn't have an account yet, the system will create one and send an e-mail to your end user.
@@ -29,4 +29,4 @@ Now, you just have to pick the edition of Xen Orchestra you want to purchase for
That's it, you have now completed the purchase.
**Once you have bound the plan to your end user account, you cannot change it. Double check the spelling of the e-mail before binding the account.**
**Once you have bound the plan to your end user account, you cannot change it. Double check the spelling of the e-mail before binding the account.**

View File

@@ -16,9 +16,9 @@
"eslint-plugin-react": "^7.6.1",
"eslint-plugin-standard": "^4.0.0",
"exec-promise": "^0.7.0",
"flow-bin": "^0.87.0",
"globby": "^8.0.0",
"husky": "^1.0.0-rc.15",
"flow-bin": "^0.90.0",
"globby": "^9.0.0",
"husky": "^1.2.1",
"jest": "^23.0.1",
"lodash": "^4.17.4",
"prettier": "^1.10.2",
@@ -34,7 +34,6 @@
}
},
"jest": {
"timers": "fake",
"collectCoverage": true,
"projects": [
"<rootDir>"

View File

@@ -1,6 +1,6 @@
{
"name": "vhd-cli",
"version": "0.1.0",
"version": "0.2.0",
"license": "ISC",
"description": "",
"keywords": [],
@@ -26,11 +26,12 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/fs": "^0.5.0",
"@xen-orchestra/fs": "^0.6.1",
"cli-progress": "^2.0.0",
"exec-promise": "^0.7.0",
"getopts": "^2.2.3",
"struct-fu": "^1.2.0",
"vhd-lib": "^0.4.0"
"vhd-lib": "^0.5.1"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -1,12 +1,24 @@
import Vhd from 'vhd-lib'
import Vhd, { checkVhdChain } from 'vhd-lib'
import getopts from 'getopts'
import { getHandler } from '@xen-orchestra/fs'
import { resolve } from 'path'
export default async args => {
const checkVhd = (handler, path) => new Vhd(handler, path).readHeaderAndFooter()
export default async rawArgs => {
const { chain, _: args } = getopts(rawArgs, {
boolean: ['chain'],
default: {
chain: false,
},
})
const check = chain ? checkVhdChain : checkVhd
const handler = getHandler({ url: 'file:///' })
for (const vhd of args) {
try {
await new Vhd(handler, resolve(vhd)).readHeaderAndFooter()
await check(handler, resolve(vhd))
console.log('ok:', vhd)
} catch (error) {
console.error('nok:', vhd, error)

View File

@@ -2,6 +2,8 @@
import execPromise from 'exec-promise'
import pkg from '../package.json'
import commands from './commands'
function runCommand(commands, [command, ...args]) {
@@ -16,9 +18,11 @@ function runCommand(commands, [command, ...args]) {
return `Usage:
${Object.keys(commands)
.filter(command => command !== 'help')
.map(command => ` ${this.command} ${command}`)
.join('\n\n')}`
.filter(command => command !== 'help')
.map(command => ` ${this.command} ${command}`)
.join('\n\n')}
vhd-cli ${pkg.version}`
}
throw `invalid command ${command}` // eslint-disable-line no-throw-literal

View File

@@ -1,6 +1,6 @@
{
"name": "vhd-lib",
"version": "0.4.0",
"version": "0.5.1",
"license": "AGPL-3.0",
"description": "Primitives for VHD file handling",
"keywords": [],
@@ -34,7 +34,7 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"@xen-orchestra/fs": "^0.5.0",
"@xen-orchestra/fs": "^0.6.1",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",

View File

@@ -0,0 +1,6 @@
import { dirname, resolve } from 'path'
const resolveRelativeFromFile = (file, path) =>
resolve('/', dirname(file), path).slice(1)
export { resolveRelativeFromFile as default }

View File

@@ -0,0 +1,16 @@
import Vhd from './vhd'
import resolveRelativeFromFile from './_resolveRelativeFromFile'
import { DISK_TYPE_DYNAMIC } from './_constants'
export default async function checkChain(handler, path) {
while (true) {
const vhd = new Vhd(handler, path)
await vhd.readHeaderAndFooter()
if (vhd.footer.diskType === DISK_TYPE_DYNAMIC) {
break
}
path = resolveRelativeFromFile(path, vhd.header.parentUnicodeName)
}
}

View File

@@ -1,5 +1,6 @@
import asyncIteratorToStream from 'async-iterator-to-stream'
import { dirname, resolve } from 'path'
import resolveRelativeFromFile from './_resolveRelativeFromFile'
import Vhd from './vhd'
import {
@@ -12,9 +13,6 @@ import {
import { fuFooter, fuHeader, checksumStruct } from './_structs'
import { test as mapTestBit } from './_bitmap'
const resolveRelativeFromFile = (file, path) =>
resolve('/', dirname(file), path).slice(1)
export default async function createSyntheticStream(handler, path) {
const fds = []
const cleanup = () => {

View File

@@ -3,6 +3,7 @@ import 'core-js/features/symbol/async-iterator'
export { default } from './vhd'
export { default as chainVhd } from './chain'
export { default as checkVhdChain } from './checkChain'
export { default as createContentStream } from './createContentStream'
export { default as createReadableRawStream } from './createReadableRawStream'
export {

View File

@@ -216,7 +216,9 @@ export default class Vhd {
// return the first sector (bitmap) of a block
_getBatEntry(block) {
return this.blockTable.readUInt32BE(block * 4)
const i = block * 4
const { blockTable } = this
return i < blockTable.length ? blockTable.readUInt32BE(i) : BLOCK_UNUSED
}
_readBlock(blockId, onlyBitmap = false) {

View File

@@ -40,7 +40,7 @@
"human-format": "^0.10.0",
"lodash": "^4.17.4",
"pw": "^0.0.4",
"xen-api": "^0.23.0"
"xen-api": "^0.24.2"
},
"devDependencies": {
"@babel/cli": "^7.1.5",

View File

@@ -1,30 +1,42 @@
#!/usr/bin/env node
process.env.DEBUG = '*'
process.env.DEBUG = 'xen-api'
const createProgress = require('progress-stream')
const createTop = require('process-top')
const defer = require('golike-defer').default
const pump = require('pump')
const { CancelToken, fromCallback } = require('promise-toolbox')
const getopts = require('getopts')
const { CancelToken } = require('promise-toolbox')
const { createClient } = require('../')
const { createOutputStream, resolveRef } = require('./utils')
const {
createOutputStream,
formatProgress,
pipeline,
resolveRecord,
throttle,
} = require('./utils')
defer(async ($defer, args) => {
let raw = false
if (args[0] === '--raw') {
raw = true
args.shift()
}
defer(async ($defer, rawArgs) => {
const { raw, throttle: bps, _: args } = getopts(rawArgs, {
boolean: 'raw',
alias: {
raw: 'r',
throttle: 't',
},
})
if (args.length < 2) {
return console.log('Usage: export-vdi [--raw] <XS URL> <VDI identifier> [<VHD file>]')
return console.log(
'Usage: export-vdi [--raw] <XS URL> <VDI identifier> [<VHD file>]'
)
}
const xapi = createClient({
allowUnauthorized: true,
url: args[0],
watchEvents: false
watchEvents: false,
})
await xapi.connect()
@@ -33,21 +45,32 @@ defer(async ($defer, args) => {
const { cancel, token } = CancelToken.source()
process.on('SIGINT', cancel)
const vdi = await resolveRecord(xapi, 'VDI', args[1])
// https://xapi-project.github.io/xen-api/snapshots.html#downloading-a-disk-or-snapshot
const exportStream = await xapi.getResource(token, '/export_raw_vdi/', {
query: {
format: raw ? 'raw' : 'vhd',
vdi: await resolveRef(xapi, 'VDI', args[1])
}
vdi: vdi.$ref,
},
})
console.warn('Export task:', exportStream.headers['task-id'])
await fromCallback(cb => pump(
const top = createTop()
const progressStream = createProgress()
$defer(
clearInterval,
setInterval(() => {
console.warn('\r %s | %s', top.toString(), formatProgress(progressStream.progress()))
}, 1e3)
)
await pipeline(
exportStream,
createOutputStream(args[2]),
cb
))
})(process.argv.slice(2)).catch(
console.error.bind(console, 'error')
)
progressStream,
throttle(bps),
createOutputStream(args[2])
)
})(process.argv.slice(2)).catch(console.error.bind(console, 'error'))

View File

@@ -2,15 +2,25 @@
process.env.DEBUG = '*'
const createProgress = require('progress-stream')
const defer = require('golike-defer').default
const pump = require('pump')
const { CancelToken, fromCallback } = require('promise-toolbox')
const getopts = require('getopts')
const { CancelToken } = require('promise-toolbox')
const { createClient } = require('../')
const { createOutputStream, resolveRef } = require('./utils')
const {
createOutputStream,
formatProgress,
pipeline,
resolveRecord,
} = require('./utils')
defer(async ($defer, rawArgs) => {
const { gzip, zstd, _: args } = getopts(rawArgs, {
boolean: ['gzip', 'zstd'],
})
defer(async ($defer, args) => {
if (args.length < 2) {
return console.log('Usage: export-vm <XS URL> <VM identifier> [<XVA file>]')
}
@@ -18,7 +28,7 @@ defer(async ($defer, args) => {
const xapi = createClient({
allowUnauthorized: true,
url: args[0],
watchEvents: false
watchEvents: false,
})
await xapi.connect()
@@ -30,18 +40,16 @@ defer(async ($defer, args) => {
// https://xapi-project.github.io/xen-api/importexport.html
const exportStream = await xapi.getResource(token, '/export/', {
query: {
ref: await resolveRef(xapi, 'VM', args[1]),
use_compression: 'true'
}
ref: (await resolveRecord(xapi, 'VM', args[1])).$ref,
use_compression: zstd ? 'zstd' : gzip ? 'true' : 'false',
},
})
console.warn('Export task:', exportStream.headers['task-id'])
await fromCallback(cb => pump(
await pipeline(
exportStream,
createOutputStream(args[2]),
cb
))
})(process.argv.slice(2)).catch(
console.error.bind(console, 'error')
)
createProgress({ time: 1e3 }, p => console.warn(formatProgress(p))),
createOutputStream(args[2])
)
})(process.argv.slice(2)).catch(console.error.bind(console, 'error'))

View File

@@ -1,6 +1,12 @@
{
"dependencies": {
"getopts": "^2.2.3",
"golike-defer": "^0.4.1",
"pump": "^3.0.0"
"human-format": "^0.10.1",
"process-top": "^1.0.0",
"progress-stream": "^2.0.0",
"promise-toolbox": "^0.11.0",
"readable-stream": "^3.1.1",
"throttle": "^1.0.3"
}
}

View File

@@ -1,5 +1,8 @@
const { createReadStream, createWriteStream, statSync } = require('fs')
const { PassThrough } = require('stream')
const { fromCallback } = require('promise-toolbox')
const { PassThrough, pipeline } = require('readable-stream')
const humanFormat = require('human-format')
const Throttle = require('throttle')
const { isOpaqueRef } = require('../')
@@ -26,7 +29,32 @@ exports.createOutputStream = path => {
return stream
}
exports.resolveRef = (xapi, type, refOrUuidOrNameLabel) =>
const formatSizeOpts = { scale: 'binary', unit: 'B' }
const formatSize = bytes => humanFormat(bytes, formatSizeOpts)
exports.formatProgress = p =>
[
formatSize(p.transferred),
' / ',
formatSize(p.length),
' | ',
p.runtime,
's / ',
p.eta,
's | ',
formatSize(p.speed),
'/s',
].join('')
exports.pipeline = (...streams) => {
return fromCallback(cb => {
streams = streams.filter(_ => _ != null)
streams.push(cb)
pipeline.apply(undefined, streams)
})
}
const resolveRef = (xapi, type, refOrUuidOrNameLabel) =>
isOpaqueRef(refOrUuidOrNameLabel)
? refOrUuidOrNameLabel
: xapi.call(`${type}.get_by_uuid`, refOrUuidOrNameLabel).catch(() =>
@@ -41,3 +69,10 @@ exports.resolveRef = (xapi, type, refOrUuidOrNameLabel) =>
)
})
)
exports.resolveRecord = async (xapi, type, refOrUuidOrNameLabel) =>
xapi.getRecord(type, await resolveRef(xapi, type, refOrUuidOrNameLabel))
exports.resolveRef = resolveRef
exports.throttle = opts => (opts != null ? new Throttle(opts) : undefined)

View File

@@ -2,29 +2,178 @@
# yarn lockfile v1
end-of-stream@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
debug@2:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
once "^1.4.0"
ms "2.0.0"
event-loop-delay@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/event-loop-delay/-/event-loop-delay-1.0.0.tgz#5af6282549494fd0d868c499cbdd33e027978b8c"
integrity sha512-8YtyeIWHXrvTqlAhv+fmtaGGARmgStbvocERYzrZ3pwhnQULe5PuvMUTjIWw/emxssoaftfHZsJtkeY8xjiXCg==
dependencies:
napi-macros "^1.8.2"
node-gyp-build "^3.7.0"
getopts@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.3.tgz#11d229775e2ec2067ed8be6fcc39d9b4bf39cf7d"
integrity sha512-viEcb8TpgeG05+Nqo5EzZ8QR0hxdyrYDp6ZSTZqe2M/h53Bk036NmqG38Vhf5RGirC/Of9Xql+v66B2gp256SQ==
golike-defer@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/golike-defer/-/golike-defer-0.4.1.tgz#7a1cd435d61e461305805d980b133a0f3db4e1cc"
once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
wrappy "1"
human-format@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/human-format/-/human-format-0.10.1.tgz#107793f355912e256148d5b5dcf66a0230187ee9"
integrity sha512-UzCHToSw3HI9MxH9tYzMr1JbHJbgzr6o0hZCun7sruv59S1leps21bmgpBkkwEvQon5n/2OWKH1iU7BEko02cg==
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
inherits@^2.0.3, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
wrappy@1:
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
make-error@^1.3.2:
version "1.3.5"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
napi-macros@^1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-1.8.2.tgz#299265c1d8aa401351ad0675107d751228c03eda"
integrity sha512-Tr0DNY4RzTaBG2W2m3l7ZtFuJChTH6VZhXVhkGGjF/4cZTt+i8GcM9ozD+30Lmr4mDoZ5Xx34t2o4GJqYWDGcg==
node-gyp-build@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d"
integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==
prettier-bytes@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/prettier-bytes/-/prettier-bytes-1.0.4.tgz#994b02aa46f699c50b6257b5faaa7fe2557e62d6"
integrity sha1-mUsCqkb2mcULYle1+qp/4lV+YtY=
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
process-top@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/process-top/-/process-top-1.0.0.tgz#52892bedb581c5abf0df2d0aa5c429e34275cc7e"
integrity sha512-er8iSmBMslOt5cgIHg9m6zilTPsuUqpEb1yfQ4bDmO80zr/e/5hNn+Tay3CJM/FOBnJo8Bt3fFiDDH6GvIgeAg==
dependencies:
event-loop-delay "^1.0.0"
prettier-bytes "^1.0.4"
progress-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-2.0.0.tgz#fac63a0b3d11deacbb0969abcc93b214bce19ed5"
integrity sha1-+sY6Cz0R3qy7CWmrzJOyFLzhntU=
dependencies:
speedometer "~1.0.0"
through2 "~2.0.3"
promise-toolbox@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/promise-toolbox/-/promise-toolbox-0.11.0.tgz#9ed928355355395072dace3f879879504e07d1bc"
integrity sha512-bjHk0kq+Ke3J3zbkbbJH6kXCyQZbFHwOTrE/Et7vS0uS0tluoV+PLqU/kEyxl8aARM7v04y2wFoDo/wWAEPvjA==
dependencies:
make-error "^1.3.2"
"readable-stream@>= 0.3.0", readable-stream@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
integrity sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@~2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
speedometer@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-1.0.0.tgz#cd671cb06752c22bca3370e2f334440be4fc62e2"
integrity sha1-zWccsGdSwivKM3Di8zREC+T8YuI=
"stream-parser@>= 0.0.2":
version "0.3.1"
resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773"
integrity sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=
dependencies:
debug "2"
string_decoder@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
dependencies:
safe-buffer "~5.1.0"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
throttle@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/throttle/-/throttle-1.0.3.tgz#8a32e4a15f1763d997948317c5ebe3ad8a41e4b7"
integrity sha1-ijLkoV8XY9mXlIMXxevjrYpB5Lc=
dependencies:
readable-stream ">= 0.3.0"
stream-parser ">= 0.0.2"
through2@~2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
dependencies:
readable-stream "~2.3.6"
xtend "~4.0.1"
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=

View File

@@ -1,6 +1,6 @@
{
"name": "xen-api",
"version": "0.23.0",
"version": "0.24.2",
"license": "ISC",
"description": "Connector to the Xen API",
"keywords": [
@@ -36,7 +36,7 @@
"debug": "^4.0.1",
"event-to-promise": "^0.8.0",
"exec-promise": "^0.7.0",
"http-request-plus": "^0.6.0",
"http-request-plus": "^0.7.1",
"iterable-backoff": "^0.0.0",
"jest-diff": "^23.5.0",
"json-rpc-protocol": "^0.13.1",

View File

@@ -109,6 +109,9 @@ export const wrapError = error => {
} else {
code = error.message
params = error.data
if (!isArray(params)) {
params = []
}
}
return new XapiError(code, params)
}
@@ -253,6 +256,9 @@ const CONNECTED = 'connected'
const CONNECTING = 'connecting'
const DISCONNECTED = 'disconnected'
// timeout of XenAPI HTTP connections
const HTTP_TIMEOUT = 24 * 3600 * 1e3
// -------------------------------------------------------------------
export class Xapi extends EventEmitter {
@@ -301,7 +307,7 @@ export class Xapi extends EventEmitter {
this._taskWatchers = Object.create(null)
if (this.status === CONNECTED) {
ignoreErrors.call(this._watchEvents())
this._watchEvents()
}
this.on('connected', this._watchEvents)
@@ -538,6 +544,8 @@ export class Xapi extends EventEmitter {
async getRecord(type, ref) {
return this._wrapRecord(
type,
ref,
await this._sessionCall(`${type}.get_record`, [ref])
)
}
@@ -571,17 +579,20 @@ export class Xapi extends EventEmitter {
}
}
let promise = httpRequest(
$cancelToken,
this._url,
host && {
hostname: this.getObject(host).address,
},
{
pathname,
query,
rejectUnauthorized: !this._allowUnauthorized,
}
let promise = pTimeout.call(
httpRequest(
$cancelToken,
this._url,
host && {
hostname: this.getObject(host).address,
},
{
pathname,
query,
rejectUnauthorized: !this._allowUnauthorized,
}
),
HTTP_TIMEOUT
)
if (taskResult !== undefined) {
@@ -629,21 +640,23 @@ export class Xapi extends EventEmitter {
}
const doRequest = (...opts) =>
httpRequest.put(
$cancelToken,
this._url,
host && {
hostname: this.getObject(host).address,
},
{
body,
headers,
query,
pathname,
maxRedirects: 0,
rejectUnauthorized: !this._allowUnauthorized,
},
...opts
pTimeout.call(
httpRequest.put(
$cancelToken,
this._url,
host && {
hostname: this.getObject(host).address,
},
{
body,
headers,
query,
pathname,
rejectUnauthorized: !this._allowUnauthorized,
},
...opts
),
HTTP_TIMEOUT
)
// if a stream, sends a dummy request to probe for a
@@ -654,15 +667,17 @@ export class Xapi extends EventEmitter {
// omit task_id because this request will fail on purpose
query: 'task_id' in query ? omit(query, 'task_id') : query,
maxRedirects: 0,
}).then(
response => {
response.req.abort()
response.cancel()
return doRequest()
},
error => {
let response
if (error != null && (response = error.response) != null) {
response.req.abort()
response.cancel()
const {
headers: { location },
@@ -690,12 +705,12 @@ export class Xapi extends EventEmitter {
}
if (req.finished) {
req.abort()
response.cancel()
return taskResult
}
return fromEvents(req, ['close', 'finish']).then(() => {
req.abort()
response.cancel()
return taskResult
})
})
@@ -954,16 +969,18 @@ export class Xapi extends EventEmitter {
throw error
}
return pCatch.call(
loop(),
isMethodUnknown,
ignoreErrors.call(
pCatch.call(
loop(),
isMethodUnknown,
// If the server failed, it is probably due to an excessively
// large response.
// Falling back to legacy events watch should be enough.
error => error && error.res && error.res.statusCode === 500,
// If the server failed, it is probably due to an excessively
// large response.
// Falling back to legacy events watch should be enough.
error => error && error.res && error.res.statusCode === 500,
() => this._watchEventsLegacy()
() => this._watchEventsLegacy()
)
)
}
@@ -1080,7 +1097,10 @@ export class Xapi extends EventEmitter {
props[`update_${field}`] = function(entries) {
return xapi.setFieldEntries(this, field, entries)
}
} else if (isOpaqueRef(value)) {
} else if (value === '' || isOpaqueRef(value)) {
// 2019-02-07 - JFT: even if `value` should not be an empty string for
// a ref property, an user had the case on XenServer 7.0 on the CD VBD
// of a VM created by XenCenter
getters[$field] = function() {
return objectsByRef[this[field]]
}

View File

@@ -33,7 +33,7 @@
"chalk": "^2.2.0",
"exec-promise": "^0.7.0",
"fs-promise": "^2.0.3",
"http-request-plus": "^0.6.0",
"http-request-plus": "^0.7.1",
"human-format": "^0.10.0",
"l33teral": "^3.0.3",
"lodash": "^4.17.4",

View File

@@ -9,11 +9,7 @@ same identifier.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-auth-github):
```
> npm install --global xo-server-auth-github
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -9,11 +9,7 @@ same identifier.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-auth-google):
```
> npm install --global xo-server-auth-google
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -9,11 +9,7 @@ same identifier.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-auth-ldap):
```
> npm install --global xo-server-auth-ldap
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -1,5 +1,5 @@
import { forEach, isFinite, isInteger } from 'lodash'
import { pforOwn } from 'promise-toolbox'
import { pForOwn } from 'promise-toolbox'
import { prompt } from 'inquirer'
// ===================================================================
@@ -160,7 +160,7 @@ const promptByType = {
}
}
await pforOwn.call(schema.properties || {}, promptProperty)
await pForOwn.call(schema.properties || {}, promptProperty)
return value
},

View File

@@ -9,11 +9,7 @@ same identifier.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-auth-saml):
```
> npm install --global xo-server-auth-saml
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server-auth-saml",
"version": "0.5.2",
"version": "0.5.3",
"license": "AGPL-3.0",
"description": "SAML authentication plugin for XO-Server",
"keywords": [
@@ -32,7 +32,7 @@
"node": ">=6"
},
"dependencies": {
"passport-saml": "^0.35.0"
"passport-saml": "^1.0.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -42,7 +42,12 @@ class AuthSamlXoPlugin {
configure({ usernameField, ...conf }) {
this._usernameField = usernameField
this._conf = conf
this._conf = {
...conf,
// must match the callback URL
path: '/signin/saml/callback',
}
}
load() {

View File

@@ -6,11 +6,7 @@ XO-Server plugin which sends email reports and Xmpp messages when backup jobs ar
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-backup-reports):
```
> npm install --global xo-server-backup-reports
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -2,11 +2,7 @@
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-cloud):
```
> npm install --global xo-server-cloud
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -31,7 +31,7 @@
"node": ">=6"
},
"dependencies": {
"http-request-plus": "^0.6.0",
"http-request-plus": "^0.7.1",
"jsonrpc-websocket-client": "^0.4.1"
},
"devDependencies": {

View File

@@ -4,11 +4,7 @@ XO-Server plugin that allows load balancing.
## Install
Go inside your `xo-server` folder and install it:
```
> npm install --global xo-server-load-balancer
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -4,11 +4,7 @@
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-perf-alert):
```
> npm install --global xo-server-perf-alert
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -4,11 +4,7 @@
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-transport-email):
```
> npm install --global xo-server-transport-email
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -31,7 +31,7 @@
"node": ">=6"
},
"dependencies": {
"nodemailer": "^4.4.1",
"nodemailer": "^5.0.0",
"nodemailer-markdown": "^1.0.1",
"promise-toolbox": "^0.11.0"
},

View File

@@ -4,11 +4,7 @@
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-transport-nagios):
```
> npm install --global xo-server-transport-nagios
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -120,13 +120,9 @@ class XoServerNagios {
const client = new net.Socket()
client.connect(
this._conf.port,
this._conf.server,
() => {
console.log('Successful connection')
}
)
client.connect(this._conf.port, this._conf.server, () => {
console.log('Successful connection')
})
client.on('data', data => {
const timestamp = data.readInt32BE(128)

View File

@@ -4,11 +4,7 @@
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-transport-slack):
```
> npm install --global xo-server-transport-slack
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -4,11 +4,7 @@ XO-Server plugin which sends XMPP messages.
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-transport-xmpp):
```
> npm install --global xo-server-transport-xmpp
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -4,11 +4,7 @@
## Install
Installation of the [npm package](https://npmjs.org/package/xo-server-usage-report):
```
> npm install --save xo-server-usage-report
```
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage

View File

@@ -1,59 +0,0 @@
// Vendor config: DO NOT TOUCH!
//
// See sample.config.yaml to override.
{
"apiWebSocketOptions": {
// https://github.com/websockets/ws#websocket-compression
// "perMessageDeflate": {
// "threshold": 524288 // 512kiB
// }
},
"http": {
"listen": [
{
"port": 80
}
],
// These options are applied to all listen entries.
"listenOptions": {
// Ciphers to use.
//
// These are the default ciphers in Node 4.2.6, we are setting
// them explicitly for older Node versions.
"ciphers": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA",
// Tell Node to respect the cipher order.
"honorCipherOrder": true,
// Specify to use at least TLSv1.1.
// See: https://github.com/certsimple/minimum-tls-version
"secureOptions": 117440512
},
"mounts": {}
},
"datadir": "/var/lib/xo-server/data",
// Should users be created on first sign in?
//
// Necessary for external authentication providers.
"createUserOnFirstSignin": true,
"remoteOptions": {
"mountsDir": "/run/xo-server/mounts",
// timeout in milliseconds (set to 0 to disable)
"timeout": 600e3
},
// Whether API logs should contains the full request/response on
// errors.
//
// This is disabled by default for performance (lots of data) and
// security concerns (avoiding sensitive data in the logs) but can
// be turned for investigation by the administrator.
"verboseApiLogsOnErrors": false
}

View File

@@ -0,0 +1,51 @@
# Vendor config: DO NOT TOUCH!
#
# See sample.config.toml to override.
datadir = '/var/lib/xo-server/data'
# Should users be created on first sign in?
#
# Necessary for external authentication providers.
createUserOnFirstSignin = true
# Whether API logs should contains the full request/response on
# errors.
#
# This is disabled by default for performance (lots of data) and
# security concerns (avoiding sensitive data in the logs) but can
# be turned for investigation by the administrator.
verboseApiLogsOnErrors = false
# https:#github.com/websockets/ws#websocket-compression
[apiWebSocketOptions]
perMessageDeflate = { threshold = 524288 } # 512kiB
[[http.listen]]
port = 80
# These options are applied to all listen entries.
[http.listenOptions]
# Ciphers to use.
#
# These are the default ciphers in Node 4.2.6, we are setting
# them explicitly for older Node versions.
ciphers = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA'
# Tell Node to respect the cipher order.
honorCipherOrder = true
# Specify to use at least TLSv1.1.
# See: https:#github.com/certsimple/minimum-tls-version
secureOptions = 117440512
[http.mounts]
[remoteOptions]
mountsDir = '/run/xo-server/mounts'
# timeout in milliseconds (set to 0 to disable)
timeout = 600e3
# see https:#github.com/vatesfr/xen-orchestra/issues/3419
# useSudo = false

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server",
"version": "5.31.1",
"version": "5.35.0",
"license": "AGPL-3.0",
"description": "Server part of Xen-Orchestra",
"keywords": [
@@ -20,7 +20,7 @@
"better-stacks.js",
"bin/",
"dist/",
"config.json",
"config.toml",
"index.js",
"signin.pug"
],
@@ -31,10 +31,11 @@
"node": ">=6"
},
"dependencies": {
"@iarna/toml": "^2.2.1",
"@xen-orchestra/async-map": "^0.0.0",
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/emit-async": "^0.0.0",
"@xen-orchestra/fs": "^0.5.0",
"@xen-orchestra/fs": "^0.6.1",
"@xen-orchestra/log": "^0.1.4",
"@xen-orchestra/mixin": "^0.0.0",
"ajv": "^6.1.1",
@@ -67,12 +68,12 @@
"helmet": "^3.9.0",
"highland": "^2.11.1",
"http-proxy": "^1.16.2",
"http-request-plus": "^0.6.0",
"http-request-plus": "^0.7.1",
"http-server-plus": "^0.10.0",
"human-format": "^0.10.0",
"is-redirect": "^1.0.0",
"iterable-backoff": "^0.0.0",
"jest-worker": "^23.0.0",
"jest-worker": "^24.0.0",
"js-yaml": "^3.10.0",
"json-rpc-peer": "^0.15.3",
"json5": "^2.0.1",
@@ -91,6 +92,7 @@
"ms": "^2.1.1",
"multikey-hash": "^1.0.4",
"ndjson": "^1.5.0",
"otplib": "^10.0.1",
"parse-pairs": "^0.2.2",
"partial-stream": "0.0.0",
"passport": "^0.4.0",
@@ -114,15 +116,15 @@
"tmp": "^0.0.33",
"uuid": "^3.0.1",
"value-matcher": "^0.2.0",
"vhd-lib": "^0.4.0",
"vhd-lib": "^0.5.1",
"ws": "^6.0.0",
"xen-api": "^0.23.0",
"xen-api": "^0.24.2",
"xml2js": "^0.4.19",
"xo-acl-resolver": "^0.4.1",
"xo-collection": "^0.4.1",
"xo-common": "^0.2.0",
"xo-remote-parser": "^0.5.0",
"xo-vmdk-to-vhd": "^0.1.5",
"xo-vmdk-to-vhd": "^0.1.6",
"yazl": "^2.4.3"
},
"devDependencies": {

View File

@@ -0,0 +1,157 @@
# Example XO-Server configuration.
#
# This file is automatically looking for at the following places:
# - `$HOME/.config/xo-server/config.toml`
# - `/etc/xo-server/config.toml`
#
# The first entries have priority.
#
# Note: paths are relative to the configuration file.
#=====================================================================
# HTTP proxy configuration used by xo-server to fetch resources on the Internet.
#
# See: https://github.com/TooTallNate/node-proxy-agent#maps-proxy-protocols-to-httpagent-implementations
# httpProxy = 'http://jsmith:qwerty@proxy.lan:3128'
#=====================================================================
# It may be necessary to run XO-Server as a privileged user (e.g. `root`) for
# instance to allow the HTTP server to listen on a
# [privileged ports](http://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html).
#
# To avoid security issues, XO-Server can drop its privileges by changing the
# user and the group is running with.
#
# Note: XO-Server will change them just after reading the configuration.
# User to run XO-Server as.
#
# Note: The user can be specified using either its name or its numeric
# identifier.
#
# Default: undefined
#user = 'nobody'
# Group to run XO-Server as.
#
# Note: The group can be specified using either its name or its numeric
# identifier.
#
# Default: undefined
# group = 'nogroup'
#=====================================================================
# Configuration of the embedded HTTP server.
[http]
# If set to true, all HTTP traffic will be redirected to the first HTTPs
# configuration.
# redirectToHttps = true
# Settings applied to cookies created by xo-server's embedded HTTP server.
#
# See https://www.npmjs.com/package/cookie#options-1
[http.cookies]
#sameSite = true
#secure = true
# Basic HTTP.
[[http.listen]]
# Address on which the server is listening on.
#
# Sets it to 'localhost' for IP to listen only on the local host.
#
# Default: all IPv6 addresses if available, otherwise all IPv4 addresses.
# hostname = 'localhost'
# Port on which the server is listening on.
#
# Default: undefined
port = 80
# Instead of `host` and `port` a path to a UNIX socket may be specified
# (overrides `host` and `port`).
#
# Default: undefined
# socket = './http.sock'
# # Basic HTTPS.
# #
# # You can find the list of possible options there
# # https://nodejs.org/docs/latest/api/tls.html#tls.createServer
# #
# # The only difference is the presence of the certificate and the key.
# [[http.listen]]
# #hostname = '127.0.0.1'
# port = 443
#
# # File containing the certificate (PEM format).
# #
# # If a chain of certificates authorities is needed, you may bundle them
# # directly in the certificate.
# #
# # Note: the order of certificates does matter, your certificate should come
# # first followed by the certificate of the above
# # certificate authority up to the root.
# #
# # Default: undefined
# cert = './certificate.pem'
#
# # File containing the private key (PEM format).
# #
# # If the key is encrypted, the passphrase will be asked at
# # server startup.
# #
# # Default: undefined
# key = './key.pem'
# List of files/directories which will be served.
[http.mounts]
#'/' = '/path/to/xo-web/dist/'
# List of proxied URLs (HTTP & WebSockets).
[http.proxies]
#'/any/url' = 'http://localhost:54722'
#=====================================================================
# Connection to the Redis server.
[redis]
# Unix sockets can be used
#
# Default: undefined
#socket = '/var/run/redis/redis.sock'
# Syntax: redis://[db[:password]@]hostname[:port][/db-number]
#
# Default: redis://localhost:6379/0
#uri = 'redis://redis.company.lan/42'
# List of aliased commands.
#
# See http://redis.io/topics/security#disabling-of-specific-commands
#renameCommands:
# del = '3dda29ad-3015-44f9-b13b-fa570de92489'
# srem = '3fd758c9-5610-4e9d-a058-dbf4cb6d8bf0'
# Directory containing the database of XO.
# Currently used for logs.
#
# Default: '/var/lib/xo-server/data'
#datadir = '/var/lib/xo-server/data'
#=====================================================================
# Configuration for remotes
[remoteOptions]
# Directory used to mount remotes
#
# Default: '/run/xo-server/mounts'
#mountsDir = '/run/xo-server/mounts'
# Use sudo for mount with non-root user
#
# Default: false
#useSudo = false

View File

@@ -1,144 +0,0 @@
# BE *VERY* CAREFUL WHEN EDITING!
# YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
# visit http://www.yamllint.com/ to validate this file as needed
#=====================================================================
# Example XO-Server configuration.
#
# This file is automatically looking for at the following places:
# - `$HOME/.config/xo-server/config.yaml`
# - `/etc/xo-server/config.yaml`
#
# The first entries have priority.
#
# Note: paths are relative to the configuration file.
#=====================================================================
# It may be necessary to run XO-Server as a privileged user (e.g.
# `root`) for instance to allow the HTTP server to listen on a
# [privileged ports](http://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html).
#
# To avoid security issues, XO-Server can drop its privileges by
# changing the user and the group is running with.
#
# Note: XO-Server will change them just after reading the
# configuration.
# User to run XO-Server as.
#
# Note: The user can be specified using either its name or its numeric
# identifier.
#
# Default: undefined
#user: 'nobody'
# Group to run XO-Server as.
#
# Note: The group can be specified using either its name or its
# numeric identifier.
#
# Default: undefined
#group: 'nogroup'
#=====================================================================
# Configuration of the embedded HTTP server.
http:
# Hosts & ports on which to listen.
#
# By default, the server listens on [::]:80.
listen:
# Basic HTTP.
- # Address on which the server is listening on.
#
# Sets it to 'localhost' for IP to listen only on the local host.
#
# Default: all IPv6 addresses if available, otherwise all IPv4
# addresses.
#hostname: 'localhost'
# Port on which the server is listening on.
#
# Default: undefined
port: 80
# Instead of `host` and `port` a path to a UNIX socket may be
# specified (overrides `host` and `port`).
#
# Default: undefined
#socket: './http.sock'
# Basic HTTPS.
#
# You can find the list of possible options there https://nodejs.org/docs/latest/api/tls.html#tls.createServer
# -
# # The only difference is the presence of the certificate and the
# # key.
# #
# #hostname: '127.0.0.1'
# port: 443
# # File containing the certificate (PEM format).
#
# # If a chain of certificates authorities is needed, you may bundle
# # them directly in the certificate.
# #
# # Note: the order of certificates does matter, your certificate
# # should come first followed by the certificate of the above
# # certificate authority up to the root.
# #
# # Default: undefined
# cert: './certificate.pem'
# # File containing the private key (PEM format).
# #
# # If the key is encrypted, the passphrase will be asked at
# # server startup.
# #
# # Default: undefined
# key: './key.pem'
# If set to true, all HTTP traffic will be redirected to the first
# HTTPs configuration.
#redirectToHttps: true
# List of files/directories which will be served.
mounts:
#'/': '/path/to/xo-web/dist/'
# List of proxied URLs (HTTP & WebSockets).
proxies:
# '/any/url': 'http://localhost:54722'
# HTTP proxy configuration used by xo-server to fetch resources on the
# Internet.
#
# See: https://github.com/TooTallNate/node-proxy-agent#maps-proxy-protocols-to-httpagent-implementations
#httpProxy: 'http://jsmith:qwerty@proxy.lan:3128'
#=====================================================================
# Connection to the Redis server.
redis:
# Unix sockets can be used
#
# Default: undefined
#socket: /var/run/redis/redis.sock
# Syntax: redis://[db[:password]@]hostname[:port][/db-number]
#
# Default: redis://localhost:6379/0
#uri: redis://redis.company.lan/42
# List of aliased commands.
#
# See http://redis.io/topics/security#disabling-of-specific-commands
#renameCommands:
# del: '3dda29ad-3015-44f9-b13b-fa570de92489'
# srem: '3fd758c9-5610-4e9d-a058-dbf4cb6d8bf0'
# Directory containing the database of XO.
# Currently used for logs.
#
# Default: '/var/lib/xo-server/data'
#datadir: '/var/lib/xo-server/data'

View File

@@ -12,39 +12,60 @@ html
div.mb-2(style = 'display: flex;')
img(src = 'assets/logo.png' style = 'margin: auto;')
h2.text-xs-center.mb-2 Xen Orchestra
form(action = 'signin/local' method = 'post')
fieldset
if error
p.text-danger #{error}
.input-group.mb-1
span.input-group-addon
i.xo-icon-user.fa-fw
input.form-control(
name = 'username'
type = 'text'
placeholder = 'Username'
required
)
.input-group.mb-1
span.input-group-addon
i.fa.fa-key.fa-fw
input.form-control(
name = 'password'
type = 'password'
placeholder = 'Password'
required
)
.checkbox
label
input(
name = 'remember-me'
type = 'checkbox'
if error
p.text-danger #{error}
if otp
form(action = 'signin-otp' method = 'post')
fieldset
.input-group.mb-1
span.input-group-addon
i.fa.fa-key.fa-fw
input.form-control(
autocomplete = 'off'
autofocus
name = 'otp'
type = 'text'
placeholder = 'Code'
required
)
| &nbsp;
| Remember me
div
button.btn.btn-block.btn-info
i.fa.fa-sign-in
| Sign in
each label, id in strategies
div: a(href = 'signin/' + id) Sign in with #{label}
div
button.btn.btn-block.btn-info
i.fa.fa-sign-in
| Sign in
else
div.mb-2
each label, id in strategies
div: a(href = 'signin/' + id).btn.btn-block.btn-primary.mb-1 Sign in with #{label}
form(action = 'signin/local' method = 'post')
fieldset
.input-group.mb-1
span.input-group-addon
i.xo-icon-user.fa-fw
input.form-control(
autofocus
name = 'username'
placeholder = 'Username'
required
type = 'text'
)
.input-group.mb-1
span.input-group-addon
i.fa.fa-key.fa-fw
input.form-control(
name = 'password'
type = 'password'
placeholder = 'Password'
required
)
.checkbox
label
input(
name = 'remember-me'
type = 'checkbox'
)
| &nbsp;
| Remember me
div
button.btn.btn-block.btn-info
i.fa.fa-sign-in
| Sign in

View File

@@ -0,0 +1,36 @@
import iteratee from 'lodash/iteratee'
import pDelay from 'promise-toolbox/delay'
function stopRetry(error) {
this.error = error
// eslint-disable-next-line no-throw-literal
throw this
}
// do not retry on ReferenceError and TypeError which are programmer errors
const defaultMatcher = error =>
!(error instanceof ReferenceError || error instanceof TypeError)
export default async function pRetry(
fn,
{ delay = 1e3, tries = 10, when } = {}
) {
const container = { error: undefined }
const stop = stopRetry.bind(container)
when = when === undefined ? defaultMatcher : iteratee(when)
while (true) {
try {
return await fn(stop)
} catch (error) {
if (error === container) {
throw container.error
}
if (--tries === 0 || !when(error)) {
throw error
}
}
await pDelay(delay)
}
}

View File

@@ -0,0 +1,46 @@
/* eslint-env jest */
import pRetry from './_pRetry'
describe('pRetry()', () => {
it('retries until the function succeeds', async () => {
let i = 0
expect(
await pRetry(
() => {
if (++i < 3) {
throw new Error()
}
return 'foo'
},
{ delay: 0 }
)
).toBe('foo')
expect(i).toBe(3)
})
it('returns the last error', async () => {
let tries = 5
const e = new Error()
await expect(
pRetry(
() => {
throw --tries > 0 ? new Error() : e
},
{ delay: 0, tries }
)
).rejects.toBe(e)
})
;[ReferenceError, TypeError].forEach(ErrorType => {
it(`does not retry if a ${ErrorType.name} is thrown`, async () => {
let i = 0
await expect(
pRetry(() => {
++i
throw new ErrorType()
})
).rejects.toBeInstanceOf(ErrorType)
expect(i).toBe(1)
})
})
})

View File

@@ -1,5 +1,4 @@
import { basename } from 'path'
import { isEmpty, pickBy } from 'lodash'
import { safeDateFormat } from '../utils'
@@ -11,7 +10,7 @@ export function createJob({ schedules, ...job }) {
createJob.permission = 'admin'
createJob.params = {
compression: {
enum: ['', 'native'],
enum: ['', 'native', 'zstd'],
optional: true,
},
mode: {
@@ -68,7 +67,7 @@ export function editJob(props) {
editJob.permission = 'admin'
editJob.params = {
compression: {
enum: ['', 'native'],
enum: ['', 'native', 'zstd'],
optional: true,
},
id: {
@@ -151,13 +150,24 @@ runJob.params = {
// -----------------------------------------------------------------------------
export async function getAllLogs(filter) {
const logs = await this.getBackupNgLogs()
return isEmpty(filter) ? logs : pickBy(logs, filter)
export function getAllLogs() {
return this.getBackupNgLogs()
}
getAllLogs.permission = 'admin'
export function getLogs({ after, before, limit, ...filter }) {
return this.getBackupNgLogsSorted({ after, before, limit, filter })
}
getLogs.permission = 'admin'
getLogs.params = {
after: { type: ['number', 'string'], optional: true },
before: { type: ['number', 'string'], optional: true },
limit: { type: 'number', optional: true },
}
// -----------------------------------------------------------------------------
export function deleteVmBackup({ id }) {

View File

@@ -2,14 +2,22 @@ import { format } from 'json-rpc-peer'
// ===================================================================
export function set({
export async function set({
host,
multipathing,
// TODO: use camel case.
name_label: nameLabel,
name_description: nameDescription,
}) {
return this.getXapi(host).setHostProperties(host._xapiId, {
const xapi = this.getXapi(host)
const hostId = host._xapiId
if (multipathing !== undefined) {
await xapi.setHostMultipathing(hostId, multipathing)
}
return xapi.setHostProperties(hostId, {
nameLabel,
nameDescription,
})
@@ -27,6 +35,10 @@ set.params = {
type: 'string',
optional: true,
},
multipathing: {
type: 'boolean',
optional: true,
},
}
set.resolve = {

View File

@@ -15,6 +15,13 @@ get.params = {
id: { type: 'string' },
}
export async function getAllInfo() {
return this.getAllRemotesInfo()
}
getAllInfo.permission = 'admin'
getAllInfo.description = 'Gets all info of remote'
export async function test({ id }) {
return this.testRemote(id)
}

View File

@@ -431,7 +431,7 @@ export async function probeHba({ host }) {
hba: hbaDevice.hba.trim(),
path: hbaDevice.path.trim(),
scsiId: hbaDevice.SCSIid.trim(),
size: hbaDevice.size.trim(),
size: +hbaDevice.size.trim(),
vendor: hbaDevice.vendor.trim(),
})
})

View File

@@ -587,6 +587,8 @@ set.params = {
// Kernel arguments for PV VM.
PV_args: { type: 'string', optional: true },
cpuMask: { type: 'array', optional: true },
cpuWeight: { type: ['integer', 'null'], optional: true },
cpuCap: { type: ['integer', 'null'], optional: true },
@@ -706,7 +708,7 @@ export async function copy({ compress, name: nameLabel, sr, vm }) {
copy.params = {
compress: {
type: 'boolean',
type: ['boolean', 'string'],
optional: true,
},
name: {
@@ -747,14 +749,25 @@ export { convertToTemplate as convert }
// TODO: implement resource sets
export const snapshot = defer(async function(
$defer,
{ vm, name = `${vm.name_label}_${new Date().toISOString()}` }
{
vm,
name = `${vm.name_label}_${new Date().toISOString()}`,
saveMemory = false,
description,
}
) {
await checkPermissionOnSrs.call(this, vm)
const xapi = this.getXapi(vm)
const { $id: snapshotId } = await xapi.snapshotVm(vm._xapiRef, name)
const { $id: snapshotId } = await (saveMemory
? xapi.checkpointVm(vm._xapiRef, name)
: xapi.snapshotVm(vm._xapiRef, name))
$defer.onFailure(() => xapi.deleteVm(snapshotId))
if (description !== undefined) {
await xapi.editVm(snapshotId, { name_description: description })
}
const { user } = this
if (user.permission !== 'admin') {
await this.addAcl(user.id, snapshotId, 'admin')
@@ -763,8 +776,10 @@ export const snapshot = defer(async function(
})
snapshot.params = {
description: { type: 'string', optional: true },
id: { type: 'string' },
name: { type: 'string', optional: true },
saveMemory: { type: 'boolean', optional: true },
}
snapshot.resolve = {
@@ -1125,7 +1140,7 @@ revert.resolve = {
async function handleExport(req, res, { xapi, id, compress }) {
const stream = await xapi.exportVm(id, {
compress: compress != null ? compress : true,
compress,
})
res.on('close', () => stream.cancel())
// Remove the filename as it is already part of the URL.
@@ -1160,7 +1175,7 @@ async function export_({ vm, compress }) {
export_.params = {
vm: { type: 'string' },
compress: { type: 'boolean', optional: true },
compress: { type: ['boolean', 'string'], optional: true },
}
export_.resolve = {

View File

@@ -16,6 +16,7 @@
// }
// })
import assert from 'assert'
import { boot16 as fat16 } from 'fatfs/structs'
const SECTOR_SIZE = 512
@@ -23,7 +24,10 @@ const SECTOR_SIZE = 512
const TEN_MIB = 10 * 1024 * 1024
// Creates a 10MB buffer and initializes it as a FAT 16 volume.
export function init() {
export function init({ label = 'NO LABEL ' } = {}) {
assert.strictEqual(typeof label, 'string')
assert.strictEqual(label.length, 11)
const buf = Buffer.alloc(TEN_MIB)
// https://github.com/natevw/fatfs/blob/master/structs.js
@@ -47,7 +51,7 @@ export function init() {
Reserved1: 0,
BootSig: 41,
VolID: 895111106,
VolLab: 'NO NAME ',
VolLab: label,
FilSysType: 'FAT16 ',
},
buf

View File

@@ -1,9 +1,11 @@
import appConf from 'app-conf'
import assert from 'assert'
import authenticator from 'otplib/authenticator'
import bind from 'lodash/bind'
import blocked from 'blocked'
import createExpress from 'express'
import createLogger from '@xen-orchestra/log'
import crypto from 'crypto'
import has from 'lodash/has'
import helmet from 'helmet'
import includes from 'lodash/includes'
@@ -45,6 +47,11 @@ import { configure } from '@xen-orchestra/log/configure'
// ===================================================================
// https://github.com/yeojz/otplib#using-specific-otp-implementations
authenticator.options = { crypto }
// ===================================================================
configure([
{
filter: process.env.DEBUG,
@@ -79,14 +86,14 @@ async function loadConfiguration() {
// ===================================================================
function createExpressApp() {
function createExpressApp(config) {
const app = createExpress()
app.use(helmet())
// Registers the cookie-parser and express-session middlewares,
// necessary for connect-flash.
app.use(cookieParser())
app.use(cookieParser(null, config.http.cookies))
app.use(
expressSession({
resave: false,
@@ -140,6 +147,52 @@ async function setUpPassport(express, xo) {
res.redirect('/')
})
express.get('/signin-otp', (req, res, next) => {
if (req.session.user === undefined) {
return res.redirect('/signin')
}
res.send(
signInPage({
error: req.flash('error')[0],
otp: true,
strategies,
})
)
})
express.post('/signin-otp', (req, res, next) => {
const { user } = req.session
if (user === undefined) {
return res.redirect(303, '/signin')
}
if (authenticator.check(req.body.otp, user.preferences.otp)) {
setToken(req, res, next)
} else {
req.flash('error', 'Invalid code')
return res.redirect(303, '/signin-otp')
}
})
const setToken = async (req, res, next) => {
const { user, isPersistent } = req.session
const token = (await xo.createAuthenticationToken({ userId: user.id })).id
// Persistent cookie ? => 1 year
// Non-persistent : external provider as Github, Twitter...
res.cookie(
'token',
token,
isPersistent ? { maxAge: 1000 * 60 * 60 * 24 * 365 } : undefined
)
delete req.session.isPersistent
delete req.session.user
res.redirect(303, req.flash('return-url')[0] || '/')
}
const SIGNIN_STRATEGY_RE = /^\/signin\/([^/]+)(\/callback)?(:?\?.*)?$/
express.use(async (req, res, next) => {
const { url } = req
@@ -153,41 +206,22 @@ async function setUpPassport(express, xo) {
if (!user) {
req.flash('error', info ? info.message : 'Invalid credentials')
return res.redirect('/signin')
return res.redirect(303, '/signin')
}
// The cookie will be set in via the next request because some
// browsers do not save cookies on redirect.
req.flash(
'token',
(await xo.createAuthenticationToken({ userId: user.id })).id
)
// The session is only persistent for internal provider and if 'Remember me' box is checked
req.flash(
'session-is-persistent',
req.session.user = { id: user.id, preferences: user.preferences }
req.session.isPersistent =
matches[1] === 'local' && req.body['remember-me'] === 'on'
)
res.redirect(req.flash('return-url')[0] || '/')
if (user.preferences?.otp !== undefined) {
return res.redirect(303, '/signin-otp')
}
setToken(req, res, next)
})(req, res, next)
}
const token = req.flash('token')[0]
if (token) {
const isPersistent = req.flash('session-is-persistent')[0]
if (isPersistent) {
// Persistent cookie ? => 1 year
res.cookie('token', token, { maxAge: 1000 * 60 * 60 * 24 * 365 })
} else {
// Non-persistent : external provider as Github, Twitter...
res.cookie('token', token)
}
next()
} else if (req.cookies.token) {
if (req.cookies.token) {
next()
} else if (
/favicon|fontawesome|images|styles|\.(?:css|jpg|png)$/.test(url)
@@ -601,7 +635,7 @@ export default async function main(args) {
await xo.clean()
// Express is used to manage non WebSocket connections.
const express = createExpressApp()
const express = createExpressApp(config)
if (config.http.redirectToHttps) {
let port

View File

@@ -347,9 +347,11 @@ export const streamToArray = (stream, { filter, mapper } = {}) =>
// Create a serializable object from an error.
export const serializeError = error => ({
message: error.message,
stack: error.stack,
...error, // Copy enumerable properties.
code: error.code,
message: error.message,
name: error.name,
stack: error.stack,
})
// -------------------------------------------------------------------

View File

@@ -173,6 +173,7 @@ const TRANSFORMS = {
total: 0,
}
})(),
multipathing: obj.multipathing,
patches: patches || link(obj, 'patches'),
powerOnMode: obj.power_on_mode,
power_state: metrics ? (isRunning ? 'Running' : 'Halted') : 'Unknown',
@@ -426,6 +427,7 @@ const TRANSFORMS = {
let tmp
if ((tmp = obj.VCPUs_params)) {
tmp.cap && (vm.cpuCap = +tmp.cap)
tmp.mask && (vm.cpuMask = tmp.mask.split(',').map(_ => +_))
tmp.weight && (vm.cpuWeight = +tmp.weight)
}
@@ -476,6 +478,7 @@ const TRANSFORMS = {
host: link(obj, 'host'),
SR: link(obj, 'SR'),
device_config: obj.device_config,
otherConfig: obj.other_config,
}
},
@@ -505,6 +508,7 @@ const TRANSFORMS = {
// A physical PIF cannot be unplugged
physical: Boolean(obj.physical),
vlan: +obj.VLAN,
speed: metrics && +metrics.speed,
$host: link(obj, 'host'),
$network: link(obj, 'network'),
}
@@ -744,7 +748,7 @@ const TRANSFORMS = {
// ===================================================================
export default function xapiObjectToXo(xapiObj, dependents) {
export default function xapiObjectToXo(xapiObj, dependents = {}) {
const transform = TRANSFORMS[xapiObj.$type.toLowerCase()]
if (!transform) {
return

View File

@@ -36,6 +36,7 @@ import { satisfies as versionSatisfies } from 'semver'
import createSizeStream from '../size-stream'
import fatfsBuffer, { init as fatfsBufferInit } from '../fatfs-buffer'
import pRetry from '../_pRetry'
import {
camelToSnakeCase,
ensureArray,
@@ -415,6 +416,36 @@ export default class Xapi extends XapiBase {
await this.call('host.enable', this.getObject(hostId).$ref)
}
@deferrable.onError(log.warn)
async setHostMultipathing($defer, hostId, multipathing) {
const host = this.getObject(hostId)
const pluggedPbds = host.$PBDs.filter(pbd => pbd.currently_attached)
await asyncMap(pluggedPbds, async pbd => {
const ref = pbd.$ref
await this.unplugPbd(ref)
$defer(() => this.plugPbd(ref))
})
if (host.enabled) {
await this.disableHost(hostId)
$defer(() => this.enableHost(hostId))
}
return this._updateObjectMapProperty(
host,
'other_config',
multipathing
? {
multipathing: 'true',
multipathhandle: 'dmp',
}
: {
multipathing: 'false',
}
)
}
async powerOnHost(hostId) {
await this.call('host.power_on', this.getObject(hostId).$ref)
}
@@ -509,7 +540,7 @@ export default class Xapi extends XapiBase {
vmId,
targetXapi,
targetSrId,
{ compress = true, nameLabel = undefined } = {}
{ compress, nameLabel = undefined } = {}
) {
// Fall back on local copy if possible.
if (targetXapi === this) {
@@ -753,7 +784,7 @@ export default class Xapi extends XapiBase {
// Returns a stream to the exported VM.
@concurrency(2, stream => stream.then(stream => fromEvent(stream, 'end')))
@cancelable
async exportVm($cancelToken, vmId, { compress = true } = {}) {
async exportVm($cancelToken, vmId, { compress = false } = {}) {
const vm = this.getObject(vmId)
const useSnapshot = isVmRunning(vm)
const exportedVm = useSnapshot
@@ -763,7 +794,12 @@ export default class Xapi extends XapiBase {
const promise = this.getResource($cancelToken, '/export/', {
query: {
ref: exportedVm.$ref,
use_compression: compress ? 'true' : 'false',
use_compression:
compress === 'zstd'
? 'zstd'
: compress === true || compress === 'gzip'
? 'true'
: 'false',
},
task: this.createTask('VM export', vm.name_label),
}).catch(error => {
@@ -1510,15 +1546,50 @@ export default class Xapi extends XapiBase {
}`
)
const vmRef = vm.$ref
let ref
do {
if (!vm.tags.includes('xo-disable-quiesce')) {
try {
ref = await this.callAsync(
$cancelToken,
'VM.snapshot_with_quiesce',
vm.$ref,
nameLabel
vm = await this.barrier(vmRef)
ref = await pRetry(
async bail => {
try {
return await this.callAsync(
$cancelToken,
'VM.snapshot_with_quiesce',
vmRef,
nameLabel
)
} catch (error) {
if (error?.code !== 'VM_SNAPSHOT_WITH_QUIESCE_FAILED') {
throw bail(error)
}
// detect and remove new broken snapshots
//
// see https://github.com/vatesfr/xen-orchestra/issues/3936
const prevSnapshotRefs = new Set(vm.snapshots)
const snapshotNameLabelPrefix = `Snapshot of ${vm.uuid} [`
vm = await this.barrier(vmRef)
const createdSnapshots = vm.$snapshots.filter(
_ =>
!prevSnapshotRefs.has(_.$ref) &&
_.name_label.startsWith(snapshotNameLabelPrefix)
)
// be safe: only delete if there was a single match
if (createdSnapshots.length === 1) {
ignoreErrors.call(this._deleteVm(createdSnapshots[0]))
}
throw error
}
},
{
delay: 60e3,
tries: 3,
}
).then(extractOpaqueRef)
this.addTag(ref, 'quiesce')::ignoreErrors()
@@ -1540,7 +1611,7 @@ export default class Xapi extends XapiBase {
ref = await this.callAsync(
$cancelToken,
'VM.snapshot',
vm.$ref,
vmRef,
nameLabel
).then(extractOpaqueRef)
} while (false)
@@ -1814,10 +1885,13 @@ export default class Xapi extends XapiBase {
}`
)
try {
await this.call('VDI.pool_migrate', vdi.$ref, sr.$ref, {})
await pRetry(() => this.call('VDI.pool_migrate', vdi.$ref, sr.$ref, {}), {
when: { code: 'TOO_MANY_STORAGE_MIGRATES' },
})
} catch (error) {
const { code } = error
if (
code !== 'NO_HOSTS_AVAILABLE' &&
code !== 'LICENCE_RESTRICTION' &&
code !== 'VDI_NEEDS_VM_FOR_MIGRATE'
) {
@@ -1826,15 +1900,13 @@ export default class Xapi extends XapiBase {
const newVdi = await this.barrier(
await this.call('VDI.copy', vdi.$ref, sr.$ref)
)
await asyncMap(vdi.$VBDs, vbd =>
Promise.all([
this.call('VBD.destroy', vbd.$ref),
this.createVbd({
...vbd,
vdi: newVdi,
}),
])
)
await asyncMap(vdi.$VBDs, async vbd => {
await this.call('VBD.destroy', vbd.$ref)
await this.createVbd({
...vbd,
vdi: newVdi,
})
})
await this._deleteVdi(vdi)
}
}
@@ -2296,7 +2368,7 @@ export default class Xapi extends XapiBase {
const sr = this.getObject(srId)
// First, create a small VDI (10MB) which will become the ConfigDrive
const buffer = fatfsBufferInit()
const buffer = fatfsBufferInit({ label: 'cidata ' })
const vdi = await this.createVdi({
name_label: 'XO CloudConfigDrive',
size: buffer.length,
@@ -2307,14 +2379,9 @@ export default class Xapi extends XapiBase {
// Then, generate a FAT fs
const fs = promisifyAll(fatfs.createFileSystem(fatfsBuffer(buffer)))
await fs.mkdir('openstack')
await fs.mkdir('openstack/latest')
await Promise.all([
fs.writeFile(
'openstack/latest/meta_data.json',
'{\n "uuid": "' + vm.uuid + '"\n}\n'
),
fs.writeFile('openstack/latest/user_data', config),
fs.writeFile('meta-data', 'instance-id: ' + vm.uuid + '\n'),
fs.writeFile('user-data', config),
])
// ignore errors, I (JFT) don't understand why they are emitted

View File

@@ -93,7 +93,7 @@ declare export class Xapi {
exportVm(
cancelToken: mixed,
vm: Vm,
options ?: Object
options?: { compress?: true | false | 'gzip' | 'zstd' }
): Promise<AugmentedReadable>;
getObject(object: Id): XapiObject;
importDeltaVm(data: DeltaVmImport, options: Object): Promise<{ vm: Vm }>;

View File

@@ -29,15 +29,15 @@ export default {
// FIXME: should be static
@debounce(24 * 60 * 60 * 1000)
async _getXenUpdates() {
const { readAll, statusCode } = await this.xo.httpRequest(
const response = await this.xo.httpRequest(
'http://updates.xensource.com/XenServer/updates.xml'
)
if (statusCode !== 200) {
if (response.statusCode !== 200) {
throw new Error('cannot fetch patches list from Citrix')
}
const data = parseXml(await readAll()).patchdata
const data = parseXml(await response.readAll()).patchdata
const patches = { __proto__: null }
forEach(data.patches.patch, patch => {

View File

@@ -5,13 +5,36 @@ import { NULL_REF } from 'xen-api'
import { forEach, mapToArray, parseSize } from '../../utils'
import { isVmHvm, isVmRunning, makeEditObject } from '../utils'
import {
extractOpaqueRef,
isVmHvm,
isVmRunning,
makeEditObject,
} from '../utils'
// According to: https://xenserver.org/blog/entry/vga-over-cirrus-in-xenserver-6-2.html.
const XEN_VGA_VALUES = ['std', 'cirrus']
const XEN_VIDEORAM_VALUES = [1, 2, 4, 8, 16]
export default {
// https://xapi-project.github.io/xen-api/classes/vm.html#checkpoint
async checkpointVm(vmId, nameLabel) {
const vm = this.getObject(vmId)
try {
const ref = await this.callAsync(
'VM.checkpoint',
vm.$ref,
nameLabel != null ? nameLabel : vm.name_label
).then(extractOpaqueRef)
return this.barrier(ref)
} catch (error) {
if (error.code === 'VM_BAD_POWER_STATE') {
return this._snapshotVm(vm, nameLabel)
}
throw error
}
},
// TODO: clean up on error.
@deferrable
async createVm(
@@ -309,6 +332,15 @@ export default {
},
},
cpuMask: {
get: vm => vm.VCPUs_params.mask && vm.VCPUs_params.mask.split(','),
set(cpuMask, vm) {
return this._updateObjectMapProperty(vm, 'VCPUs_params', {
mask: cpuMask == null ? cpuMask : cpuMask.join(','),
})
},
},
cpusMax: 'cpusStaticMax',
cpusStaticMax: {
constraints: {

View File

@@ -210,6 +210,7 @@ export default class Api {
}
async callApiMethod(session, name, params = {}) {
const xo = this._xo
const startTime = Date.now()
const method = this._methods[name]
@@ -219,7 +220,7 @@ export default class Api {
// FIXME: it can cause issues if there any property assignments in
// XO methods called from the API.
const context = Object.create(this._xo, {
const context = Object.create(xo, {
api: {
// Used by system.*().
value: this,
@@ -231,9 +232,24 @@ export default class Api {
// Fetch and inject the current user.
const userId = session.get('user_id', undefined)
context.user = userId && (await this._xo.getUser(userId))
context.user = userId && (await xo.getUser(userId))
const userName = context.user ? context.user.email : '(unknown user)'
const data = {
userId,
method: name,
params: sensitiveValues.replace(params, '* obfuscated *'),
}
const callId = Math.random()
.toString(36)
.slice(2)
xo.emit('xo:preCall', {
...data,
callId,
})
try {
await checkPermission.call(context, method)
@@ -271,23 +287,33 @@ export default class Api {
)}] ==> ${kindOf(result)}`
)
xo.emit('xo:postCall', {
callId,
method: name,
result,
})
return result
} catch (error) {
params = sensitiveValues.replace(params, '* obfuscated *')
const data = {
userId,
const serializedError = serializeError(error)
xo.emit('xo:postCall', {
callId,
error: serializedError,
method: name,
params,
})
const message = `${userName} | ${name}(${JSON.stringify(
data.params
)}) [${ms(Date.now() - startTime)}] =!> ${error}`
this._logger.error(message, {
...data,
duration: Date.now() - startTime,
error: serializeError(error),
}
const message = `${userName} | ${name}(${JSON.stringify(params)}) [${ms(
Date.now() - startTime
)}] =!> ${error}`
error: serializedError,
})
this._logger.error(message, data)
if (this._xo._config.verboseLogsOnErrors) {
if (xo._config.verboseLogsOnErrors) {
log.warn(message, { error })
} else {
log.warn(
@@ -301,7 +327,7 @@ export default class Api {
if (xoError) {
throw xoError(error.params, ref => {
try {
return this._xo.getObject(ref).id
return xo.getObject(ref).id
} catch (e) {
return ref
}

View File

@@ -1,4 +1,5 @@
import { forEach } from 'lodash'
import ms from 'ms'
import { forEach, isEmpty, iteratee, sortedIndexBy } from 'lodash'
import { noSuchObject } from 'xo-common/api-errors'
const isSkippedError = error =>
@@ -32,6 +33,10 @@ const computeStatusAndSortTasks = (status, tasks) => {
return status
}
function getPropertyValue(key) {
return this[key]
}
const taskTimeComparator = ({ start: s1, end: e1 }, { start: s2, end: e2 }) => {
if (e1 !== undefined) {
if (e2 !== undefined) {
@@ -170,4 +175,48 @@ export default {
return runId === undefined ? consolidated : consolidated[runId]
},
async getBackupNgLogsSorted({ after, before, filter, limit }) {
let logs = await this.getBackupNgLogs()
// convert to array
logs = Object.keys(logs).map(getPropertyValue, logs)
if (!isEmpty(filter)) {
logs = logs.filter(iteratee(filter))
}
logs.sort((a, b) => a.start - b.start)
// only extract the range we are interested in
const i =
after === undefined
? 0
: sortedIndexBy(
logs,
{
start: typeof after === 'number' ? after : Date.now() - ms(after),
},
'start'
)
let j =
before === undefined
? logs.length
: sortedIndexBy(
logs,
{
start:
typeof before === 'number' ? before : Date.now() - ms(before),
},
'start'
)
limit += i
if (limit < j) {
j = limit
}
logs = logs.slice(i, j)
return logs
},
}

View File

@@ -32,6 +32,7 @@ import {
} from 'promise-toolbox'
import Vhd, {
chainVhd,
checkVhdChain,
createSyntheticStream as createVhdReadStream,
} from 'vhd-lib'
@@ -80,7 +81,7 @@ type SimpleIdPattern = {|
export type BackupJob = {|
...$Exact<Job>,
compression?: 'native',
compression?: 'native' | 'zstd' | '',
mode: Mode,
remotes?: SimpleIdPattern,
settings: $Dict<Settings>,
@@ -176,10 +177,13 @@ const isMetadataFile = (filename: string) => filename.endsWith('.json')
const isVhd = (filename: string) => filename.endsWith('.vhd')
const isXva = (filename: string) => filename.endsWith('.xva')
const getJobCompression = ({ compression: c }) =>
c === undefined || c === '' ? false : c === 'native' ? 'gzip' : 'zstd'
const listReplicatedVms = (
xapi: Xapi,
scheduleId: string,
srId: string,
srId?: string,
vmUuid?: string
): Vm[] => {
const { all } = xapi.objects
@@ -476,13 +480,26 @@ const disableVmHighAvailability = async (xapi: Xapi, vm: Vm) => {
// ├─ <YYYYMMDD>T<HHmmss>.xva
// └─ <YYYYMMDD>T<HHmmss>.xva.checksum
//
// Attributes on created VM snapshots:
//
// - `other_config`:
// - `xo:backup:datetime` = snapshot.snapshot_time (allow sorting replicated VMs)
// - `xo:backup:job` = job.id
// - `xo:backup:schedule` = schedule.id
// - `xo:backup:vm` = vm.uuid
// - `xo:backup:exported` = 'true' (added at the end of the backup)
//
// Attributes of created VMs:
//
// - name: `${original name} - ${job name} - (${safeDateFormat(backup timestamp)})`
// - all snapshots attributes (see above)
// - `name_label`: `${original name} - ${job name} - (${safeDateFormat(backup timestamp)})`
// - tag:
// - copy in delta mode: `Continuous Replication`
// - copy in full mode: `Disaster Recovery`
// - imported from backup: `restored from backup`
// - `blocked_operations.start`: message
// - for copies/replications only, added after complete transfer
// - `other_config[xo:backup:sr]` = sr.uuid
//
// Task logs emitted in a backup execution:
//
@@ -901,6 +918,7 @@ export default class BackupNg {
// - [x] possibility to (re-)run a single VM in a backup?
// - [x] validate VHDs after exports and before imports, how?
// - [x] check merge/transfert duration/size are what we want for delta
// - [x] delete interrupted *importing* VMs
@defer
async _backupVm(
$defer: any,
@@ -1118,7 +1136,7 @@ export default class BackupNg {
parentId: taskId,
},
xapi.exportVm($cancelToken, snapshot, {
compress: job.compression === 'native',
compress: getJobCompression(job),
})
)
const exportTask = xva.task
@@ -1219,6 +1237,14 @@ export default class BackupNg {
const { $id: srId, xapi } = sr
// delete previous interrupted copies
ignoreErrors.call(
this._deleteVms(
xapi,
listReplicatedVms(xapi, scheduleId, undefined, vmUuid)
)
)
const oldVms = getOldEntries(
copyRetention - 1,
listReplicatedVms(xapi, scheduleId, srId, vmUuid)
@@ -1276,29 +1302,6 @@ export default class BackupNg {
$defer.onSuccess.call(xapi, 'deleteVm', baseSnapshot)
}
// JFT: TODO: remove when enough time has passed (~2018-09)
//
// Fix VHDs UUID (= VDI.uuid), which was not done before 2018-06-16.
await asyncMap(remotes, async ({ handler }) =>
asyncMap(
this._listVmBackups(handler, vmUuid, _ => _.mode === 'delta'),
({ _filename, vdis, vhds }) => {
const vmDir = dirname(_filename)
return asyncMap(vhds, async (vhdPath, vdiId) => {
const uuid = parseUuid(vdis[vdiId].uuid)
const vhd = new Vhd(handler, `${vmDir}/${vhdPath}`)
await vhd.readHeaderAndFooter()
if (!vhd.footer.uuid.equals(uuid)) {
vhd.footer.uuid = uuid
await vhd.readBlockAllocationTable()
await vhd.writeFooter()
}
})
}
)
)
let fullVdisRequired
await (async () => {
if (baseSnapshot === undefined) {
@@ -1351,16 +1354,20 @@ export default class BackupNg {
await asyncMap(files, async file => {
if (file[0] !== '.') {
try {
const vhd = new Vhd(handler, `${dir}/${file}`)
const path = `${dir}/${file}`
const vhd = new Vhd(handler, path)
await vhd.readHeaderAndFooter()
if (vhd.footer.uuid.equals(parseUuid(vdi.uuid))) {
await checkVhdChain(handler, path)
full = false
}
return
} catch (error) {
if (!(error instanceof AssertionError)) {
const corruptedVhdOrMissingParent =
error instanceof AssertionError || error?.code === 'ENOENT'
if (!corruptedVhdOrMissingParent) {
throw error
}
}
@@ -1573,6 +1580,14 @@ export default class BackupNg {
const { $id: srId, xapi } = sr
// delete previous interrupted copies
ignoreErrors.call(
this._deleteVms(
xapi,
listReplicatedVms(xapi, scheduleId, undefined, vmUuid)
)
)
const oldVms = getOldEntries(
copyRetention - 1,
listReplicatedVms(xapi, scheduleId, srId, vmUuid)

View File

@@ -1,7 +1,8 @@
import asyncMap from '@xen-orchestra/async-map'
import synchronized from 'decorator-synchronized'
import { format, parse } from 'xo-remote-parser'
import { getHandler } from '@xen-orchestra/fs'
import { ignoreErrors } from 'promise-toolbox'
import { ignoreErrors, timeout } from 'promise-toolbox'
import { noSuchObject } from 'xo-common/api-errors'
import * as sensitiveValues from '../sensitive-values'
@@ -18,13 +19,14 @@ const obfuscateRemote = ({ url, ...remote }) => {
export default class {
constructor(xo, { remoteOptions }) {
this._handlers = { __proto__: null }
this._remoteOptions = remoteOptions
this._remotes = new Remotes({
connection: xo._redis,
prefix: 'xo:remote',
indexes: ['enabled'],
})
this._handlers = { __proto__: null }
this._remotesInfo = {}
xo.on('clean', () => this._remotes.rebuildIndexes())
xo.on('start', async () => {
@@ -84,6 +86,23 @@ export default class {
return handler.test()
}
async getAllRemotesInfo() {
const remotes = await this._remotes.get()
await asyncMap(remotes, async remote => {
try {
const handler = await this.getRemoteHandler(remote.id)
await timeout.call(
handler.getInfo().then(info => {
this._remotesInfo[remote.id] = info
}),
5e3
)
} catch (_) {}
})
return this._remotesInfo
}
async getAllRemotes() {
return (await this._remotes.get()).map(_ => obfuscateRemote(_))
}

View File

@@ -250,7 +250,7 @@ export default class {
async connectXenServer(id) {
const server = (await this._getXenServer(id)).properties
const xapi = new Xapi({
const xapi = (this._xapis[server.id] = new Xapi({
allowUnauthorized: Boolean(server.allowUnauthorized),
readOnly: Boolean(server.readOnly),
@@ -262,7 +262,7 @@ export default class {
},
url: server.host,
watchEvents: false,
})
}))
try {
await xapi.connect()
@@ -278,7 +278,6 @@ export default class {
}
serverIdsByPool[poolId] = server.id
this._xapis[server.id] = xapi
xapi.xo = (() => {
const conId = server.id
@@ -376,7 +375,14 @@ export default class {
xapi.watchEvents()
this.updateXenServer(id, { error: null })::ignoreErrors()
xapi.once('disconnected', () => {
xapi.xo.uninstall()
delete this._xapis[server.id]
delete this._serverIdsByPool[poolId]
})
} catch (error) {
delete this._xapis[server.id]
xapi.disconnect()::ignoreErrors()
this.updateXenServer(id, { error: serializeError(error) })::ignoreErrors()
throw error

View File

@@ -1,6 +1,6 @@
{
"name": "xo-vmdk-to-vhd",
"version": "0.1.5",
"version": "0.1.6",
"license": "AGPL-3.0",
"description": "JS lib streaming a vmdk file to a vhd",
"keywords": [
@@ -28,7 +28,7 @@
"pipette": "^0.9.3",
"promise-toolbox": "^0.11.0",
"tmp": "^0.0.33",
"vhd-lib": "^0.4.0"
"vhd-lib": "^0.5.1"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -54,10 +54,9 @@ export default async function readVmdkGrainTable(fileAccessor) {
const grainSize =
getLongLong(headerBuffer, GRAIN_SIZE_OFFSET, 'grain size') * SECTOR_SIZE
const grainCount = Math.ceil(capacity / grainSize)
const numGTEsPerGT = getLongLong(
headerBuffer,
const numGTEsPerGT = new DataView(headerBuffer).getUint32(
NUM_GTE_PER_GT_OFFSET,
'num GTE per GT'
true
)
const grainTablePhysicalSize = numGTEsPerGT * 4
const grainDirectoryEntries = Math.ceil(grainCount / numGTEsPerGT)

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