Compare commits

...

569 Commits

Author SHA1 Message Date
Julien Fontanet
c83f56166d feat(xo-server): 5.42.1 2019-05-29 17:18:29 +02:00
Julien Fontanet
7199e1a214 feat(CHANGELOG): add channel badges 2019-05-29 16:48:07 +02:00
Julien Fontanet
cc2c71c076 chore(xo-server): remove unnecessary methods 2019-05-28 18:53:57 +02:00
Pierre Donias
9ca273b2c4 chore(CHANGELOG.unreleased): add some missing links (#4253) 2019-05-28 14:09:19 +02:00
Julien Fontanet
b85c2f35b6 fix(xo-server/vm.set): autoPoweronauto_poweron in XAPI
Related to 0e1e32d241
2019-05-28 11:48:00 +02:00
HamadaBrest
fdd79885f9 feat(xo-web/VM): display VDI size in migrate modal (#4250)
Fixes #2534
2019-05-27 16:56:45 +02:00
Julien Fontanet
b2eb970796 fix(xo-server/vm.set): cast weight to string
Follow-up of 49e1b0ba7
2019-05-27 16:23:38 +02:00
Julien Fontanet
3ee9c1b550 chore(xo-server/Xapi): remove unused setVcpuWeight 2019-05-27 16:23:37 +02:00
HamadaBrest
2566c24753 fix(xo-web/host): incorrect hypervisor name in RAM usage tooltip (#4248)
Fixes #4246
2019-05-27 15:44:59 +02:00
Julien Fontanet
49e1b0ba7e fix(xo-server/vm.set): cast logical numbers to XAPI string values 2019-05-27 11:10:19 +02:00
Julien Fontanet
453c329f14 fix(xo-server/vm.set): videoram is strictly a number 2019-05-27 11:04:44 +02:00
badrAZ
27193f38f3 feat(xo-web): 5.42.0 2019-05-24 15:32:16 +02:00
badrAZ
d3dc94e210 feat(xo-server): 5.42.0 2019-05-24 15:31:54 +02:00
Julien Fontanet
6dad860635 fix(xo-server/getRemoteHandler): only cache on success
Otherwise subsequent calls will use an invalid handler.

Related to xoa-support#1498
2019-05-23 18:13:58 +02:00
Julien Fontanet
0362ac8909 feat(xo-web/home): case-sensitive filtering 2019-05-23 17:34:19 +02:00
Pierre Donias
e7b79f83d1 fix(xo-web): cast XOA_PLAN for strict equality tests (#4241) 2019-05-23 17:17:48 +02:00
Pierre Donias
62379c1e41 feat(xo-web/settings/logs): suggest XCP-ng when LICENCE_RESTRICTION (#4238)
Fixes #3876
2019-05-23 17:16:39 +02:00
Pierre Donias
23b422e3df feat(xo-server,xo-web/user): forget all authentication tokens (#4224)
Fixes #4214
2019-05-23 17:13:27 +02:00
Pierre Donias
f8e6dee635 fix(xo-web/vm/networks/addresses): avoid duplicate IP addresses (#4239)
Fixes support#1227
2019-05-23 14:33:32 +02:00
Nicolas Raynaud
c8e9b287f4 fix(xo-server/Xapi#_importOvaVm): userdevice must be string not a number (#4232)
Fixes xoa-support#1479
2019-05-22 16:12:19 +02:00
HamadaBrest
c9412dbcd0 fix(xo-web/xoa): ask confirm on upgrade if running jobs (#4235)
Fixes #4164
2019-05-22 15:19:31 +02:00
Julien Fontanet
77222e9e6b Update PULL_REQUEST_TEMPLATE.md 2019-05-22 14:50:57 +02:00
HamadaBrest
9d0f24eae1 feat(xo-web/xoa): release channels support (#4202)
Fixes #4200
2019-05-22 09:32:01 +02:00
Pierre Donias
6e527947be fix(xo-web): adminOnly breaks the routes (#4231)
Introduced by 59e68682bd

`@routes` must always be on top because it decorates add a `routes` property to the component which will be used by the parent.
2019-05-21 17:45:27 +02:00
Julien Fontanet
e7051c1129 fix(xo-server/network.set): dont pass undefined to update_other_config()
Related to 0e1e32d241
2019-05-21 16:47:22 +02:00
Julien Fontanet
3196c7ca09 chore(xo-server): use Xapi's setters (#4229) 2019-05-21 15:44:10 +02:00
Julien Fontanet
0e1e32d241 chore(xo-server): use Xapi's field entries updaters (#4230) 2019-05-21 15:25:37 +02:00
Julien Fontanet
a34912fb0d chore(xo-server): move some calls to Xapi#callAsync (#4227)
Fixes #4226

`.callAsync` is more robust to disconnections than `.call` and should be used for all non-instantaneous calls.

Unfortunately the result can be embedded into XML, you should either not use the result or add `.then(extractOpaquerRef)` if you are expecting an opaque ref.
2019-05-21 15:04:24 +02:00
Dustin B
c7c6e0e2ff chore(xo-web/messages): one shot job → onetime job (#4222) 2019-05-21 11:30:42 +02:00
Julien Fontanet
1e529c995a chore(xo-server/vm.reboot): move into Xapi#rebootVm 2019-05-21 10:37:56 +02:00
Julien Fontanet
7be1c7a47b fix(xo-server): always use Xapi#callAsync for migration 2019-05-21 10:24:14 +02:00
HamadaBrest
b17380443b chore(@xen-orchestra/fs): test truncate() (#4225)
Related to #4180
2019-05-21 10:20:31 +02:00
Pierre Donias
59e68682bd fix(xo-web): lock admin pages (#4220)
Related to xoa-support#1460
2019-05-20 17:31:26 +02:00
Julien Fontanet
b7a92cfe92 feat(xo-server/vif.set): rateLimit support
Server-side of #4215
2019-05-20 16:06:42 +02:00
HamadaBrest
5ebe27da49 feat(fs): add truncate method (#4180) 2019-05-20 14:03:54 +02:00
Julien Fontanet
42df6ba6fa chore: update dependencies 2019-05-17 16:31:39 +02:00
Pierre Donias
8210fddfab fix(xo-web/charts): ensure consistent series order (#4221)
Fixes support#1481
2019-05-16 13:54:27 +02:00
Pierre Donias
f55ed273c5 chore(xo-web): remove unused messages (#4219) 2019-05-16 13:28:39 +02:00
Dustin B
d67e95af7b fix(xo-web/messages): more verbiage and typo fixes, clarifications as well (#4218) 2019-05-15 17:36:00 +02:00
badrAZ
0b0f235252 feat(xo-web/new/metadata): ability to set the backup report when property (#4149) 2019-05-15 16:47:59 +02:00
Jon Sands
36a5f52068 fix(docu/interface) grammar fixes for interface messages (#4213)
* Grammar fixes and typo for messages
2019-05-15 15:10:43 +02:00
Julien Fontanet
31266728f7 feat(xo-server): add / mounts to vendor config 2019-05-15 14:28:20 +02:00
Rajaa.BARHTAOUI
8c79ea4ce3 feat(xo-web/vm/general): display 'Started... ago' for paused state (#4170)
Fixes #3750
2019-05-15 10:41:02 +02:00
Dustin B
c73a4204cb Verbiage change to align with main messages.js 2019-05-14 22:52:58 +02:00
Dustin B
0b3c2cc252 Verbiage changes (#4211)
* Verbiage changes
2019-05-14 22:03:50 +02:00
Dustin B
2bd3ca1d0b Grammar and typos adjustments (#4210)
* Grammar and typos adjustments
2019-05-14 20:55:50 +02:00
badrAZ
ce8649d991 fix(xo-web/backup-ng): handle improper "reportWhen" value (#4199)
Additional change to #4178 to actually fix #4092.
2019-05-14 17:12:53 +02:00
badrAZ
9bd563b111 chore(CHANGELOG): update next 2019-05-14 16:32:31 +02:00
badrAZ
6ceb924a85 feat(xo-web): 5.41.0 2019-05-14 16:20:31 +02:00
badrAZ
c2ef0ded43 feat(xo-server): 5.41.0 2019-05-14 16:20:09 +02:00
badrAZ
6081a6f6db feat(vhd-lib): 0.7.0 2019-05-14 16:19:15 +02:00
badrAZ
a0d92a0b1d feat(@xen-orchestra/fs): 0.9.0 2019-05-14 16:17:19 +02:00
badrAZ
3cf1f7ede2 feat(xo-server-backup-reports): 0.16.1 2019-05-14 16:15:29 +02:00
Julien Fontanet
5757afa1d8 fix(xo-server/proxies): use Host of the config
Use the hostname of the URL in the config instead of the one from the incoming request.
2019-05-14 14:08:04 +02:00
Julien Fontanet
86e9b9c1b8 fix(xo-server/api/*): wrap with JsonRpcError 2019-05-14 10:21:43 +02:00
Danp2
1cdd1fa00e Switch forum link to new XCP subforum (#4203)
* Fix usage of "backuping"

* Switch to XCP sub-forum
2019-05-10 17:49:26 +02:00
Julien Fontanet
9d12759c68 fix(xo-server/VM import): forward error message 2019-05-10 17:32:23 +02:00
Julien Fontanet
594341fab6 fix(xo-server/remotes): dont fail is benchmarks JSON is broken 2019-05-09 14:06:50 +02:00
Julien Fontanet
4e88125cbe fix(xo-server/remotes): always JSON encode benchmarks (#4197)
See xoa-support#1464

Fix an issue when restoring from config.
2019-05-09 13:58:36 +02:00
badrAZ
13237180a2 fix(Backup-ng): report sent even though "Never" is selected (#4178)
Fixes #4092
2019-05-09 09:58:35 +02:00
Olivier Lambert
f64d7e0b6e fix README title and also update installation doc (#4198) 2019-05-09 09:34:36 +02:00
Olivier Lambert
040a6930a4 improving support page (#4196)
* improving support page
2019-05-09 09:27:26 +02:00
Julien Fontanet
c54b9189a6 feat(scripts/normalize-package): add version if missing 2019-05-09 09:23:49 +02:00
Julien Fontanet
8882f1b019 chore(bump-pkg): stop on errors 2019-05-07 14:39:17 +02:00
Julien Fontanet
ae6416c4d2 feat(xo-server): add XenServers from XenStore (#4194)
XenStore can be used to pass XenServers to connect to to xo-server:

`vm-data/xen-servers` entry:

```json
[
  {
    "allowUnauthorized": true,
    "host": "xs1.company.tld",
    "label": "My XenServer",
    "password": "super%secret+password",
    "readOnly": false,
    "username": "root"
  }
]
```
2019-05-07 10:25:00 +02:00
Dustin B
8faed87656 Documentation typo
Typo "on" to "one" in support page
2019-05-06 22:41:37 +02:00
Dustin B
0983f05969 Update support documentation 2019-05-06 22:27:57 +02:00
Julien Fontanet
d43e2544a1 feat(xo-server): admin account from XenStore (#4184)
XenStore can be used to pass the credentials of the default admin account to xo-server:

`vm-data/admin-account` entry:

```json
{ "email": "admin@admin.net", "password": "admin" }
```
2019-05-06 17:40:02 +02:00
Pierre Donias
ca83d11ac8 fix(xo-server/xapi-object-to-xo): cast patch size to number (#4193)
Fixes #4192
2019-05-06 16:55:54 +02:00
badrAZ
1cdcdd9b5f chore(reaclette): v0.8.0 (#4188) 2019-05-06 15:03:08 +02:00
Julien Fontanet
cc7806e35b chore(xo-server plugins): are not published as well 2019-05-06 11:45:00 +02:00
Julien Fontanet
0ee48b6623 chore(xo-{server,web}): mark as private
Explicit that these packages are not published on the npm registry.
2019-05-06 11:32:55 +02:00
Julien Fontanet
8c02e0efbd chore(scripts): publish on version bumping 2019-05-06 11:32:55 +02:00
marcpezin
34d3ca82bc adding free limited support section (#4186) 2019-05-06 10:21:18 +02:00
HamadaBrest
43822d3667 chore(vhd-lib): use write instead of createOutputStream (#4179)
See #4156
2019-05-03 14:07:10 +02:00
Pierre Donias
f4ac73b3b4 fix(xo-web/new-vm): cloud init should only be available in Premium (#4174) 2019-05-02 17:01:06 +02:00
HamadaBrest
f084b6def9 feat(fs#write): write buffer at specific position (#4169)
Fixes #4156
2019-05-02 16:20:57 +02:00
Julien Fontanet
a00d101ff7 chore: update dependencies 2019-05-02 10:58:38 +02:00
badrAZ
9d5900d9b6 chore(CHANGELOG): 5.34.0 2019-04-30 11:39:03 +02:00
badrAZ
28fb4e8216 feat(xo-web): 5.40.1 2019-04-30 11:21:14 +02:00
Julien Fontanet
bec4dbe652 feat(xo-web): unlock basic stats for all editions (#4172)
Fixes #4166
2019-04-30 10:17:31 +02:00
Pierre Donias
72cc14f508 feat(xo-web): add banner to clarify support conditions (#4167)
Fixes #4165
2019-04-30 09:54:40 +02:00
Julien Fontanet
d20941cc2c chore(CHANGELOG): Highlights sections 2019-04-29 14:10:27 +02:00
Julien Fontanet
9cb8a05316 chore(CHANGELOG): update next 2019-04-26 16:31:26 +02:00
Julien Fontanet
dccd799f6d feat(xo-web): 5.40.0 2019-04-26 16:27:25 +02:00
Julien Fontanet
b42b3d1b01 feat(xo-server): 5.40.0 2019-04-26 16:27:15 +02:00
Julien Fontanet
a40d6f772a feat(complex-matcher): 0.6.0 2019-04-26 16:25:47 +02:00
Julien Fontanet
6e9bfd18d9 feat(xo-server-backup-reports): 0.16.0 2019-04-26 16:24:22 +02:00
Julien Fontanet
3b92dd0139 feat(scripts): bump-pkg 2019-04-26 16:17:28 +02:00
HamadaBrest
564d53610a fix(xo-web/editable): notify user when undo fails (#4157)
Fixes #3799
2019-04-26 11:25:23 +02:00
Pierre Donias
b4c7b8ac7f fix(xo-web/new-vm): typos (#4158)
Introduced by 7acd90307b
2019-04-25 14:44:25 +02:00
HamadaBrest
7acd90307b feat(xo-web/new-vm): network config box for cloud-init (#4150)
Fixes #3872
2019-04-24 17:04:54 +02:00
Julien Fontanet
d3ec76c19f feat(lint): add eslint-comments plugin 2019-04-19 16:27:11 +02:00
HamadaBrest
688cb20674 feat(xo-web/self): remove ID from end user resource sets and add it to Self UI (#4151)
Fixes #4100
2019-04-18 16:45:52 +02:00
HamadaBrest
c63be20bea fix(xo-web/home): J/K navigation loop (#4152)
Fixes #2793
2019-04-18 16:15:25 +02:00
Rajaa.BARHTAOUI
df36633223 feat(xo-web/vm/attach disk): warning if VDI is on another local SR (#4117)
See #3911

Show a warning message if the VM has a VDI sitting on a local SR and the user
select a VDI sitting on a local SR on a different host since the VM won't be
able to start
2019-04-18 16:00:24 +02:00
badrAZ
3597621d88 feat(xo-server-backup-reports): metadata report implementation (#4084) 2019-04-18 09:49:18 +02:00
Pierre Donias
8387684839 fix(xo-web/migrateVm): don't pass SR if same-pool migration (#4146)
Fixes #4145

Introduced by f581e93b88
2019-04-17 16:04:05 +02:00
Pierre Donias
f261f395f1 fix(xo-web/migrateVm): typo (#4147) 2019-04-17 15:31:20 +02:00
Rajaa.BARHTAOUI
f27170ff0e feat(xo-web/vm/disk): notify user before breaking action (#4035)
See #3911

- New disk: warning if the selected SR is local to another host than another VDI
- Migrate VDI (row action only): warning if the selected SR is local to another host than another VDI
2019-04-16 11:04:12 +02:00
Enishowk
d82c951db6 feat(home): use regexp for tags filtering (#4112)
Avoid substring false positives.

Fixes #4087
2019-04-16 10:31:39 +02:00
Rajaa.BARHTAOUI
41ca853e03 feat(xo-web/new-vm): warning on high resource consumption (#4127)
Fixes #4044
2019-04-15 14:26:17 +02:00
Julien Fontanet
a08d098265 chore: update dependencies 2019-04-15 09:54:58 +02:00
Rajaa.BARHTAOUI
875681b8ce fix(xo-web/New VM): template selector won't load (#3565)
Fixes #3265
2019-04-12 14:51:13 +02:00
Julien Fontanet
a03dcbbf55 feat(xo-server): make Helmet configurable (#4141) 2019-04-12 13:49:51 +02:00
badrAZ
97cabbbc69 chore(CHANGELOG): update next 2019-04-11 17:42:52 +02:00
badrAZ
13725a9e21 feat(xo-web): v5.39.1 2019-04-11 17:22:34 +02:00
badrAZ
f47df961f7 fix(xo-web/backup-ng): transfer/merge tasks not displayed in the logs (#4140)
Introduced by 865d2df124
2019-04-11 17:12:47 +02:00
badrAZ
2f644d5eeb chore(CHANGELOG): update next 2019-04-11 16:19:29 +02:00
badrAZ
4b292bb78c feat(xo-web): v5.39.0 2019-04-11 16:02:57 +02:00
badrAZ
804891cc81 feat(xo-server): v5.39.0 2019-04-11 16:00:36 +02:00
badrAZ
d335e06371 feat(vhd-lib): v0.6.1 2019-04-11 15:54:48 +02:00
badrAZ
477058ad23 feat(xo-vmdk-to-vhd): v0.1.7 2019-04-11 15:35:15 +02:00
badrAZ
eb3b68401d feat(xo-server/metadata-backups): reportWhen implementation (#4135) 2019-04-11 15:24:15 +02:00
badrAZ
865d2df124 feat(xo-web/metadata-backups): metadata logs implementation (#4014)
Fixes #4005
2019-04-11 12:00:33 +02:00
badrAZ
88160bae1d fix(xo-server,xo-web/metadata-backups): handle null retentions (#4133)
Introduced by fea5117ed8
2019-04-11 11:00:04 +02:00
Rajaa.BARHTAOUI
f581e93b88 feat(xo-web/vm): migrate modal improvements (#4121)
Fixes #3326

- auto-select default SR as main SR
- hide optional per-VDI SR selection
2019-04-11 10:13:40 +02:00
Rajaa.BARHTAOUI
21a7cf7158 fix(xo-web/menu/xoa): display icon when no notifications nor updates (#4068)
Fixes #4012
2019-04-10 14:35:58 +02:00
Rajaa.BARHTAOUI
5edee4bae0 feat(xo-web/dashboard/overview): display 'Report' for admin only (#4126)
Fixes #4123
2019-04-09 17:13:28 +02:00
Julien Fontanet
916ca5576a feat(xen-api/cli): everything in context
Example: `xapi.pool` → `pool`
2019-04-09 17:09:03 +02:00
Julien Fontanet
6c861bfd1f feat(xen-api): name record classes with types 2019-04-09 16:28:46 +02:00
Rajaa.BARHTAOUI
56961b55bd fix(xo-web/dashboard/health): fix 'an error has occurred' (#4132)
Fixes  #4128
2019-04-09 15:17:34 +02:00
badrAZ
cdcd7154ba fix(xo-web/backup-ng): only display full interval in case of delta (#4125) 2019-04-09 15:12:52 +02:00
badrAZ
654a2ee870 feat(xo-web/backup-ng): make backup list title clearer (#4129)
Fixes #4111
2019-04-09 09:44:09 +02:00
Julien Fontanet
903634073a chore: update dependencies 2019-04-09 08:56:15 +02:00
badrAZ
0d4818feb6 fix(xo-server/metadata-backups): various changes (#4114)
- fix uncompleted log if one of the backup fails
- fix the case of a backup with xo mode and pool mode, if one fails the other will not be executed.
- log xo/pool by remotes
- log a warning task if a pool or a remote is missed
- log a warning task if a backup is not properly deleted
2019-04-08 17:24:02 +02:00
Julien Fontanet
d6aa40679b feat(xo-server/_assertHealthyVdiChains): attach info to error 2019-04-05 15:48:12 +02:00
Jon Sands
b7cc31c94d feat(docs/metadata backup): add restore instructions (#4116) 2019-04-05 11:21:21 +02:00
Julien Fontanet
6860156b6f chore(CHANGELOG): v5.33.1 2019-04-04 14:39:32 +02:00
Julien Fontanet
29486c9ce2 feat(xo-server): 5.38.2 2019-04-04 14:20:46 +02:00
Julien Fontanet
7cfa6a5da4 feat(xen-api): v0.25.1 2019-04-04 14:01:59 +02:00
Julien Fontanet
2563be472b fix(xen-api/_interruptOnDisconnect): dont use Promise.race
`Promise.race()` leads to memory leaks if some promises are never resolved.

See nodejs/node#17469
2019-04-04 13:42:45 +02:00
Julien Fontanet
7289e856d9 chore(xen-api/_sessionCall): dont use _interruptOnDisconnect 2019-04-04 13:39:45 +02:00
Nicolas Raynaud
975de1954e feat(xo-web/vm-import): don't block the UI when dropping a big OVA file (#4018) 2019-04-04 10:59:44 +02:00
Julien Fontanet
95bcf0c080 fix(xo-web/vms/import): various fixes (#4118)
- dont swallow `importVm` error
- `importVms`: display errors on console
- dont redirect on failure
2019-04-04 10:10:45 +02:00
Enishowk
f900a5ef4f feat(xo-web/backup): add warning regarding DST (#4056)
Fixes #4042
2019-04-03 11:42:24 +02:00
badrAZ
7f1ab529ae feat(xo-server/metadata-backups): logs implementation (#4108)
See #4014
2019-04-02 15:53:12 +02:00
Julien Fontanet
49fc86e4b1 chore(xen-api): rewrite inject-event test CLI 2019-04-02 15:24:25 +02:00
Julien Fontanet
924aef84f1 chore: drop Node 4 support 2019-04-02 11:40:27 +02:00
Rajaa.BARHTAOUI
96e6e2b72a feat(xo-web/xoa): registration panel enhancements (#4104)
Fixes #4043

- Remove useless "Trial" title
- Make the "Start trial" button bigger
2019-04-02 11:39:27 +02:00
Enishowk
71997d4e65 feat(xo-web/remotes): expose mount options field for SMB (#4067)
Fixes #4063
2019-04-02 10:49:45 +02:00
Nicolas Raynaud
447f2f9506 fix(vhd-lib/createVhdStreamWithLength): handle empty VHD (#4107)
Fixes #4105
2019-04-01 16:53:02 +02:00
Julien Fontanet
79aef9024b chore(CHANGELOG): move packages after fixes 2019-03-29 16:45:51 +01:00
Julien Fontanet
fdf6f4fdf3 chore(CHANGELOG): add missing packages list 2019-03-29 16:38:59 +01:00
Julien Fontanet
4d1eaaaade feat(xo-server): 5.38.1 2019-03-29 16:38:06 +01:00
Julien Fontanet
bdad6c0f6d feat(xen-api): v0.25.0 2019-03-29 16:35:19 +01:00
Julien Fontanet
ff1ca5d933 feat(xen-api/call): 1 hour timeout 2019-03-29 16:26:36 +01:00
Julien Fontanet
2cf4c494a4 feat(xen-api/connect): handle disconnect 2019-03-29 16:21:19 +01:00
Julien Fontanet
95ac0a861a chore(xen-api/getObjectByUuid): explicit test 2019-03-29 16:13:10 +01:00
Julien Fontanet
746c301f39 feat(xen-api): expose objectsFetched signal 2019-03-29 16:12:39 +01:00
Julien Fontanet
6455b12b58 chore(xen-api): real status state 2019-03-29 16:10:04 +01:00
Julien Fontanet
485b8fe993 chore(xen-api): rework events watching (#4103) 2019-03-29 15:59:51 +01:00
Julien Fontanet
d7527f280c chore(xen-api): rework call methods (#4102) 2019-03-29 15:39:31 +01:00
Julien Fontanet
d57fa4375d chore(xen-api/signals): not disconnected when connecting 2019-03-29 15:27:37 +01:00
Julien Fontanet
d9e42c6625 chore(xen-api): remove unused property 2019-03-29 15:08:57 +01:00
badrAZ
28293d3fce chore(CHANGELOG): v5.33.0 2019-03-29 15:04:27 +01:00
badrAZ
d505401446 feat(xo-web): v5.38.0 2019-03-29 14:37:25 +01:00
badrAZ
fafc24aeae feat(xo-server): v5.38.0 2019-03-29 14:35:48 +01:00
badrAZ
f78ef0d208 feat(xo-server-usage-report): v0.7.2 2019-03-29 14:33:08 +01:00
badrAZ
8384cc3652 feat(@xen-orchestra/fs): v0.8.0 2019-03-29 14:27:25 +01:00
badrAZ
60aa18a229 feat(vhd-lib): v0.6.0 2019-03-29 14:11:09 +01:00
badrAZ
3d64b42a89 feat(xen-api): v0.24.6 2019-03-29 14:05:14 +01:00
badrAZ
b301997d4b feat(xo-web): ability to restore a metadata backup (#4023)
Fixes #4004
2019-03-29 13:54:54 +01:00
Enishowk
ab34743250 feat(xo-web/hosts): suggest XCP-ng as alternative to XS Free (#4094)
Fixes #4091
2019-03-29 11:59:52 +01:00
badrAZ
bc14a1d167 feat(xo-web/backup-ng): ability to set the full backup interval (#4099)
Fixes #1783
2019-03-29 11:43:37 +01:00
badrAZ
2886ec116f feat(xo-server/metadata-backups): ability to restore metadata backup (#4096)
See #4004
2019-03-29 11:21:03 +01:00
Julien Fontanet
c2beb2a5fa chore(server/backup-ng-logs): initial documentation 2019-03-29 11:03:34 +01:00
Nicolas Raynaud
d6ac10f527 feat(xo-web/vm-import): improve VM import wording (#4020) 2019-03-29 09:23:39 +01:00
Julien Fontanet
9dcd8a707a feat(xen-api): add connected/disconnected signals 2019-03-28 18:39:33 +01:00
Julien Fontanet
e1e97ef158 chore(xen-api): set empty sessionId to undefined instead of null 2019-03-28 18:39:28 +01:00
Julien Fontanet
5d6b37f81a fix(xen-api/connect): dont stay disconnecting on failure 2019-03-28 18:19:50 +01:00
Julien Fontanet
e1da08ba38 chore(xen-api/connect): assert initially disconnected 2019-03-28 18:19:18 +01:00
Julien Fontanet
1dfb50fefd feat(xo-server/backup): fullInterval setting (#4086)
See #4083
2019-03-28 18:10:05 +01:00
Julien Fontanet
5c06ebc9c8 feat(xen-api/{,dis}connect): dont fail if already in expected state 2019-03-28 17:38:12 +01:00
Julien Fontanet
52a9270fb0 feat(xen-api): coalesce connect calls 2019-03-28 17:30:26 +01:00
Julien Fontanet
82247d7422 chore(xen-api): various changes 2019-03-28 17:30:25 +01:00
Julien Fontanet
b34688043f chore(xen-api): rewrite barrier and createTask 2019-03-28 17:30:24 +01:00
Julien Fontanet
ce4bcbd19d chore(xen-api): move more methods 2019-03-28 17:30:24 +01:00
Pierre Donias
cde9a02c32 fix(xo-server,xo-web,xo-server-usage-report): patches (#4077)
See #2565
See #3655
Fixes #2188
Fixes #3777
Fixes #3783
Fixes #3934
Fixes support#1228
Fixes support#1338
Fixes support#1362

- mergeInto: fix auto-patching on XS < 7.2
- mergeInto: homogenize both the host and pool's patches
- correctly install specific patches
- XCP-ng: fix "xcp-ng-updater not installed" bug
2019-03-28 17:05:04 +01:00
Julien Fontanet
fe1da4ea12 chore(xen-api): _addObject → _addRecordToCache, _removeObject → _removeRecordFromCache 2019-03-28 16:17:53 +01:00
Julien Fontanet
a73306817b chore(xen-api): move more methods 2019-03-28 16:15:09 +01:00
Julien Fontanet
54e683d3d4 chore(xen-api): move getField to object handling helpers section 2019-03-28 16:01:10 +01:00
Enishowk
f49910ca82 feat(xo-web, xo-server): display link to pool (#4045)
Fixes #4041
2019-03-28 15:42:37 +01:00
Julien Fontanet
4052f7f736 chore(xen-api): regroup HTTP requests 2019-03-28 13:58:23 +01:00
Julien Fontanet
b47e097983 feat(xen-api/{get,put}Resource): add inactivity detection (#4090) 2019-03-28 13:55:56 +01:00
Julien Fontanet
e44dbfb2a4 fix(xen-api/examples): use isOpaqueRef private module 2019-03-28 13:30:08 +01:00
Julien Fontanet
7d69dd9400 fix(xen-api): add missing Babel plugin 2019-03-28 12:21:55 +01:00
Julien Fontanet
e6aae8fcfa chore(xen-api): regroup object handling helpers 2019-03-28 12:19:08 +01:00
Julien Fontanet
da800b3391 chore(xo-collection): minor improvements (#4089) 2019-03-28 12:15:04 +01:00
Julien Fontanet
3a574bcecc chore(xen-api): clean call/callAsync code 2019-03-28 12:14:03 +01:00
Julien Fontanet
1bb0e234e7 chore(xen-api): modularize (#4088) 2019-03-28 11:17:25 +01:00
Julien Fontanet
b7e14ebf2a fix(xo-server/snapshotVm): dont retry and unconditionaly clean (#4075)
Fixes #4074
2019-03-28 10:54:50 +01:00
Nicolas Raynaud
2af1207702 feat(vhd-lib,xo-server): guess VHD size on import (#3726) 2019-03-28 10:16:28 +01:00
Julien Fontanet
ecfed30e6e fix(xo-web/JSON schema object input): clear when un-use (#4076) 2019-03-28 10:05:15 +01:00
Enishowk
d06c3e3dd8 fix(xo-web/smart-backup): StringNode → RegExpNode to anchor strings (#4085)
Fixes #4078
2019-03-27 22:11:23 +01:00
Julien Fontanet
16b3fbeb16 fix(scripts/travis-tests): integration tests on branches 2019-03-27 15:45:16 +01:00
Julien Fontanet
0938804947 fix(xo-server/worker): forget remote after use (#4079)
Fixes xoa-support#1378
Fixes xoa-support#1384
Fixes xoa-support#1399
2019-03-27 10:52:42 +01:00
Julien Fontanet
851bcf9816 feat(xo-server/api): close connection when session expires (#4071)
See xoa-support#1389
2019-03-27 10:36:15 +01:00
Julien Fontanet
9f6fc785bc fix(xo-server/remotes): dont sync unnecessarily 2019-03-27 10:04:01 +01:00
Julien Fontanet
56636bf5d4 chore(xo-server/MultiKeyMap): better implementation (#4070)
No longer unnecessarily use a map per entry and avoid creating Node as much as possible.
2019-03-26 17:50:22 +01:00
Julien Fontanet
3899a65167 fix(xo-server/plugin.configure): properly merge previous config 2019-03-26 17:29:33 +01:00
Rajaa.BARHTAOUI
628e53c1c3 feat(xo-web/settings/plugins): display plugin count (#4050)
Fixes #4008
2019-03-26 16:55:55 +01:00
Enishowk
9fa424dd8d fix(xo-web/remotes): test if writeRate or readRate are defined (#4073)
Fixes #4072
2019-03-26 15:35:01 +01:00
Julien Fontanet
3e6f2eecfa chore(xo-server/index): allow console 2019-03-26 09:38:43 +01:00
Julien Fontanet
cc655c8ba8 feat(xo-server): pDebounceWithKey (#4066) 2019-03-25 17:36:41 +01:00
Julien Fontanet
78aa0474ee chore(ESLint): ban console logs 2019-03-25 17:02:40 +01:00
Enishowk
9caefa2f49 fix(xo-web/remotes): remove console.log (#4065) 2019-03-25 17:02:21 +01:00
Julien Fontanet
478726fa3b chore(xo-server/ensureArray): move into own module 2019-03-25 16:48:15 +01:00
Julien Fontanet
f64917ec52 feat(xo-server): configurable session validity (#4059)
See xoa-support#1389
2019-03-25 16:24:27 +01:00
badrAZ
2bc25f91c4 chore(xo-server/XapiStats): improve the cache implementation (#3859) 2019-03-25 10:45:54 +01:00
Julien Fontanet
623d7ffe2f feat(cloud config): Config Drive datasource as a fallback (#4053)
Fixes xoa-support#1179
2019-03-25 09:34:21 +01:00
Julien Fontanet
07510b5099 chore(xo-server/authenticateUser): better doc and explicit tests 2019-03-22 17:58:00 +01:00
Julien Fontanet
9f21f9a7bc chore(xo-server/authenticateUser): throws invalidCredentials instead of returning false 2019-03-22 17:56:58 +01:00
Julien Fontanet
93da70709e fix(xo-web): dont try reconnecting websocket on sign out 2019-03-22 17:34:46 +01:00
Julien Fontanet
00436e744a fix(xo-server/_parseDuration): add missing file
Related to 6baef2450
2019-03-22 16:19:09 +01:00
Julien Fontanet
1e642fc512 chore(xo-server/store): dont access xo._config 2019-03-22 15:45:09 +01:00
Julien Fontanet
6baef2450c feat(xo-server/authentication): configurable token validity 2019-03-22 15:29:11 +01:00
Julien Fontanet
600f34f85a fix(xo-server/console proxy): close socket on authentication error 2019-03-22 11:43:52 +01:00
Julien Fontanet
6c0c6bc5c4 fix(xo-server-recover-account): remove OTP (#4055)
Otherwise it's not possible to recover if the OTP setting has been lost.
2019-03-22 11:10:16 +01:00
Enishowk
fcd62ed3cd feat(remote): add read/write speeds on remote (#4015)
Fixes #3991
2019-03-21 18:25:03 +01:00
Julien Fontanet
785f2e3a6d chore: update Babel
Fixes #4052
2019-03-21 12:33:18 +01:00
Julien Fontanet
c2925f7c1e chore(xo-import-servers-csv): remove csv-parser types
Already available in csv-parser@2.2.0.
2019-03-21 12:31:05 +01:00
Jon Sands
60814d8b58 fix(docs): add pictures to manual seeding instructions (#4019) 2019-03-21 08:38:18 +01:00
Julien Fontanet
2dec448f2c chore: update dependencies 2019-03-20 11:26:05 +01:00
Pierre Donias
b71f4f6800 fix(xo-web/home): always sort by name_label as a secondary sort (#4047)
Fixes #3983
2019-03-20 09:43:06 +01:00
badrAZ
558083a916 chore(CHANGELOG): update next 2019-03-19 15:30:24 +01:00
badrAZ
d507ed9dff feat(xo-web): v5.37.0 2019-03-19 15:05:41 +01:00
badrAZ
7ed0242662 feat(xo-server): v5.37.0 2019-03-19 14:59:59 +01:00
badrAZ
d7b3d989d7 feat(xo-server-auth-google): v0.2.1 2019-03-19 14:51:25 +01:00
badrAZ
707b2f77f0 fix(xo-web/backup-ng): display compression only in full mode (#4021)
Fixes xoa-support#1346
2019-03-19 11:58:11 +01:00
Rajaa.BARHTAOUI
5ddbb76979 feat(xo-web/vm/disk): warning when SRs on 2 different hosts (#3969)
See #3911

Show a warning message when at least 2 VDIs attached to the VM are on 2 local SRs on 2 different hosts because the VM won't be able to start (NO_HOSTS_AVAILABLE)
2019-03-19 11:14:30 +01:00
Enishowk
97b0fe62d4 feat(xo-server/vm.delete): ensure suspend VDI is destroyed (#4038)
Fixes #4027
2019-03-18 10:29:54 +01:00
badrAZ
8ac9b2cdc7 fix(xo-server/xapi-stats): synchronize requests to improve caching (#4028)
Fixes #4017
2019-03-15 11:46:21 +01:00
badrAZ
bc4c1a13e6 chore(xo-server): remove deprecated syntax for decorator-synchronized (#4037) 2019-03-14 18:13:39 +01:00
Julien Fontanet
d3ec303ade feat(xo-server): properly streams NDJSON (#4030) 2019-03-14 11:21:09 +01:00
Rajaa.BARHTAOUI
6cfc2a1ba6 fix(CHANGELOG.unreleased): remove duplicate entry (#4034) 2019-03-13 10:54:40 +01:00
Enishowk
e15cadc863 feat(xo-web/home): add current page in url (#3999)
Fixes #3993
2019-03-13 08:54:30 +01:00
Julien Fontanet
2f9284c263 fix(xo-server/sample.config.toml): datadir is not in redis section 2019-03-12 22:39:37 +01:00
badrAZ
2465852fd6 chore(xo-web): rename ret. to retention (#4029) 2019-03-12 15:18:03 +01:00
badrAZ
a9f48a0d50 fix(xo-web/migrateVms): VM disks migrated to the wrong SR (#3987)
Fixes #3986
2019-03-12 14:36:56 +01:00
badrAZ
4ed0035c67 chore(xo-server/xapi-stats): add documentation (#4031) 2019-03-12 13:24:34 +01:00
Rajaa.BARHTAOUI
b66f2dfb80 feat(xo-web/vm/disks): same-pool SRs first in migrate selector (#3996)
Fixes #3945
2019-03-12 11:44:38 +01:00
Julien Fontanet
3cb155b129 feat(CHANGELOG.unreleased): add compression 2019-03-12 11:04:12 +01:00
Julien Fontanet
df7efc04e2 feat(backup NG logs): use NDJSON (#4026)
Fixes #4025
2019-03-12 11:02:15 +01:00
Rajaa.BARHTAOUI
a21a8457a4 feat(xo-web/new/vm): warning when SRs not on same host (#3967)
See #3911
2019-03-12 10:20:40 +01:00
Julien Fontanet
020955f535 chore(yarn.lock): refresh 2019-03-11 21:05:48 +01:00
Julien Fontanet
51f23a5f03 chore(xo-web): update otplib to 11.0.0 2019-03-11 19:07:51 +01:00
Julien Fontanet
d024319441 fix(xo-server-auth-google): update passport-google-oauth20 to 2.0.0 2019-03-11 19:07:34 +01:00
Julien Fontanet
f8f35938c0 feat(xo-server): set NODE_ENV to production 2019-03-11 18:52:25 +01:00
Julien Fontanet
2573ace368 feat(xo-server): enable HTTP compression 2019-03-11 18:27:05 +01:00
Rajaa.BARHTAOUI
6bf7269814 feat(xo-server,xo-web/VM): start delay (#4002)
Fixes #3909
2019-03-11 15:39:10 +01:00
Julien Fontanet
6695c7bf5e feat(CHANGELOG): 5.32.1 and 5.32.2 2019-03-11 15:23:51 +01:00
Julien Fontanet
44a83fd817 fix(docs/cr/seed): fix CLI package name 2019-03-06 19:23:33 +01:00
Enishowk
08ddfe0649 feat(VM creation): support automatic networks (#3958)
Fixes #3916
2019-03-06 14:46:22 +01:00
Enishowk
5ba170bf1f feat(xo-web/SR/disks): disable actions on unmanaged VDIs (#4000)
Fixes #3988
2019-03-06 09:40:07 +01:00
Julien Fontanet
8150d3110c fix(vhd-cli/repl): various fixes 2019-03-05 11:46:14 +01:00
Pierre Donias
312b33ae85 fix(xo-web/new-network): PIF should not be required (#4010)
Introduced by 7a2a88b7ad

Requiring a PIF prevented from creating private networks
2019-03-04 17:45:48 +01:00
Julien Fontanet
008eb995ed feat(vhd-cli): 0.3.0 2019-03-01 20:07:58 +01:00
Julien Fontanet
6d8848043c feat(vhd-cli): repl command 2019-03-01 20:00:18 +01:00
Julien Fontanet
cf572c0cc5 feat(xo-server): 5.36.3 2019-03-01 17:21:09 +01:00
Julien Fontanet
18cfa7dd29 feat(xen-api): 0.24.5 2019-03-01 17:20:19 +01:00
Julien Fontanet
72cac2bbd6 chore(xen-api/json-rpc): link to XenCenter code 2019-03-01 16:41:15 +01:00
Julien Fontanet
48ffa28e0b fix(xen-api/_watchEvents): timeout must be a float
Required by XML-RPC transport (XenServer < 7.3).
2019-03-01 16:39:49 +01:00
Julien Fontanet
2e6baeb95a feat(xo-server): 5.36.2 2019-03-01 13:53:28 +01:00
Julien Fontanet
3b5650dc1e feat(xen-api): 0.24.4 2019-03-01 13:52:26 +01:00
Julien Fontanet
3279728e4b chore(xen-api/events): prints errors 2019-03-01 13:42:13 +01:00
Julien Fontanet
fe0dcbacc5 fix(xen-api/_watchEvents): pTimeout expects milliseconds 2019-03-01 13:40:03 +01:00
Julien Fontanet
7c5d90fe40 feat(xo-server/createCloudInit): support network config (#3997)
* feat(xo-server/createCloudInit): support network config

See #3872

* Update index.js
2019-03-01 09:50:37 +01:00
marcpezin
944dad6e36 feat(docs): metadata backups (#4001) 2019-03-01 09:49:25 +01:00
Julien Fontanet
6713d3ec66 chore: update dependencies 2019-03-01 09:44:12 +01:00
Julien Fontanet
6adadb2359 feat(xo-server): 5.35.1 2019-02-28 18:32:17 +01:00
Julien Fontanet
b01096876c feat(fs): 0.7.1 2019-02-28 18:31:58 +01:00
Julien Fontanet
60243d8517 fix(fs/_mount/_sync): dont use --target
Because it also checks the parents of the path.
2019-02-28 18:31:57 +01:00
badrAZ
94d0809380 chore(CHANGELOG): v5.32.0 2019-02-28 17:42:40 +01:00
badrAZ
e935dd9bad feat(xo-web): v5.36.0 2019-02-28 17:35:29 +01:00
badrAZ
30aa2b83d0 feat(xo-server): v5.36.0 2019-02-28 17:33:36 +01:00
badrAZ
fc42c58079 feat(xen-api): v0.24.3 2019-02-28 17:20:59 +01:00
badrAZ
ee9443cf16 feat(@xen-orchestra/fs): v0.7.0 2019-02-28 17:17:33 +01:00
Julien Fontanet
f91d4a07eb fix(xen-api/_watchEvents): dont stop when fail to get records 2019-02-28 16:32:30 +01:00
Julien Fontanet
c5a5ef6c93 fix(xen-api/_watchEvents): dont fetch events while fetching tasks
When our tasks cache is desynchronized we re-fetch all tasks.

We must wait before fetching the next events to have fetch the tasks otherwise we risk a race condition.
2019-02-28 16:30:39 +01:00
Julien Fontanet
7559fbdab7 chore: update to http-request-plus 0.7.2
Work around a Node issue which led to incorrect *aborted* error events.
2019-02-28 16:21:07 +01:00
Julien Fontanet
7925ee8fee fix(fs/_mount#_sync): use findmnt to check mount success (#4003)
Fixes #3973
2019-02-28 15:32:06 +01:00
badrAZ
fea5117ed8 feat(metadata backup): backup XO config and pool metadata (#3912)
Fixes #3501
2019-02-28 15:31:17 +01:00
Julien Fontanet
468a2c5bf3 fix(xen-api/connect): always pass params to _transporCall 2019-02-28 12:36:57 +01:00
Julien Fontanet
c728eeaffa feat(fs/mount): keep open file on mount to avoid external umount (#3998) 2019-02-28 11:52:45 +01:00
Julien Fontanet
6aa8e0d4ce feat(xo-server/CR): share full between schedules (#3995)
Fixes #3973
2019-02-27 16:36:22 +01:00
Enishowk
76ae54ff05 feat(xo-web): add button to download log (#3985)
Fixes #3957
2019-02-27 10:02:30 +01:00
Julien Fontanet
344e9e06d0 feat(xen-api/objects): buffer objects' events on initial fetch (#3994)
XO requires all objects to be available at the same time.
2019-02-26 15:03:33 +01:00
Julien Fontanet
d866bccf3b chore(xen-api/getResource): options are optional 2019-02-26 14:44:55 +01:00
Julien Fontanet
3931c4cf4c chore(xo-server/snapshotVm): eventless implementation (#3992)
Previous implementation relied on events but had issues where it did not correctly detect and remove broken quiesced snapshot.

The new implementation is less magical and does not rely on events at all.
2019-02-26 14:41:55 +01:00
Julien Fontanet
420f1c77a1 fix: XAPI record types are now properly cased 2019-02-26 09:45:57 +01:00
Julien Fontanet
59106aa29e chore(xen-api/_watchEvents): new implementation (#3990)
- fetch each types independently: no more huge requests
- only fall back to legacy implementation if `event.inject` is not available
- can only watch certain types
- `Xapi#objectsFetched` is a promise which resolves when objects have been fetched
2019-02-26 09:45:21 +01:00
Julien Fontanet
4216a5808a chore(xen-api/setFieldEntry): always returns undefined 2019-02-24 18:17:26 +01:00
Julien Fontanet
12a7000e36 fix(xen-api): correct $type for records from event
XenApi event system returns lowercased types which things difficult, for
instance, `Record#set_name_label` methods did not work for some VM
because the lib called `vm.set_name_label` instead of
`VM.set_name_label`.

To work-around this problem, a map of types from lowercased is
constructed at connection.
2019-02-24 18:17:26 +01:00
Jon Sands
685355c6fb fix(docs): clarify build and fix link
- from sources: clarify yarn build
- backups: fix quiesce link
2019-02-24 13:27:16 +01:00
Julien Fontanet
66f685165e feat(xen-api/Record#update_): easier use for single entry
```js
// before
await object.update_property({
  entry: 'value',
})

// after
await object.update_property('entry', 'value')
```
2019-02-22 19:51:36 +01:00
Julien Fontanet
8e8b1c009a feat(xen-api#unsetField): replaced by setField(_, null) 2019-02-22 19:51:36 +01:00
Julien Fontanet
705d069246 feat(xen-api#getField): get a specific record field 2019-02-22 19:51:35 +01:00
Julien Fontanet
58e8d75935 chore(xen-api/*setField*): take separate type and ref 2019-02-22 19:51:34 +01:00
Julien Fontanet
5eb1454e67 fix(xen-api/_transportCall): avoid logging session ID 2019-02-22 19:51:34 +01:00
Julien Fontanet
04b31db41b feat(xen-api/getRecords): fetch multiple records 2019-02-22 19:51:33 +01:00
badrAZ
29b4cf414a fix(xo-server/xen-servers): pool property not deleted on disconnecting a connecting server (#3977)
Fixes #3976
2019-02-21 17:15:39 +01:00
Rajaa.BARHTAOUI
7a2a88b7ad feat(xo-web/new-network): dedicated view (#3906)
Fixes #3895
2019-02-21 11:43:40 +01:00
Nicolas Raynaud
dc34f3478d fix(xo-web): strip XML prefixes in OVA import parser (#3974)
Fixes #3962

- Parses the OVF XML without taking into account any namespace.
- Empty the import screen when we drop a new file on the drop zone to avoid displaying stale information during long parsing.
2019-02-21 09:24:01 +01:00
Julien Fontanet
58175a4f5e chore(ESLint): update config 2019-02-20 11:05:57 +01:00
badrAZ
c4587c11bd feat(xo-web/multipathing): display multipathing required state info (#3975) 2019-02-19 12:00:04 +01:00
Julien Fontanet
5b1a5f4fe7 feat(xo-web/editable): blur always submits (#3980)
Previous behavior (blur cancels) was surprising to users.

Enter still submits and Escape still cancels.
2019-02-19 11:29:50 +01:00
Jon Sands
ee2db918f3 feat(docs/from sources): Debian 8 → 9 (#3978)
* update cloud init docu

* update cloudinit images

* fix png links

* add emergency shutdown feature doc

* fix emergency shutdown typo

* Update to Debian 9 recommendation
2019-02-19 09:56:47 +01:00
Julien Fontanet
0695bafb90 fix(xen-api#_transportCall): pTimeout.call
Fixes 8e116063b
2019-02-17 19:39:11 +01:00
Julien Fontanet
8e116063bf feat(xen-api#_transportCall): timeout after 24 hours 2019-02-15 17:37:45 +01:00
Julien Fontanet
3f3b372f89 feat(xapi/Record#$xapi): link connection from record 2019-02-15 17:29:00 +01:00
Julien Fontanet
24cc1e8e29 chore(xo-server/pRetry): more tests 2019-02-15 14:38:12 +01:00
Julien Fontanet
e988ad4df9 chore: add package.repository.directory
See npm/rfcs#19
2019-02-15 14:38:11 +01:00
Julien Fontanet
5c12d4a546 chore(fs/PrefixWrapper): _remote → _handler 2019-02-15 14:38:11 +01:00
Enishowk
d90b85204d feat(xo-web): sort VMs by start time (#3970)
Fixes #3955
2019-02-15 10:09:53 +01:00
badrAZ
6332355031 fix(xo-server/multipathing): disable host before unplugging PBDs (#3965) 2019-02-14 16:03:48 +01:00
Rajaa.BARHTAOUI
4ce702dfdf feat(xo-web/vm/migrate): same-pool hosts first in selector (#3890)
Fixes #3262
2019-02-14 11:55:58 +01:00
Pierre Donias
362a381dfb fix(xo-web/getMessages): handle errors (#3966) 2019-02-13 18:15:54 +01:00
Enishowk
0eec4ee2f7 fix(xo-server,xo-web/VM): hide creation date if not available (#3959)
Fixes #3953
2019-02-13 14:01:45 +01:00
badrAZ
b92390087b fix(xo-server/host): multipathing status for XS < 7.5 (#3961)
Fixes #3956
2019-02-12 17:36:33 +01:00
Jon Sands
bce4d5d96f (Docu) Add page for emergency shutdown feature (#3960)
Fix emergency shutdown typo
2019-02-12 10:55:18 +01:00
Pierre Donias
27262ff3e8 fix(CHANGELOG): wrong version 2019-02-08 13:57:16 +01:00
Pierre Donias
444b6642f1 chore(CHANGELOG): 5.31.1 2019-02-08 13:49:44 +01:00
Pierre Donias
67d11020bb feat(xo-web): 5.35.0 2019-02-08 13:45:36 +01:00
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
Pierre Donias
0d26ac9858 feat(xo-acl-resolver): 0.4.1 2018-12-18 14:00:00 +01:00
Pierre Donias
4069264ad8 feat(xen-api): 0.23.0 2018-12-18 13:57:58 +01:00
Pierre Donias
120e01897d feat(fs): 0.5.0 2018-12-18 13:55:16 +01:00
badrAZ
06755cb6b6 feat(xo-web/backup-ng/logs): merge status and display log details (#3800)
Fixes #3797
2018-12-18 11:00:47 +01:00
Julien Fontanet
27409f4fd5 fix(xo-server/worker): sync the remote handlers
Fixes #3739
2018-12-17 18:00:02 +01:00
Julien Fontanet
82253509d0 fix(xo-server/backup NG): fix issues due to fs changes 2018-12-17 17:59:54 +01:00
Julien Fontanet
c450685ddd chore(fs/{sync,forget}): add comments 2018-12-17 17:59:54 +01:00
badrAZ
9a79088e8a chore(xo-web/backup-ng): use btnStyle (#3798) 2018-12-17 15:43:39 +01:00
Julien Fontanet
83760157ad chore(xen-api): silence Bluebird warning 2018-12-17 15:32:52 +01:00
Julien Fontanet
985aa2225e fix(fs/local#create{Read,Write}Stream): wait for file to be opened
This also fix `createOutputStream` when directories are missing.
2018-12-17 15:25:35 +01:00
badrAZ
0ad340d971 feat(xo-web/backup-ng/overview): ability to copy schedule/job id to clipboard (#3791)
Fixes #3753
2018-12-17 10:20:51 +01:00
Rajaa.BARHTAOUI
97726dce12 fix(xo-web/self): all objects are missing (#3096)
Fixes #2689
2018-12-14 16:24:10 +01:00
Julien Fontanet
342320b481 feat(xo-server/xen-servers): add metadata to PoolAlreadyConnected (#3792) 2018-12-14 15:09:43 +01:00
badrAZ
1bfcbf49b9 feat(xo-web/backup-ng/new): add a link to the documentation (#3790)
Fixes #3789
2018-12-13 15:49:37 +01:00
Julien Fontanet
9d1eb8182b fix(xo-server/remote.list): add path to Handler#list()
Fixes #3768

Introduced in 043b381733
2018-12-13 15:19:04 +01:00
Julien Fontanet
18a6c57f02 chore(CHANGELOG): add fs to the list of packages 2018-12-13 14:53:59 +01:00
badrAZ
d3b6d1a97f fix(xo-server/servers): always remove pool→connection association (#3782)
* fix(xo-server/servers): delete correctly the connection from the cashe

* useless findKey

* CHANGELOG

* fix error

* code improvement

* code improvement

* code improvement

* Update CHANGELOG.md

* fix errors

* Update xen-servers.js

* missing entry in CHANGELOG
2018-12-13 12:21:02 +01:00
Julien Fontanet
ece881c02c fix(fs#unlink): remove timeout
This is not an `O(1)` operation.
2018-12-13 11:07:28 +01:00
badrAZ
631e8ce52d feat(xo-web/backup-ng/overview): display last run status (#3779)
Fixes #3769
2018-12-12 16:27:34 +01:00
badrAZ
cb5d3b9750 feat(xo-server/backup-ng): disable VM HA on DR/CR (#3755)
Fixes #2359
2018-12-12 16:04:03 +01:00
Julien Fontanet
995e6664f9 fix(fs/smb#create{Read,Write}Stream): works with opened files (#3785) 2018-12-12 15:38:54 +01:00
Julien Fontanet
1f497aa4df chore(fs): update @marsaud/smb2 to v0.13 2018-12-12 15:12:58 +01:00
Julien Fontanet
184dbc5516 chore(fs/smb): use a single client/connection (#3775) 2018-12-11 18:47:46 +01:00
Julien Fontanet
a1f25a4e3e chore(docs/supported-versions): add XS and XCP-ng 7.6 2018-12-11 10:46:13 +01:00
Julien Fontanet
cc4ab94428 chore: reformat with Prettier 2018-12-11 10:45:00 +01:00
Rajaa.BARHTAOUI
48727740c4 feat(xo-web/new-xosan): use SortedTable (#3691)
See #2416
2018-12-10 16:15:04 +01:00
Julien Fontanet
cc26e378e5 chore(fs/{createOutputStream,outputFile}): move dir logic in abstract 2018-12-07 17:12:12 +01:00
Julien Fontanet
28579258b3 feat(docs/sources): add git checkout before pull
Related to #3764.
2018-12-07 14:54:26 +01:00
Julien Fontanet
70b9b67f67 chore(fs/test): add TEST_DATA_LEN constant 2018-12-07 11:14:02 +01:00
Julien Fontanet
224b053eb1 chore(fs/test): test read() 2018-12-07 11:12:00 +01:00
Julien Fontanet
39bce978bc chore(fs/test): testWithFileDescriptor helper 2018-12-07 10:57:14 +01:00
Julien Fontanet
9435bd5493 chore(fs#openFile): flags are not optional 2018-12-07 10:45:56 +01:00
badrAZ
2284b3ef0a feat(xo-server): obfuscate token param (#3761) 2018-12-07 10:35:54 +01:00
Julien Fontanet
99dc64e8bb fix: occured → occurred (2) 2018-12-06 18:53:35 +01:00
Julien Fontanet
e47525b60b chore(fs/test): test list prependDir option 2018-12-06 18:53:09 +01:00
Julien Fontanet
10d4782ee2 feat(fs/mk{dir,tree}): new methods to create dirs (#3763) 2018-12-06 18:50:39 +01:00
Julien Fontanet
11cff2c065 feat(fs): prefix get/set → addPrefix method (#3766)
This method returns a new object which will transparently prefix all paths.

It is better than previous API because:

- the same handler can be reused and will not be invalidated when changing the prefix
- Abstract and implementations can call high-level methods without worrying about doubling the prefix
2018-12-06 17:22:37 +01:00
Julien Fontanet
11f742b020 fix: occured → occurred 2018-12-06 17:18:08 +01:00
Julien Fontanet
2353552e11 chore(fs/abstract): use path.posix
All paths in this lib are Posix paths and should be treated as such, even on Windows.
2018-12-06 13:59:51 +01:00
Rajaa.BARHTAOUI
d6012d8639 feat(xo-web/VM): add tooltip for VM status icon (#3765)
Fixes #3749
2018-12-06 11:48:21 +01:00
Julien Fontanet
7089ee778a fix(xo-server/migrateLegacyBackupJob): dont migrate when ids contain : 2018-12-05 17:49:07 +01:00
Julien Fontanet
629931782e feat(fs/{rmdir,unlink}): dont throw on missing entries 2018-12-05 17:20:51 +01:00
Julien Fontanet
1b48c626f4 fix(fs/smb): create{Read,Output}Stream and getSize with opened files 2018-12-05 16:31:38 +01:00
Julien Fontanet
ba35f51459 chore(fs/test): test createOutputStream 2018-12-05 16:31:38 +01:00
Julien Fontanet
ee5f3fc68d chore(fs/test): test createReadStream and getSize on opened files 2018-12-05 16:31:39 +01:00
Julien Fontanet
7be671f0f7 chore(fs/test): ensure test dir exists 2018-12-05 16:26:38 +01:00
Julien Fontanet
48c3748c28 feat(fs/abstract#rmdir): implement timeout 2018-12-05 15:37:21 +01:00
Julien Fontanet
3814a261d6 fix(fs/abstract#kResolve): edge case with / path 2018-12-05 15:35:24 +01:00
Julien Fontanet
81b82ce06b fix(fs/smb#_rmdir): error code on non-empty dir 2018-12-05 15:35:24 +01:00
Julien Fontanet
20c3f76278 feat(fs): split rmdir into rmdir and rmtree 2018-12-05 15:35:17 +01:00
Julien Fontanet
2c93b69144 chore(fs): simplify sync/forget
- These methods are now optional
- sync no longer returns anything
2018-12-05 14:39:12 +01:00
Julien Fontanet
043b381733 chore(fs/abstract#list): remove default value 2018-12-05 14:30:55 +01:00
Julien Fontanet
ff014df231 chore(fs): remove unnecessary default params 2018-12-05 14:29:35 +01:00
Julien Fontanet
055d1e81da chore(fs): sort methods 2018-12-05 14:27:20 +01:00
Julien Fontanet
b60678e79f fix(fs/abstract#test): resolve path 2018-12-05 14:06:53 +01:00
Julien Fontanet
fd93dfbc18 feat(fs): prefix support (#3759)
It is a prefix that will be used as root for all other paths.
2018-12-05 13:51:38 +01:00
Julien Fontanet
d74a5d73f0 chore(fs/abstract#test): Buffer#compare → Buffer#equals 2018-12-05 13:49:33 +01:00
Julien Fontanet
16a6d395c8 chore(fs/abstract): only call low-level methods 2018-12-05 13:48:42 +01:00
badrAZ
d6654807fa fix(xo-server/logs): sensitive params not hidden (#3760) 2018-12-05 13:41:32 +01:00
Julien Fontanet
e25ff221ba fix(fs/abstract#_outputFile): missing callback 2018-12-05 11:05:01 +01:00
Julien Fontanet
7df965ccd7 chore(fs): remove unnecessary tests 2018-12-05 11:00:55 +01:00
Julien Fontanet
f6b73b8303 chore(fs/abstract#_outputFile): use readable-stream/finished 2018-12-05 11:00:12 +01:00
Julien Fontanet
d617214c62 chore: update dependencies 2018-12-05 10:39:38 +01:00
Julien Fontanet
e85744cec0 chore(fs/smb): preserve stack on wrapped errors 2018-12-05 10:27:13 +01:00
Julien Fontanet
da74555e02 feat(xo-remote-parser/smb): handle missing path 2018-12-04 17:48:38 +01:00
Enishowk
4a9f489f20 fix(changelog): XOSAN line in wrong place (#3756) 2018-12-04 17:46:01 +01:00
Enishowk
18b17bda7c feat(@xen-orchestra/fs): add unit tests (#3736) 2018-12-04 16:36:10 +01:00
badrAZ
7faff824ff feat(xo-server/xen-servers): auto-connect to ejected host (#3738)
See #2238
2018-12-04 13:28:52 +01:00
Pierre Donias
e08d03687e feat(xo-web/render-xo-item/SR): minor improvements (#3752)
- Space left before container
- Hide context to self users
- Don't use text-muted on links
2018-12-04 11:35:57 +01:00
Rajaa.BARHTAOUI
75f1d80a86 feat(xo-web): contextualize SR (#3751)
See  #3021
2018-12-04 10:09:31 +01:00
Julien Fontanet
3967bfa099 chore(xo-server/config/remoteOptions.timeout): add comment 2018-12-03 17:50:34 +01:00
Rajaa.BARHTAOUI
6f6f463592 feat(xo-web/health/Orphan VDIs): display SR container (#3747)
See #3021
2018-12-03 16:56:34 +01:00
Rajaa.BARHTAOUI
8a760823b8 feat(xo-web/render-xo-item): contextualize VDI (#3285)
See #3021
2018-12-03 15:27:58 +01:00
Enishowk
8cd66af3f8 feat(backup-ng): add XOSAN in excluded tags by default (#3563)
Fixes #2128
2018-12-03 15:04:38 +01:00
Julien Fontanet
8569dbf985 chore: String#substring() → String#slice()
- homogeneity
- [faster in v8](https://bugs.chromium.org/p/v8/issues/detail?id=6730)
2018-12-03 14:59:59 +01:00
Julien Fontanet
9a03a70a3d fix(ESLint): disable react/jsx-indent rule 2018-12-03 14:59:48 +01:00
Julien Fontanet
42badbb08e fix(lint-staged): ESLint should not format 2018-12-03 14:54:41 +01:00
Julien Fontanet
956bdf0e03 feat(fs/smb#_writeFile): support flags 2018-12-03 14:39:24 +01:00
Julien Fontanet
91ff02d5c3 fix(fs/smb): always normalize errors 2018-12-03 14:38:51 +01:00
Julien Fontanet
84d88cf2b9 fix(fs/local#_{output,read}File): correctly handle flags 2018-12-03 14:35:52 +01:00
Julien Fontanet
fca2693730 feat(fs/{output,read}File): only support flags option 2018-12-03 14:35:17 +01:00
Julien Fontanet
d7ac1b9659 feat(fs/smb): implement rmdir 2018-12-03 13:11:31 +01:00
Julien Fontanet
ddb1a8ff51 fix(fs/smb): ensure connection is always closed (#3733) 2018-12-03 11:41:27 +01:00
Pierre Donias
56a2f8858b feat(xo-web/render-xo-item): simpler item components (#3547)
See #2605
2018-12-03 11:11:55 +01:00
Pierre Donias
55d7a1def0 fix(xo-web/backup-ng): canDeltaBackup import (#3745) 2018-12-03 10:34:46 +01:00
Pierre Donias
7b354f364c fix(xo-acl-resolver): add PBD resolution rule (#3742)
Fixes #2204
2018-12-03 09:32:18 +01:00
Pierre Donias
12dd40d330 fix(xo-web): missingPaths → missingPatches (#3741) 2018-11-30 15:12:10 +01:00
badrAZ
205f09a633 feat(xo-web/settings/users): display user groups (#3740)
Fixes #3719
2018-11-30 14:34:44 +01:00
badrAZ
f7dcccd8af chore(xo-web/backup-ng/new): improve setting pools implementation (#3715)
See #2578
2018-11-30 11:00:05 +01:00
Julien Fontanet
75592023f2 fix(xo-server/file restore): correctly use _getFilePath
Fixes #3739
2018-11-30 08:24:52 +01:00
Pierre Donias
a794a61c9b feat(xo-server): 5.31.1 2018-11-29 17:10:51 +01:00
Pierre Donias
804da115c9 chore(xo-server): use app-conf@0.6.1 2018-11-29 17:10:20 +01:00
Julien Fontanet
89df4f771b fix(fs/local#_rmdir): correctly prefix path 2018-11-29 15:35:58 +01:00
Pierre Donias
db3c5cfcb8 chore(CHANGELOG): 5.29.0 2018-11-29 14:22:08 +01:00
Pierre Donias
19d191a472 feat(xo-web): 5.31.0 2018-11-29 14:15:55 +01:00
Pierre Donias
d906fec236 feat(xo-server): 5.31.0 2018-11-29 14:15:18 +01:00
Pierre Donias
552482275d feat(xo-server-usage-report): 0.7.1 2018-11-29 14:13:54 +01:00
Pierre Donias
f06d40cf95 feat(xo-server-perf-alert): 0.2.0 2018-11-29 14:13:03 +01:00
567 changed files with 20377 additions and 11385 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

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
# xo_fs_nfs=nfs://ip:/folder
# xo_fs_smb=smb://login:pass@domain\\ip\folder

View File

@@ -1,5 +1,13 @@
module.exports = {
extends: ['standard', 'standard-jsx', 'prettier'],
extends: [
'plugin:eslint-comments/recommended',
'standard',
'standard-jsx',
'prettier',
'prettier/standard',
'prettier/react',
],
globals: {
__DEV__: true,
$Dict: true,
@@ -10,6 +18,16 @@ module.exports = {
$PropertyType: true,
$Shape: true,
},
overrides: [
{
files: ['cli.js', '*-cli.js', 'packages/*cli*/**/*.js'],
rules: {
'no-console': 'off',
},
},
],
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
@@ -17,6 +35,7 @@ module.exports = {
},
},
rules: {
'no-console': ['error', { allow: ['warn', 'error'] }],
'no-var': 'error',
'node/no-extraneous-import': 'error',
'node/no-extraneous-require': 'error',

1
.gitignore vendored
View File

@@ -30,3 +30,4 @@ pnpm-debug.log
pnpm-debug.log.*
yarn-error.log
yarn-error.log.*
.env

View File

@@ -7,6 +7,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/async-map",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/async-map",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -45,6 +46,7 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepare": "yarn run build",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -14,7 +14,7 @@ const configs = {
'@babel/plugin-proposal-pipeline-operator': {
proposal: 'minimal',
},
'@babel/preset-env' (pkg) {
'@babel/preset-env'(pkg) {
return {
debug: !__TEST__,
@@ -46,7 +46,13 @@ const getConfig = (key, ...args) => {
: config
}
module.exports = function (pkg, plugins, presets) {
// some plugins must be used in a specific order
const pluginsOrder = [
'@babel/plugin-proposal-decorators',
'@babel/plugin-proposal-class-properties',
]
module.exports = function(pkg, plugins, presets) {
plugins === undefined && (plugins = {})
presets === undefined && (presets = {})
@@ -61,7 +67,13 @@ module.exports = function (pkg, plugins, presets) {
return {
comments: !__PROD__,
ignore: __TEST__ ? undefined : [/\.spec\.js$/],
plugins: Object.keys(plugins).map(plugin => [plugin, plugins[plugin]]),
plugins: Object.keys(plugins)
.map(plugin => [plugin, plugins[plugin]])
.sort(([a], [b]) => {
const oA = pluginsOrder.indexOf(a)
const oB = pluginsOrder.indexOf(b)
return oA !== -1 && oB !== -1 ? oA - oB : a < b ? -1 : 1
}),
presets: Object.keys(presets).map(preset => [preset, presets[preset]]),
}
}

View File

@@ -5,6 +5,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/babel-config",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/babel-config",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
}

View File

@@ -5,7 +5,7 @@ const { NULL_REF, Xapi } = require('xen-api')
const pkg = require('./package.json')
Xapi.prototype.getVmDisks = async function (vm) {
Xapi.prototype.getVmDisks = async function(vm) {
const disks = { __proto__: null }
await Promise.all([
...vm.VBDs.map(async vbdRef => {
@@ -19,7 +19,7 @@ Xapi.prototype.getVmDisks = async function (vm) {
return disks
}
defer(async function main ($defer, args) {
defer(async function main($defer, args) {
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
const cliName = Object.keys(pkg.bin)[0]
return console.error(
@@ -82,35 +82,26 @@ ${cliName} v${pkg.version}
)
await Promise.all([
srcXapi.setFieldEntries(srcSnapshot, 'other_config', metadata),
srcXapi.setFieldEntries(srcSnapshot, 'other_config', {
'xo:backup:exported': 'true',
}),
tgtXapi.setField(
tgtVm,
'name_label',
`${srcVm.name_label} (${srcSnapshot.snapshot_time})`
),
tgtXapi.setFieldEntries(tgtVm, 'other_config', metadata),
tgtXapi.setFieldEntries(tgtVm, 'other_config', {
srcSnapshot.update_other_config(metadata),
srcSnapshot.update_other_config('xo:backup:exported', 'true'),
tgtVm.set_name_label(`${srcVm.name_label} (${srcSnapshot.snapshot_time})`),
tgtVm.update_other_config(metadata),
tgtVm.update_other_config({
'xo:backup:sr': tgtSr.uuid,
'xo:copy_of': srcSnapshotUuid,
}),
tgtXapi.setFieldEntries(tgtVm, 'blocked_operations', {
start:
'Start operation for this vm is blocked, clone it if you want to use it.',
}),
tgtVm.update_blocked_operations(
'start',
'Start operation for this vm is blocked, clone it if you want to use it.'
),
Promise.all(
userDevices.map(userDevice => {
const srcDisk = srcDisks[userDevice]
const tgtDisk = tgtDisks[userDevice]
return tgtXapi.setFieldEntry(
tgtDisk,
'other_config',
'xo:copy_of',
srcDisk.uuid
)
return tgtDisk.update_other_config({
'xo:copy_of': srcDisk.uuid,
})
})
),
])

View File

@@ -4,6 +4,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/cr-seed-cli",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/cr-seed-cli",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -15,6 +16,9 @@
},
"dependencies": {
"golike-defer": "^0.4.1",
"xen-api": "^0.22.0"
"xen-api": "^0.25.1"
},
"scripts": {
"postversion": "npm publish"
}
}

View File

@@ -17,6 +17,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/cron",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/cron",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -54,6 +55,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -6,7 +6,7 @@ import parse from './parse'
const MAX_DELAY = 2 ** 31 - 1
class Job {
constructor (schedule, fn) {
constructor(schedule, fn) {
const wrapper = () => {
let result
try {
@@ -33,18 +33,18 @@ class Job {
this._timeout = undefined
}
start () {
start() {
this.stop()
this._scheduleNext()
}
stop () {
stop() {
clearTimeout(this._timeout)
}
}
class Schedule {
constructor (pattern, zone = 'utc') {
constructor(pattern, zone = 'utc') {
this._schedule = parse(pattern)
this._createDate =
zone.toLowerCase() === 'utc'
@@ -54,11 +54,11 @@ class Schedule {
: () => moment.tz(zone)
}
createJob (fn) {
createJob(fn) {
return new Job(this, fn)
}
next (n) {
next(n) {
const dates = new Array(n)
const schedule = this._schedule
let date = this._createDate()
@@ -68,12 +68,12 @@ class Schedule {
return dates
}
_nextDelay () {
_nextDelay() {
const now = this._createDate()
return next(this._schedule, now) - now
}
startJob (fn) {
startJob(fn) {
const job = this.createJob(fn)
job.start()
return job.stop.bind(job)

View File

@@ -7,6 +7,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/defined",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/defined",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -42,6 +43,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -13,7 +13,7 @@
// process.env.http_proxy
// ])
// ```
export default function defined () {
export default function defined() {
let args = arguments
let n = args.length
if (n === 1) {

View File

@@ -7,6 +7,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/emit-async",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/emit-async",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -41,6 +42,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1,4 +1,4 @@
export default function emitAsync (event) {
export default function emitAsync(event) {
let opts
let i = 1

View File

@@ -1,12 +1,13 @@
{
"name": "@xen-orchestra/fs",
"version": "0.4.1",
"version": "0.9.0",
"license": "AGPL-3.0",
"description": "The File System for Xen Orchestra backups.",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/fs",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/fs",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -20,25 +21,31 @@
"node": ">=6"
},
"dependencies": {
"@marsaud/smb2": "^0.9.0",
"@marsaud/smb2": "^0.14.0",
"@sindresorhus/df": "^2.1.0",
"@xen-orchestra/async-map": "^0.0.0",
"decorator-synchronized": "^0.5.0",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",
"fs-extra": "^8.0.1",
"get-stream": "^4.0.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.11.0",
"promise-toolbox": "^0.12.1",
"readable-stream": "^3.0.6",
"through2": "^3.0.0",
"tmp": "^0.0.33",
"tmp": "^0.1.0",
"xo-remote-parser": "^0.5.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.1.6",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"async-iterator-to-stream": "^1.1.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"dotenv": "^8.0.0",
"index-modules": "^0.3.0",
"rimraf": "^2.6.2"
},
@@ -48,6 +55,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepare": "yarn run build"
"prepare": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -0,0 +1,108 @@
import execa from 'execa'
import fs from 'fs-extra'
import { ignoreErrors } from 'promise-toolbox'
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._keeper = undefined
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() {
const keeper = this._keeper
if (keeper === undefined) {
return
}
this._keeper = undefined
await fs.close(keeper)
await ignoreErrors.call(
this._execa('umount', [this._getRealPath()], {
env: {
LANG: 'C',
},
})
)
}
_getRealPath() {
return this._realPath
}
async _sync() {
// in case of multiple `sync`s, ensure we properly close previous keeper
{
const keeper = this._keeper
if (keeper !== undefined) {
this._keeper = undefined
ignoreErrors.call(fs.close(keeper))
}
}
const realPath = this._getRealPath()
await fs.ensureDir(realPath)
try {
const { type, device, options, env } = this._params
await this._execa(
'mount',
['-t', type, device, realPath, '-o', options],
{
env: {
LANG: 'C',
...env,
},
}
)
} catch (error) {
try {
// the failure may mean it's already mounted, use `findmnt` to check
// that's the case
await this._execa('findmnt', [realPath], {
stdio: 'ignore',
})
} catch (_) {
throw error
}
}
// keep an open file on the mount to prevent it from being unmounted if used
// by another handler/process
const keeperPath = `${realPath}/.keeper_${Math.random()
.toString(36)
.slice(2)}`
this._keeper = await fs.open(keeperPath, 'w')
ignoreErrors.call(fs.unlink(keeperPath))
}
}

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,74 @@
// @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 synchronized from 'decorator-synchronized'
import { fromCallback, fromEvent, ignoreErrors, timeout } from 'promise-toolbox'
import { parse } from 'xo-remote-parser'
import { randomBytes } from 'crypto'
import { resolve } from 'path'
import { type Readable, type Writable } from 'stream'
import normalizePath from './_normalizePath'
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
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 computeRate = (hrtime: number[], size: number) => {
const seconds = hrtime[0] + hrtime[1] / 1e9
return size / seconds
}
const DEFAULT_TIMEOUT = 6e5 // 10 min
const ignoreEnoent = error => {
if (error == null || error.code !== 'ENOENT') {
throw error
}
}
class PrefixWrapper {
constructor(handler, prefix) {
this._prefix = prefix
this._handler = handler
}
get type() {
return this._handler.type
}
// necessary to remove the prefix from the path with `prependDir` option
async list(dir, opts) {
const entries = await this._handler.list(this._resolve(dir), opts)
if (opts != null && opts.prependDir) {
const n = this._prefix.length
entries.forEach((entry, i, entries) => {
entries[i] = entry.slice(n)
})
}
return entries
}
rename(oldPath, newPath) {
return this._handler.rename(this._resolve(oldPath), this._resolve(newPath))
}
_resolve(path) {
return this._prefix + normalizePath(path)
}
}
export default class RemoteHandlerAbstract {
_remote: Object
_timeout: number
@@ -43,278 +85,21 @@ export default class RemoteHandlerAbstract {
;({ timeout: this._timeout = DEFAULT_TIMEOUT } = options)
}
// Public members
get type(): string {
throw new Error('Not implemented')
}
/**
* Asks the handler to sync the state of the effective remote with its' metadata
*/
async sync(): Promise<mixed> {
return this._sync()
}
async _sync(): Promise<mixed> {
throw new Error('Not implemented')
}
/**
* Free the resources possibly dedicated to put the remote at work, when it is no more needed
*/
async forget(): Promise<void> {
await this._forget()
}
async _forget(): Promise<void> {
throw new Error('Not implemented')
}
async test(): Promise<Object> {
const testFileName = `/${Date.now()}.test`
const data = await fromCallback(cb => randomBytes(1024 * 1024, cb))
let step = 'write'
try {
await this.outputFile(testFileName, data)
step = 'read'
const read = await this.readFile(testFileName)
if (data.compare(read) !== 0) {
throw new Error('output and input did not match')
}
return {
success: true,
}
} catch (error) {
return {
success: false,
step,
file: testFileName,
error: error.message || String(error),
}
} finally {
ignoreErrors.call(this.unlink(testFileName))
}
}
async outputFile(file: string, data: Data, options?: Object): Promise<void> {
return this._outputFile(normalizePath(file), data, {
flags: 'wx',
...options,
})
}
async _outputFile(file: string, data: Data, options?: Object): Promise<void> {
const stream = await this.createOutputStream(normalizePath(file), options)
const promise = fromEvent(stream, 'finish')
stream.end(data)
await promise
}
async read(
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
return this._read(
typeof file === 'string' ? normalizePath(file) : file,
buffer,
position
)
}
_read(
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
throw new Error('Not implemented')
}
async readFile(file: string, options?: Object): Promise<Buffer> {
return this._readFile(normalizePath(file), options)
}
_readFile(file: string, options?: Object): Promise<Buffer> {
return this.createReadStream(file, options).then(getStream.buffer)
}
async rename(
oldPath: string,
newPath: string,
{ checksum = false }: Object = {}
) {
oldPath = normalizePath(oldPath)
newPath = normalizePath(newPath)
let p = timeout.call(this._rename(oldPath, newPath), this._timeout)
if (checksum) {
p = Promise.all([
p,
this._rename(checksumFile(oldPath), checksumFile(newPath)),
])
}
return p
}
async _rename(oldPath: string, newPath: string) {
throw new Error('Not implemented')
}
async rmdir(
dir: string,
{ recursive = false }: { recursive?: boolean } = {}
) {
dir = normalizePath(dir)
await (recursive ? this._rmtree(dir) : this._rmdir(dir))
}
async _rmdir(dir: string) {
throw new Error('Not implemented')
}
async _rmtree(dir: string) {
try {
return await this._rmdir(dir)
} catch (error) {
if (error.code !== 'ENOTEMPTY') {
throw error
}
}
const files = await this._list(dir)
await asyncMap(files, file =>
this._unlink(`${dir}/${file}`).catch(error => {
if (error.code === 'EISDIR') {
return this._rmtree(`${dir}/${file}`)
}
throw error
})
)
return this._rmtree(dir)
}
async list(
dir: string = '.',
{
filter,
prependDir = false,
}: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
): Promise<string[]> {
dir = normalizePath(dir)
let entries = await timeout.call(this._list(dir), this._timeout)
if (filter !== undefined) {
entries = entries.filter(filter)
}
if (prependDir) {
entries.forEach((entry, i) => {
entries[i] = dir + '/' + entry
})
}
return entries
}
async _list(dir: string): Promise<string[]> {
throw new Error('Not implemented')
}
createReadStream(
file: File,
{ checksum = false, ignoreMissingChecksum = false, ...options }: Object = {}
): Promise<LaxReadable> {
if (typeof file === 'string') {
file = normalizePath(file)
}
const path = typeof file === 'string' ? file : file.path
const streamP = timeout
.call(this._createReadStream(file, options), this._timeout)
.then(stream => {
// detect early errors
let promise = fromEvent(stream, 'readable')
// try to add the length prop if missing and not a range stream
if (
stream.length === undefined &&
options.end === undefined &&
options.start === undefined
) {
promise = Promise.all([
promise,
ignoreErrors.call(
this.getSize(file).then(size => {
stream.length = size
})
),
])
}
return promise.then(() => stream)
})
if (!checksum) {
return streamP
}
// avoid a unhandled rejection warning
ignoreErrors.call(streamP)
return this.readFile(checksumFile(path)).then(
checksum =>
streamP.then(stream => {
const { length } = stream
stream = (validChecksumOfReadStream(
stream,
String(checksum).trim()
): LaxReadable)
stream.length = length
return stream
}),
error => {
if (ignoreMissingChecksum && error && error.code === 'ENOENT') {
return streamP
}
throw error
}
)
}
async _createReadStream(file: File, options?: Object): Promise<LaxReadable> {
throw new Error('Not implemented')
}
async openFile(path: string, flags?: string): Promise<FileDescriptor> {
path = normalizePath(path)
return {
fd: await timeout.call(this._openFile(path, flags), this._timeout),
path,
}
}
async _openFile(path: string, flags?: string): Promise<mixed> {
throw new Error('Not implemented')
addPrefix(prefix: string) {
prefix = normalizePath(prefix)
return prefix === '/' ? this : new PrefixWrapper(this, prefix)
}
async closeFile(fd: FileDescriptor): Promise<void> {
await timeout.call(this._closeFile(fd.fd), this._timeout)
}
async _closeFile(fd: mixed): Promise<void> {
throw new Error('Not implemented')
}
async refreshChecksum(path: string): Promise<void> {
path = normalizePath(path)
const stream = (await this.createReadStream(path)).pipe(
createChecksumStream()
)
stream.resume() // start reading the whole file
await this.outputFile(checksumFile(path), await stream.checksum)
}
async createOutputStream(
file: File,
{ checksum = false, ...options }: Object = {}
@@ -346,31 +131,103 @@ export default class RemoteHandlerAbstract {
// $FlowFixMe
checksumStream.checksumWritten = checksumStream.checksum
.then(value => this.outputFile(checksumFile(path), value))
.then(value =>
this._outputFile(checksumFile(path), value, { flags: 'wx' })
)
.catch(forwardError)
return checksumStream
}
async _createOutputStream(
createReadStream(
file: File,
options?: Object
): Promise<LaxWritable> {
throw new Error('Not implemented')
}
{ checksum = false, ignoreMissingChecksum = false, ...options }: Object = {}
): Promise<LaxReadable> {
if (typeof file === 'string') {
file = normalizePath(file)
}
const path = typeof file === 'string' ? file : file.path
const streamP = timeout
.call(this._createReadStream(file, options), this._timeout)
.then(stream => {
// detect early errors
let promise = fromEvent(stream, 'readable')
async unlink(file: string, { checksum = true }: Object = {}): Promise<void> {
file = normalizePath(file)
// try to add the length prop if missing and not a range stream
if (
stream.length === undefined &&
options.end === undefined &&
options.start === undefined
) {
promise = Promise.all([
promise,
ignoreErrors.call(
this._getSize(file).then(size => {
stream.length = size
})
),
])
}
if (checksum) {
ignoreErrors.call(this._unlink(checksumFile(file)))
return promise.then(() => stream)
})
if (!checksum) {
return streamP
}
await timeout.call(this._unlink(file), this._timeout)
// avoid a unhandled rejection warning
ignoreErrors.call(streamP)
return this._readFile(checksumFile(path), { flags: 'r' }).then(
checksum =>
streamP.then(stream => {
const { length } = stream
stream = (validChecksumOfReadStream(
stream,
String(checksum).trim()
): LaxReadable)
stream.length = length
return stream
}),
error => {
if (ignoreMissingChecksum && error && error.code === 'ENOENT') {
return streamP
}
throw error
}
)
}
async _unlink(file: string): Promise<void> {
throw new Error('Not implemented')
createWriteStream(
file: File,
options: { end?: number, flags?: string, start?: number } = {}
): Promise<LaxWritable> {
return timeout.call(
this._createWriteStream(
typeof file === 'string' ? normalizePath(file) : file,
{
flags: 'wx',
...options,
}
)
)
}
// Free the resources possibly dedicated to put the remote at work, when it
// is no more needed
//
// FIXME: Some handlers are implemented based on system-wide mecanisms (such
// as mount), forgetting them might breaking other processes using the same
// remote.
@synchronized()
async forget(): Promise<void> {
await this._forget()
}
async getInfo(): Promise<RemoteInfo> {
return timeout.call(this._getInfo(), this._timeout)
}
async getSize(file: File): Promise<number> {
@@ -380,7 +237,392 @@ export default class RemoteHandlerAbstract {
)
}
async list(
dir: string,
{
filter,
prependDir = false,
}: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
): Promise<string[]> {
const virtualDir = normalizePath(dir)
dir = normalizePath(dir)
let entries = await timeout.call(this._list(dir), this._timeout)
if (filter !== undefined) {
entries = entries.filter(filter)
}
if (prependDir) {
entries.forEach((entry, i) => {
entries[i] = virtualDir + '/' + entry
})
}
return entries
}
async mkdir(dir: string): Promise<void> {
dir = normalizePath(dir)
try {
await this._mkdir(dir)
} catch (error) {
if (error == null || error.code !== 'EEXIST') {
throw error
}
// this operation will throw if it's not already a directory
await this._list(dir)
}
}
async mktree(dir: string): Promise<void> {
await this._mktree(normalizePath(dir))
}
async openFile(path: string, flags: string): Promise<FileDescriptor> {
path = normalizePath(path)
return {
fd: await timeout.call(this._openFile(path, flags), this._timeout),
path,
}
}
async outputFile(
file: string,
data: Data,
{ flags = 'wx' }: { flags?: string } = {}
): Promise<void> {
await this._outputFile(normalizePath(file), data, { flags })
}
async read(
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
return this._read(
typeof file === 'string' ? normalizePath(file) : file,
buffer,
position
)
}
async readFile(
file: string,
{ flags = 'r' }: { flags?: string } = {}
): Promise<Buffer> {
return this._readFile(normalizePath(file), { flags })
}
async refreshChecksum(path: string): Promise<void> {
path = normalizePath(path)
const stream = (await this._createReadStream(path, { flags: 'r' })).pipe(
createChecksumStream()
)
stream.resume() // start reading the whole file
await this._outputFile(checksumFile(path), await stream.checksum, {
flags: 'wx',
})
}
async rename(
oldPath: string,
newPath: string,
{ checksum = false }: Object = {}
) {
oldPath = normalizePath(oldPath)
newPath = normalizePath(newPath)
let p = timeout.call(this._rename(oldPath, newPath), this._timeout)
if (checksum) {
p = Promise.all([
p,
this._rename(checksumFile(oldPath), checksumFile(newPath)),
])
}
return p
}
async rmdir(dir: string): Promise<void> {
await timeout.call(
this._rmdir(normalizePath(dir)).catch(ignoreEnoent),
this._timeout
)
}
async rmtree(dir: string): Promise<void> {
await this._rmtree(normalizePath(dir))
}
// Asks the handler to sync the state of the effective remote with its'
// metadata
//
// This method MUST ALWAYS be called before using the handler.
@synchronized()
async sync(): Promise<void> {
await this._sync()
}
async test(): Promise<Object> {
const SIZE = 1024 * 1024 * 10
const testFileName = normalizePath(`${Date.now()}.test`)
const data = await fromCallback(cb => randomBytes(SIZE, cb))
let step = 'write'
try {
const writeStart = process.hrtime()
await this._outputFile(testFileName, data, { flags: 'wx' })
const writeDuration = process.hrtime(writeStart)
step = 'read'
const readStart = process.hrtime()
const read = await this._readFile(testFileName, { flags: 'r' })
const readDuration = process.hrtime(readStart)
if (!data.equals(read)) {
throw new Error('output and input did not match')
}
return {
success: true,
writeRate: computeRate(writeDuration, SIZE),
readRate: computeRate(readDuration, SIZE),
}
} catch (error) {
return {
success: false,
step,
file: testFileName,
error: error.message || String(error),
}
} finally {
ignoreErrors.call(this._unlink(testFileName))
}
}
async truncate(file: string, len: number): Promise<void> {
await this._truncate(file, len)
}
async unlink(file: string, { checksum = true }: Object = {}): Promise<void> {
file = normalizePath(file)
if (checksum) {
ignoreErrors.call(this._unlink(checksumFile(file)))
}
await this._unlink(file).catch(ignoreEnoent)
}
async write(
file: File,
buffer: Buffer,
position: number
): Promise<{| bytesWritten: number, buffer: Buffer |}> {
await this._write(
typeof file === 'string' ? normalizePath(file) : file,
buffer,
position
)
}
async writeFile(
file: string,
data: Data,
{ flags = 'wx' }: { flags?: string } = {}
): Promise<void> {
await this._writeFile(normalizePath(file), data, { flags })
}
// Methods that can be implemented by inheriting classes
async _closeFile(fd: mixed): Promise<void> {
throw new Error('Not implemented')
}
async _createOutputStream(file: File, options: Object): Promise<LaxWritable> {
try {
return await this._createWriteStream(file, options)
} catch (error) {
if (typeof file !== 'string' || error.code !== 'ENOENT') {
throw error
}
}
await this._mktree(dirname(file))
return this._createOutputStream(file, options)
}
async _createReadStream(file: File, options?: Object): Promise<LaxReadable> {
throw new Error('Not implemented')
}
async _createWriteStream(file: File, options: Object): Promise<LaxWritable> {
throw new Error('Not implemented')
}
// 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')
}
async _list(dir: string): Promise<string[]> {
throw new Error('Not implemented')
}
async _mkdir(dir: string): Promise<void> {
throw new Error('Not implemented')
}
async _mktree(dir: string): Promise<void> {
try {
return await this.mkdir(dir)
} catch (error) {
if (error.code !== 'ENOENT') {
throw error
}
}
await this._mktree(dirname(dir))
return this._mktree(dir)
}
async _openFile(path: string, flags: string): Promise<mixed> {
throw new Error('Not implemented')
}
async _outputFile(
file: string,
data: Data,
options: { flags?: string }
): Promise<void> {
try {
return await this._writeFile(file, data, options)
} catch (error) {
if (error.code !== 'ENOENT') {
throw error
}
}
await this._mktree(dirname(file))
return this._outputFile(file, data, options)
}
_read(
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
throw new Error('Not implemented')
}
_readFile(file: string, options?: Object): Promise<Buffer> {
return this._createReadStream(file, options).then(getStream.buffer)
}
async _rename(oldPath: string, newPath: string) {
throw new Error('Not implemented')
}
async _rmdir(dir: string) {
throw new Error('Not implemented')
}
async _rmtree(dir: string) {
try {
return await this._rmdir(dir)
} catch (error) {
if (error.code !== 'ENOTEMPTY') {
throw error
}
}
const files = await this._list(dir)
await asyncMap(files, file =>
this._unlink(`${dir}/${file}`).catch(error => {
if (error.code === 'EISDIR') {
return this._rmtree(`${dir}/${file}`)
}
throw error
})
)
return this._rmtree(dir)
}
// called to initialize the remote
async _sync(): Promise<void> {}
async _unlink(file: string): Promise<void> {
throw new Error('Not implemented')
}
async _write(file: File, buffer: Buffer, position: number): Promise<void> {
const isPath = typeof file === 'string'
if (isPath) {
file = await this.openFile(file, 'r+')
}
try {
return await this._writeFd(file, buffer, position)
} finally {
if (isPath) {
await this.closeFile(file)
}
}
}
async _writeFd(
fd: FileDescriptor,
buffer: Buffer,
position: number
): Promise<void> {
throw new Error('Not implemented')
}
async _writeFile(
file: string,
data: Data,
options: { flags?: string }
): Promise<void> {
throw new Error('Not implemented')
}
}
function createPrefixWrapperMethods() {
const pPw = PrefixWrapper.prototype
const pRha = RemoteHandlerAbstract.prototype
const {
defineProperty,
getOwnPropertyDescriptor,
prototype: { hasOwnProperty },
} = Object
Object.getOwnPropertyNames(pRha).forEach(name => {
let descriptor, value
if (
hasOwnProperty.call(pPw, name) ||
name[0] === '_' ||
typeof (value = (descriptor = getOwnPropertyDescriptor(pRha, name))
.value) !== 'function'
) {
return
}
descriptor.value = function() {
let path
if (arguments.length !== 0 && typeof (path = arguments[0]) === 'string') {
arguments[0] = this._resolve(path)
}
return value.apply(this._handler, arguments)
}
defineProperty(pPw, name, descriptor)
})
}
createPrefixWrapperMethods()

View File

@@ -16,53 +16,7 @@ class TestHandler extends AbstractHandler {
}
}
describe('rename()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
rename: () => new Promise(() => {}),
})
const promise = testHandler.rename('oldPath', 'newPath')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('list()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
list: () => new Promise(() => {}),
})
const promise = testHandler.list()
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('createReadStream()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
createReadStream: () => new Promise(() => {}),
})
const promise = testHandler.createReadStream('file')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('openFile()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
openFile: () => new Promise(() => {}),
})
const promise = testHandler.openFile('path')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
jest.useFakeTimers()
describe('closeFile()', () => {
it(`throws in case of timeout`, async () => {
@@ -88,13 +42,25 @@ describe('createOutputStream()', () => {
})
})
describe('unlink()', () => {
describe('createReadStream()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
unlink: () => new Promise(() => {}),
createReadStream: () => new Promise(() => {}),
})
const promise = testHandler.unlink('')
const promise = testHandler.createReadStream('file')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
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)
})
@@ -111,3 +77,51 @@ describe('getSize()', () => {
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('list()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
list: () => new Promise(() => {}),
})
const promise = testHandler.list('.')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('openFile()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
openFile: () => new Promise(() => {}),
})
const promise = testHandler.openFile('path')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('rename()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
rename: () => new Promise(() => {}),
})
const promise = testHandler.rename('oldPath', 'newPath')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})
describe('rmdir()', () => {
it(`throws in case of timeout`, async () => {
const testHandler = new TestHandler({
rmdir: () => new Promise(() => {}),
})
const promise = testHandler.rmdir('dir')
jest.advanceTimersByTime(TIMEOUT)
await expect(promise).rejects.toThrowError(TimeoutError)
})
})

View File

@@ -1,26 +0,0 @@
/* eslint-env jest */
import rimraf from 'rimraf'
import tmp from 'tmp'
import { pFromCallback } from 'promise-toolbox'
import { getHandler } from '.'
const initialDir = process.cwd()
beforeEach(async () => {
const dir = await pFromCallback(cb => tmp.dir(cb))
process.chdir(dir)
})
afterEach(async () => {
const tmpDir = process.cwd()
process.chdir(initialDir)
await pFromCallback(cb => rimraf(tmpDir, cb))
})
test("fs test doesn't crash", async () => {
const handler = getHandler({ url: 'file://' + process.cwd() })
const result = await handler.test()
expect(result.success).toBeTruthy()
})

View File

@@ -0,0 +1,379 @@
/* eslint-env jest */
import 'dotenv/config'
import asyncIteratorToStream from 'async-iterator-to-stream'
import getStream from 'get-stream'
import { forOwn, random } from 'lodash'
import { fromCallback } from 'promise-toolbox'
import { pipeline } from 'readable-stream'
import { tmpdir } from 'os'
import { getHandler } from '.'
// https://gist.github.com/julien-f/3228c3f34fdac01ade09
const unsecureRandomBytes = n => {
const bytes = Buffer.alloc(n)
const odd = n & 1
for (let i = 0, m = n - odd; i < m; i += 2) {
bytes.writeUInt16BE((Math.random() * 65536) | 0, i)
}
if (odd) {
bytes.writeUInt8((Math.random() * 256) | 0, n - 1)
}
return bytes
}
const TEST_DATA_LEN = 1024
const TEST_DATA = unsecureRandomBytes(TEST_DATA_LEN)
const createTestDataStream = asyncIteratorToStream(function*() {
yield TEST_DATA
})
const rejectionOf = p =>
p.then(
value => {
throw value
},
reason => reason
)
const handlers = [`file://${tmpdir()}`]
if (process.env.xo_fs_nfs) handlers.push(process.env.xo_fs_nfs)
if (process.env.xo_fs_smb) handlers.push(process.env.xo_fs_smb)
handlers.forEach(url => {
describe(url, () => {
let handler
const testWithFileDescriptor = (path, flags, fn) => {
it('with path', () => fn({ file: path, flags }))
it('with file descriptor', async () => {
const file = await handler.openFile(path, flags)
try {
await fn({ file })
} finally {
await handler.closeFile(file)
}
})
}
beforeAll(async () => {
handler = getHandler({ url }).addPrefix(`xo-fs-tests-${Date.now()}`)
await handler.sync()
})
afterAll(async () => {
await handler.forget()
handler = undefined
})
beforeEach(async () => {
// ensure test dir exists
await handler.mkdir('.')
})
afterEach(async () => {
await handler.rmtree('.')
})
describe('#type', () => {
it('returns the type of the remote', () => {
expect(typeof handler.type).toBe('string')
})
})
describe('#createOutputStream()', () => {
it('creates parent dir if missing', async () => {
const stream = await handler.createOutputStream('dir/file')
await fromCallback(cb => pipeline(createTestDataStream(), stream, cb))
await expect(await handler.readFile('dir/file')).toEqual(TEST_DATA)
})
})
describe('#createReadStream()', () => {
beforeEach(() => handler.outputFile('file', TEST_DATA))
testWithFileDescriptor('file', 'r', async ({ file, flags }) => {
await expect(
await getStream.buffer(
await handler.createReadStream(file, { flags })
)
).toEqual(TEST_DATA)
})
})
describe('#createWriteStream()', () => {
testWithFileDescriptor('file', 'wx', async ({ file, flags }) => {
const stream = await handler.createWriteStream(file, { flags })
await fromCallback(cb => pipeline(createTestDataStream(), stream, cb))
await expect(await handler.readFile('file')).toEqual(TEST_DATA)
})
it('fails if parent dir is missing', async () => {
const error = await rejectionOf(handler.createWriteStream('dir/file'))
expect(error.code).toBe('ENOENT')
})
})
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))
testWithFileDescriptor('file', 'r', async () => {
expect(await handler.getSize('file')).toEqual(TEST_DATA_LEN)
})
})
describe('#list()', () => {
it(`should list the content of folder`, async () => {
await handler.outputFile('file', TEST_DATA)
await expect(await handler.list('.')).toEqual(['file'])
})
it('can prepend the directory to entries', async () => {
await handler.outputFile('dir/file', '')
expect(await handler.list('dir', { prependDir: true })).toEqual([
'/dir/file',
])
})
it('can prepend the directory to entries', async () => {
await handler.outputFile('dir/file', '')
expect(await handler.list('dir', { prependDir: true })).toEqual([
'/dir/file',
])
})
})
describe('#mkdir()', () => {
it('creates a directory', async () => {
await handler.mkdir('dir')
await expect(await handler.list('.')).toEqual(['dir'])
})
it('does not throw on existing directory', async () => {
await handler.mkdir('dir')
await handler.mkdir('dir')
})
it('throws ENOTDIR on existing file', async () => {
await handler.outputFile('file', '')
const error = await rejectionOf(handler.mkdir('file'))
expect(error.code).toBe('ENOTDIR')
})
})
describe('#mktree()', () => {
it('creates a tree of directories', async () => {
await handler.mktree('dir/dir')
await expect(await handler.list('.')).toEqual(['dir'])
await expect(await handler.list('dir')).toEqual(['dir'])
})
it('does not throw on existing directory', async () => {
await handler.mktree('dir/dir')
await handler.mktree('dir/dir')
})
it('throws ENOTDIR on existing file', async () => {
await handler.outputFile('dir/file', '')
const error = await rejectionOf(handler.mktree('dir/file'))
expect(error.code).toBe('ENOTDIR')
})
it('throws ENOTDIR on existing file in path', async () => {
await handler.outputFile('file', '')
const error = await rejectionOf(handler.mktree('file/dir'))
expect(error.code).toBe('ENOTDIR')
})
})
describe('#outputFile()', () => {
it('writes data to a file', async () => {
await handler.outputFile('file', TEST_DATA)
expect(await handler.readFile('file')).toEqual(TEST_DATA)
})
it('throws on existing files', async () => {
await handler.outputFile('file', '')
const error = await rejectionOf(handler.outputFile('file', ''))
expect(error.code).toBe('EEXIST')
})
})
describe('#read()', () => {
beforeEach(() => handler.outputFile('file', TEST_DATA))
const start = random(TEST_DATA_LEN)
const size = random(TEST_DATA_LEN)
testWithFileDescriptor('file', 'r', async ({ file }) => {
const buffer = Buffer.alloc(size)
const result = await handler.read(file, buffer, start)
expect(result.buffer).toBe(buffer)
expect(result).toEqual({
buffer,
bytesRead: Math.min(size, TEST_DATA_LEN - start),
})
})
})
describe('#readFile', () => {
it('returns a buffer containing the contents of the file', async () => {
await handler.outputFile('file', TEST_DATA)
expect(await handler.readFile('file')).toEqual(TEST_DATA)
})
it('throws on missing file', async () => {
const error = await rejectionOf(handler.readFile('file'))
expect(error.code).toBe('ENOENT')
})
})
describe('#rename()', () => {
it(`should rename the file`, async () => {
await handler.outputFile('file', TEST_DATA)
await handler.rename('file', `file2`)
expect(await handler.list('.')).toEqual(['file2'])
expect(await handler.readFile(`file2`)).toEqual(TEST_DATA)
})
})
describe('#rmdir()', () => {
it('should remove an empty directory', async () => {
await handler.mkdir('dir')
await handler.rmdir('dir')
expect(await handler.list('.')).toEqual([])
})
it(`should throw on non-empty directory`, async () => {
await handler.outputFile('dir/file', '')
const error = await rejectionOf(handler.rmdir('.'))
await expect(error.code).toEqual('ENOTEMPTY')
})
it('does not throw on missing directory', async () => {
await handler.rmdir('dir')
})
})
describe('#rmtree', () => {
it(`should remove a directory resursively`, async () => {
await handler.outputFile('dir/file', '')
await handler.rmtree('dir')
expect(await handler.list('.')).toEqual([])
})
})
describe('#test()', () => {
it('tests the remote appears to be working', async () => {
const answer = await handler.test()
expect(answer.success).toBe(true)
expect(typeof answer.writeRate).toBe('number')
expect(typeof answer.readRate).toBe('number')
})
})
describe('#unlink()', () => {
it(`should remove the file`, async () => {
await handler.outputFile('file', TEST_DATA)
await handler.unlink('file')
await expect(await handler.list('.')).toEqual([])
})
it('does not throw on missing file', async () => {
await handler.unlink('file')
})
})
describe('#write()', () => {
beforeEach(() => handler.outputFile('file', TEST_DATA))
const PATCH_DATA_LEN = Math.ceil(TEST_DATA_LEN / 2)
const PATCH_DATA = unsecureRandomBytes(PATCH_DATA_LEN)
forOwn(
{
'dont increase file size': (() => {
const offset = random(0, TEST_DATA_LEN - PATCH_DATA_LEN)
const expected = Buffer.from(TEST_DATA)
PATCH_DATA.copy(expected, offset)
return { offset, expected }
})(),
'increase file size': (() => {
const offset = random(
TEST_DATA_LEN - PATCH_DATA_LEN + 1,
TEST_DATA_LEN
)
const expected = Buffer.alloc(offset + PATCH_DATA_LEN)
TEST_DATA.copy(expected)
PATCH_DATA.copy(expected, offset)
return { offset, expected }
})(),
},
({ offset, expected }, title) => {
describe(title, () => {
testWithFileDescriptor('file', 'r+', async ({ file }) => {
await handler.write(file, PATCH_DATA, offset)
await expect(await handler.readFile('file')).toEqual(expected)
})
})
}
)
})
describe('#truncate()', () => {
forOwn(
{
'shrinks file': (() => {
const length = random(0, TEST_DATA_LEN)
const expected = TEST_DATA.slice(0, length)
return { length, expected }
})(),
'grows file': (() => {
const length = random(TEST_DATA_LEN, TEST_DATA_LEN * 2)
const expected = Buffer.alloc(length)
TEST_DATA.copy(expected)
return { length, expected }
})(),
},
({ length, expected }, title) => {
it(title, async () => {
await handler.outputFile('file', TEST_DATA)
await handler.truncate('file', length)
await expect(await handler.readFile('file')).toEqual(expected)
})
}
)
})
})
})

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,6 +1,6 @@
import df from '@sindresorhus/df'
import fs from 'fs-extra'
import { dirname } from 'path'
import { noop } from 'lodash'
import { fromEvent } from 'promise-toolbox'
import RemoteHandlerAbstract from './abstract'
@@ -17,22 +17,57 @@ export default class LocalHandler extends RemoteHandlerAbstract {
return this._getRealPath() + file
}
async _sync() {
const path = this._getRealPath()
await fs.ensureDir(path)
await fs.access(path, fs.R_OK | fs.W_OK)
return this._remote
async _closeFile(fd) {
return fs.close(fd)
}
async _forget() {
return noop()
async _createReadStream(file, options) {
if (typeof file === 'string') {
const stream = fs.createReadStream(this._getFilePath(file), options)
await fromEvent(stream, 'open')
return stream
}
return fs.createReadStream('', {
autoClose: false,
...options,
fd: file.fd,
})
}
async _outputFile(file, data, options) {
const path = this._getFilePath(file)
await fs.ensureDir(dirname(path))
await fs.writeFile(path, data, options)
async _createWriteStream(file, options) {
if (typeof file === 'string') {
const stream = fs.createWriteStream(this._getFilePath(file), options)
await fromEvent(stream, 'open')
return stream
}
return fs.createWriteStream('', {
autoClose: false,
...options,
fd: file.fd,
})
}
_getInfo() {
return df.file(this._getFilePath('/'))
}
async _getSize(file) {
const stats = await fs.stat(
this._getFilePath(typeof file === 'string' ? file : file.path)
)
return stats.size
}
async _list(dir) {
return fs.readdir(this._getFilePath(dir))
}
_mkdir(dir) {
return fs.mkdir(this._getFilePath(dir))
}
async _openFile(path, flags) {
return fs.open(this._getFilePath(path), flags)
}
async _read(file, buffer, position) {
@@ -61,58 +96,29 @@ export default class LocalHandler extends RemoteHandlerAbstract {
return fs.rename(this._getFilePath(oldPath), this._getFilePath(newPath))
}
async _list(dir = '.') {
return fs.readdir(this._getFilePath(dir))
async _rmdir(dir) {
return fs.rmdir(this._getFilePath(dir))
}
async _createReadStream(file, options) {
return typeof file === 'string'
? fs.createReadStream(this._getFilePath(file), options)
: fs.createReadStream('', {
autoClose: false,
...options,
fd: file.fd,
})
async _sync() {
const path = this._getRealPath('/')
await fs.ensureDir(path)
await fs.access(path, fs.R_OK | fs.W_OK)
}
async _createOutputStream(file, options) {
if (typeof file === 'string') {
const path = this._getFilePath(file)
await fs.ensureDir(dirname(path))
return fs.createWriteStream(path, options)
}
return fs.createWriteStream('', {
autoClose: false,
...options,
fd: file.fd,
})
_truncate(file, len) {
return fs.truncate(this._getFilePath(file), len)
}
async _unlink(file) {
return fs.unlink(this._getFilePath(file)).catch(error => {
// do not throw if the file did not exist
if (error == null || error.code !== 'ENOENT') {
throw error
}
})
return fs.unlink(this._getFilePath(file))
}
async _getSize(file) {
const stats = await fs.stat(
this._getFilePath(typeof file === 'string' ? file : file.path)
)
return stats.size
_writeFd(file, buffer, position) {
return fs.write(file.fd, buffer, 0, buffer.length, position)
}
async _openFile(path, flags) {
return fs.open(this._getFilePath(path), flags)
}
async _closeFile(fd) {
return fs.close(fd)
}
async _rmdir(dir) {
return fs.rmdir(dir)
_writeFile(file, data, { flags }) {
return fs.writeFile(this._getFilePath(file), data, { flag: flags })
}
}

View File

@@ -1,86 +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)
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 _sync() {
await this._mount()
return this._remote
}
async _forget() {
try {
await this._umount(this._remote)
} catch (_) {
// We have to go on...
}
}
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
}
})
}
}

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

@@ -1,33 +1,39 @@
import Smb2 from '@marsaud/smb2'
import { pFinally } from 'promise-toolbox'
import RemoteHandlerAbstract from './abstract'
const noop = () => {}
// Normalize the error code for file not found.
class ErrorWrapper extends Error {
constructor(error, newCode) {
super(error.message)
this.cause = error
this.code = newCode
}
}
const wrapError = (error, code) => ({
__proto__: error,
cause: error,
code,
})
const normalizeError = (error, shouldBeDirectory) => {
const { code } = error
return code === 'STATUS_OBJECT_NAME_NOT_FOUND' ||
code === 'STATUS_OBJECT_PATH_NOT_FOUND'
? new ErrorWrapper(error, 'ENOENT')
throw code === 'STATUS_DIRECTORY_NOT_EMPTY'
? wrapError(error, 'ENOTEMPTY')
: code === 'STATUS_FILE_IS_A_DIRECTORY'
? wrapError(error, 'EISDIR')
: code === 'STATUS_NOT_A_DIRECTORY'
? wrapError(error, 'ENOTDIR')
: code === 'STATUS_OBJECT_NAME_NOT_FOUND' ||
code === 'STATUS_OBJECT_PATH_NOT_FOUND'
? wrapError(error, 'ENOENT')
: code === 'STATUS_OBJECT_NAME_COLLISION'
? wrapError(error, 'EEXIST')
: code === 'STATUS_NOT_SUPPORTED' || code === 'STATUS_INVALID_PARAMETER'
? new ErrorWrapper(error, shouldBeDirectory ? 'ENOTDIR' : 'EISDIR')
? wrapError(error, shouldBeDirectory ? 'ENOTDIR' : 'EISDIR')
: error
}
const normalizeDirError = error => normalizeError(error, true)
export default class SmbHandler extends RemoteHandlerAbstract {
constructor(remote, opts) {
super(remote, opts)
this._forget = noop
// defined in _sync()
this._client = undefined
const prefix = this._remote.path
this._prefix = prefix !== '' ? prefix + '\\' : prefix
@@ -37,20 +43,13 @@ export default class SmbHandler extends RemoteHandlerAbstract {
return 'smb'
}
_getClient() {
const remote = this._remote
return new Smb2({
share: `\\\\${remote.host}`,
domain: remote.domain,
username: remote.username,
password: remote.password,
autoCloseTimeout: 0,
})
}
_getFilePath(file) {
return this._prefix + file.slice(1).replace(/\//g, '\\')
return (
this._prefix +
(typeof file === 'string' ? file : file.path)
.slice(1)
.replace(/\//g, '\\')
)
}
_dirname(file) {
@@ -59,175 +58,120 @@ export default class SmbHandler extends RemoteHandlerAbstract {
return parts.join('\\')
}
async _sync() {
// Check access (smb2 does not expose connect in public so far...)
await this.list()
return this._remote
_closeFile(file) {
return this._client.close(file).catch(normalizeError)
}
async _outputFile(file, data, options = {}) {
const client = this._getClient()
const path = this._getFilePath(file)
const dir = this._dirname(path)
if (dir) {
await client.ensureDir(dir)
}
return client.writeFile(path, data, options)::pFinally(() => {
client.disconnect()
})
}
async _read(file, buffer, position) {
const needsClose = typeof file === 'string'
let client
if (needsClose) {
client = this._getClient()
file = await client.open(this._getFilePath(file))
_createReadStream(file, options) {
if (typeof file === 'string') {
file = this._getFilePath(file)
} else {
;({ client, file } = file.fd)
}
try {
return await client.read(file, buffer, 0, buffer.length, position)
} finally {
if (needsClose) {
await client.close(file)
client.disconnect()
}
options = { autoClose: false, ...options, fd: file.fd }
file = ''
}
return this._client.createReadStream(file, options).catch(normalizeError)
}
async _readFile(file, options = {}) {
const client = this._getClient()
let content
try {
content = await client
.readFile(this._getFilePath(file), options)
::pFinally(() => {
client.disconnect()
})
} catch (error) {
throw normalizeError(error)
_createWriteStream(file, options) {
if (typeof file === 'string') {
file = this._getFilePath(file)
} else {
options = { autoClose: false, ...options, fd: file.fd }
file = ''
}
return content
return this._client.createWriteStream(file, options).catch(normalizeError)
}
async _rename(oldPath, newPath) {
const client = this._getClient()
try {
await client
.rename(this._getFilePath(oldPath), this._getFilePath(newPath), {
replace: true,
})
::pFinally(() => {
client.disconnect()
})
} catch (error) {
throw normalizeError(error)
}
_forget() {
const client = this._client
this._client = undefined
return client.disconnect()
}
async _list(dir = '.') {
const client = this._getClient()
let list
try {
list = await client.readdir(this._getFilePath(dir))::pFinally(() => {
client.disconnect()
})
} catch (error) {
throw normalizeError(error, true)
}
return list
_getSize(file) {
return this._client.getSize(this._getFilePath(file)).catch(normalizeError)
}
async _createReadStream(file, options = {}) {
if (typeof file !== 'string') {
file = file.path
}
const client = this._getClient()
let stream
try {
// FIXME ensure that options are properly handled by @marsaud/smb2
stream = await client.createReadStream(this._getFilePath(file), options)
stream.on('end', () => client.disconnect())
} catch (error) {
throw normalizeError(error)
}
return stream
_list(dir) {
return this._client.readdir(this._getFilePath(dir)).catch(normalizeDirError)
}
async _createOutputStream(file, options = {}) {
if (typeof file !== 'string') {
file = file.path
}
const client = this._getClient()
const path = this._getFilePath(file)
const dir = this._dirname(path)
let stream
try {
if (dir) {
await client.ensureDir(dir)
}
stream = await client.createWriteStream(path, options) // FIXME ensure that options are properly handled by @marsaud/smb2
} catch (err) {
client.disconnect()
throw err
}
stream.on('finish', () => client.disconnect())
return stream
}
async _unlink(file) {
const client = this._getClient()
try {
await client.unlink(this._getFilePath(file))::pFinally(() => {
client.disconnect()
})
} catch (error) {
throw normalizeError(error)
}
}
async _getSize(file) {
const client = await this._getClient()
let size
try {
size = await client
.getSize(this._getFilePath(typeof file === 'string' ? file : file.path))
::pFinally(() => {
client.disconnect()
})
} catch (error) {
throw normalizeError(error)
}
return size
_mkdir(dir) {
return this._client.mkdir(this._getFilePath(dir)).catch(normalizeDirError)
}
// TODO: add flags
async _openFile(path) {
const client = this._getClient()
return {
client,
file: await client.open(this._getFilePath(path)),
_openFile(path, flags) {
return this._client
.open(this._getFilePath(path), flags)
.catch(normalizeError)
}
async _read(file, buffer, position) {
const client = this._client
const needsClose = typeof file === 'string'
file = needsClose ? await client.open(this._getFilePath(file)) : file.fd
try {
return await client.read(file, buffer, 0, buffer.length, position)
} catch (error) {
normalizeError(error)
} finally {
if (needsClose) {
await client.close(file)
}
}
}
async _closeFile({ client, file }) {
await client.close(file)
client.disconnect()
_readFile(file, options) {
return this._client
.readFile(this._getFilePath(file), options)
.catch(normalizeError)
}
_rename(oldPath, newPath) {
return this._client
.rename(this._getFilePath(oldPath), this._getFilePath(newPath), {
replace: true,
})
.catch(normalizeError)
}
_rmdir(dir) {
return this._client.rmdir(this._getFilePath(dir)).catch(normalizeDirError)
}
_sync() {
const remote = this._remote
this._client = new Smb2({
share: `\\\\${remote.host}`,
domain: remote.domain,
username: remote.username,
password: remote.password,
autoCloseTimeout: 0,
})
// Check access (smb2 does not expose connect in public so far...)
return this.list('.')
}
_truncate(file, len) {
return this._client
.truncate(this._getFilePath(file), len)
.catch(normalizeError)
}
_unlink(file) {
return this._client.unlink(this._getFilePath(file)).catch(normalizeError)
}
_writeFd(file, buffer, position) {
return this._client.write(file.fd, buffer, 0, buffer.length, position)
}
_writeFile(file, data, options) {
return this._client
.writeFile(this._getFilePath(file), data, options)
.catch(normalizeError)
}
}

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

@@ -7,6 +7,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/log",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/log",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -26,11 +27,11 @@
">2%"
],
"engines": {
"node": ">=4"
"node": ">=6"
},
"dependencies": {
"lodash": "^4.17.4",
"promise-toolbox": "^0.11.0"
"promise-toolbox": "^0.12.1"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
@@ -47,6 +48,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -12,7 +12,7 @@ const createTransport = config => {
if (Array.isArray(config)) {
const transports = config.map(createTransport)
const { length } = transports
return function () {
return function() {
for (let i = 0; i < length; ++i) {
transports[i].apply(this, arguments)
}
@@ -29,14 +29,14 @@ const createTransport = config => {
}
const orig = transport
transport = function (log) {
transport = function(log) {
if ((level !== undefined && log.level >= level) || filter(log)) {
return orig.apply(this, arguments)
}
}
} else if (level !== undefined) {
const orig = transport
transport = function (log) {
transport = function(log) {
if (log.level >= level) {
return orig.apply(this, arguments)
}
@@ -85,7 +85,7 @@ export const catchGlobalErrors = logger => {
const EventEmitter = require('events')
const { prototype } = EventEmitter
const { emit } = prototype
function patchedEmit (event, error) {
function patchedEmit(event, error) {
if (event === 'error' && this.listenerCount(event) === 0) {
logger.error('unhandled error event', { error })
return false

View File

@@ -14,7 +14,7 @@ if (!(symbol in global)) {
// -------------------------------------------------------------------
function Log (data, level, namespace, message, time) {
function Log(data, level, namespace, message, time) {
this.data = data
this.level = level
this.namespace = namespace
@@ -22,7 +22,7 @@ function Log (data, level, namespace, message, time) {
this.time = time
}
function Logger (namespace) {
function Logger(namespace) {
this._namespace = namespace
// bind all logging methods
@@ -37,11 +37,11 @@ const { prototype } = Logger
for (const name in LEVELS) {
const level = LEVELS[name]
prototype[name.toLowerCase()] = function (message, data) {
prototype[name.toLowerCase()] = function(message, data) {
if (typeof message !== 'string') {
if (message instanceof Error) {
data = { error: message }
;({ message = 'an error has occured' } = message)
;({ message = 'an error has occurred' } = message)
} else {
return this.warn('incorrect value passed to logger', {
level,
@@ -53,13 +53,13 @@ for (const name in LEVELS) {
}
}
prototype.wrap = function (message, fn) {
prototype.wrap = function(message, fn) {
const logger = this
const warnAndRethrow = error => {
logger.warn(message, { error })
throw error
}
return function () {
return function() {
try {
const result = fn.apply(this, arguments)
const then = result != null && result.then

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

@@ -7,6 +7,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/mixin",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/mixin",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -44,6 +45,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -61,7 +61,7 @@ const mixin = Mixins => Class => {
const n = Mixins.length
function DecoratedClass (...args) {
function DecoratedClass(...args) {
const instance = new Class(...args)
for (let i = 0; i < n; ++i) {

View File

@@ -1,6 +1,301 @@
# ChangeLog
## *next*
## **next** (2019-05-14)
### Enhancements
### Bug fixes
- [Pool/Patches] Fix "an error has occurred" in "Applied patches" [#4192](https://github.com/vatesfr/xen-orchestra/issues/4192) (PR [#4193](https://github.com/vatesfr/xen-orchestra/pull/4193))
- [Backup NG] Fix report sent even though "Never" is selected [#4092](https://github.com/vatesfr/xen-orchestra/issues/4092) (PR [#4178](https://github.com/vatesfr/xen-orchestra/pull/4178))
- [Remotes] Fix issues after a config import (PR [#4197](https://github.com/vatesfr/xen-orchestra/pull/4197))
### Released packages
- xo-server-backup-reports v0.16.1
- @xen-orchestra/fs v0.9.0
- vhd-lib v0.7.0
- xo-server v5.41.0
- xo-web v5.41.0
## **5.34.0** (2019-04-30)
![Channel: latest](https://badgen.net/badge/channel/latest/yellow)
### Highlights
- [Self/New VM] Add network config box to custom cloud-init [#3872](https://github.com/vatesfr/xen-orchestra/issues/3872) (PR [#4150](https://github.com/vatesfr/xen-orchestra/pull/4150))
- [Metadata backup] Detailed logs [#4005](https://github.com/vatesfr/xen-orchestra/issues/4005) (PR [#4014](https://github.com/vatesfr/xen-orchestra/pull/4014))
- [Backup reports] Support metadata backups (PR [#4084](https://github.com/vatesfr/xen-orchestra/pull/4084))
- [VM migration] Auto select default SR and collapse optional actions [#3326](https://github.com/vatesfr/xen-orchestra/issues/3326) (PR [#4121](https://github.com/vatesfr/xen-orchestra/pull/4121))
- Unlock basic stats on all editions [#4166](https://github.com/vatesfr/xen-orchestra/issues/4166) (PR [#4172](https://github.com/vatesfr/xen-orchestra/pull/4172))
### Enhancements
- [Settings/remotes] Expose mount options field for SMB [#4063](https://github.com/vatesfr/xen-orchestra/issues/4063) (PR [#4067](https://github.com/vatesfr/xen-orchestra/pull/4067))
- [Backup/Schedule] Add warning regarding DST when you add a schedule [#4042](https://github.com/vatesfr/xen-orchestra/issues/4042) (PR [#4056](https://github.com/vatesfr/xen-orchestra/pull/4056))
- [Import] Avoid blocking the UI when dropping a big OVA file on the UI (PR [#4018](https://github.com/vatesfr/xen-orchestra/pull/4018))
- [Backup NG/Overview] Make backup list title clearer [#4111](https://github.com/vatesfr/xen-orchestra/issues/4111) (PR [#4129](https://github.com/vatesfr/xen-orchestra/pull/4129))
- [Dashboard] Hide "Report" section for non-admins [#4123](https://github.com/vatesfr/xen-orchestra/issues/4123) (PR [#4126](https://github.com/vatesfr/xen-orchestra/pull/4126))
- [Self/New VM] Display confirmation modal when user will use a large amount of resources [#4044](https://github.com/vatesfr/xen-orchestra/issues/4044) (PR [#4127](https://github.com/vatesfr/xen-orchestra/pull/4127))
- [VDI migration, New disk] Warning when SR host is different from the other disks [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#4035](https://github.com/vatesfr/xen-orchestra/pull/4035))
- [Attach disk] Display warning message when VDI SR is on different host from the other disks [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#4117](https://github.com/vatesfr/xen-orchestra/pull/4117))
- [Editable] Notify user when editable undo fails [#3799](https://github.com/vatesfr/xen-orchestra/issues/3799) (PR [#4150](https://github.com/vatesfr/xen-orchestra/pull/4157))
- [XO] Add banner for sources users to clarify support conditions [#4165](https://github.com/vatesfr/xen-orchestra/issues/4165) (PR [#4167](https://github.com/vatesfr/xen-orchestra/pull/4167))
### Bug fixes
- [Continuous Replication] Fix VHD size guess for empty files [#4105](https://github.com/vatesfr/xen-orchestra/issues/4105) (PR [#4107](https://github.com/vatesfr/xen-orchestra/pull/4107))
- [Backup NG] Only display full backup interval in case of a delta backup (PR [#4125](https://github.com/vatesfr/xen-orchestra/pull/4107))
- [Dashboard/Health] fix 'an error has occurred' on the storage state table [#4128](https://github.com/vatesfr/xen-orchestra/issues/4128) (PR [#4132](https://github.com/vatesfr/xen-orchestra/pull/4132))
- [Menu] XOA: Fixed empty slot when menu is collapsed [#4012](https://github.com/vatesfr/xen-orchestra/issues/4012) (PR [#4068](https://github.com/vatesfr/xen-orchestra/pull/4068)
- [Self/New VM] Fix missing templates when refreshing page [#3265](https://github.com/vatesfr/xen-orchestra/issues/3265) (PR [#3565](https://github.com/vatesfr/xen-orchestra/pull/3565))
- [Home] No more false positives when select Tag on Home page [#4087](https://github.com/vatesfr/xen-orchestra/issues/4087) (PR [#4112](https://github.com/vatesfr/xen-orchestra/pull/4112))
### Released packages
- xo-server-backup-reports v0.16.0
- complex-matcher v0.6.0
- xo-vmdk-to-vhd v0.1.7
- vhd-lib v0.6.1
- xo-server v5.40.0
- xo-web v5.40.1
## **5.33.1** (2019-04-04)
![Channel: stable](https://badgen.net/badge/channel/stable/green)
### Bug fix
- Fix major memory leak [2563be4](https://github.com/vatesfr/xen-orchestra/commit/2563be472bfd84c6ed867efd21c4aeeb824d387f)
### Released packages
- xen-api v0.25.1
- xo-server v5.38.2
## **5.33.0** (2019-03-29)
### Enhancements
- [SR/Disk] Disable actions on unmanaged VDIs [#3988](https://github.com/vatesfr/xen-orchestra/issues/3988) (PR [#4000](https://github.com/vatesfr/xen-orchestra/pull/4000))
- [Pool] Specify automatic networks on a Pool [#3916](https://github.com/vatesfr/xen-orchestra/issues/3916) (PR [#3958](https://github.com/vatesfr/xen-orchestra/pull/3958))
- [VM/advanced] Manage start delay for VM [#3909](https://github.com/vatesfr/xen-orchestra/issues/3909) (PR [#4002](https://github.com/vatesfr/xen-orchestra/pull/4002))
- [New/Vm] SR section: Display warning message when the selected SRs aren't in the same host [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#3967](https://github.com/vatesfr/xen-orchestra/pull/3967))
- Enable compression for HTTP requests (and initial objects fetch)
- [VDI migration] Display same-pool SRs first in the selector [#3945](https://github.com/vatesfr/xen-orchestra/issues/3945) (PR [#3996](https://github.com/vatesfr/xen-orchestra/pull/3996))
- [Home] Save the current page in url [#3993](https://github.com/vatesfr/xen-orchestra/issues/3993) (PR [#3999](https://github.com/vatesfr/xen-orchestra/pull/3999))
- [VDI] Ensure suspend VDI is destroyed when destroying a VM [#4027](https://github.com/vatesfr/xen-orchestra/issues/4027) (PR [#4038](https://github.com/vatesfr/xen-orchestra/pull/4038))
- [VM/disk]: Warning when 2 VDIs are on 2 different hosts' local SRs [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#3969](https://github.com/vatesfr/xen-orchestra/pull/3969))
- [Remotes] Benchmarks (read and write rate speed) added when remote is tested [#3991](https://github.com/vatesfr/xen-orchestra/issues/3991) (PR [#4015](https://github.com/vatesfr/xen-orchestra/pull/4015))
- [Cloud Config] Support both NoCloud and Config Drive 2 datasources for maximum compatibility (PR [#4053](https://github.com/vatesfr/xen-orchestra/pull/4053))
- [Advanced] Configurable cookie validity (PR [#4059](https://github.com/vatesfr/xen-orchestra/pull/4059))
- [Plugins] Display number of installed plugins [#4008](https://github.com/vatesfr/xen-orchestra/issues/4008) (PR [#4050](https://github.com/vatesfr/xen-orchestra/pull/4050))
- [Continuous Replication] Opt-in mode to guess VHD size, should help with XenServer 7.1 CU2 and various `VDI_IO_ERROR` errors (PR [#3726](https://github.com/vatesfr/xen-orchestra/pull/3726))
- [VM/Snapshots] Always delete broken quiesced snapshots [#4074](https://github.com/vatesfr/xen-orchestra/issues/4074) (PR [#4075](https://github.com/vatesfr/xen-orchestra/pull/4075))
- [Settings/Servers] Display link to pool [#4041](https://github.com/vatesfr/xen-orchestra/issues/4041) (PR [#4045](https://github.com/vatesfr/xen-orchestra/pull/4045))
- [Import] Change wording of drop zone (PR [#4020](https://github.com/vatesfr/xen-orchestra/pull/4020))
- [Backup NG] Ability to set the interval of the full backups [#1783](https://github.com/vatesfr/xen-orchestra/issues/1783) (PR [#4083](https://github.com/vatesfr/xen-orchestra/pull/4083))
- [Hosts] Display a warning icon if you have XenServer license restrictions [#4091](https://github.com/vatesfr/xen-orchestra/issues/4091) (PR [#4094](https://github.com/vatesfr/xen-orchestra/pull/4094))
- [Restore] Ability to restore a metadata backup [#4004](https://github.com/vatesfr/xen-orchestra/issues/4004) (PR [#4023](https://github.com/vatesfr/xen-orchestra/pull/4023))
- Improve connection to XCP-ng/XenServer hosts:
- never disconnect by itself even in case of errors
- never stop watching events
### Bug fixes
- [New network] PIF was wrongly required which prevented from creating a private network (PR [#4010](https://github.com/vatesfr/xen-orchestra/pull/4010))
- [Google authentication] Migrate to new endpoint
- [Backup NG] Better handling of huge logs [#4025](https://github.com/vatesfr/xen-orchestra/issues/4025) (PR [#4026](https://github.com/vatesfr/xen-orchestra/pull/4026))
- [Home/VM] Bulk migration: fixed VM VDIs not migrated to the selected SR [#3986](https://github.com/vatesfr/xen-orchestra/issues/3986) (PR [#3987](https://github.com/vatesfr/xen-orchestra/pull/3987))
- [Stats] Fix cache usage with simultaneous requests [#4017](https://github.com/vatesfr/xen-orchestra/issues/4017) (PR [#4028](https://github.com/vatesfr/xen-orchestra/pull/4028))
- [Backup NG] Fix compression displayed for the wrong backup mode (PR [#4021](https://github.com/vatesfr/xen-orchestra/pull/4021))
- [Home] Always sort the items by their names as a secondary sort criteria [#3983](https://github.com/vatesfr/xen-orchestra/issues/3983) (PR [#4047](https://github.com/vatesfr/xen-orchestra/pull/4047))
- [Remotes] Fixes `spawn mount EMFILE` error during backup
- Properly redirect to sign in page instead of being stuck in a refresh loop
- [Backup-ng] No more false positives when list matching VMs on Home page [#4078](https://github.com/vatesfr/xen-orchestra/issues/4078) (PR [#4085](https://github.com/vatesfr/xen-orchestra/pull/4085))
- [Plugins] Properly remove optional settings when unchecking _Fill information_ (PR [#4076](https://github.com/vatesfr/xen-orchestra/pull/4076))
- [Patches] (PR [#4077](https://github.com/vatesfr/xen-orchestra/pull/4077))
- Add a host to a pool: fixes the auto-patching of the host on XenServer < 7.2 [#3783](https://github.com/vatesfr/xen-orchestra/issues/3783)
- Add a host to a pool: homogenizes both the host and **pool**'s patches [#2188](https://github.com/vatesfr/xen-orchestra/issues/2188)
- Safely install a subset of patches on a pool [#3777](https://github.com/vatesfr/xen-orchestra/issues/3777)
- XCP-ng: no longer requires to run `yum install xcp-ng-updater` when it's already installed [#3934](https://github.com/vatesfr/xen-orchestra/issues/3934)
### Released packages
- xen-api v0.25.0
- vhd-lib v0.6.0
- @xen-orchestra/fs v0.8.0
- xo-server-usage-report v0.7.2
- xo-server v5.38.1
- xo-web v5.38.0
## **5.32.2** (2019-02-28)
### Bug fixes
- Fix XAPI events monitoring on old version (XenServer 7.2)
## **5.32.1** (2019-02-28)
### Bug fixes
- Fix a very short timeout in the monitoring of XAPI events which may lead to unresponsive XenServer hosts
## **5.32.0** (2019-02-28)
### Enhancements
- [VM migration] Display same-pool hosts first in the selector [#3262](https://github.com/vatesfr/xen-orchestra/issues/3262) (PR [#3890](https://github.com/vatesfr/xen-orchestra/pull/3890))
- [Home/VM] Sort VM by start time [#3955](https://github.com/vatesfr/xen-orchestra/issues/3955) (PR [#3970](https://github.com/vatesfr/xen-orchestra/pull/3970))
- [Editable fields] Unfocusing (clicking outside) submits the change instead of canceling (PR [#3980](https://github.com/vatesfr/xen-orchestra/pull/3980))
- [Network] Dedicated page for network creation [#3895](https://github.com/vatesfr/xen-orchestra/issues/3895) (PR [#3906](https://github.com/vatesfr/xen-orchestra/pull/3906))
- [Logs] Add button to download the log [#3957](https://github.com/vatesfr/xen-orchestra/issues/3957) (PR [#3985](https://github.com/vatesfr/xen-orchestra/pull/3985))
- [Continuous Replication] Share full copy between schedules [#3973](https://github.com/vatesfr/xen-orchestra/issues/3973) (PR [#3995](https://github.com/vatesfr/xen-orchestra/pull/3995))
- [Backup] Ability to backup XO configuration and pool metadata [#808](https://github.com/vatesfr/xen-orchestra/issues/808) [#3501](https://github.com/vatesfr/xen-orchestra/issues/3501) (PR [#3912](https://github.com/vatesfr/xen-orchestra/pull/3912))
### Bug fixes
- [Host] Fix multipathing status for XenServer < 7.5 [#3956](https://github.com/vatesfr/xen-orchestra/issues/3956) (PR [#3961](https://github.com/vatesfr/xen-orchestra/pull/3961))
- [Home/VM] Show creation date of the VM on if it available [#3953](https://github.com/vatesfr/xen-orchestra/issues/3953) (PR [#3959](https://github.com/vatesfr/xen-orchestra/pull/3959))
- [Notifications] Fix invalid notifications when not registered (PR [#3966](https://github.com/vatesfr/xen-orchestra/pull/3966))
- [Import] Fix import of some OVA files [#3962](https://github.com/vatesfr/xen-orchestra/issues/3962) (PR [#3974](https://github.com/vatesfr/xen-orchestra/pull/3974))
- [Servers] Fix *already connected error* after a server has been removed during connection [#3976](https://github.com/vatesfr/xen-orchestra/issues/3976) (PR [#3977](https://github.com/vatesfr/xen-orchestra/pull/3977))
- [Backup] Fix random _mount_ issues with NFS/SMB remotes [#3973](https://github.com/vatesfr/xen-orchestra/issues/3973) (PR [#4003](https://github.com/vatesfr/xen-orchestra/pull/4003))
### Released packages
- @xen-orchestra/fs v0.7.0
- xen-api v0.24.3
- xoa-updater v0.15.2
- xo-server v5.36.0
- xo-web v5.36.0
## **5.31.2** (2019-02-08)
### 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
## **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
- [Users] Display user groups [#3719](https://github.com/vatesfr/xen-orchestra/issues/3719) (PR [#3740](https://github.com/vatesfr/xen-orchestra/pull/3740))
- [VDI] Display VDI's SR [3021](https://github.com/vatesfr/xen-orchestra/issues/3021) (PR [#3285](https://github.com/vatesfr/xen-orchestra/pull/3285))
- [Health, VM/disks] Display SR's container [#3021](https://github.com/vatesfr/xen-orchestra/issues/3021) (PRs [#3747](https://github.com/vatesfr/xen-orchestra/pull/3747), [#3751](https://github.com/vatesfr/xen-orchestra/pull/3751))
- [Servers] Auto-connect to ejected host [#2238](https://github.com/vatesfr/xen-orchestra/issues/2238) (PR [#3738](https://github.com/vatesfr/xen-orchestra/pull/3738))
- [Backup NG] Add "XOSAN" in excluded tags by default [#2128](https://github.com/vatesfr/xen-orchestra/issues/3563) (PR [#3559](https://github.com/vatesfr/xen-orchestra/pull/3563))
- [VM] add tooltip for VM status icon [#3749](https://github.com/vatesfr/xen-orchestra/issues/3749) (PR [#3765](https://github.com/vatesfr/xen-orchestra/pull/3765))
- [New XOSAN] Improve view and possibility to sort SRs by name/size/free space [#2416](https://github.com/vatesfr/xen-orchestra/issues/2416) (PR [#3691](https://github.com/vatesfr/xen-orchestra/pull/3691))
- [Backup NG] Disable HA on replicated VM (CR, DR) [#2359](https://github.com/vatesfr/xen-orchestra/issues/2359) (PR [#3755](https://github.com/vatesfr/xen-orchestra/pull/3755))
- [Backup NG] Display the last run status for each schedule with the possibility to show the associated log [#3769](https://github.com/vatesfr/xen-orchestra/issues/3769) (PR [#3779](https://github.com/vatesfr/xen-orchestra/pull/3779))
- [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
- [Home/SRs] Fixed SR status for non admin users [#2204](https://github.com/vatesfr/xen-orchestra/issues/2204) (PR [#3742](https://github.com/vatesfr/xen-orchestra/pull/3742))
- [Servers] Fix occasional "server's pool already connected" errors when pool is not connected (PR [#3782](https://github.com/vatesfr/xen-orchestra/pull/3782))
- [Self] Fix missing objects when the self service view is the first one to be loaded when opening XO [#2689](https://github.com/vatesfr/xen-orchestra/issues/2689) (PR [#3096](https://github.com/vatesfr/xen-orchestra/pull/3096))
### Released packages
- @xen-orchestra/fs v0.5.0
- xen-api v0.23.0
- xo-acl-resolver v0.4.1
- xo-server v5.32.0
- xo-web v5.32.0
## **5.29.0** (2018-11-29)
### Enhancements

25
CHANGELOG.unreleased.md Normal file
View File

@@ -0,0 +1,25 @@
> This file contains all changes that have not been released yet.
### Enhancements
- [VM/general] Display 'Started... ago' instead of 'Halted... ago' for paused state [#3750](https://github.com/vatesfr/xen-orchestra/issues/3750) (PR [#4170](https://github.com/vatesfr/xen-orchestra/pull/4170))
- [Metadata backup] Ability to define when the backup report will be sent (PR [#4149](https://github.com/vatesfr/xen-orchestra/pull/4149))
- [XOA/Update] Ability to select release channel [#4200](https://github.com/vatesfr/xen-orchestra/issues/4200) (PR [#4202](https://github.com/vatesfr/xen-orchestra/pull/4202))
- [User] Forget connection tokens on password change or on demand [#4214](https://github.com/vatesfr/xen-orchestra/issues/4214) (PR [#4224](https://github.com/vatesfr/xen-orchestra/pull/4224))
- [Settings/Logs] LICENCE_RESTRICTION errors: suggest XCP-ng as an Open Source alternative [#3876](https://github.com/vatesfr/xen-orchestra/issues/3876) (PR [#4238](https://github.com/vatesfr/xen-orchestra/pull/4238))
- [VM/Migrate] Display VDI size on migrate modal [#2534](https://github.com/vatesfr/xen-orchestra/issues/2534) (PR [#4250](https://github.com/vatesfr/xen-orchestra/pull/4250))
### Bug fixes
- [Charts] Fixed the chart lines sometimes changing order/color (PR [#4221](https://github.com/vatesfr/xen-orchestra/pull/4221))
- Prevent non-admin users to access admin pages with URL (PR [#4220](https://github.com/vatesfr/xen-orchestra/pull/4220))
- [Upgrade] Fix alert before upgrade while running backup jobs [#4164](https://github.com/vatesfr/xen-orchestra/issues/4164) (PR [#4235](https://github.com/vatesfr/xen-orchestra/pull/4235))
- [Import] Fix import OVA files (PR [#4232](https://github.com/vatesfr/xen-orchestra/pull/4232))
- [VM/network] Fix duplicate IPv4 (PR [#4239](https://github.com/vatesfr/xen-orchestra/pull/4239))
- [Remotes] Fix disconnected remotes which may appear to work
- [Host] Fix incorrect hypervisor name [#4246](https://github.com/vatesfr/xen-orchestra/issues/4246) (PR [#4248](https://github.com/vatesfr/xen-orchestra/pull/4248))
### Released packages
- xo-server v5.42.0
- xo-web v5.42.0

View File

@@ -4,14 +4,15 @@
- [ ] 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
1. create a PR as soon as possible
1. mark it as `WiP:` (Work in Progress) if not ready to be merged
1. when you want a review, add a reviewer
1. when you want a review, add a reviewer (and only one)
1. if necessary, update your PR, and re- add a reviewer

View File

@@ -1,4 +1,4 @@
# Xen Orchestra [![Chat with us](https://storage.crisp.im/plugins/images/936925df-f37b-4ba8-bab0-70cd2edcb0be/badge.svg)](https://go.crisp.im/chat/embed/?website_id=-JzqzzwddSV7bKGtEyAQ) [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
# Xen Orchestra [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
![](http://i.imgur.com/tRffA5y.png)

View File

@@ -33,6 +33,7 @@
* [Disaster recovery](disaster_recovery.md)
* [Smart Backup](smart_backup.md)
* [File level Restore](file_level_restore.md)
* [Metadata Backup](metadata_backup.md)
* [Backup Concurrency](concurrency.md)
* [Configure backup reports](backup_reports.md)
* [Backup troubleshooting](backup_troubleshooting.md)
@@ -51,6 +52,7 @@
* [Job manager](scheduler.md)
* [Alerts](alerts.md)
* [Load balancing](load_balancing.md)
* [Emergency Shutdown](emergency_shutdown.md)
* [Auto scalability](auto_scalability.md)
* [Forecaster](forecaster.md)
* [Recipes](recipes.md)

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

BIN
docs/assets/cr-seed-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/assets/cr-seed-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/assets/cr-seed-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/assets/cr-seed-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 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

BIN
docs/assets/metadata-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
docs/assets/metadata-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/assets/metadata-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/assets/metadata-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/assets/metadata-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
docs/assets/metadata-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
docs/assets/metadata-7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -12,7 +12,9 @@ Another good way to check if there is activity is the XOA VM stats view (on the
### VDI chain protection
This means your previous VM disks and snapshots should be "merged" (*coalesced* in the XenServer world) before we can take a new snapshot. This mechanism is handled by XenServer itself, not Xen Orchestra. But we can check your existing VDI chain and avoid creating more snapshots than your storage can merge. Otherwise, this will lead to catastrophic consequences. Xen Orchestra is the **only** XenServer/XCP backup product dealing with this.
Backup jobs regularly delete snapshots. When a snapshot is deleted, either manually or via a backup job, it triggers the need for Xenserver to coalesce the VDI chain - to merge the remaining VDIs and base copies in the chain. This means generally we cannot take too many new snapshots on said VM until Xenserver has finished running a coalesce job on the VDI chain.
This mechanism and scheduling is handled by XenServer itself, not Xen Orchestra. But we can check your existing VDI chain and avoid creating more snapshots than your storage can merge. If we don't, this will lead to catastrophic consequences. Xen Orchestra is the **only** XenServer/XCP backup product that takes this into account and offers protection.
Without this detection, you could have 2 potential issues:
@@ -21,9 +23,9 @@ Without this detection, you could have 2 potential issues:
The first issue is a chain that contains more than 30 elements (fixed XenServer limit), and the other one means it's full because the "coalesce" process couldn't keep up the pace and the storage filled up.
In the end, this message is a **protection mechanism against damaging your SR**. The backup job will fail, but XenServer itself should eventually automatically coalesce the snapshot chain, and the the next time the backup job should complete.
In the end, this message is a **protection mechanism preventing damage to your SR**. The backup job will fail, but XenServer itself should eventually automatically coalesce the snapshot chain, and the the next time the backup job should complete.
Just remember this: **coalesce will happen every time a snapshot is removed**.
Just remember this: **a coalesce should happen every time a snapshot is removed**.
> You can read more on this on our dedicated blog post regarding [XenServer coalesce detection](https://xen-orchestra.com/blog/xenserver-coalesce-detection-in-xen-orchestra/).
@@ -33,11 +35,13 @@ 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).
If you don't see any running coalesce jobs, and can't find any other reason that XenServer has not started one, you can attempt to make it start a coalesce job by rescanning the SR. This is harmless to try, but will not always result in a coalesce. Visit the problematic SR in the XOA UI, then click the "Rescan All Disks" button towards the top right: it looks like a refresh circle icon. This should begin the coalesce process - if you click the Advanced tab in the SR view, the "disks needing to be coalesced" list should become smaller and smaller.
If you don't see any running coalesce jobs, and can't find any other reason that XenServer has not started one, you can attempt to make it start a coalesce job by rescanning the SR. This is harmless to try, but will not always result in a coalesce. Visit the problematic SR in the XOA UI, then click the "Rescan All Disks" button towards the top right: it looks like a refresh circle icon. This should begin the coalesce process - if you click the Advanced tab in the SR view, the "disks needing to be coalesced" list should become smaller and smaller.
As a last resort, migrating the VM (more specifically, its disks) to a new storage repository will also force a coalesce and solve this issue. That means migrating a VM to another host (with its own storage) and back will force the VDI chain for that VM to be coalesced, and get rid of the `VDI Chain Protection` message.
### Parse Error

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:
@@ -8,6 +10,7 @@ There are several ways to protect your VMs:
* [Rolling Snapshots](rolling_snapshots.md) [*Starter Edition*]
* [Delta Backups](delta_backups.md) (best of both previous ones) [*Enterprise Edition*]
* [Disaster Recovery](disaster_recovery.md) [*Enterprise Edition*]
* [Metadata Backups](metadata_backup.md) [*Enterprise Edition*]
* [Continuous Replication](continuous_replication.md) [*Premium Edition*]
* [File Level Restore](file_level_restore.md) [*Premium Edition*]
@@ -39,7 +42,7 @@ Each backups' job execution is identified by a `runId`. You can find this `runId
All backup types rely on snapshots. But what about data consistency? By default, Xen Orchestra will try to take a **quiesced snapshot** every time a snapshot is done (and fall back to normal snapshots if it's not possible).
Snapshots of Windows VMs can be quiesced (especially MS SQL or Exchange services) after you have installed Xen Tools in your VMs. However, [there is an extra step to install the VSS provider on windows](quiesce). A quiesced snapshot means the operating system will be notified and the cache will be flushed to disks. This way, your backups will always be consistent.
Snapshots of Windows VMs can be quiesced (especially MS SQL or Exchange services) after you have installed Xen Tools in your VMs. However, [there is an extra step to install the VSS provider on windows](https://xen-orchestra.com/blog/xenserver-quiesce-snapshots/). A quiesced snapshot means the operating system will be notified and the cache will be flushed to disks. This way, your backups will always be consistent.
To see if you have quiesced snapshots for a VM, just go into its snapshot tab, then the "info" icon means it is a quiesced snapshot:
@@ -141,3 +144,12 @@ To make the mount point persistent in XOA, edit the `/etc/fstab` file, and add:
```
This way, without modifying your previous scheduled snapshot, they will be written to this new local mountpoint!
## High availability (HA) disabled on replicated VMs
Replicated VMs HA are taken into account by XS/XCP-ng. To avoid the resultant troubles, HA will be disabled from the replicated VMs and a tag indicating this change will be added.
![](./assets/disabled-dr-ha-tag.png)
![](./assets/disabled-cr-ha-tag.png)
> The tag won't be automatically removed by XO on the replicated VMs, even if HA is re-enabled.

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,62 @@ 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, copy the job's `backupJobId` by hovering to the left of the shortened ID and clicking the copy to clipboard button:
In your source host:
![](./assets/cr-seed-1.png)
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>`
Copy it somewhere temporarily. Now we need to also copy the ID of the job schedule, `backupScheduleId`. Do this by hovering over the schedule name in the same panel as before, and clicking the copy to clipboard button. Keep it with the `backupJobId` you copied previously as we will need them all later:
In XO:
![](./assets/cr-seed-2.png)
1. Edit the replication job and select the new destination SR
### Seed creation
On the destination host; to avoid data corruption, you need to avoid any VM start:
Manually create a snapshot on the VM being backed up, then copy this snapshot UUID, `snapshotUuid` from the snapshot panel of the VM:
![](./assets/cr-seed-3.png)
> DO NOT ever delete or alter this snapshot, feel free to rename it to make that clear.
### Seed copy
Export this snapshot to a file, then import it on the target SR.
We need to copy the UUID of this newly created VM as well, `targetVmUuid`:
![](./assets/cr-seed-4.png)
> 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>
sudo npm i -g --unsafe-perm @xen-orchestra/cr-seed-cli
```
### 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

@@ -0,0 +1,27 @@
# Emergency Shutdown
If you have a UPS for your hosts, and lose power, you may have a limited amount of time to shut down all of your VM infrastructure before the batteries run out. If you find yourself in this situation, or any other situation requiring the fast shutdown of everything, you can use the **Emergency Shutdown** feature.
## How to activate
On the host view, clicking on this button will trigger the _Emergency Shutdown_ procedure:
![](./assets/e-shutdown-1.png)
1. **All running VMs will be suspended** (think of it like "hibernate" on your laptop: the RAM will be stored in the storage repository).
2. Only after this is complete, the host will be halted.
Here, you can see the running VMs are being suspended:
![](./assets/e-shutdown-2.png)
And finally, that's it. They are cleanly shut down with the RAM saved to disk to be resumed later:
![](./assets/e-shutdown-3.png)
Now the host is halted automatically.
## Powering back on
When the power outage is over, all you need to do is:
1. Start your host.
2. All your VMs can be resumed, your RAM is preserved and therefore your VMs will be in the exact same state as they were before the power outage.

View File

@@ -6,9 +6,9 @@
> Please take time to read this guide carefully.
This installation has been validated against a fresh Debian 8 (Jessie) x64 install. It should be nearly the same on other dpkg systems. For RPM based OS's, it should be close, as most of our dependencies come from NPM and not the OS itself.
This installation has been validated against a fresh Debian 9 (Stretch) x64 install. It should be nearly the same on other dpkg systems. For RPM based OS's, it should be close, as most of our dependencies come from NPM and not the OS itself.
As you may have seen,in other parts of the documentation, XO is composed of two parts: [xo-server](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server/) and [xo-web](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-web/). They can be installed separately, even on different machines, but for the sake of simplicity we will set them up together.
As you may have seen in other parts of the documentation, XO is composed of two parts: [xo-server](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server/) and [xo-web](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-web/). They can be installed separately, even on different machines, but for the sake of simplicity we will set them up together.
## Packages and Pre-requisites
@@ -38,24 +38,25 @@ 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
```
> Note: xo-server and xo-web have been migrated to the [xen-orchestra](https://github.com/vatesfr/xen-orchestra) mono-repository.
> Note: xo-server and xo-web have been migrated to the [xen-orchestra](https://github.com/vatesfr/xen-orchestra) mono-repository - so you only need the single clone command above
## Installing dependencies
Once you have it, use `yarn`, as the non-root (or root) user owning the fetched code, to install the other dependencies. Enter the `xen-orchestra` directory and run the following commands:
Now that you have the code, you can enter the `xen-orchestra` directory and use `yarn` to install other dependencies. Then finally build it using `yarn build`. Be sure to run `yarn` commands as the same user you will be using to run Xen Orchestra:
```
$ cd xen-orchestra
$ yarn
$ yarn build
```
@@ -64,17 +65,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.
@@ -88,7 +87,7 @@ WebServer listening on localhost:80
## Running XO
The only part you need to launch is xo-server which is quite easy to do. From the `xen-orchestra/packages/xo-server` directory, run the following:
The only part you need to launch is xo-server, which is quite easy to do. From the `xen-orchestra/packages/xo-server` directory, run the following:
```
$ yarn start
@@ -100,6 +99,9 @@ That's it! Use your browser to visit the xo-server IP address, and it works! :)
If you would like to update your current version, enter your `xen-orchestra` directory and run the following:
```
# This will clear any changes you made in the repository!!
$ git checkout .
$ git pull --ff-only
$ yarn
$ yarn build
@@ -140,9 +142,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:
@@ -184,3 +183,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

@@ -1,13 +1,13 @@
# Installation
SSH to your XenServer and execute the following:
SSH to your XenServer/XCP-ng host and execute the following:
```
bash -c "$(curl -s http://xoa.io/deploy)"
```
This will automatically download/import/start the XOA appliance. Nothing is changed on your XenServer host itself, it's 100% safe.
This will automatically download/import/start the XOA appliance. Nothing is changed on your host itself, it's 100% safe.
## [More on XOA](xoa.md)

48
docs/metadata_backup.md Normal file
View File

@@ -0,0 +1,48 @@
# Metadata backup
> WARNING: Metadata backup is an experimental feature. Unexpected issues are possible, but unlikely.
## Introduction
XCP-ng and Citrix Hypervisor (Xenserver) hosts use a database to store metadata about VMs and their associated resources such as storage and networking. Metadata forms this complete view of all VMs available on your pool. Backing up the metadata of your pool allows you to recover from a physical hardware failure scenario in which you lose your hosts without losing your storage (SAN, NAS...).
In Xen Orchestra, Metadata backup is divided into two different options:
* Pool metadata backup
* XO configuration backup
### Performing a backup
In the backup job section, when creating a new backup job, you will now have a choice between backing up VMs and backing up Metadata:
![](./assets/metadata-1.png)
When you select Metadata backup, you will have a new backup job screen, letting you choose between a pool metadata backup and an XO configuration backup (or both at the same time):
![](./assets/metadata-2.png)
Define the name and retention for the job.
![](./assets/metadata-3.png)
Once created, the job is displayed with the other classic jobs.
![](./assets/metadata-4.png)
### Performing a restore
> WARNING: restoring pool metadata completely overwrites the XAPI database of a host. Only perform a metadata restore if it is a new server with nothing running on it (eg replacing a host with new hardware).
If you browse to the Backup NG Restore panel, you will now notice a Metadata filter button:
![](./assets/metadata-5.png)
If you click this button, it will show you Metadata backups available for restore:
![](./assets/metadata-6.png)
You can see both our Xen Orchestra config backup, and our pool metadata backup. To restore one, simply click the blue restore arrow, choose a backup date to restore, and click OK:
![](./assets/metadata-7.png)
That's it!

View File

@@ -1,24 +1,33 @@
# Support
You can access our pro support if you subscribe to any of these plans:
Xen Orchestra will run in a controlled/tested environment thanks to XOA ([Xen Orchestra virtual Appliance](https://xen-orchestra.com/#!/xoa)). **This is the way to get pro support**. Any account with a registered XOA can access a [dedicated support panel](https://xen-orchestra.com/#!/member/support).
XOA is available in multiple plans:
* Free
* Starter
* Enterprise
* Premium
The better the plan, the faster the support will be with higher priority.
Higher tier support plans include faster ticket response times (and cover more features). Paid support plans and response times are based on the plan you have, plans can be [reviewed here](https://xen-orchestra.com/#!/xo-pricing).
## XOA Free support
With the free version of the Xen Orchestra Appliance (XOA free), you can open support tickets and we will do our best to assist you, however, this support is limited and is not guaranteed in regards to response times or resolutions offered.
## Community support
If you are using Xen Orchestra via the sources, you can ask questions and try to recieve help two different ways:
If you are using Xen Orchestra via the source and not XOA, you can ask questions and try to recieve help through a number of different ways:
* In our [forum](https://xen-orchestra.com/forum/)
* In our [forum](https://xcp-ng.org/forum/category/12/xen-orchestra)
* In our IRC - `#xen-orchestra` on `Freenode`
However, there's no guarantee you will receive an answer and no guaranteed response time. If you are using XO from sources, we encourage you to give back to the community by assisting other users via these two avenues as well.
We encourage you to give back to the community by assisting other users via these two avenues as well.
If you are using Xen Orchestra in production, please subscribe to a plan.
Lastly while Xen Orchestra is free and Open Source software, supporting and developing it takes a lot of effort. If you are considering using Xen Orchestra in production, please subscribe for one of our [professional support plans](https://xen-orchestra.com/#!/xo-pricing).
> Note: support from the sources is harder, because Xen Orchestra can potentially run on any Linux distro (or even FreeBSD and Windows!). Always try to double check that you followed our guide on how to [install it from the sources](https://xen-orchestra.com/docs/from_the_sources.html) before going further.
## Open a ticket
If you have a subscription, you can open a ticket describing your issue directly from your personal account page [here](https://xen-orchestra.com/#!/member/support)
If you have a subscription (or at least a registered free XOA), you can open a ticket describing your issue directly from your personal account page [here](https://xen-orchestra.com/#!/member/support)

View File

@@ -6,10 +6,11 @@ Xen Orchestra is designed to work exclusively on [XCP-ng](https://xcp-ng.org/) a
Backup restore for large VM disks (>1TiB usage) is [broken on all XenServer versions](https://bugs.xenserver.org/browse/XSO-868) until Citrix release a fix.
* XenServer 7.6
* XenServer 7.5
* [VDI I/O error](https://bugs.xenserver.org/browse/XSO-873), waiting for Citrix to release our fix
* XenServer 7.4
* XenServer 7.3
* XenServer 7.3
* XenServer 7.2
* XenServer 7.1
* XenServer 7.0
@@ -26,7 +27,8 @@ Backup restore for large VM disks (>1TiB usage) is [broken on all XenServer vers
All the pending fixes are already integrated in the latest XCP-ng version. We strongly suggest people to keep using the latest XCP-ng version as far as possible.
* XCP-ng 7.6
* XCP-ng 7.5
* XCP-ng 7.4.1
![](https://xen-orchestra.com/blog/content/images/2018/02/logo1glossy.png)
![](https://xen-orchestra.com/blog/content/images/2018/02/logo1glossy.png)

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

@@ -4,25 +4,26 @@
"@babel/register": "^7.0.0",
"babel-core": "^7.0.0-0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.0.1",
"babel-jest": "^24.1.0",
"benchmark": "^2.1.4",
"eslint": "^5.1.0",
"eslint-config-prettier": "^3.3.0",
"eslint-config-prettier": "^4.1.0",
"eslint-config-standard": "12.0.0",
"eslint-config-standard-jsx": "^6.0.2",
"eslint-plugin-eslint-comments": "^3.1.1",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-node": "^9.0.1",
"eslint-plugin-promise": "^4.0.0",
"eslint-plugin-react": "^7.6.1",
"eslint-plugin-standard": "^4.0.0",
"exec-promise": "^0.7.0",
"flow-bin": "^0.86.0",
"globby": "^8.0.0",
"husky": "^1.0.0-rc.15",
"jest": "^23.0.1",
"flow-bin": "^0.98.0",
"globby": "^9.0.0",
"husky": "^2.2.0",
"jest": "^24.1.0",
"lodash": "^4.17.4",
"prettier": "^1.10.2",
"promise-toolbox": "^0.11.0",
"promise-toolbox": "^0.12.1",
"sorted-object": "^2.0.1"
},
"engines": {
@@ -34,7 +35,6 @@
}
},
"jest": {
"timers": "fake",
"collectCoverage": true,
"projects": [
"<rootDir>"

View File

@@ -1,12 +1,13 @@
{
"name": "complex-matcher",
"version": "0.5.0",
"version": "0.6.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/complex-matcher",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/complex-matcher",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -24,7 +25,7 @@
">2%"
],
"engines": {
"node": ">=4"
"node": ">=6"
},
"dependencies": {
"lodash": "^4.17.4"
@@ -43,6 +44,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -33,17 +33,17 @@ const isRawString = string => {
// -------------------------------------------------------------------
class Node {
createPredicate () {
createPredicate() {
return value => this.match(value)
}
}
export class Null extends Node {
match () {
match() {
return true
}
toString () {
toString() {
return ''
}
}
@@ -51,7 +51,7 @@ export class Null extends Node {
const formatTerms = terms => terms.map(term => term.toString(true)).join(' ')
export class And extends Node {
constructor (children) {
constructor(children) {
super()
if (children.length === 1) {
@@ -60,29 +60,29 @@ export class And extends Node {
this.children = children
}
match (value) {
match(value) {
return this.children.every(child => child.match(value))
}
toString (isNested) {
toString(isNested) {
const terms = formatTerms(this.children)
return isNested ? `(${terms})` : terms
}
}
export class Comparison extends Node {
constructor (operator, value) {
constructor(operator, value) {
super()
this._comparator = Comparison.comparators[operator]
this._operator = operator
this._value = value
}
match (value) {
match(value) {
return typeof value === 'number' && this._comparator(value, this._value)
}
toString () {
toString() {
return this._operator + String(this._value)
}
}
@@ -94,7 +94,7 @@ Comparison.comparators = {
}
export class Or extends Node {
constructor (children) {
constructor(children) {
super()
if (children.length === 1) {
@@ -103,33 +103,33 @@ export class Or extends Node {
this.children = children
}
match (value) {
match(value) {
return this.children.some(child => child.match(value))
}
toString () {
toString() {
return `|(${formatTerms(this.children)})`
}
}
export class Not extends Node {
constructor (child) {
constructor(child) {
super()
this.child = child
}
match (value) {
match(value) {
return !this.child.match(value)
}
toString () {
toString() {
return '!' + this.child.toString(true)
}
}
export class NumberNode extends Node {
constructor (value) {
constructor(value) {
super()
this.value = value
@@ -140,21 +140,21 @@ export class NumberNode extends Node {
})
}
match (value) {
match(value) {
return (
value === this.value ||
(value !== null && typeof value === 'object' && some(value, this.match))
)
}
toString () {
toString() {
return String(this.value)
}
}
export { NumberNode as Number }
export class NumberOrStringNode extends Node {
constructor (value) {
constructor(value) {
super()
this.value = value
@@ -165,7 +165,7 @@ export class NumberOrStringNode extends Node {
})
}
match (lcValue, numValue, value) {
match(lcValue, numValue, value) {
return (
value === numValue ||
(typeof value === 'string'
@@ -175,25 +175,25 @@ export class NumberOrStringNode extends Node {
)
}
toString () {
toString() {
return this.value
}
}
export { NumberOrStringNode as NumberOrString }
export class Property extends Node {
constructor (name, child) {
constructor(name, child) {
super()
this.name = name
this.child = child
}
match (value) {
match(value) {
return value != null && this.child.match(value[this.name])
}
toString () {
toString() {
return `${formatString(this.name)}:${this.child.toString(true)}`
}
}
@@ -207,7 +207,7 @@ const formatString = value =>
: `"${value}"`
export class GlobPattern extends Node {
constructor (value) {
constructor(value) {
// fallback to string node if no wildcard
if (value.indexOf('*') === -1) {
return new StringNode(value)
@@ -232,7 +232,7 @@ export class GlobPattern extends Node {
})
}
match (re, value) {
match(re, value) {
if (typeof value === 'string') {
return re.test(value)
}
@@ -244,13 +244,13 @@ export class GlobPattern extends Node {
return false
}
toString () {
toString() {
return this.value
}
}
export class RegExpNode extends Node {
constructor (pattern, flags) {
constructor(pattern, flags) {
super()
this.re = new RegExp(pattern, flags)
@@ -261,7 +261,7 @@ export class RegExpNode extends Node {
})
}
match (value) {
match(value) {
if (typeof value === 'string') {
return this.re.test(value)
}
@@ -273,14 +273,14 @@ export class RegExpNode extends Node {
return false
}
toString () {
toString() {
return this.re.toString()
}
}
export { RegExpNode as RegExp }
export class StringNode extends Node {
constructor (value) {
constructor(value) {
super()
this.value = value
@@ -291,7 +291,7 @@ export class StringNode extends Node {
})
}
match (lcValue, value) {
match(lcValue, value) {
if (typeof value === 'string') {
return value.toLowerCase().indexOf(lcValue) !== -1
}
@@ -303,24 +303,24 @@ export class StringNode extends Node {
return false
}
toString () {
toString() {
return formatString(this.value)
}
}
export { StringNode as String }
export class TruthyProperty extends Node {
constructor (name) {
constructor(name) {
super()
this.name = name
}
match (value) {
match(value) {
return value != null && !!value[this.name]
}
toString () {
toString() {
return formatString(this.name) + '?'
}
}
@@ -330,12 +330,12 @@ export class TruthyProperty extends Node {
// https://gist.github.com/yelouafi/556e5159e869952335e01f6b473c4ec1
class Failure {
constructor (pos, expected) {
constructor(pos, expected) {
this.expected = expected
this.pos = pos
}
get value () {
get value() {
throw new Error(
`parse error: expected ${this.expected} at position ${this.pos}`
)
@@ -343,7 +343,7 @@ class Failure {
}
class Success {
constructor (pos, value) {
constructor(pos, value) {
this.pos = pos
this.value = value
}
@@ -352,7 +352,7 @@ class Success {
// -------------------------------------------------------------------
class P {
static alt (...parsers) {
static alt(...parsers) {
const { length } = parsers
return new P((input, pos, end) => {
for (let i = 0; i < length; ++i) {
@@ -365,7 +365,7 @@ class P {
})
}
static grammar (rules) {
static grammar(rules) {
const grammar = {}
Object.keys(rules).forEach(k => {
const rule = rules[k]
@@ -374,14 +374,14 @@ class P {
return grammar
}
static lazy (parserCreator, arg) {
static lazy(parserCreator, arg) {
const parser = new P((input, pos, end) =>
(parser._parse = parserCreator(arg)._parse)(input, pos, end)
)
return parser
}
static regex (regex) {
static regex(regex) {
regex = new RegExp(regex.source, 'y')
return new P((input, pos) => {
regex.lastIndex = pos
@@ -392,7 +392,7 @@ class P {
})
}
static seq (...parsers) {
static seq(...parsers) {
const { length } = parsers
return new P((input, pos, end) => {
const values = new Array(length)
@@ -408,7 +408,7 @@ class P {
})
}
static text (text) {
static text(text) {
const { length } = text
return new P((input, pos) =>
input.startsWith(text, pos)
@@ -417,11 +417,11 @@ class P {
)
}
constructor (parse) {
constructor(parse) {
this._parse = parse
}
map (fn) {
map(fn) {
return new P((input, pos, end) => {
const result = this._parse(input, pos, end)
if (result instanceof Success) {
@@ -431,11 +431,11 @@ class P {
})
}
parse (input, pos = 0, end = input.length) {
parse(input, pos = 0, end = input.length) {
return this._parse(input, pos, end).value
}
repeat (min = 0, max = Infinity) {
repeat(min = 0, max = Infinity) {
return new P((input, pos, end) => {
const value = []
let result
@@ -461,7 +461,7 @@ class P {
})
}
skip (otherParser) {
skip(otherParser) {
return new P((input, pos, end) => {
const result = this._parse(input, pos, end)
if (result instanceof Failure) {
@@ -599,6 +599,13 @@ export const parse = parser.parse.bind(parser)
// -------------------------------------------------------------------
const _extractStringFromRegexp = child => {
const unescapedRegexp = child.re.source.replace(/^(\^)|\\|\$$/g, '')
if (child.re.source === `^${escapeRegExp(unescapedRegexp)}$`) {
return unescapedRegexp
}
}
const _getPropertyClauseStrings = ({ child }) => {
if (child instanceof Or) {
const strings = []
@@ -606,6 +613,12 @@ const _getPropertyClauseStrings = ({ child }) => {
if (child instanceof StringNode) {
strings.push(child.value)
}
if (child instanceof RegExpNode) {
const unescapedRegexp = _extractStringFromRegexp(child)
if (unescapedRegexp !== undefined) {
strings.push(unescapedRegexp)
}
}
})
return strings
}
@@ -613,6 +626,12 @@ const _getPropertyClauseStrings = ({ child }) => {
if (child instanceof StringNode) {
return [child.value]
}
if (child instanceof RegExpNode) {
const unescapedRegexp = _extractStringFromRegexp(child)
if (unescapedRegexp !== undefined) {
return [unescapedRegexp]
}
}
return []
}

View File

@@ -12,10 +12,13 @@ import {
} from './'
it('getPropertyClausesStrings', () => {
const tmp = getPropertyClausesStrings(parse('foo bar:baz baz:|(foo bar)'))
const tmp = getPropertyClausesStrings(
parse('foo bar:baz baz:|(foo bar /^boo$/ /^far$/) foo:/^bar$/')
)
expect(tmp).toEqual({
bar: ['baz'],
baz: ['foo', 'bar'],
baz: ['foo', 'bar', 'boo', 'far'],
foo: ['bar'],
})
})

View File

@@ -7,6 +7,7 @@
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/value-matcher",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/value-matcher",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -24,7 +25,7 @@
">2%"
],
"engines": {
"node": ">=4"
"node": ">=6"
},
"dependencies": {},
"devDependencies": {
@@ -42,6 +43,7 @@
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepare": "yarn run build",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1,12 +1,13 @@
{
"name": "vhd-cli",
"version": "0.1.0",
"version": "0.3.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/vhd-cli",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/vhd-cli",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -26,11 +27,12 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/fs": "^0.4.1",
"@xen-orchestra/fs": "^0.9.0",
"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.7.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
@@ -40,15 +42,16 @@
"cross-env": "^5.1.3",
"execa": "^1.0.0",
"index-modules": "^0.3.0",
"promise-toolbox": "^0.11.0",
"promise-toolbox": "^0.12.1",
"rimraf": "^2.6.1",
"tmp": "^0.0.33"
"tmp": "^0.1.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/ && index-modules --cjs-lazy src/commands",
"predev": "yarn run prebuild",
"prepare": "yarn run build"
"prepare": "yarn run build",
"postversion": "npm publish"
}
}

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

@@ -3,7 +3,7 @@ import { mergeVhd } from 'vhd-lib'
import { getHandler } from '@xen-orchestra/fs'
import { resolve } from 'path'
export default async function main (args) {
export default async function main(args) {
if (args.length < 2 || args.some(_ => _ === '-h' || _ === '--help')) {
return `Usage: ${this.command} <child VHD> <parent VHD>`
}
@@ -11,7 +11,7 @@ export default async function main (args) {
const handler = getHandler({ url: 'file:///' })
let bar
await mergeVhd(handler, resolve(args[1]), handler, resolve(args[0]), {
onProgress ({ done, total }) {
onProgress({ done, total }) {
if (bar === undefined) {
bar = new Bar({
format:

View File

@@ -0,0 +1,33 @@
import { asCallback, fromCallback, fromEvent } from 'promise-toolbox'
import { getHandler } from '@xen-orchestra/fs'
import { relative } from 'path'
import { start as createRepl } from 'repl'
import Vhd, * as vhdLib from 'vhd-lib'
export default async args => {
const cwd = process.cwd()
const handler = getHandler({ url: 'file://' + cwd })
await handler.sync()
try {
const repl = createRepl({
prompt: 'vhd> ',
})
Object.assign(repl.context, vhdLib)
repl.context.handler = handler
repl.context.open = path => new Vhd(handler, relative(cwd, path))
// Make the REPL waits for promise completion.
repl.eval = (evaluate => (cmd, context, filename, cb) => {
asCallback.call(
fromCallback(cb => {
evaluate.call(repl, cmd, context, filename, cb)
}).then(value => (Array.isArray(value) ? Promise.all(value) : value)),
cb
)
})(repl.eval)
await fromEvent(repl, 'exit')
} finally {
await handler.forget()
}
}

View File

@@ -3,7 +3,7 @@ import { createSyntheticStream } from 'vhd-lib'
import { createWriteStream } from 'fs'
import { getHandler } from '@xen-orchestra/fs'
export default async function main (args) {
export default async function main(args) {
if (args.length < 2 || args.some(_ => _ === '-h' || _ === '--help')) {
return `Usage: ${this.command} <input VHD> <output VHD>`
}

View File

@@ -2,9 +2,11 @@
import execPromise from 'exec-promise'
import pkg from '../package.json'
import commands from './commands'
function runCommand (commands, [command, ...args]) {
function runCommand(commands, [command, ...args]) {
if (command === undefined || command === '-h' || command === '--help') {
command = 'help'
}
@@ -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,45 +1,47 @@
/* eslint-env jest */
import asyncIteratorToStream from 'async-iterator-to-stream'
import execa from 'execa'
import fs from 'fs-extra'
import getStream from 'get-stream'
import rimraf from 'rimraf'
import tmp from 'tmp'
import { fromEvent, pFromCallback } from 'promise-toolbox'
import { getHandler } from '@xen-orchestra/fs'
import { pFromCallback } from 'promise-toolbox'
import { pipeline } from 'readable-stream'
import { randomBytes } from 'crypto'
import Vhd, { chainVhd, createSyntheticStream, mergeVhd as vhdMerge } from './'
import { SECTOR_SIZE } from './src/_constants'
const initialDir = process.cwd()
let tempDir = null
jest.setTimeout(60000)
beforeEach(async () => {
const dir = await pFromCallback(cb => tmp.dir(cb))
process.chdir(dir)
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
const tmpDir = process.cwd()
process.chdir(initialDir)
await pFromCallback(cb => rimraf(tmpDir, cb))
await pFromCallback(cb => rimraf(tempDir, cb))
})
async function createRandomFile (name, sizeMb) {
await execa('bash', [
'-c',
`< /dev/urandom tr -dc "\\t\\n [:alnum:]" | head -c ${sizeMb}M >${name}`,
])
async function createRandomFile(name, sizeMB) {
const createRandomStream = asyncIteratorToStream(function*(size) {
while (size-- > 0) {
yield Buffer.from([Math.floor(Math.random() * 256)])
}
})
const input = createRandomStream(sizeMB * 1024 * 1024)
await pFromCallback(cb => pipeline(input, fs.createWriteStream(name), cb))
}
async function checkFile (vhdName) {
async function checkFile(vhdName) {
await execa('vhd-util', ['check', '-p', '-b', '-t', '-n', vhdName])
}
async function recoverRawContent (vhdName, rawName, originalSize) {
async function recoverRawContent(vhdName, rawName, originalSize) {
await checkFile(vhdName)
await execa('qemu-img', ['convert', '-fvpc', '-Oraw', vhdName, rawName])
if (originalSize !== undefined) {
@@ -47,37 +49,41 @@ async function recoverRawContent (vhdName, rawName, originalSize) {
}
}
async function convertFromRawToVhd (rawName, vhdName) {
async function convertFromRawToVhd(rawName, vhdName) {
await execa('qemu-img', ['convert', '-f', 'raw', '-Ovpc', rawName, vhdName])
}
test('blocks can be moved', async () => {
const initalSize = 4
await createRandomFile('randomfile', initalSize)
await convertFromRawToVhd('randomfile', 'randomfile.vhd')
const handler = getHandler({ url: 'file://' + process.cwd() })
const originalSize = await handler.getSize('randomfile')
const newVhd = new Vhd(handler, 'randomfile.vhd')
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const handler = getHandler({ url: 'file://' })
const originalSize = await handler.getSize(rawFileName)
const newVhd = new Vhd(handler, vhdFileName)
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd._freeFirstBlockSpace(8000000)
await recoverRawContent('randomfile.vhd', 'recovered', originalSize)
expect(await fs.readFile('recovered')).toEqual(
await fs.readFile('randomfile')
const recoveredFileName = `${tempDir}/recovered`
await recoverRawContent(vhdFileName, recoveredFileName, originalSize)
expect(await fs.readFile(recoveredFileName)).toEqual(
await fs.readFile(rawFileName)
)
})
test('the BAT MSB is not used for sign', async () => {
const randomBuffer = await pFromCallback(cb => randomBytes(SECTOR_SIZE, cb))
await execa('qemu-img', ['create', '-fvpc', 'empty.vhd', '1.8T'])
const handler = getHandler({ url: 'file://' + process.cwd() })
const vhd = new Vhd(handler, 'empty.vhd')
const emptyFileName = `${tempDir}/empty.vhd`
await execa('qemu-img', ['create', '-fvpc', emptyFileName, '1.8T'])
const handler = getHandler({ url: 'file://' })
const vhd = new Vhd(handler, emptyFileName)
await vhd.readHeaderAndFooter()
await vhd.readBlockAllocationTable()
// we want the bit 31 to be on, to prove it's not been used for sign
const hugeWritePositionSectors = Math.pow(2, 31) + 200
await vhd.writeData(hugeWritePositionSectors, randomBuffer)
await checkFile('empty.vhd')
await checkFile(emptyFileName)
// here we are moving the first sector very far in the VHD to prove the BAT doesn't use signed int32
const hugePositionBytes = hugeWritePositionSectors * SECTOR_SIZE
await vhd._freeFirstBlockSpace(hugePositionBytes)
@@ -85,9 +91,10 @@ test('the BAT MSB is not used for sign', async () => {
// we recover the data manually for speed reasons.
// fs.write() with offset is way faster than qemu-img when there is a 1.5To
// hole before the block of data
const recoveredFile = await fs.open('recovered', 'w')
const recoveredFileName = `${tempDir}/recovered`
const recoveredFile = await fs.open(recoveredFileName, 'w')
try {
const vhd2 = new Vhd(handler, 'empty.vhd')
const vhd2 = new Vhd(handler, emptyFileName)
await vhd2.readHeaderAndFooter()
await vhd2.readBlockAllocationTable()
for (let i = 0; i < vhd.header.maxTableEntries; i++) {
@@ -107,7 +114,7 @@ test('the BAT MSB is not used for sign', async () => {
fs.close(recoveredFile)
}
const recovered = await getStream.buffer(
await fs.createReadStream('recovered', {
await fs.createReadStream(recoveredFileName, {
start: hugePositionBytes,
end: hugePositionBytes + randomBuffer.length - 1,
})
@@ -117,27 +124,33 @@ test('the BAT MSB is not used for sign', async () => {
test('writeData on empty file', async () => {
const mbOfRandom = 3
await createRandomFile('randomfile', mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', 'empty.vhd', mbOfRandom + 'M'])
const randomData = await fs.readFile('randomfile')
const handler = getHandler({ url: 'file://' + process.cwd() })
const originalSize = await handler.getSize('randomfile')
const newVhd = new Vhd(handler, 'empty.vhd')
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const handler = getHandler({ url: 'file://' })
const originalSize = await handler.getSize(rawFileName)
const newVhd = new Vhd(handler, emptyFileName)
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd.writeData(0, randomData)
await recoverRawContent('empty.vhd', 'recovered', originalSize)
expect(await fs.readFile('recovered')).toEqual(randomData)
const recoveredFileName = `${tempDir}/recovered`
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
expect(await fs.readFile(recoveredFileName)).toEqual(randomData)
})
test('writeData in 2 non-overlaping operations', async () => {
const mbOfRandom = 3
await createRandomFile('randomfile', mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', 'empty.vhd', mbOfRandom + 'M'])
const randomData = await fs.readFile('randomfile')
const handler = getHandler({ url: 'file://' + process.cwd() })
const originalSize = await handler.getSize('randomfile')
const newVhd = new Vhd(handler, 'empty.vhd')
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
const recoveredFileName = `${tempDir}/recovered`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const handler = getHandler({ url: 'file://' })
const originalSize = await handler.getSize(rawFileName)
const newVhd = new Vhd(handler, emptyFileName)
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
const splitPointSectors = 2
@@ -146,18 +159,21 @@ test('writeData in 2 non-overlaping operations', async () => {
splitPointSectors,
randomData.slice(splitPointSectors * 512)
)
await recoverRawContent('empty.vhd', 'recovered', originalSize)
expect(await fs.readFile('recovered')).toEqual(randomData)
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
expect(await fs.readFile(recoveredFileName)).toEqual(randomData)
})
test('writeData in 2 overlaping operations', async () => {
const mbOfRandom = 3
await createRandomFile('randomfile', mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', 'empty.vhd', mbOfRandom + 'M'])
const randomData = await fs.readFile('randomfile')
const handler = getHandler({ url: 'file://' + process.cwd() })
const originalSize = await handler.getSize('randomfile')
const newVhd = new Vhd(handler, 'empty.vhd')
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
const recoveredFileName = `${tempDir}/recovered`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const handler = getHandler({ url: 'file://' })
const originalSize = await handler.getSize(rawFileName)
const newVhd = new Vhd(handler, emptyFileName)
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
const endFirstWrite = 3
@@ -167,119 +183,138 @@ test('writeData in 2 overlaping operations', async () => {
startSecondWrite,
randomData.slice(startSecondWrite * 512)
)
await recoverRawContent('empty.vhd', 'recovered', originalSize)
expect(await fs.readFile('recovered')).toEqual(randomData)
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
expect(await fs.readFile(recoveredFileName)).toEqual(randomData)
})
test('BAT can be extended and blocks moved', async () => {
const initalSize = 4
await createRandomFile('randomfile', initalSize)
await convertFromRawToVhd('randomfile', 'randomfile.vhd')
const handler = getHandler({ url: 'file://' + process.cwd() })
const originalSize = await handler.getSize('randomfile')
const newVhd = new Vhd(handler, 'randomfile.vhd')
const rawFileName = `${tempDir}/randomfile`
const recoveredFileName = `${tempDir}/recovered`
const vhdFileName = `${tempDir}/randomfile.vhd`
await createRandomFile(rawFileName, initalSize)
await convertFromRawToVhd(rawFileName, vhdFileName)
const handler = getHandler({ url: 'file://' })
const originalSize = await handler.getSize(rawFileName)
const newVhd = new Vhd(handler, vhdFileName)
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd.ensureBatSize(2000)
await recoverRawContent('randomfile.vhd', 'recovered', originalSize)
expect(await fs.readFile('recovered')).toEqual(
await fs.readFile('randomfile')
await recoverRawContent(vhdFileName, recoveredFileName, originalSize)
expect(await fs.readFile(recoveredFileName)).toEqual(
await fs.readFile(rawFileName)
)
})
test('coalesce works with empty parent files', async () => {
const mbOfRandom = 2
await createRandomFile('randomfile', mbOfRandom)
await convertFromRawToVhd('randomfile', 'randomfile.vhd')
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
const vhdFileName = `${tempDir}/randomfile.vhd`
const recoveredFileName = `${tempDir}/recovered`
await createRandomFile(rawFileName, mbOfRandom)
await convertFromRawToVhd(rawFileName, vhdFileName)
await execa('qemu-img', [
'create',
'-fvpc',
'empty.vhd',
emptyFileName,
mbOfRandom + 1 + 'M',
])
await checkFile('randomfile.vhd')
await checkFile('empty.vhd')
const handler = getHandler({ url: 'file://' + process.cwd() })
const originalSize = await handler._getSize('randomfile')
await chainVhd(handler, 'empty.vhd', handler, 'randomfile.vhd', true)
await checkFile('randomfile.vhd')
await checkFile('empty.vhd')
await vhdMerge(handler, 'empty.vhd', handler, 'randomfile.vhd')
await recoverRawContent('empty.vhd', 'recovered', originalSize)
expect(await fs.readFile('recovered')).toEqual(
await fs.readFile('randomfile')
await checkFile(vhdFileName)
await checkFile(emptyFileName)
const handler = getHandler({ url: 'file://' })
const originalSize = await handler._getSize(rawFileName)
await chainVhd(handler, emptyFileName, handler, vhdFileName, true)
await checkFile(vhdFileName)
await checkFile(emptyFileName)
await vhdMerge(handler, emptyFileName, handler, vhdFileName)
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
expect(await fs.readFile(recoveredFileName)).toEqual(
await fs.readFile(rawFileName)
)
})
test('coalesce works in normal cases', async () => {
const mbOfRandom = 5
await createRandomFile('randomfile', mbOfRandom)
await createRandomFile('small_randomfile', Math.ceil(mbOfRandom / 2))
const randomFileName = `${tempDir}/randomfile`
const random2FileName = `${tempDir}/randomfile2`
const smallRandomFileName = `${tempDir}/small_randomfile`
const parentFileName = `${tempDir}/parent.vhd`
const child1FileName = `${tempDir}/child1.vhd`
const child2FileName = `${tempDir}/child2.vhd`
const recoveredFileName = `${tempDir}/recovered`
await createRandomFile(randomFileName, mbOfRandom)
await createRandomFile(smallRandomFileName, Math.ceil(mbOfRandom / 2))
await execa('qemu-img', [
'create',
'-fvpc',
'parent.vhd',
parentFileName,
mbOfRandom + 1 + 'M',
])
await convertFromRawToVhd('randomfile', 'child1.vhd')
const handler = getHandler({ url: 'file://' + process.cwd() })
await execa('vhd-util', ['snapshot', '-n', 'child2.vhd', '-p', 'child1.vhd'])
const vhd = new Vhd(handler, 'child2.vhd')
await convertFromRawToVhd(randomFileName, child1FileName)
const handler = getHandler({ url: 'file://' })
await execa('vhd-util', [
'snapshot',
'-n',
child2FileName,
'-p',
child1FileName,
])
const vhd = new Vhd(handler, child2FileName)
await vhd.readHeaderAndFooter()
await vhd.readBlockAllocationTable()
vhd.footer.creatorApplication = 'xoa'
await vhd.writeFooter()
const originalSize = await handler._getSize('randomfile')
await chainVhd(handler, 'parent.vhd', handler, 'child1.vhd', true)
await execa('vhd-util', ['check', '-t', '-n', 'child1.vhd'])
await chainVhd(handler, 'child1.vhd', handler, 'child2.vhd', true)
await execa('vhd-util', ['check', '-t', '-n', 'child2.vhd'])
const smallRandom = await fs.readFile('small_randomfile')
const newVhd = new Vhd(handler, 'child2.vhd')
const originalSize = await handler._getSize(randomFileName)
await chainVhd(handler, parentFileName, handler, child1FileName, true)
await execa('vhd-util', ['check', '-t', '-n', child1FileName])
await chainVhd(handler, child1FileName, handler, child2FileName, true)
await execa('vhd-util', ['check', '-t', '-n', child2FileName])
const smallRandom = await fs.readFile(smallRandomFileName)
const newVhd = new Vhd(handler, child2FileName)
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd.writeData(5, smallRandom)
await checkFile('child2.vhd')
await checkFile('child1.vhd')
await checkFile('parent.vhd')
await vhdMerge(handler, 'parent.vhd', handler, 'child1.vhd')
await checkFile('parent.vhd')
await chainVhd(handler, 'parent.vhd', handler, 'child2.vhd', true)
await checkFile('child2.vhd')
await vhdMerge(handler, 'parent.vhd', handler, 'child2.vhd')
await checkFile('parent.vhd')
await recoverRawContent(
'parent.vhd',
'recovered_from_coalescing',
originalSize
)
await execa('cp', ['randomfile', 'randomfile2'])
const fd = await fs.open('randomfile2', 'r+')
await checkFile(child2FileName)
await checkFile(child1FileName)
await checkFile(parentFileName)
await vhdMerge(handler, parentFileName, handler, child1FileName)
await checkFile(parentFileName)
await chainVhd(handler, parentFileName, handler, child2FileName, true)
await checkFile(child2FileName)
await vhdMerge(handler, parentFileName, handler, child2FileName)
await checkFile(parentFileName)
await recoverRawContent(parentFileName, recoveredFileName, originalSize)
await execa('cp', [randomFileName, random2FileName])
const fd = await fs.open(random2FileName, 'r+')
try {
await fs.write(fd, smallRandom, 0, smallRandom.length, 5 * SECTOR_SIZE)
} finally {
await fs.close(fd)
}
expect(await fs.readFile('recovered_from_coalescing')).toEqual(
await fs.readFile('randomfile2')
expect(await fs.readFile(recoveredFileName)).toEqual(
await fs.readFile(random2FileName)
)
})
test('createSyntheticStream passes vhd-util check', async () => {
test.only('createSyntheticStream passes vhd-util check', async () => {
const initalSize = 4
const expectedVhdSize = 4197888
await createRandomFile('randomfile', initalSize)
await convertFromRawToVhd('randomfile', 'randomfile.vhd')
const handler = getHandler({ url: 'file://' + process.cwd() })
const stream = await createSyntheticStream(handler, 'randomfile.vhd')
expect(stream.length).toEqual(expectedVhdSize)
await fromEvent(
stream.pipe(await fs.createWriteStream('recovered.vhd')),
'finish'
const rawFileName = `${tempDir}/randomfile`
const vhdFileName = `${tempDir}/randomfile.vhd`
const recoveredVhdFileName = `${tempDir}/recovered.vhd`
await createRandomFile(rawFileName, initalSize)
await convertFromRawToVhd(rawFileName, vhdFileName)
await checkFile(vhdFileName)
const handler = getHandler({ url: 'file://' })
const stream = await createSyntheticStream(handler, vhdFileName)
const expectedVhdSize = (await fs.stat(vhdFileName)).size
expect(stream.length).toEqual((await fs.stat(vhdFileName)).size)
await pFromCallback(cb =>
pipeline(stream, fs.createWriteStream(recoveredVhdFileName), cb)
)
await checkFile('recovered.vhd')
const stats = await fs.stat('recovered.vhd')
await checkFile(recoveredVhdFileName)
const stats = await fs.stat(recoveredVhdFileName)
expect(stats.size).toEqual(expectedVhdSize)
await execa('qemu-img', ['compare', 'recovered.vhd', 'randomfile'])
await execa('qemu-img', ['compare', recoveredVhdFileName, rawFileName])
})

View File

@@ -1,12 +1,13 @@
{
"name": "vhd-lib",
"version": "0.4.0",
"version": "0.7.0",
"license": "AGPL-3.0",
"description": "Primitives for VHD file handling",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/vhd-lib",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/vhd-lib",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
@@ -21,11 +22,11 @@
},
"dependencies": {
"async-iterator-to-stream": "^1.0.2",
"core-js": "3.0.0-beta.3",
"core-js": "^3.0.0",
"from2": "^2.3.0",
"fs-extra": "^7.0.0",
"fs-extra": "^8.0.1",
"limit-concurrency-decorator": "^0.4.0",
"promise-toolbox": "^0.11.0",
"promise-toolbox": "^0.12.1",
"struct-fu": "^1.2.0",
"uuid": "^3.0.1"
},
@@ -34,15 +35,16 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"@xen-orchestra/fs": "^0.4.1",
"@xen-orchestra/fs": "^0.9.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",
"fs-promise": "^2.0.0",
"get-stream": "^4.0.0",
"index-modules": "^0.3.0",
"readable-stream": "^3.0.6",
"rimraf": "^2.6.2",
"tmp": "^0.0.33"
"tmp": "^0.1.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
@@ -50,6 +52,7 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepare": "yarn run build"
"prepare": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -0,0 +1,20 @@
import assert from 'assert'
import {
DISK_TYPE_DIFFERENCING,
DISK_TYPE_DYNAMIC,
FILE_FORMAT_VERSION,
FOOTER_COOKIE,
FOOTER_SIZE,
} from './_constants'
export default footer => {
assert.strictEqual(footer.cookie, FOOTER_COOKIE)
assert.strictEqual(footer.dataOffset, FOOTER_SIZE)
assert.strictEqual(footer.fileFormatVersion, FILE_FORMAT_VERSION)
assert(footer.originalSize <= footer.currentSize)
assert(
footer.diskType === DISK_TYPE_DIFFERENCING ||
footer.diskType === DISK_TYPE_DYNAMIC
)
}

View File

@@ -0,0 +1,14 @@
import assert from 'assert'
import { HEADER_COOKIE, HEADER_VERSION, SECTOR_SIZE } from './_constants'
export default (header, footer) => {
assert.strictEqual(header.cookie, HEADER_COOKIE)
assert.strictEqual(header.dataOffset, undefined)
assert.strictEqual(header.headerVersion, HEADER_VERSION)
assert(Number.isInteger(Math.log2(header.blockSize / SECTOR_SIZE)))
if (footer !== undefined) {
assert(header.maxTableEntries >= footer.currentSize / header.blockSize)
}
}

View File

@@ -1,6 +1,6 @@
import { SECTOR_SIZE } from './_constants'
export default function computeGeometryForSize (size) {
export default function computeGeometryForSize(size) {
const totalSectors = Math.min(Math.ceil(size / 512), 65535 * 16 * 255)
let sectorsPerTrackCylinder
let heads

View File

@@ -14,7 +14,7 @@ import {
PLATFORM_WI2K,
} from './_constants'
export function createFooter (
export function createFooter(
size,
timestamp,
geometry,
@@ -39,7 +39,7 @@ export function createFooter (
return footer
}
export function createHeader (
export function createHeader(
maxTableEntries,
tableOffset = HEADER_SIZE + FOOTER_SIZE,
blockSize = VHD_BLOCK_SIZE_BYTES

View File

@@ -0,0 +1,45 @@
import assert from 'assert'
import { BLOCK_UNUSED } from './_constants'
// get the identifiers and first sectors of the first and last block
// in the file
export default bat => {
const n = bat.length
assert.notStrictEqual(n, 0)
assert.strictEqual(n % 4, 0)
let i = 0
let j = 0
let first, firstSector, last, lastSector
// get first allocated block for initialization
while ((firstSector = bat.readUInt32BE(j)) === BLOCK_UNUSED) {
i += 1
j += 4
if (j === n) {
return
}
}
lastSector = firstSector
first = last = i
while (j < n) {
const sector = bat.readUInt32BE(j)
if (sector !== BLOCK_UNUSED) {
if (sector < firstSector) {
first = i
firstSector = sector
} else if (sector > lastSector) {
last = i
lastSector = sector
}
}
i += 1
j += 4
}
return { first, firstSector, last, lastSector }
}

View File

@@ -0,0 +1,50 @@
export default async function readChunk(stream, n) {
if (n === 0) {
return Buffer.alloc(0)
}
return new Promise((resolve, reject) => {
const chunks = []
let i = 0
function clean() {
stream.removeListener('readable', onReadable)
stream.removeListener('end', onEnd)
stream.removeListener('error', onError)
}
function resolve2() {
clean()
resolve(Buffer.concat(chunks, i))
}
function onEnd() {
resolve2()
clean()
}
function onError(error) {
reject(error)
clean()
}
function onReadable() {
const chunk = stream.read(n - i)
if (chunk === null) {
return // wait for more data
}
i += chunk.length
chunks.push(chunk)
if (i >= n) {
resolve2()
}
}
stream.on('end', onEnd)
stream.on('error', onError)
stream.on('readable', onReadable)
if (stream.readable) {
onReadable()
}
})
}

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

@@ -95,7 +95,7 @@ export const unpackField = (field, buf) => {
// Returns the checksum of a raw struct.
// The raw struct (footer or header) is altered with the new sum.
export function checksumStruct (buf, struct) {
export function checksumStruct(buf, struct) {
const checksumField = struct.fields.checksum
let sum = 0

View File

@@ -3,7 +3,7 @@ import { dirname, relative } from 'path'
import Vhd from './vhd'
import { DISK_TYPE_DIFFERENCING } from './_constants'
export default async function chain (
export default async function chain(
parentHandler,
parentPath,
childHandler,

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)
}
}

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