Compare commits

..

1308 Commits

Author SHA1 Message Date
Julien Fontanet
3989bfa832 5.7.2 2017-04-11 17:02:57 +02:00
Julien Fontanet
740e1ccd5b fix(job.set): make all fields optional (2) 2017-04-10 16:45:27 +02:00
Julien Fontanet
bc03f2ed17 fix(job.set): make all fields optional 2017-04-10 16:15:45 +02:00
Julien Fontanet
4b5cb86d87 chore(Xapi#_createVbd): base default empty prop on VDI presence 2017-04-10 16:15:45 +02:00
Julien Fontanet
b877d6088b fix(models/utils/parseProp): reduce verbosity 2017-04-10 16:15:45 +02:00
Julien Fontanet
26cfd439e1 fix(xapi-objects-to-xo): use $ref for VM-template ids
Fixes vatesfr/xo-web#2075
2017-04-10 16:15:44 +02:00
Julien Fontanet
eafcf66f4d 5.7.1 2017-04-06 19:30:09 +02:00
Pierre Donias
a8fad8193b feat(pack,patch): XS 7.1 mechanism (#527)
See vatesfr/xo-web#2058
2017-04-06 19:05:43 +02:00
badrAZ
ca695c38cd feat(vm.revertSnapshot): restore power state (#526)
Fixes vatesfr/xo-web#834
2017-04-06 15:24:30 +02:00
badrAZ
6e0f98a809 feat(VBD): expose the VBD device (#525)
See vatesfr/xo-web#2064
2017-04-06 11:07:16 +02:00
Julien Fontanet
8901d3ce64 fix(sample config): IPv6 used by default 2017-03-31 18:25:30 +02:00
Julien Fontanet
2641966285 5.7.0 2017-03-31 16:37:22 +02:00
Julien Fontanet
09dc2265fe fix(Xapi#uploadPoolPatch): use HTTPs 2017-03-31 11:19:27 +02:00
Julien Fontanet
a1fa139ef1 fix(Xapi): accept self-signed certs for export/import VDI/VM 2017-03-31 11:19:26 +02:00
badrAZ
df26a500c4 feat(servers): add a label to servers (#523)
See vatesfr/xo-web#1965
2017-03-29 16:32:02 +02:00
badrAZ
a465218ba3 feat(jobs): configure job timeout (#522)
See vatesfr/xo-web#1956
2017-03-29 10:39:52 +02:00
Julien Fontanet
1ead6eb916 fix(Xapi): export/import of VDI/VM should use HTTPs 2017-03-28 17:33:17 +02:00
Julien Fontanet
44fe1b31ba chore(package): update all dependencies 2017-03-24 11:39:35 +01:00
Julien Fontanet
0856d3d5c9 fix: update yarn.lock 2017-03-24 11:38:32 +01:00
Pierre Donias
f6b74ea836 feat(vm): affinity host (#521)
See vatesfr/xo-web#1983
2017-03-24 11:35:00 +01:00
greenkeeper[bot]
e0cef71700 fix(package): update ms to version 1.0.0 (#520)
https://greenkeeper.io/
2017-03-19 22:49:02 +01:00
Julien Fontanet
19d1f70458 fix(xapi-objects-to-xo): PV drivers check for XS >= 7.1
Fixes vatesfr/xo-web#2024
2017-03-17 18:05:05 +01:00
badrAZ
43c103e436 feat(new-vm): shared VM in resource set (#518)
Fixes vatesfr/xo-web#1964.
2017-03-15 14:45:36 +01:00
Julien Fontanet
6ff4f096a3 chore(build): do not build tests 2017-03-10 14:46:06 +01:00
Pierre Donias
d8d82441c3 fix(Xapi#createVm): only one disk should be bootable (#516)
See vatesfr/xo-web#1994.

For more complex setups, create the VMs and change bootable flags afterward.
2017-03-08 16:31:22 +01:00
Olivier Lambert
4f489e1854 feat(sr): add lvmohba SR type management (#517) 2017-03-08 14:03:57 +01:00
Julien Fontanet
9ab275df5d fix(vm.set): behave if the resource set no longer exist 2017-03-04 16:27:25 +01:00
Julien Fontanet
66c1754eb8 chore(package): update xo-common to v0.1.1 2017-03-04 16:16:58 +01:00
Julien Fontanet
e67bab1f5c fix(package): require Node >= 4.5 2017-03-04 03:24:49 +01:00
Julien Fontanet
ceb6667112 fix: coding style 2017-03-03 16:32:58 +01:00
badrAZ
cafba0b361 fix(vm.resume): remove unused force param (#514) 2017-03-03 15:49:12 +01:00
Julien Fontanet
3e4efcf297 fix(vm.importBackup): fix datetime in VM name 2017-03-01 14:57:02 +01:00
Julien Fontanet
4ccadac148 5.6.4 2017-03-01 14:46:09 +01:00
Julien Fontanet
6e148c18b0 fix(vm.importDeltaBackup): fix datetime in VM name (#513) 2017-03-01 14:34:29 +01:00
Julien Fontanet
75f849982e fix(Xapi#exportVdi): returned promise has a cancel() method (#512) 2017-03-01 14:12:56 +01:00
Julien Fontanet
8722ef45ac chore: add mention-bot config 2017-03-01 13:27:54 +01:00
Julien Fontanet
b347c78a8c fix(vm.create): do not get stuck when installing from ISO 2017-02-28 15:43:06 +01:00
Julien Fontanet
88ae24855a feat(models/utils/parseProp): do not warn on empty string 2017-02-28 09:58:41 +01:00
Julien Fontanet
356884ea53 feat: reduce the default debugging level 2017-02-28 09:58:41 +01:00
badrAZ
51fba21dd6 feat(vm.import): throw if type is xva and data is provided (#506) 2017-02-27 11:57:40 +01:00
Julien Fontanet
6aa5d608bf chore(package): babel-preset-es2015 → babel-preset-env 2017-02-27 11:22:34 +01:00
Julien Fontanet
21ad2c5744 fix(package): commit-msg → commitmsg 2017-02-27 11:20:40 +01:00
Julien Fontanet
dea1163159 fix(package): jest is a dev dependency 2017-02-27 11:20:19 +01:00
Julien Fontanet
c4c2e8cf74 chore(package): fix for jest 19 2017-02-27 11:19:38 +01:00
Julien Fontanet
5883c35cf3 chore(package): update all dependencies 2017-02-27 11:18:46 +01:00
Julien Fontanet
4d2719a424 5.6.3 2017-02-24 16:12:25 +01:00
Nicolas Raynaud
7cf2d0d01c fix(xosan) (#509)
- fix 2 bricks configuration
- limit bricks size to 2TB
- fix arbiter cleanup
2017-02-24 16:11:59 +01:00
Julien Fontanet
5a08b512a7 fix(xosan.createSR): use address prop 2017-02-24 14:56:27 +01:00
Pierre Donias
aa6ff6cd64 fix(xapi.installSupplementalPackOnAllHosts): multi-pipe stream (#508)
Fixes vatesfr/xo-web#1957
2017-02-24 11:41:47 +01:00
Julien Fontanet
89421d292c 5.6.2 2017-02-23 18:38:14 +01:00
Julien Fontanet
55c6515bac fix(vhdMerge): ensure parent.bat.size >= child.bat.size
Fixes vatesfr/xo-web#1939
2017-02-23 18:35:52 +01:00
Pierre Donias
5db6f6a58c feat(XOSAN) (#505) 2017-02-23 18:33:23 +01:00
Julien Fontanet
eeedf6ab28 feat(Vhd#ensurebatSize): no need to wait before writing footer 2017-02-23 18:31:05 +01:00
Julien Fontanet
3758cd207b fix(Vhd#ensureBatSize): Buffer#size → Buffer#length 2017-02-23 18:31:05 +01:00
Julien Fontanet
c15ede6239 fix(Vhd#ensureBatSize): avoid style error 2017-02-23 18:31:05 +01:00
Julien Fontanet
e54e31e059 feat(vhdMerge): avoid unnecessary footer write 2017-02-23 18:31:05 +01:00
Julien Fontanet
8c573aa8e4 chore(Vhd): add debugs 2017-02-23 18:31:05 +01:00
Julien Fontanet
654559086c chore(Vhd#writeBlockSectors): expect end sector instead of # of sectors 2017-02-23 18:31:05 +01:00
Julien Fontanet
b0d9679568 chore(Vhd#coalesceBlock): do not pass blockAddr 2017-02-23 18:31:05 +01:00
Julien Fontanet
87fdaf7fa7 chore(Vhd): merge readBlockBitmap() readBlockData() into _readBlock() 2017-02-23 18:31:05 +01:00
Julien Fontanet
bb3a365166 fix(Vhd#writeBlockSectors): pass correct params to Vhd#readBlockBitmap() 2017-02-23 18:31:05 +01:00
Julien Fontanet
2be183d980 various updates 2017-02-23 18:31:05 +01:00
Julien Fontanet
c6dc846838 fix(Vhd#ensureBatSize): extend BAT at correct offset 2017-02-23 18:31:05 +01:00
Julien Fontanet
1142f1d59a various updates 2017-02-23 18:31:05 +01:00
Julien Fontanet
126c470979 fix(Vhd#getEndOfHeaders): correctly compute parent locator size 2017-02-23 18:31:05 +01:00
Julien Fontanet
d679dc3e8b fix(vhdMerge): update diskGeometry and originalSize 2017-02-23 18:31:05 +01:00
Julien Fontanet
d5422dfe89 fix(Vhd#ensureBatSize): do not round maxTableEntries 2017-02-23 18:31:05 +01:00
Julien Fontanet
d64237b4f2 fix(package): dependency-check should ignore constant-stream 2017-02-23 18:31:05 +01:00
Julien Fontanet
7b7e4942f2 fix(constant-stream): optimize data length 2017-02-23 18:31:05 +01:00
Julien Fontanet
e4c343a587 chore(Vhd#_writeStream): merge back into Vhd#_write() 2017-02-23 18:31:05 +01:00
Julien Fontanet
1a8ae21478 feat(Vhd#_write): can also handle streams 2017-02-23 18:31:05 +01:00
Julien Fontanet
dd37a5b584 chore(Vhd#_write): simplify code 2017-02-23 18:31:05 +01:00
Julien Fontanet
eec340e6c0 chore(Vhd#_getBatEntry): remove logging 2017-02-23 18:31:05 +01:00
Julien Fontanet
c2fb5ba1f0 fix(vhdMerge): update currentSize and timestamp 2017-02-23 18:31:05 +01:00
Julien Fontanet
36d7e17b86 fix(Vhd#ensureBatSize): update BAT before writing footer 2017-02-23 18:31:05 +01:00
Julien Fontanet
5a1dc49428 fix(Vhd#ensureBatSize): various fixes 2017-02-23 18:31:05 +01:00
Julien Fontanet
47caf54772 fix(Vhd#ensureBatSize): extend local BAT buffer as well 2017-02-23 18:31:05 +01:00
Julien Fontanet
6af50a8c44 feat(Vhd#ensurebatSize): do not move first block if not necessary 2017-02-23 18:31:05 +01:00
Julien Fontanet
1b27407970 chore(Vhd#_getFirstAndLastBlocks): value → sector 2017-02-23 18:31:05 +01:00
Julien Fontanet
4da6306c67 fix(Vhd#_getFirstAndLastBlocks): should not be async 2017-02-23 18:31:05 +01:00
Julien Fontanet
f950b7a725 fix(Vhd#_readBlockData) 2017-02-23 18:31:05 +01:00
Julien Fontanet
930cf9ed04 fix(Vhd#ensureBatSize): update header.maxTableEntries 2017-02-23 18:31:05 +01:00
Julien Fontanet
744016e752 chore(vhd-merge): Vhd#_read() 2017-02-23 18:31:05 +01:00
Julien Fontanet
2fb4e907df fix(vhdMerge): ensure parent.bat.size >= child.bat.size
Fixes vatesfr/xo-web#1939
2017-02-23 18:31:05 +01:00
Julien Fontanet
ef2a815e52 chore(vhdMerge): ensure block sizes are identical 2017-02-23 18:31:05 +01:00
Julien Fontanet
33a81d4f3c chore(vhd-merge): rename and clean BAT methods
- readAllocationTableEntry() → _getBatEntry()
- writeAllocationTableEntry() → _setBatEntry()
2017-02-23 18:31:05 +01:00
Julien Fontanet
4911df0bf9 chore(Vhd#writeAllocationTableEntry): commit the change in the file 2017-02-23 18:31:05 +01:00
Julien Fontanet
2b612b5db7 feat(constant-stream): emit the same data n times 2017-02-23 18:31:05 +01:00
Julien Fontanet
bfe81b52ef chore(vhd-merge): Vhd#_writeStream() 2017-02-23 18:31:05 +01:00
Julien Fontanet
26f6a4beb9 chore(vhd-merge): Vhd#_readStream() 2017-02-23 18:31:05 +01:00
Julien Fontanet
52e9c3053a fix(console proxy): works with ws v2
Fixes vatesfr/xo-web#1954
2017-02-23 16:59:41 +01:00
Julien Fontanet
908d1f1ec8 feat(Xo#defineProperties): helper to define multiple properties 2017-02-23 12:23:18 +01:00
Julien Fontanet
6a1120f95b feat(Xo#defineProperty): accept a thisArg param 2017-02-23 12:18:54 +01:00
Julien Fontanet
2a2780c25c feat(Xapi#deleteVm): delete disks by default 2017-02-23 11:06:10 +01:00
Julien Fontanet
7d4152197b chore: update yarn.lock 2017-02-22 22:07:52 +01:00
greenkeeper[bot]
9c742600ff fix(package): update jest to version 19.0.1 (#504)
https://greenkeeper.io/
2017-02-22 12:10:12 +01:00
greenkeeper[bot]
a035bf132a fix(package): update pretty-format to version 19.0.0 (#503)
https://greenkeeper.io/
2017-02-21 10:52:37 +01:00
Julien Fontanet
b989d157a0 feat(redis): can connect via Unix socket
Fixes vatesfr/xo-web#1944
2017-02-16 11:32:35 +01:00
Julien Fontanet
261587511b chore(package): update all dependencies 2017-02-13 16:13:19 +01:00
Julien Fontanet
ff798801fb chore(package): update xen-api to v0.10.0-2 2017-02-13 16:08:09 +01:00
Julien Fontanet
9b4aab0d19 chore: update yarn.lock 2017-02-13 16:07:20 +01:00
greenkeeper[bot]
6e42cf9952 fix(package): update fs-promise to version 2.0.0 (#501)
https://greenkeeper.io/
2017-02-13 10:34:33 +01:00
Julien Fontanet
4c3a8ca312 fix(Xapi#createVm): wait for VM record to have all its VBDs/VIFs (#500) 2017-02-09 16:49:58 +01:00
Julien Fontanet
a63eb48f03 fix(Xapi#createVm): do not wait for a new record 2017-02-09 16:06:25 +01:00
Julien Fontanet
d0214f805e fix(Xapi#createVm): _waitObject → _waitObjectState 2017-02-09 16:06:25 +01:00
badrAZ
d736bd6501 fix(vm.create): VIFs param is optional (#499) 2017-02-09 11:16:20 +01:00
Pierre Donias
2ce4a11e0a fix(vm.delete): import mapFilter (#497) 2017-02-07 11:27:30 +01:00
Pierre Donias
e5ab8fe3e4 feat(vm.delete): remove ACLs (#496) 2017-02-02 22:11:13 +01:00
Julien Fontanet
657b74a084 Revert "feat(vm.snapshot): copy ACLs from VM to snapshot (#495)"
This reverts commit dfee98b66b.

Should not be necessary: snapshots inherit ACLs from their VM.
2017-02-01 10:24:26 +01:00
Pierre Donias
dfee98b66b feat(vm.snapshot): copy ACLs from VM to snapshot (#495)
See vatesfr/xo-web#1865

Also, correctly remove ACLs on VM deletion.
2017-02-01 10:22:17 +01:00
Julien Fontanet
f65b9f695e 5.6.1 2017-01-30 18:07:39 +01:00
Julien Fontanet
4056385cd3 feat(backups): do not rely on JSON format for lvm commands (#493) 2017-01-30 18:04:27 +01:00
Pierre Donias
96d56d43bc feat(Xapi#installSupplementalPackOnAllHosts) (#491)
See vatesfr/xo-web#1896
2017-01-30 10:53:26 +01:00
Julien Fontanet
eba8f95e58 5.6.0 2017-01-27 16:42:07 +01:00
Julien Fontanet
7e2da1ff93 [WiP] feat(backups): implements file restore for LVM (#490)
Fixes vatesfr/xo-web#1878
2017-01-27 16:37:34 +01:00
Pierre Donias
b7b7e81468 feat(host.installSupplementalPack) (#487)
See vatesfr/xo-web#1460
2017-01-25 16:08:31 +01:00
Pierre Donias
0c7768f5d2 fix(vm.delete): IP addresses should always be deallocated (#488)
Fixes vatesfr/xo-web#1906
2017-01-25 15:46:33 +01:00
Pierre Donias
8fe6a56dfc fix(Xapi#installAllPoolPatchesOnHost): ignore PATCH_ALREADY_APPLIED error (#489)
Fixes vatesfr/xo-web#1904
2017-01-25 15:46:15 +01:00
Julien Fontanet
7b9dae980d fix(vm.create): properly handle optional param VDIs 2017-01-24 13:36:36 +01:00
Olivier Lambert
b59ba6b7bb feat(api): add description for some API calls (#486)
Fixes vatesfr/xo-web#1882
2017-01-17 15:15:18 +01:00
Julien Fontanet
8cdee4d173 chore(xo): disable too many listeners warning 2017-01-16 15:50:18 +01:00
Julien Fontanet
c9ed5fbe00 chore: update yarn.lock 2017-01-16 15:18:46 +01:00
Julien Fontanet
e698e89968 feat(/signout): URL to sign out 2017-01-16 14:33:58 +01:00
Pierre Donias
02f198d42c feat(backup.fetchFiles): multiple files support (#485)
See vatesfr/xo-web#1877
2017-01-16 09:33:22 +01:00
Pierre Donias
61d2d0263b feat(patching): eject tools ISOs before patching host (#479)
Fixes #1798
2017-01-13 18:20:31 +01:00
badrAZ
ed477e99a8 feat(plugin): provide a getDataDir() to plugins (#483)
It returns the path of a directory where the plugin can store data.
2017-01-13 18:13:44 +01:00
Olivier Lambert
1449be8d66 feat(host): expose supplemental packs (#480) 2017-01-12 17:54:48 +01:00
greenkeeper[bot]
28902d8747 fix(package): update execa to version 0.6.0 (#478)
https://greenkeeper.io/
2017-01-09 10:50:31 +01:00
Julien Fontanet
d534592479 5.5.4 2017-01-06 16:57:47 +01:00
Pierre Donias
b2f6ea9116 fix(vm.set): allocate resources when editing VM (#477)
Fixes vatesfr/xo-web#1695
2017-01-06 16:54:49 +01:00
Pierre Donias
8bf38bb29b feat(server): store connection error in database (#472)
See vatesfr/xo-web#1833
2017-01-06 16:38:17 +01:00
greenkeeper[bot]
9c6a78b678 fix(package): update promise-toolbox to version 0.8.0 (#476)
https://greenkeeper.io/
2017-01-06 11:34:27 +01:00
Pierre Donias
850199d7fc fix(resource-sets): recompute limits (#475)
Fixes vatesfr/xo-web#1866
2017-01-06 10:09:36 +01:00
Pierre Donias
4282928960 fix(vif/create): locking mode when allocating IP addresses (#474)
Fixes vatesfr/xo-web#1747
2017-01-06 09:55:55 +01:00
Julien Fontanet
356dd89d9f chore(package): upgrade jest to v 0.18.1 2017-01-03 18:30:28 +01:00
Julien Fontanet
7dd2391e5a fix(group.setUsers): oldUsers → oldUsersIds 2017-01-03 11:20:25 +01:00
Julien Fontanet
e0093f236a fix(group.create): do not attempt to parse empty prop 2017-01-03 10:47:10 +01:00
Julien Fontanet
8c5c32268a fix: users and groups serialization in Redis
Fixes vatesfr/xo-web#1852.
2017-01-02 16:52:51 +01:00
greenkeeper[bot]
b61ccc1af1 fix(package): update hashy to version 0.6.0 (#470)
https://greenkeeper.io/
2017-01-02 13:01:29 +01:00
Julien Fontanet
7caf0e40f4 5.5.3 2017-01-02 10:56:08 +01:00
Julien Fontanet
a16508db10 fix(remotes): do not error on disabled remote
- testRemote()
- updateRemote()
- remoteRemote()
- forgetAllRemotes()
2016-12-25 20:07:42 +01:00
Julien Fontanet
81bff342b9 chore(package): update decorator-synchronized to version 0.2.3 2016-12-22 16:25:46 +01:00
Julien Fontanet
49d41a76a0 5.5.2 2016-12-22 11:22:45 +01:00
Julien Fontanet
b1732b3298 fix(file restore): work around for invalid delta VHD path (#467)
See vatesfr/xo-web#1842.
2016-12-22 11:20:51 +01:00
Julien Fontanet
9372cdb6c7 fix(vm.rollingDeltaBackup): do not hide error 2016-12-22 10:21:38 +01:00
Julien Fontanet
1d8e54b83e chore(backups): use directly Xo#getRemoteHandler() 2016-12-22 09:50:16 +01:00
Julien Fontanet
30c5600271 chore(Xo#getRemoteHandler): use intermediary variable 2016-12-22 09:49:36 +01:00
Julien Fontanet
9f7e5c3a9a fix(Xo#getRemoteHandler): throws if remote is disabled 2016-12-22 09:49:04 +01:00
Julien Fontanet
37c9342717 fix(vm.rollingDeltaBackup): correctly delete snapshot in case of failure 2016-12-21 22:35:43 +01:00
Julien Fontanet
8827f8e940 fix(backup.fetchFiles): encode URI suffix
Fix issue with whitespaces in the filename.
2016-12-20 17:07:13 +01:00
Julien Fontanet
58334bf4a1 fix(backup.list): timestamps should be integers 2016-12-20 17:07:13 +01:00
Julien Fontanet
b898a6702c chore(package): use husky instead of ghooks 2016-12-20 17:07:13 +01:00
Julien Fontanet
6d78a810b9 perf(RemoteHandlerAbstract/createReadStream): optimise code
- avoid async function: overhead with transpilation
- do as much as possible in parallel
- fix: do not add length property in range mode
2016-12-20 17:07:13 +01:00
Julien Fontanet
8fc4eb8cdf 5.5.1 2016-12-20 13:38:02 +01:00
Julien Fontanet
b3fac0c56f fix(backup.list): datetimes should be timestamps 2016-12-20 12:50:17 +01:00
Julien Fontanet
0b063b1f5e 5.5.0 2016-12-20 12:29:16 +01:00
Olivier Lambert
480f05e676 feat(vm): add install time (#465) 2016-12-20 12:19:11 +01:00
Julien Fontanet
1ac8af34ec feat(backup): implement file restore (#461)
See vatesfr/xo-web#1590

Current implementation has following limitations:

- only support local and NFS remotes
- requires installation of libvhdi-utils
- files can only be recovered one by one
2016-12-20 12:18:22 +01:00
Julien Fontanet
34ff8b0f02 feat(Xapi#exportDeltaVm): don't export VDIs with names starting with [NOBAK] (#464)
Fixes vatesfr/xo-web#826
2016-12-14 10:57:25 +01:00
Julien Fontanet
77c3684e28 chore(tests): execute tests directly in src/ 2016-12-13 18:20:17 +01:00
Julien Fontanet
93038ea838 chore(package): remove unused trace 2016-12-13 14:08:38 +01:00
Julien Fontanet
46348f7cba feat: yarn integration 2016-12-13 12:15:26 +01:00
Julien Fontanet
ccc0e45daf feat(tests): use Jest instead of mocha/chai/must 2016-12-13 12:15:03 +01:00
Julien Fontanet
46ca03b017 chore(package): clean scripts 2016-12-13 11:55:12 +01:00
Julien Fontanet
1bfe3197a5 chore(Travis): test with Node stable 2016-12-13 11:51:04 +01:00
Julien Fontanet
4d2617fe68 chore(package): requires Node >= 4 2016-12-13 11:49:54 +01:00
Julien Fontanet
92e289f9da fix(decorators/mixin): do not use arrow function for constructor
It works because of the transpilation but it's not valid ES2015.
2016-12-13 11:41:41 +01:00
greenkeeper[bot]
a8c7558a77 chore(package): update index-modules to version 0.2.1 (#463) 2016-12-12 16:49:10 +01:00
greenkeeper[bot]
c756e7ecbe chore(package): update index-modules to version 0.2.0 (#462)
https://greenkeeper.io/
2016-12-12 16:16:44 +01:00
Pierre Donias
1998c56e84 feat(vm.delete): release resource set and IP-pool addresses (#460)
Fixes vatesfr/xo-web#1657, fixes vatesfr/xo-web#1748
2016-12-12 15:14:31 +01:00
Julien Fontanet
2ed55b1616 chore(decorators): remove unused @autobind. 2016-12-08 11:47:17 +01:00
Julien Fontanet
0c8d456fd3 chore(package): use bind-property-descriptor instead of custom implementation 2016-12-08 11:46:29 +01:00
Julien Fontanet
9e4924caf6 5.4.1 2016-12-02 16:37:17 +01:00
Julien Fontanet
7f391a5860 Merge branch 'next-release' into stable 2016-12-02 16:37:13 +01:00
Julien Fontanet
5c7249c8fc fix(Xapi#exportDeltaVm): remove TAG_BASE_DELTA if full export
Fixes vatesfr/xo-web#1811
2016-12-02 16:09:27 +01:00
Pierre Donias
932d00133d feat(job-executor.match): __not pattern property (#459)
See vatesfr/xo-web#1503
2016-12-01 14:56:52 +01:00
Julien Fontanet
32a371bf13 chore(package): use golike-defer instead of custom implementation 2016-11-30 15:40:30 +01:00
Julien Fontanet
5d0622d2cf 5.4.0 2016-11-23 11:10:01 +01:00
Pierre Donias
9ab9155bf0 fix(vif.set): remove old VIF before creating new one (#457)
Fixes #1784
2016-11-23 10:38:24 +01:00
Julien Fontanet
86a1ed6d46 chore(package): remove unused nyc 2016-11-23 10:00:45 +01:00
Julien Fontanet
b3c9936d74 chore(package): update xen-api to v0.9.6 2016-11-23 09:58:04 +01:00
greenkeeper[bot]
21b4d7cf11 chore(package): update nyc to version 10.0.0 (#456)
https://greenkeeper.io/
2016-11-23 09:12:26 +01:00
greenkeeper[bot]
4ec07f9ff8 fix(package): update get-stream to version 3.0.0 (#458)
https://greenkeeper.io/
2016-11-23 09:11:39 +01:00
greenkeeper[bot]
b7c89d6f64 fix(package): update http-server-plus to version 0.8.0 (#454)
https://greenkeeper.io/
2016-11-18 14:44:50 +01:00
greenkeeper[bot]
0eb168ec70 fix(package): update uuid to version 3.0.0 (#453)
https://greenkeeper.io/
2016-11-18 09:10:07 +01:00
Olivier Lambert
8ac1a66e93 feat(sr.shared): new boolean property (#452) 2016-11-17 14:33:45 +01:00
badrAZ
301da3662a fix(plugin.test): data param is optional (#451) 2016-11-16 16:08:11 +01:00
greenkeeper[bot]
e474946cb7 fix(package): update xo-common to version 0.1.0 (#450)
https://greenkeeper.io/
2016-11-16 12:01:27 +01:00
Pierre Donias
9a0ca1ebb2 feat(api): map 10 XAPI errors to XO errors (#449)
Fixes vatesfr/xo-web#1481
2016-11-16 11:22:31 +01:00
Julien Fontanet
520f7b2a77 feat(job.create,job.set): ability to set userId (#448)
See vatesfr/xo-web#1733
2016-11-14 17:42:19 +01:00
Pierre Donias
c0b3b3aab8 Fix userId. 2016-11-14 16:59:10 +01:00
Pierre Donias
d499332ce3 It should be possible to not change a job's user. 2016-11-14 15:56:54 +01:00
Pierre Donias
19ce06e0bb feat(job#create,job#set): userId parameter
See vatesfr/xo-web#1733
2016-11-14 15:33:09 +01:00
greenkeeper[bot]
ea6ff4224e fix(package): update fs-promise to version 1.0.0 (#447)
https://greenkeeper.io/
2016-11-10 08:56:37 +01:00
Julien Fontanet
871d1f8632 fix(plugins registration): params order 2016-11-09 17:05:10 +01:00
badrAZ
77ce2ff6d1 feat(plugin.test): plugins can be tested (#446)
See vatesfr/xo-web#1749
2016-11-09 14:58:19 +01:00
Pierre Donias
6383104796 fix(Xapi#editPif): destroy VLAN from each PIF before creating new VLAN (#444) 2016-11-08 16:50:12 +01:00
Julien Fontanet
b99b4159c8 feat(Redis): support aliased commands
Fixes #443
2016-11-08 10:23:53 +01:00
Olivier Lambert
8bedb1f3b9 Merge pull request #442 from vatesfr/pierre-fix-xo-error
fix(api): xoError is not an object
2016-11-07 18:18:45 +01:00
Pierre Donias
dc85804a27 fix(api): xoError is not an object 2016-11-07 17:58:16 +01:00
greenkeeper[bot]
42a31e512a fix(package): update json-rpc-peer to version 0.13.0 (#441)
https://greenkeeper.io/
2016-11-07 14:57:53 +01:00
Pierre Donias
2be7388696 feat(api-errors): throw custom errors when XAPI error is caught (#440)
See vatesfr/xo-web#1717
2016-11-07 14:15:23 +01:00
Julien Fontanet
bc5b00781b 5.3.3 2016-11-04 11:44:09 +01:00
Olivier Lambert
313e2b3de6 fix(Sr): add type cifs in deviceConfig. Fixes vatesfr/xo-web#1615 (#439) 2016-11-04 11:42:03 +01:00
Julien Fontanet
0bbd002060 fix(xo.importConfig): dont unnecessarily delete existing users
Do not delete existing users with same name & id
2016-11-04 09:42:56 +01:00
Julien Fontanet
5e785266a5 fix(xo.importConfig): correctly import ACLs
Fixes vatesfr/xo-web#1722
2016-11-04 09:40:41 +01:00
Julien Fontanet
5870769e7d fix(vm.import{,Delta}Backup): make restored VMs identifiable
Their names is prefixed with the exported date and they have a specific tag (*restored from backup*).

Fixes vatesfr/xo-web#1719
2016-11-03 16:22:42 +01:00
Julien Fontanet
79b80dcd07 fix(pif#carrier): cast to boolean 2016-11-02 16:50:12 +01:00
Olivier Lambert
6f6e547e6c feat(pif): add carrier (#438)
Fixes vatesfr/xo-web#1702
2016-11-02 16:25:44 +01:00
greenkeeper[bot]
352c9357df chore(package): update dependencies (#437)
https://greenkeeper.io/
2016-11-01 19:05:11 +01:00
Pierre Donias
1ba4641641 feat(acls): handle xo.clean (#436) 2016-10-31 15:53:50 +01:00
Greenkeeper
60e0047285 chore(package): update helmet to version 3.0.0 (#435)
https://greenkeeper.io/
2016-10-29 12:52:18 +02:00
Pierre Donias
235e7c143c fix(signin): new Bootstrap classes (#434) 2016-10-28 10:11:41 +02:00
Julien Fontanet
522d6eed92 5.3.2 2016-10-27 18:49:32 +02:00
Julien Fontanet
9d1d6ea4c5 feat(xo): export/import config (#427)
See vatesfr/xo-web#786
2016-10-27 18:48:19 +02:00
Julien Fontanet
0afd506a41 5.3.1 2016-10-27 18:25:16 +02:00
Julien Fontanet
9dfb837e3f fix(Xapi#importDeltaVm): gracefully handle missing vif.$network$uuid (#433) 2016-10-27 16:46:45 +02:00
fufroma
4ab63b569f fix(RemoteHandlerNfs): move mount points in /run/xo-server/mounts
Fixes vatesfr/xo-web#1405
2016-10-27 15:56:33 +02:00
Julien Fontanet
8d390d256d fix(http-request): handle redirections (#432) 2016-10-27 15:34:54 +02:00
Julien Fontanet
4eec5e06fc fix(package): test on Node 6, not 7 (#431) 2016-10-27 12:24:40 +02:00
Julien Fontanet
e4063b1ba8 feat(sample.config.yaml): add warning about YAML 2016-10-24 22:52:11 +02:00
Greenkeeper
0c3227cf8e chore(package): update promise-toolbox to version 0.7.0 (#428)
https://greenkeeper.io/
2016-10-24 15:01:17 +02:00
Pierre Donias
7bed200bf5 feat(pif): editVlan (#426)
Fix vatesfr/xo-web#1092
2016-10-24 10:24:44 +02:00
Julien Fontanet
4f763e2109 5.3.0 2016-10-20 16:01:53 +02:00
Pierre Donias
75167fb65b feat(pif): expose IP config modes (#424)
See vatesfr/xo-web#1651
2016-10-20 12:44:35 +02:00
Julien Fontanet
675588f780 feat(delta backups): force checksums refresh
See vatesfr/xo-web#1672
2016-10-20 12:38:26 +02:00
Julien Fontanet
2d6f94edd8 fix(vhd-merge/chainVhd): correctly await _write()
Fixes vatesfr/xo-web#1672
2016-10-20 12:31:20 +02:00
Julien Fontanet
247c66ef4b feat(IP pools): can be used in resource sets (#413)
See vatesfr/xo-web#1565
2016-10-19 11:17:05 +02:00
Greenkeeper
1076fac40f Update gulp-sourcemaps to version 2.1.1 🚀 (#422)
https://greenkeeper.io/
2016-10-14 10:44:27 +02:00
Julien Fontanet
14a4a415a2 5.2.6 2016-10-13 18:51:16 +02:00
Julien Fontanet
524355b59c fix(vhd-merge/chainVhd): correctly compute header checksum (#419)
Fixes vatesfr/xo-web#1656
2016-10-13 18:49:58 +02:00
Greenkeeper
36fe49f3f5 Update promise-toolbox to version 0.6.0 🚀 (#416)
https://greenkeeper.io/
2016-10-12 09:19:19 +02:00
Greenkeeper
c0c0af9b14 chore(package): update execa to version 0.5.0 (#411)
https://greenkeeper.io/
2016-10-05 10:40:31 +02:00
Julien Fontanet
d1e472d482 chore(package): use babel-plugin-lodash 2016-10-04 16:05:01 +02:00
Julien Fontanet
c80e43ad0d fix(vm.create): don't require view perm on VM template 2016-10-04 16:03:06 +02:00
Julien Fontanet
fdd395e2b6 fix(vm.create): correctly check resourceSet objects
Related to vatesfr/xo-web#1620
2016-10-04 15:51:04 +02:00
Julien Fontanet
e094437168 fix(package): update xo-acl-resolver to version 0.2.2
See vatesfr/xo-web#1620
2016-10-04 15:24:01 +02:00
Pierre Donias
2ee0be7466 fix(xapi/utils/makeEditObject): constraints works with user props (#410) 2016-10-04 15:02:27 +02:00
Julien Fontanet
2784a7cc92 Create ISSUE_TEMPLATE.md 2016-10-03 16:24:24 +02:00
Julien Fontanet
b09f998d6c 5.2.5 2016-10-03 09:39:52 +02:00
Nicolas Raynaud
bdeb5895f6 fix(deltaBackups): update checksum after altering VHD files (#408)
Fixes vatesfr/xo-web#1606
2016-09-30 14:31:33 +02:00
Pierre Donias
3944b8aaee feat(network): create a bonded network (#407)
Fixes vatesfr/xo-web#876
2016-09-30 13:51:33 +02:00
Nicolas Raynaud
6e66cffb92 feat(deltaBackups): correctly chain VHDs (#406)
The goal is for a tool like vhdimount to be able to mount any file and use it as a disk to recover specific file in it.
2016-09-29 17:31:36 +02:00
Pierre Donias
57092ee788 feat(vif.set): support for network, MAC and currently_attached (#403)
Fixes vatesfr/xo-web#1446
2016-09-28 15:09:17 +02:00
Julien Fontanet
70e9e1c706 chore(package): update human-format to version 0.7.0 2016-09-28 09:58:54 +02:00
Greenkeeper
9662b8fbee chore(package): update babel-eslint to version 7.0.0 (#404)
https://greenkeeper.io/
2016-09-27 23:39:30 +02:00
Julien Fontanet
9f66421ae7 fix(bootstrap): C-c twice force stop the server 2016-09-27 10:44:24 +02:00
Greenkeeper
50584c2e50 chore(package): update http-server-plus to version 0.7.0 (#402)
https://greenkeeper.io/
2016-09-27 09:30:16 +02:00
Julien Fontanet
7be4e1901a chore(package): use index-modules 2016-09-26 15:41:41 +02:00
Julien Fontanet
b47146de45 fix(pbd/attached): should be a boolean 2016-09-22 13:20:49 +02:00
Julien Fontanet
97b229b2c7 fix(vm.set): works with VM templates
Fixes vatesfr/xo-web#1569
2016-09-22 10:39:20 +02:00
Julien Fontanet
6bb5bb9403 5.2.4 2016-09-21 10:20:46 +02:00
Julien Fontanet
8c4b8271d8 fix(pool.setDefaultSr): remove pool param
Fixes vatesfr/xo-web#1558
2016-09-20 11:45:36 +02:00
Julien Fontanet
69291c0574 chore(package): update xo-vmdk-to-vhd to version 0.0.12
Fixes vatesfr/xo-web#1551
2016-09-20 10:41:42 +02:00
Julien Fontanet
2dc073dcd6 fix(vm.resourceSet): handle xo namespace 2016-09-19 13:15:23 +02:00
Julien Fontanet
1894cb35d2 feat(vm): expose resourceSet prop 2016-09-19 12:10:09 +02:00
Julien Fontanet
cd37420b07 Merge pull request #398 from vatesfr/greenkeeper-standard-8.1.0
Update standard to version 8.1.0 🚀
2016-09-18 05:17:41 +02:00
Julien Fontanet
55cb6b39db fix(Xo#removeSchedule): correctly test instance of SchedulerError 2016-09-18 05:12:36 +02:00
greenkeeperio-bot
89d13b2285 chore(package): update standard to version 8.1.0
https://greenkeeper.io/
2016-09-17 20:51:59 +02:00
Julien Fontanet
1b64b0468a fix(group.delete): remove associated ACLs
Fixes vatesfr/xo-web#899
2016-09-16 16:04:41 +02:00
Julien Fontanet
085fb83294 fix(user.delete): remove associated ACLs
See vatesfr/xo-web#899
2016-09-16 16:04:41 +02:00
Julien Fontanet
edd606563f feat(vm.revert): can snapshot before (#395)
See vatesfr/xo-web#1445
2016-09-15 14:59:43 +02:00
Julien Fontanet
fb804e99f0 5.2.3 2016-09-14 18:02:32 +02:00
Pierre Donias
1707cbcb54 feat(signin): use XO 5 style (#394)
Fixes vatesfr/xo-web#1161
2016-09-14 17:56:05 +02:00
Julien Fontanet
6d6a630c31 5.2.2 2016-09-14 17:37:42 +02:00
Julien Fontanet
ff2990e8e5 chore(package): update @marsaud/smb2-promise to version 0.2.1
Fixes vatesfr/xo-web#1511
2016-09-14 17:32:52 +02:00
Nicolas Raynaud
d679aff0fb chore(package): remove node-smb2 dependency (#393) 2016-09-14 16:23:28 +02:00
Julien Fontanet
603a444905 fix(Xapi#importVm): remove VM's VDIs on failure 2016-09-14 14:11:20 +02:00
Julien Fontanet
a002958448 fix(DR): remove previous VDIs
Fixes vatesfr/xo-web#1510
2016-09-14 14:11:20 +02:00
Julien Fontanet
cb4bc37424 fix(DR): delete VMs in all cases
Previous copies were not deleted when there were as many as the depth.

Fixes vatesfr/xo-web#1509
2016-09-14 14:11:19 +02:00
Julien Fontanet
0fc6f917e6 5.2.1 2016-09-13 16:44:35 +02:00
Julien Fontanet
ec0d012b24 feat(vm.set): support tags (#392)
Fixes vatesfr/xo-web#1431
2016-09-13 16:35:40 +02:00
Julien Fontanet
2cd4b171a1 chore(package): update json5 to version 0.5.0 2016-09-13 11:28:56 +02:00
Julien Fontanet
0cb6906c4d chore(package): is-my-json-valid to v2.13.1 2016-09-13 11:25:22 +02:00
Julien Fontanet
4c19b93c30 chore(package): update fs-promise to version 0.5.0 2016-09-13 11:23:42 +02:00
Julien Fontanet
6165f1b405 fix(vm.create): select SR of first disk-VDI (#391)
Fixes vatesfr/xo-web#1493
2016-09-12 16:32:43 +02:00
Julien Fontanet
37a4221e43 fix(vm.docker.containers): yes, again 2016-09-12 12:13:45 +02:00
Julien Fontanet
9831b222b5 fix(vm.docker.containers) 2016-09-12 12:11:15 +02:00
Julien Fontanet
7b6f44fb74 fix(vm.createInterface): syntax fix 2016-09-12 12:06:34 +02:00
Julien Fontanet
399f4d0ea3 feat(vm.docker.containers): like vm.docker.process.items but always an array 2016-09-12 11:43:36 +02:00
Julien Fontanet
26a668a875 fix(vm.createInterface): accept integers for position and mtu 2016-09-12 11:36:30 +02:00
Julien Fontanet
bf96262b6e feat(Xapi#createVif): default MTU is network's MTU 2016-09-12 11:05:31 +02:00
Julien Fontanet
1155fa1fe9 chore(vm.create): remove some console.log()s 2016-09-09 15:31:25 +02:00
Julien Fontanet
1875d31731 5.2.0 2016-09-09 15:16:03 +02:00
Julien Fontanet
6f855fd14e feat(IP pools): groups of IP addresses (#371) 2016-09-09 15:12:30 +02:00
Julien Fontanet
08e392bb46 fix(vm.create): correctly compute limits usage (#389)
Fixes vatesfr/xo-web#1365
2016-09-09 12:55:10 +02:00
Julien Fontanet
66d63e0546 fix(test.wait): fix setTimeout params order 2016-09-08 18:40:55 +02:00
Julien Fontanet
7ee56fe8bc feat(pool.installAllPatches): install all patches on a pool (#388)
See vatesfr/xo-web#1392
2016-09-07 17:54:00 +02:00
Julien Fontanet
669d04ee48 fix(vm.migrate): error on unused default SR
Fixes #1466
2016-09-05 14:21:17 +02:00
Julien Fontanet
cb1b37326e fix(vm.rollingDrCopy): avoid duplicates in VMs list (#387)
Fixes vatesfr/xo-web#1464
2016-09-05 13:41:20 +02:00
Julien Fontanet
7bb73bee67 feat(vm.rollingDrCopy): failure to destroy old copies is not fatal 2016-09-05 11:29:54 +02:00
Julien Fontanet
7286ddc338 chore(JobExecutor): use utils/serializeError() 2016-09-05 11:29:53 +02:00
Olivier Lambert
7d1f9e33fe feat(network): add defaultIsLocked to API (#385) 2016-09-01 14:49:20 +02:00
Ronan Abhamon
63c676ebfe feat(vm.import): supports OVA (#375)
See vatesfr/xo-web#709
2016-09-01 14:11:15 +02:00
Greenkeeper
fcaf6b7923 chore(package): update json-rpc-peer to version 0.12.0 (#383)
https://greenkeeper.io/
2016-08-25 11:56:54 -04:00
Julien Fontanet
9f347a170a fix(xapi/utils): correctly isPlainObject 2016-08-18 16:21:34 +02:00
Julien Fontanet
2f7cd4426d fix(xapi/utils/prepareXapiParam): array handling 2016-08-18 16:15:51 +02:00
Julien Fontanet
854f256470 fix(xapi/getNamespaceForType): add missing VIF 2016-08-18 15:27:47 +02:00
Julien Fontanet
5d0b40f752 fix(utils/camelToSnakeCase): better number handling 2016-08-18 15:23:57 +02:00
Julien Fontanet
27a2853ee8 fix(vif.set): add missing param 2016-08-18 15:13:46 +02:00
Julien Fontanet
67f6b80312 fix(vif.set): do not use an arrow function 2016-08-18 15:01:13 +02:00
Julien Fontanet
016037adc1 fix(user.set): can be used by non admins 2016-08-18 14:17:07 +02:00
Julien Fontanet
70d5c1034d 5.1.6 2016-08-18 10:54:36 +02:00
Greenkeeper
ed6fb8754f chore(package): update mocha to version 3.0.2 (#376)
https://greenkeeper.io/
2016-08-18 10:53:05 +02:00
Julien Fontanet
6d08a9b11c feat(JobExecutor): a current job will only run 2 calls at a time (#382)
Fixes vatesfr/xo-web#915
2016-08-18 10:52:29 +02:00
Julien Fontanet
cf6aa7cf79 fix(package): update xen-api to 0.9.4
Again, fixes vatesfr/xo-web#1384
2016-08-18 09:42:28 +02:00
Julien Fontanet
6c4e57aae0 chore(JobExecutor#_execCall): forEach+Array#push → mapToArray 2016-08-17 18:13:30 +02:00
Julien Fontanet
d08a04959c 5.1.5 2016-08-16 19:15:52 +02:00
Julien Fontanet
2762f74ce5 fix(package): update xen-api to 0.9.3 2016-08-16 19:12:46 +02:00
Julien Fontanet
6ebcf6eec5 5.1.4 2016-08-16 18:18:04 +02:00
Julien Fontanet
25b78fb7e1 fix(package): update xen-api to 0.9.2
Fixes vatesfr/xo-web#1384
2016-08-16 18:15:32 +02:00
Greenkeeper
670dd2dd96 chore(package): update promise-toolbox to version 0.5.0 (#381)
https://greenkeeper.io/
2016-08-16 12:22:57 +02:00
Julien Fontanet
1baf04f786 fix(NfsHandler#_unmount): use _getRealPath() (#380)
Fixes vatesfr/xo-web#1396.
2016-08-15 14:22:19 +02:00
Greenkeeper
ce05b7a041 chore(package): update nyc to version 8.1.0 (#379)
https://greenkeeper.io/
2016-08-14 19:06:00 +02:00
Olivier Lambert
290cc146c8 fix(xapi): allow to unplug VBDs when VM is running 2016-08-11 16:32:06 +02:00
Olivier Lambert
db4d46a584 fix(sr): don't share a local ISO SR. Fixes vatesfr/xo-web#1389 2016-08-10 14:39:05 +02:00
Olivier Lambert
8ed2e51dde feat(network): add network.set method 2016-08-08 14:54:23 +02:00
Olivier Lambert
33702c09a6 feat(vm copy): allow snapshot copy. Related to vatesfr/xo-web#1353 2016-08-08 14:07:27 +02:00
Olivier Lambert
45aeca3753 5.1.3 2016-08-05 11:08:11 +02:00
Olivier Lambert
deae7dfb4d fix(xen-api): avoid reserved key conflicts. Fixes vatesfr/xo-web#1369 2016-08-05 11:06:58 +02:00
Julien Fontanet
2af043ebdd chore(jshint): remove unused config file 2016-08-03 09:46:52 +02:00
Olivier Lambert
e121295735 Merge pull request #373 from nraynaud/next-release
fix (readme): fix installation documentation link
2016-08-02 12:32:05 +02:00
Nicolas Raynaud
7c1c405a64 fix installation documentation link 2016-08-02 12:22:39 +02:00
Olivier Lambert
5d7c95a34d fix(xapi): typo on host disable method. Fixes vatesfr/xo-web#1351 2016-07-30 20:22:12 +02:00
Julien Fontanet
504c934fc9 fix(JobExecutor#_execCall): xo.api.call() → xo.callApiMethod() 2016-07-29 15:28:24 +02:00
Julien Fontanet
81b0223f73 fix(JobExecutor#exec): forward the error 2016-07-29 15:27:58 +02:00
Julien Fontanet
6d1e410bfd fix(JobExecutor#exec): correctly log the error 2016-07-29 15:27:32 +02:00
Julien Fontanet
26c5c6152d fix(job-executor/map): paramName handling 2016-07-29 14:37:42 +02:00
Julien Fontanet
d83bf0ebaf fix(Xo#_watchObject): check for notify() 2016-07-29 14:29:57 +02:00
Julien Fontanet
5adfe9a552 chore(index): remove debug trace 2016-07-29 13:54:54 +02:00
ABHAMON Ronan
883f461dc7 feat(job-executor): supports dynamic param vectors (#369)
See vatesfr/xo-web#837
2016-07-29 13:26:53 +02:00
Julien Fontanet
8595ebc258 feat(api): generate logs on errors
See vatesfr/xo-web#1344
2016-07-29 10:32:48 +02:00
Julien Fontanet
2bd31f4560 chore(api): remove legacy helpers 2016-07-28 15:21:59 +02:00
Julien Fontanet
6df85ecadd fix(vm.*): add missing import 2016-07-28 15:21:59 +02:00
Julien Fontanet
07829918e4 5.1.2 2016-07-28 15:21:12 +02:00
Julien Fontanet
b0d400b6eb fix(Xapi#exportDeltaVm): better handling of removed VDIs (#370)
Fixes vatesfr/xo-web#1333
2016-07-28 15:19:44 +02:00
Julien Fontanet
706cb895ad 5.1.1 2016-07-27 16:36:51 +02:00
Julien Fontanet
45bf539b3c fix(user.delete): fix tokens deletion 2016-07-27 13:23:16 +02:00
Julien Fontanet
0923981f8d fix(user.set): typo in error message 2016-07-27 13:01:32 +02:00
Julien Fontanet
b0ac14363d 5.1.0 2016-07-26 16:52:49 +02:00
Julien Fontanet
5d346aba37 fix(vm.create): cloudConfig handling 2016-07-26 14:26:24 +02:00
Julien Fontanet
124cb15ebe fix(resource sets): fix VM resources computation
Fixes vatesfr/xo-web#1276
2016-07-25 17:08:09 +02:00
Julien Fontanet
a244ab898d fix(vm.create): correctly store the resource set 2016-07-25 17:08:08 +02:00
Julien Fontanet
3c551590eb fix(vm.set): correctly save memory in limits 2016-07-25 17:08:07 +02:00
ABHAMON Ronan
10e30cccbc feat(models/schedule): null properly remove timezone (#368)
Related to vatesfr/xo-web#1314
2016-07-25 15:54:27 +02:00
Julien Fontanet
806a6b86a2 fix(signin): fix styles when /v4 2016-07-25 13:40:57 +02:00
Julien Fontanet
9719fdf5cc fix(sr.probe*): correctly prepare port param 2016-07-23 16:18:03 +02:00
Julien Fontanet
6d8764f8cb fix(Xapi#createVm): add missing param 2016-07-23 15:49:27 +02:00
Julien Fontanet
d9fd9cb408 fix(vm.create): better VBDs creation (#361)
Fixes vatesfr/xo-web#1257
2016-07-23 15:31:15 +02:00
Julien Fontanet
7710ec0aba feat(schemas): add user schema 2016-07-20 12:10:23 +02:00
Julien Fontanet
c97bd78cd0 fix(VM): cpuCap & cpuWeight are integers 2016-07-20 10:57:15 +02:00
ABHAMON Ronan
728c5aa86e feat(plugins): supports predefined configurations (#365)
See vatesfr/xo-web#1289
2016-07-19 17:28:53 +02:00
Pierre Donias
83d68ca293 feat(vm.set): make cpuWeight and cpuCap nullable (#364) 2016-07-19 16:53:47 +02:00
Julien Fontanet
47d7561db4 fix(VM): cpuCap can be defined when cpuWeight is not 2016-07-19 15:37:07 +02:00
ABHAMON Ronan
7d993e8319 feat(schedules): schedules support timezones (#363)
Fixes vatesfr/xo-web#1258
2016-07-19 13:32:27 +02:00
Julien Fontanet
1d1a597b22 feat(VM): expose cpuCap 2016-07-19 11:02:38 +02:00
Julien Fontanet
23082f9300 feat(vm.set): support for cpuCap (#362) 2016-07-19 10:35:03 +02:00
Julien Fontanet
ea1a7f9376 chore(Xapi#_getXenUpdates): use ensureArray() 2016-07-15 12:57:20 +02:00
Greenkeeper
1796c7bab8 chore(package): update nyc to version 7.0.0 (#358)
https://greenkeeper.io/
2016-07-14 13:09:12 +02:00
Greenkeeper
65ad76479a chore(package): update base64url to version 2.0.0 (#360)
https://greenkeeper.io/
2016-07-14 11:33:12 +02:00
Olivier Lambert
422db04ec8 5.0.5 2016-07-13 15:20:56 +02:00
Olivier Lambert
d12f60fe37 Merge pull request #359 from vatesfr/pierre-fix-create-vm
fix(vm/create): missing single quotes
2016-07-13 09:37:23 +02:00
Pierre Donias
194c1c991c fix(vm/create): missing single quotes 2016-07-12 16:40:32 +02:00
Olivier Lambert
3e8e2222c1 Merge pull request #357 from vatesfr/marsaudf-fix-job-log-error
Add message to job log error
2016-07-07 15:26:15 +02:00
Fabrice Marsaud
1620327a33 Add message to job log error 2016-07-07 14:55:43 +02:00
Olivier Lambert
b1131e3667 5.0.4 2016-07-07 12:12:54 +02:00
Olivier Lambert
db0250ac08 Merge pull request #356 from vatesfr/marsaudf-fix-patch-conflicts
Fix(xapi): handle correctly single XML elements
2016-07-07 11:22:27 +02:00
Fabrice Marsaud
0a6b605760 Handle single patch elements in parsed XML 2016-07-07 10:11:21 +02:00
Olivier Lambert
81ac2375e5 5.0.3 2016-07-06 23:23:14 +02:00
Olivier Lambert
6bcaca6cd7 Merge pull request #355 from vatesfr/issue-1233
fix(Xapi#importDeltaVm): correctly handle missing network
2016-07-06 23:21:55 +02:00
Olivier Lambert
ec8375252e fix(Xapi#importDeltaVm): correctly handle missing network 2016-07-06 23:11:47 +02:00
Julien Fontanet
766aa1762f 5.0.2 2016-07-05 17:56:02 +02:00
Julien Fontanet
5165e0a54c feat(user.set): support preferences 2016-07-05 17:19:38 +02:00
Julien Fontanet
a2f7ad627e feat(Xapi#migrateVm): allow non-running VMs
Fixes vatesfr/xo-web#1216
2016-07-05 17:09:54 +02:00
Julien Fontanet
1176c162d4 5.0.1 2016-06-30 15:46:27 +02:00
Fabrice Marsaud
a4880cd017 feat(remote.test): perform a write/read test on a remote (#354)
See vatesfr/xo-web#1075
2016-06-30 15:00:00 +02:00
Julien Fontanet
383bdce416 fix(plugin.configure): fix undefined handling 2016-06-29 13:08:02 +02:00
Julien Fontanet
7cc300dd83 fix(Xapi#createVif): fix handling when neither device nor position is not provided 2016-06-28 17:36:24 +02:00
Fabrice Marsaud
687809db9d fix(user.set): cannot change self permission (#353) 2016-06-28 13:28:31 +02:00
Julien Fontanet
1127ec3a90 feat(vif.set): allowed IPv4/IPv6 addresses (#328) 2016-06-27 15:11:46 +02:00
Julien Fontanet
a797edfae9 chore(xapi/mixins/vm): simplify _editVm() specs 2016-06-27 12:10:57 +02:00
Julien Fontanet
938e106252 feat(xapi/utils/makeEditObject): support camelCase and snake_case aliases 2016-06-27 12:10:54 +02:00
Julien Fontanet
a0eb9caaa2 feat(xapi/utils/makeEditObject): set, set.get, set.set can be true 2016-06-27 11:54:13 +02:00
Julien Fontanet
442f53d45e fix(xapi/utils/makeEditObject): use deep equality 2016-06-27 09:52:02 +02:00
Greenkeeper
68de1ca248 chore(package): update ws to version 1.1.1 (#348)
https://greenkeeper.io/
2016-06-26 20:19:47 +02:00
Greenkeeper
e16061141e chore(package): update d3-time-format to version 2.0.0 (#350)
https://greenkeeper.io/
2016-06-26 20:18:24 +02:00
Julien Fontanet
64cbe3d209 feat(build): delete dist before building 2016-06-26 17:47:56 +02:00
Julien Fontanet
ebdc6376d8 5.0.0 2016-06-24 18:34:31 +02:00
Julien Fontanet
68335123a1 feat(vm.create): all vm.set params are supported (#340) 2016-06-24 18:33:43 +02:00
Julien Fontanet
25b18f4ef8 chore(package): update xo-acl-resolver to 0.2.1 2016-06-24 14:43:18 +02:00
Julien Fontanet
9ad615b0ff fix(Xapi#_waitObjectState): fix failure when object is initially missing 2016-06-22 12:20:22 +02:00
Julien Fontanet
12eaceb032 fix(xapi-objects-to-xo): fix CPUs.number when no tools 2016-06-21 13:19:29 +02:00
Julien Fontanet
3263511b72 fix(Xapi#snapshotVm): fallback if quiesce failed
Fixes vatesfr/xo-web#1088
2016-06-21 11:21:01 +02:00
Julien Fontanet
75cae8c647 fix(Xapi#_updateObjectMapProperty): prepare XAPI param 2016-06-21 11:21:00 +02:00
Julien Fontanet
9991ef624c feat(Xapi#getObject): accept objects with _xapiId property 2016-06-21 11:21:00 +02:00
Julien Fontanet
489e9fce27 fix(xapi/index): work around Babel T2877 2016-06-21 11:21:00 +02:00
Julien Fontanet
0655628073 fix(xapi/index): incorrect import 2016-06-21 11:20:59 +02:00
Fabrice Marsaud
9460822529 feat(vm.importBackup): returns the new VM id (#345) 2016-06-20 18:07:14 +02:00
Julien Fontanet
d02358ac0d chore(xapi): move utilities into dedicated module 2016-06-17 18:43:10 +02:00
ABHAMON Ronan
366237a625 fix(XapiStats): fix unit for host free memory (#339) 2016-06-17 10:16:58 +02:00
Julien Fontanet
2f2da18994 chore: remove some unnecessary logs 2016-06-16 09:22:26 +02:00
Greenkeeper
ecd30db215 chore(package): update d3-time-format to version 1.0.0 (#338)
https://greenkeeper.io/
2016-06-15 08:40:56 +02:00
ABHAMON Ronan
1980854f6f feat(Xapi#importDeltaVm): attach VIFs to original networks if available (#335)
Fixes vatesfr/xo-web#1016
2016-06-10 11:05:54 +02:00
Julien Fontanet
7d4f006c25 feat(Xapi#exportDeltaVm): inject network/SR UUIDs in VIF/VDI records 2016-06-09 17:25:02 +02:00
Julien Fontanet
b697be2383 fix(Xapi#_snapshotVm): returns the up-to-date snapshot record 2016-06-09 17:17:14 +02:00
Fabrice Marsaud
143e53c43f chore(package): update xo-remote-parser to version 0.3.0 (#333) 2016-06-08 17:26:08 +02:00
Julien Fontanet
6dde1ade01 fix(xo-server-logs): fix broken require since Babel 6 2016-06-08 11:12:45 +02:00
Greenkeeper
d4de391ac5 chore(package): update d3-time-format to version 0.4.0 (#332)
https://greenkeeper.io/
2016-06-08 09:05:45 +02:00
Greenkeeper
af15f4bc6a chore(package): update xo-acl-resolver to version 0.2.0 (#330)
https://greenkeeper.io/
2016-06-07 16:46:23 +02:00
Fabrice Marsaud
d4ace24caa fix(job.set): protects userId from modification (#329) 2016-06-07 09:25:15 +02:00
Julien Fontanet
c5ab47fa66 chore(package): fix deps order 2016-06-06 13:38:16 +02:00
Julien Fontanet
d60051b629 fix(package): update xo-remote-parser to 0.2.1 2016-06-06 13:37:47 +02:00
Julien Fontanet
22ff330ee7 fix(package): update @marsaud/smb2 to 0.7.1 2016-06-03 18:22:37 +02:00
Olivier Lambert
dd62bef66d feat(host): expose correct timestamp for license expiry value 2016-05-31 17:24:49 +02:00
Julien Fontanet
e7feb99f8d feat(vm.create): clone param may be use to disable cloning (#318)
See vatesfr/xo-web#960
2016-05-30 11:34:39 +02:00
Julien Fontanet
6358accece fix(plugin.configure): correctly handle undefined 2016-05-30 11:12:11 +02:00
Olivier Lambert
9ce8a24eea feat(sr): add disconnect and connect all PBDs to a SR (#324) 2016-05-27 18:31:09 +02:00
Julien Fontanet
4d0673f489 feat(sr.forget): automatically disconnect PBDs (#323) 2016-05-27 18:15:09 +02:00
Olivier Lambert
fbe1e6a7d5 fix(vm): missing parameters and wrong value for set_memory_static_max 2016-05-27 15:03:49 +02:00
Greenkeeper
4ed02ca501 chore(package): update cookie to version 0.3.0 (#322)
https://greenkeeper.io/
2016-05-27 04:36:35 +02:00
Julien Fontanet
af245ed9fe fix(log.delete): id can be an array 2016-05-26 13:34:47 +02:00
Julien Fontanet
fc86a3e882 fix(vm): always consider memory dynamic max when updating resource set 2016-05-24 16:22:55 +02:00
Julien Fontanet
f9109edcf1 fix(vm.set): memoryMax should update resource set 2016-05-24 16:21:21 +02:00
Julien Fontanet
ec100e1a91 fix(vm.set): memoryMax should change dynamic max 2016-05-24 16:20:25 +02:00
Julien Fontanet
746c5f4a79 fix(vm.set): cpusMax (shame) 2016-05-24 15:13:53 +02:00
Julien Fontanet
b2611728a1 fix(vm): fix indent 2016-05-24 14:38:11 +02:00
Julien Fontanet
fc6cc4234d chore(vm.set): fix some comments 2016-05-24 14:33:40 +02:00
Julien Fontanet
7706c1cb63 feat(vm.set): memoryStaticMax 2016-05-24 14:33:02 +02:00
Julien Fontanet
4d7a07220c feat(vm.set): memoryMax increase static max if necessary 2016-05-24 14:32:14 +02:00
Julien Fontanet
436875f7dc fix(vm.set): memoryMin should not change static min 2016-05-24 14:30:26 +02:00
Julien Fontanet
21c6f53ecc fix(vm.set): cpusMax 2016-05-24 14:23:21 +02:00
Julien Fontanet
5472be8b72 4.17.0 2016-05-24 11:51:15 +02:00
Julien Fontanet
d22542fcf3 Revert "fix(leveldown): fix leveldown to version 1.4.4"
This reverts commit 5fa4c95480.
2016-05-24 11:50:36 +02:00
Julien Fontanet
1d8341eb27 Merge branch 'next-release' into stable 2016-05-24 11:49:47 +02:00
Julien Fontanet
1897a7ada3 fix(log.get): only usable by admins 2016-05-23 16:18:21 +02:00
Julien Fontanet
a048698c66 feat(log.*): add params schemas 2016-05-23 16:17:54 +02:00
Julien Fontanet
f891e57f4a fix(xapi-objects-to-xo): a SR should always have a container 2016-05-23 16:00:51 +02:00
Olivier Lambert
fcc590e48a feat(vm.snapshot): name param is optional (#320) 2016-05-23 12:56:20 +02:00
Julien Fontanet
9a02a2a65b fix(vm.set): fix call to $isVmRunning
Fixes #319
2016-05-23 10:02:34 +02:00
Julien Fontanet
536a6c5c60 feat(vm.rollingDrCopy): accepts a sr param (#315)
See vatesfr/xo-web#955
2016-05-21 14:24:16 +02:00
Julien Fontanet
86a6871ee8 fix(vm.set): correctly change min dynamic memory if necessary (#317)
Fixes vatesfr/xo-web#970
2016-05-21 14:14:57 +02:00
Julien Fontanet
6046045151 feat(vm.createInterface): position param is now optional 2016-05-21 13:11:51 +02:00
Julien Fontanet
9c3ddd4ba4 fix(Xapi#_createVm()): license issue with Dundee (#316)
Fixes vatesfr/xo-web#964.
2016-05-20 12:22:42 +02:00
Julien Fontanet
6c9f55c1d7 style(utils): fix lightSet 2016-05-17 09:08:51 +02:00
Julien Fontanet
5bec3d7dcd fix(xapi-object-to-xo): correctly set host memory size 2016-05-16 11:50:01 +02:00
Julien Fontanet
a4c309efe8 fix(package): ship signin.pug 2016-05-12 18:18:56 +02:00
Jon Sands
4e22a208dd fix(autopoweron): set pool other_config entry to true instead of on (#310)
Fixes #309 
Fixes vatesfr/xo-web#937
2016-05-12 13:17:02 +02:00
Julien Fontanet
ff9e77118e fix(Xapi): VM creation on Dundee (#303) 2016-05-11 18:03:58 +02:00
Julien Fontanet
6c6dfa9ac4 perf(Promise): use Bluebird as default implementation 2016-05-11 18:01:52 +02:00
Greenkeeper
d60d5207d8 chore(package): update xen-api to version 0.9.0 (#308)
https://greenkeeper.io/
2016-05-11 17:55:35 +02:00
ABHAMON Ronan
8c0ae892f5 feat(api): rename <namespace> param to id (#305) 2016-05-11 14:35:49 +02:00
Greenkeeper
f570492a11 chore(package): update xo-remote-parser to version 0.2.0 (#307)
https://greenkeeper.io/
2016-05-11 14:07:23 +02:00
Julien Fontanet
cc447304f5 fix(bin/xo-server): remove ES6 syntax 2016-05-10 13:43:53 +02:00
Julien Fontanet
8f8c6366e3 chore(xo-mixins/backup): use default value for remote handler flags 2016-05-05 18:22:19 +02:00
Julien Fontanet
3b13bcb098 fix(Xapi#exportDeltaVm): make streams property non-enumerable 2016-05-05 18:19:41 +02:00
Julien Fontanet
df60784b51 chore(signin): jade renamed to pug 2016-05-04 16:00:28 +02:00
Julien Fontanet
bae3122bb5 chore: various updates 2016-05-04 12:16:02 +02:00
Julien Fontanet
0770aef4bf chore(package): update standard to version 7.0.0 2016-05-04 11:59:56 +02:00
ABHAMON Ronan
c198350bfa feat(remote-handlers): cannot overwrite files by default (#297) 2016-05-03 16:56:26 +02:00
Greenkeeper
a2ed388777 chore(package): update helmet to version 2.0.0 (#298)
https://greenkeeper.io/
2016-04-30 08:10:22 +02:00
Julien Fontanet
f6670c699a 4.16.1 2016-04-29 10:28:03 +02:00
Julien Fontanet
5fa4c95480 fix(leveldown): fix leveldown to version 1.4.4
Due to Level/leveldown#276.
2016-04-29 10:27:37 +02:00
Julien Fontanet
5b8608c186 feat(utils/streamToBuffer): rebase on get-stream and use everywhere (#295) 2016-04-29 09:52:36 +02:00
Julien Fontanet
bb75d42ede 4.16.0 2016-04-29 09:44:42 +02:00
Julien Fontanet
b4b6def07a Merge branch 'next-release' into stable 2016-04-29 09:43:30 +02:00
Greenkeeper
b305700987 chore(package): update get-stream to version 2.1.0 (#294)
https://greenkeeper.io/
2016-04-29 09:14:30 +02:00
Greenkeeper
40232b7eb1 chore(package): update fs-extra to version 0.30.0 (#293)
https://greenkeeper.io/
2016-04-28 18:17:34 +02:00
Julien Fontanet
67ff666db4 Use the new execa.stdout() 2016-04-28 10:18:05 +02:00
Greenkeeper
5960fd4fe0 chore(package): update fs-extra to version 0.29.0 (#292)
https://greenkeeper.io/
2016-04-28 09:04:39 +02:00
Greenkeeper
f8b28c519c chore(package): update xo-acl-resolver to version 0.1.0 (#291)
https://greenkeeper.io/
2016-04-28 08:56:31 +02:00
Julien Fontanet
ee1105b6dd fix(Xapi#importVdiContent): find first attached PBD (#279) 2016-04-27 09:37:30 +02:00
Julien Fontanet
4778274c97 fix(Xapi#call): retries on TOO_MANY_PENDING_TASKS
Fixes fix vatesfr/xo-web#861
2016-04-27 09:28:36 +02:00
Julien Fontanet
d7ecb32238 Xapi#snapshotVm(): wait for the uptodate obj on quiesce. (#282)
Fixes vatesfr/xo-web#904
2016-04-27 09:24:00 +02:00
Greenkeeper
744306fc50 chore(package): update execa to version 0.4.0 (#290)
https://greenkeeper.io/
2016-04-27 09:13:16 +02:00
Olivier Lambert
11bbb8ed4d add host startTime and agentStartTime 2016-04-26 11:30:57 +02:00
Julien Fontanet
b5092a4444 feat(toTimestamp): handle timestamps. 2016-04-26 11:27:26 +02:00
Greenkeeper
e2442c07a9 https://greenkeeper.io/Fixes vatesfr/xo-web#769
https://greenkeeper.io/

Fixes vatesfr/xo-web#769.
2016-04-26 09:07:33 +02:00
Julien Fontanet
6f924d4e83 fix(user.delete): fix vatesfr/xo-web#901. 2016-04-25 14:33:29 +02:00
Greenkeeper
faf1508914 chore(package): update execa to version 0.3.0 (#284)
https://greenkeeper.io/
2016-04-23 17:51:56 +01:00
Julien Fontanet
7eb8152835 4.15.3 2016-04-22 16:18:21 +02:00
Julien Fontanet
8f45905831 fix(vm.deltaCopy()): delete snapshot when import fails. 2016-04-22 16:18:03 +02:00
Julien Fontanet
4ba2ffce5b fix(vm.deltaCopy()): delete snapshot when import fails. 2016-04-22 13:39:21 +02:00
Greenkeeper
ffb3659ef5 chore(package): update fs-extra to version 0.28.0 (#280)
http://greenkeeper.io/
2016-04-18 12:09:06 +01:00
Julien Fontanet
6dec07d562 signin form: fix redirect on success. 2016-04-18 11:57:58 +01:00
Julien Fontanet
afb22f3279 Merge pull request #278 from vatesfr/greenkeeper-fs-extra-0.27.0
Update fs-extra to version 0.27.0 🚀
2016-04-15 14:14:31 +02:00
greenkeeperio-bot
f2f369db64 chore(package): update fs-extra to version 0.27.0
http://greenkeeper.io/
2016-04-15 14:05:41 +02:00
Julien Fontanet
635c76db93 Deprecate host#CPUs and introduce host#cpus. 2016-04-13 10:59:29 +02:00
Julien Fontanet
5f50f1928d Merge pull request #276 from vatesfr/fix-auto-poweron
Fix auto poweron (fix vatesfr/xo-web#879).
2016-04-11 15:53:37 +02:00
Julien Fontanet
32c9ed1dc2 Fix auto poweron (fix vatesfr/xo-web#879). 2016-04-11 15:31:59 +02:00
Julien Fontanet
0536926a1f 4.15.2 2016-04-08 11:17:47 +02:00
Julien Fontanet
3959c98479 Update xo-acl-resolver to 0.0.0. 2016-04-08 11:17:26 +02:00
Julien Fontanet
2ce5735676 Fix ACLs in API calls (fix vatesfr/xo-web#870). 2016-04-08 11:05:17 +02:00
Julien Fontanet
71741e144e Merge pull request #274 from vatesfr/abhamonr-set-vm-ram-min-max-values
api.vm: Set the min/max ram values.
2016-04-07 10:08:34 +02:00
wescoeur
f2e64cdd5e api.vm: Set the min/max ram values. 2016-04-07 09:25:45 +02:00
Julien Fontanet
afaa5d5e9e Merge pull request #275 from vatesfr/abhamonr-set-vm-cpus-max
api.vm: Set vcpus max.
2016-04-06 17:39:16 +02:00
wescoeur
d82861727d api.vm: Set vcpus max. 2016-04-06 17:32:51 +02:00
Julien Fontanet
90f0795416 Merge pull request #272 from vatesfr/abhamonr-fix-smb-backup-location
Ensure remote smb path is a directory. (fix vatesfr/xo-web#865)
2016-04-06 16:25:28 +02:00
Julien Fontanet
9efbe7771c Merge pull request #273 from vatesfr/abhamonr-consistent-stats-object
vm.stats(): Returns empty stats if none can be found.
2016-04-06 12:10:34 +02:00
wescoeur
a75caac13d Vm stats are consistents. Even without RRD stats. 2016-04-06 11:55:14 +02:00
wescoeur
279d0d20ea Ensure remote smb path is a directory. (fix vatesfr/xo-web#865) 2016-04-06 09:51:20 +02:00
Julien Fontanet
332ba96d34 ghooks: use commit-msg instead of pre-commit. 2016-04-04 11:33:30 +02:00
Julien Fontanet
3f6e5b7606 decorators/@autobind: Minor improvements. 2016-04-04 11:29:31 +02:00
Julien Fontanet
94703492fd Use http-proxy for HTTP/ws proxy. 2016-03-30 17:33:50 +02:00
Julien Fontanet
df78117617 Do not crash on error in the console proxy. 2016-03-30 17:33:50 +02:00
Julien Fontanet
909b9480e4 Better error message in console proxy. 2016-03-30 17:33:49 +02:00
Julien Fontanet
21762ac1aa Return to the correct page after sign in. 2016-03-30 17:33:49 +02:00
Julien Fontanet
412bc175b4 Merge pull request #270 from vatesfr/abhamonr-stats-object-contains-interval
Stats object contains interval attr.
2016-03-30 14:49:25 +02:00
wescoeur
dc0eb76e88 Stats object contains interval attr. 2016-03-30 14:34:37 +02:00
Julien Fontanet
2695941a3c Remove pFinally() tests, now implemented in promise-toolbox. 2016-03-29 18:05:32 +02:00
Julien Fontanet
3506be1a70 Update promise-toolbox to 0.3.2. 2016-03-29 09:54:24 +02:00
Julien Fontanet
cbf4786b39 Do not crash on unhandled error event. 2016-03-27 13:31:31 +02:00
Julien Fontanet
8dbf334208 Merge pull request #267 from vatesfr/back-to-babel-6
Back to babel 6
2016-03-25 17:37:52 +01:00
Julien Fontanet
60ba5fbc72 Merge pull request #268 from vatesfr/abhamonr-stats-with-halted-vm
Throw an error if a vm is halted and its stats are requested.
2016-03-25 17:37:27 +01:00
Julien Fontanet
c3ace0c44f Simply do npm test before git commit. 2016-03-25 17:36:37 +01:00
Olivier Lambert
8eceb90e63 add startTime 2016-03-25 17:33:34 +01:00
wescoeur
4754e19e83 Throw an error if a vm is halted and its stats are requested. 2016-03-25 15:49:52 +01:00
Julien Fontanet
a0559d0dc9 Revert "Work around Babel T7172."
This reverts commit ca8476d466.
2016-03-23 09:45:59 +01:00
Julien Fontanet
8d03ce19b0 Revert "Merge pull request #248 from vatesfr/babel-t7172"
This reverts commit f125b593bf, reversing
changes made to ca8476d466.
2016-03-23 09:43:30 +01:00
Julien Fontanet
2470d851e9 Revert "Merge pull request #266 from vatesfr/babel-5-workaround"
This reverts commit b77d3f123d, reversing
changes made to c10b0afaa8.
2016-03-23 09:41:54 +01:00
Julien Fontanet
df99f5c0a5 Revert "Merge pull request #265 from vatesfr/revert-babel-6"
This reverts commit 8907290d27, reversing
changes made to eb55cba34a.
2016-03-23 09:41:08 +01:00
Julien Fontanet
36f5084c52 4.15.1 2016-03-22 15:13:54 +01:00
Olivier Lambert
b77d3f123d Merge pull request #266 from vatesfr/babel-5-workaround
Xapi#migrateVm(): Babel 5 workaround. Fix vatesfr/xo-web/issues/831
2016-03-22 13:46:31 +01:00
Julien Fontanet
3c14405155 Xapi#migrateVm(): Babel 5 workaround. 2016-03-22 13:21:56 +01:00
Julien Fontanet
c10b0afaa8 Xapi#copyVm(): wait copy to finish before deleting snapshot. 2016-03-22 12:14:15 +01:00
Julien Fontanet
3f7a2d6bfb Xapi#copyVm(): fix snapshot deletion. 2016-03-22 12:07:35 +01:00
Julien Fontanet
f2a0d56e01 Update xen-api to 0.7.4. 2016-03-22 10:23:29 +01:00
Julien Fontanet
0736cc8414 4.15.0 2016-03-21 16:36:17 +01:00
Julien Fontanet
53240d40a0 vm.delete(): Fix :: usage in CoffeeScript. 2016-03-21 15:55:19 +01:00
Julien Fontanet
4137dd7cc8 Xo/ResourceSet#computeVmResourcesUsage(): fix access to Xo#getXapi(). 2016-03-21 15:29:41 +01:00
Julien Fontanet
8907290d27 Merge pull request #265 from vatesfr/revert-babel-6
Revert "Merge pull request #193 from vatesfr/babel-6"
2016-03-21 15:09:37 +01:00
Julien Fontanet
401dc1cb10 Revert "Minor fixes related to Babel 6."
This reverts commit a9a1472cb7.
2016-03-21 13:02:34 +01:00
Julien Fontanet
a6b5d26f56 Revert "Merge pull request #193 from vatesfr/babel-6"
This reverts commit 250b0eee28, reversing
changes made to 57ebd5bb7a.
2016-03-21 13:02:34 +01:00
Julien Fontanet
eb55cba34a Xapi#exportDeltaVm(): fix handling of empty VBDs. 2016-03-21 13:00:56 +01:00
Julien Fontanet
b0b41d984e Update @marsaud/smb2-promise to 0.2. 2016-03-21 12:38:12 +01:00
Julien Fontanet
947f64e32d SmbHandler: fix error normalization. 2016-03-18 16:54:58 +01:00
Julien Fontanet
24ccbfa9b6 Test before commit. 2016-03-18 16:40:15 +01:00
Olivier Lambert
8110acb795 removing the debug mode for Vhd merge 2016-03-18 16:30:48 +01:00
Olivier Lambert
7473aede60 Merge pull request #259 from vatesfr/abhamonr-delta-backups-with-smb
Delta backups works with samba
2016-03-18 16:28:44 +01:00
wescoeur
6f204f721b Delta backups works with samba.
Many fixes (linux paths, windows errors support...)
All options in smb handlers have a default value.
Remove the handler._remote.path usage.
...
2016-03-18 14:57:14 +01:00
Olivier Lambert
7b0e08094a Merge pull request #264 from vatesfr/abhamonr-delta-backup-with-quiesce
Delta backup works with quiesce. (fix vatesfr/xo-web#812)
2016-03-18 14:47:12 +01:00
wescoeur
322e1a75b9 Delta backup works with quiesce. (fix vatesfr/xo-web#812) 2016-03-18 10:20:49 +01:00
Olivier Lambert
a0806d98a1 Merge pull request #263 from vatesfr/pierre-feedback-when-error-on-sr
`GenericXoError` to throw errors with human readable message.
2016-03-16 17:48:33 +01:00
Olivier Lambert
182897d971 Merge branch 'next-release' into pierre-feedback-when-error-on-sr 2016-03-16 17:42:32 +01:00
Pierre
f90a639fcc Minor fix. 2016-03-16 15:52:29 +01:00
Pierre
d95d7208a2 Minor fix. 2016-03-16 15:42:53 +01:00
Pierre
bbac8ffe64 Substituted all JsonRpcErrors by GenericErrors. 2016-03-16 15:34:19 +01:00
Pierre
801a649fb1 GenericXoError: Error with human readable message property. Used on PBD.unplug 2016-03-16 14:59:32 +01:00
Olivier Lambert
7c09ceecfd Merge pull request #262 from vatesfr/pierre-network-management
PIFs and networks management (See vatesfr/xo-web#805)
2016-03-16 11:22:06 +01:00
Pierre
8c4954fb9b Camel case fixes in pif.js 2016-03-16 10:55:15 +01:00
Pierre
fbe892105b Cast. Removed unused pieces of code. Minor fixes. 2016-03-16 10:29:42 +01:00
Pierre
584e1bb847 reconfigureIp to set the IP, netmask, DNS and gateway of a PIF 2016-03-16 10:12:40 +01:00
Pierre
c437ab282e Detroy VLANs and destroy network 2016-03-16 10:12:40 +01:00
Pierre
42a100d138 Delete network 2016-03-16 10:12:40 +01:00
Pierre
65807bf35d Fixes. 2016-03-16 10:12:40 +01:00
Pierre
2995f48ede network.create instead of createNetwork for host and pool 2016-03-16 10:12:40 +01:00
Julien Fontanet
d452702aef Better error messages on web server failure. 2016-03-11 23:41:51 +01:00
Julien Fontanet
f8ed9c7357 Merge pull request #260 from vatesfr/pierre-pool-networks
`createNetwork` on pool: network creation on pool.master (See vatesfr/xo-web#226)
2016-03-11 15:26:37 +01:00
Pierre
9143120177 network.create instead of createNetwork for host and pool 2016-03-11 11:57:12 +01:00
Pierre
fd3b1bee92 Code mutualization between host and pool. 2016-03-11 11:57:11 +01:00
Pierre
bff42954d1 createNetwork on pool: network creation on pool.master 2016-03-11 11:57:11 +01:00
Julien Fontanet
6b74fd6a02 plugin.get(): Expose versions (see vatesfr/xo-web#807). 2016-03-09 14:51:34 +01:00
Julien Fontanet
0547cebfe2 Import package.json explicitely to make dependency-check happy. 2016-03-09 11:46:09 +01:00
Julien Fontanet
caefdf4300 system.getServerVersion() (see vatesfr/xo-web#807). 2016-03-09 11:37:03 +01:00
Olivier Lambert
a59df15994 Merge pull request #255 from vatesfr/olivierlambert-cifs-iso
Prepare user/pass for CIFS ISO share
2016-03-04 13:49:08 +01:00
Olivier Lambert
33304eb8d9 add type in the new SR API 2016-03-04 13:37:37 +01:00
Olivier Lambert
eb21a1bfb3 support SMB ISO SR 2016-03-04 13:37:37 +01:00
Olivier Lambert
ce0333b0a7 prepare user/pass for CIFS ISO share 2016-03-04 13:37:37 +01:00
Julien Fontanet
25a1b53a91 Prints unhandled rejection ASAP. 2016-03-04 11:46:15 +01:00
Julien Fontanet
6aba73f970 Use ::pCatch(noop) instead of .catch(noop).
Avoid hiding programmer errors.
2016-03-04 11:21:33 +01:00
Julien Fontanet
6406bb7fb6 FIXME: remote type gessing should be done in xo-remote-parser. 2016-03-03 17:32:09 +01:00
Julien Fontanet
2458107903 Merge pull request #258 from vatesfr/marsaudf-fix-nfsHandler
Fix NFS remote handler.
2016-03-03 12:41:42 +01:00
Fabrice Marsaud
628f9bd9b5 Fixed findmnt call 2016-03-03 12:32:22 +01:00
Julien Fontanet
2d791571d5 Merge pull request #257 from vatesfr/julien-f-remove-unnecessary-await
Remove unnecessary `await`s.
2016-03-03 12:28:50 +01:00
Olivier Lambert
ed57127a79 Merge pull request #251 from vatesfr/host-state-unknown
New host state: Unknown.
2016-03-03 12:25:00 +01:00
Julien Fontanet
6d9bcff8e1 Remove unnecessary awaits.
- slightly improve perf
- slightly better stack traces
2016-03-03 12:24:22 +01:00
Julien Fontanet
8126cd1879 New host state: Unknown. 2016-03-03 12:15:30 +01:00
Julien Fontanet
ab34c2261c server.getAll(): fix related to xen-servers mixin. 2016-03-03 11:25:20 +01:00
Olivier Lambert
6953f65970 Merge pull request #256 from vatesfr/fix-vm-recoveryStart
Xapi#startVmOnCd(): fix for HVM (fix vatesfr/xo-web#794).
2016-03-03 10:10:10 +01:00
Julien Fontanet
52073e79fa Xapi#startVmOnCd(): fix for HVM (fix vatesfr/xo-web#794). 2016-03-03 10:02:09 +01:00
Julien Fontanet
8e3484bb17 Merge pull request #252 from vatesfr/better-default-https
Better defaults HTTPs config.
2016-03-02 18:04:05 +01:00
Julien Fontanet
7110da8a36 Better defaults HTTPs config. 2016-03-02 17:12:32 +01:00
Julien Fontanet
7ffd6ded51 Merge pull request #250 from vatesfr/pierre-licenses
Host and pool licenses (Fix vatesfr/xo-web#763)
2016-03-02 16:12:04 +01:00
Julien Fontanet
5e04547ecf Merge pull request #249 from vatesfr/recompute-resource-sets-limits
resourceSet.recomputeAllLimits()
2016-03-02 15:43:40 +01:00
Julien Fontanet
7cbe5f64ce Fix Xo#getXapi(). 2016-03-02 15:41:27 +01:00
Julien Fontanet
47ed78031a Xo#getAllXenServers(). 2016-03-02 15:23:32 +01:00
Pierre
fd3d24b834 getLicenseState 2016-03-02 15:15:10 +01:00
Pierre
c2f607b452 license_params and license_servers in XO host object 2016-03-02 15:15:09 +01:00
Julien Fontanet
b1328bb6e2 resourceSet.recomputeAllLimits() 2016-03-02 15:09:16 +01:00
Julien Fontanet
2a02583e27 Fix Xo#getXapi() with identifier. 2016-03-02 13:48:33 +01:00
Julien Fontanet
cfb49f9136 Merge pull request #247 from vatesfr/too-many-storage-migrates
Xapi#migrateVm(): handle TOO_MANY_STORAGE_MIGRATES (fix vatesfr/xo-we…
2016-03-02 12:47:00 +01:00
Julien Fontanet
5f20091f24 Xapi#migrateVm(): handle TOO_MANY_STORAGE_MIGRATES (fix vatesfr/xo-web#783).
When this error occurs, simply wait 10 seconds and retry.
2016-03-02 12:45:25 +01:00
Julien Fontanet
a37b8e35a1 utils: isInteger(). 2016-03-02 11:30:23 +01:00
Julien Fontanet
84c980c3ea utils: isObject(). 2016-03-02 11:07:13 +01:00
Julien Fontanet
5823057b41 utils: isArray(), isBoolean(), isFunction() & isString(). 2016-03-02 11:02:42 +01:00
Julien Fontanet
024a9b1763 Merge pull request #246 from vatesfr/xo-mixins-2
Xo mixins 2
2016-03-02 10:50:17 +01:00
Julien Fontanet
0425780cd3 vm.create(): fix setting name/description for disks (vatesfr/xo-web#774). 2016-03-02 10:49:15 +01:00
Julien Fontanet
20734dc7f3 Move authorization to a new mixin. 2016-03-02 10:38:06 +01:00
Julien Fontanet
0574c58f16 Move ACLs management to a new mixin. 2016-03-02 10:38:06 +01:00
Julien Fontanet
31e3117190 Move Xen servers management to a new mixin. 2016-03-02 10:38:06 +01:00
Julien Fontanet
f780ba2c5a vm.create(): fix updating existing disks (vatesfr/xo-web#774). 2016-03-02 10:34:44 +01:00
Olivier Lambert
f125b593bf Merge pull request #248 from vatesfr/babel-t7172
Work around Babel T7172 (second attempt).
2016-03-02 10:22:47 +01:00
Julien Fontanet
baee4e185d Work around Babel T7172 (second attempt). 2016-03-02 10:14:50 +01:00
Julien Fontanet
ca8476d466 Work around Babel T7172. 2016-03-02 10:04:12 +01:00
Julien Fontanet
757bf82a78 Merge branch 'stable' into next-release 2016-02-29 09:31:12 +01:00
Olivier Lambert
644887f727 4.14.4 2016-02-28 17:20:17 +01:00
Olivier Lambert
563b643461 Fix NFS mount issues for Linux target 2016-02-28 17:20:02 +01:00
Julien Fontanet
0e4a6fd2e1 Merge pull request #245 from vatesfr/pierre-cpu-weight-in-resource-set
vm.set(): only admins can change cpuWeight if VM in a resource set
2016-02-26 12:29:31 +01:00
Pierre
d452bf1f1c vm.set(): only admins can change cpuWeight if VM in a resource set 2016-02-26 12:22:40 +01:00
Julien Fontanet
126828a813 http-request: another work around for the Babel issue 2016-02-26 11:26:02 +01:00
Julien Fontanet
03dc6fb73a http-request: work around Babel issue. 2016-02-26 11:16:43 +01:00
Julien Fontanet
3653e89714 Merge branch 'stable' into next-release 2016-02-26 10:23:51 +01:00
Julien Fontanet
318dd14e42 4.14.3 2016-02-26 10:23:27 +01:00
Julien Fontanet
2d13844b5d Disable npm {pre,post}version magic for now. 2016-02-26 10:23:20 +01:00
Julien Fontanet
b777b7432a Merge pull request #244 from vatesfr/olivierlambert-nfs-fix
Add v3 parameter for NFS. Fix vatesfr/xo-web/issues/771
2016-02-26 10:12:24 +01:00
Olivier Lambert
6f91c225c2 Add v3 parameter for NFS. Fix vatesfr/xo-web/issues/771 2016-02-26 10:06:18 +01:00
Julien Fontanet
c355e9ca4a Xo/subjects: fix access to Xo private props (fix #755). 2016-02-25 11:36:52 +01:00
Julien Fontanet
4514ea8123 Minor fixes related to promise-toolbox. 2016-02-24 18:22:18 +01:00
Julien Fontanet
a9a1472cb7 Minor fixes related to Babel 6. 2016-02-24 18:22:00 +01:00
Julien Fontanet
250b0eee28 Merge pull request #193 from vatesfr/babel-6
Upgrade to Babel 6.
2016-02-24 16:18:36 +01:00
Julien Fontanet
5cd7527937 Upgrade to Babel 6. 2016-02-24 16:11:35 +01:00
Julien Fontanet
57ebd5bb7a Merge pull request #242 from vatesfr/use-promise-toolbox
Use promise-toolbox.
2016-02-24 16:11:13 +01:00
Julien Fontanet
c18a697d6b Use promise-toolbox. 2016-02-24 15:17:11 +01:00
Julien Fontanet
ad40b72508 4.14.2 2016-02-24 13:51:49 +01:00
Julien Fontanet
3a72e5910d Add preversion/postversion scripts. 2016-02-24 13:50:47 +01:00
Julien Fontanet
8f3eb65a05 Various fixes. 2016-02-24 13:34:12 +01:00
Julien Fontanet
700cd83ff5 4.14.1 2016-02-24 10:34:10 +01:00
Julien Fontanet
0c27881eaf vm.create(): register up-to-date VM object (fix vatesfr/xo-web#749 2016-02-24 10:21:53 +01:00
Julien Fontanet
f7fdc6acd2 4.14.0 2016-02-23 19:10:57 +01:00
Julien Fontanet
2c5f844edc Merge pull request #229 from vatesfr/resource-sets
Resource sets
2016-02-23 19:03:34 +01:00
Julien Fontanet
a253de43c5 vm.create(): correctly handle updating SR on existing disks. 2016-02-23 18:57:00 +01:00
Julien Fontanet
dbaf67a986 vm.set(): correctly count limits for CPUs. 2016-02-23 18:03:54 +01:00
Julien Fontanet
5175d06e37 vm.create(): handle limits on template disks. 2016-02-23 17:53:41 +01:00
Julien Fontanet
651a27b558 resource set: limit.available cannot be > limit.total. 2016-02-23 17:16:13 +01:00
Julien Fontanet
fd41f8def6 vm.delete(): correctly free resource set limits. 2016-02-23 17:12:19 +01:00
Julien Fontanet
208ea04fd5 resourceSet.set(): better handling of limits. 2016-02-23 15:20:20 +01:00
Julien Fontanet
5ee83a1af9 Merge pull request #241 from vatesfr/a-schedule-links-a-function
Scheduler is now in scheduling.js and use ScheduleFn function.
2016-02-23 15:06:55 +01:00
wescoeur
901c7704f4 Scheduler is now in scheduling.js and use ScheduleFn function. 2016-02-23 15:01:30 +01:00
Julien Fontanet
c6f7290f92 resourceSet.{create,set}(): limits are optional. 2016-02-23 13:11:40 +01:00
Julien Fontanet
5368eda98b vm.set(): works with resource sets. 2016-02-23 13:06:36 +01:00
Julien Fontanet
7b9be209c8 resourceSet: initial limits. 2016-02-23 10:33:50 +01:00
Julien Fontanet
cee05fea7c Some permission fixes fro vm.*(). 2016-02-22 19:23:32 +01:00
Julien Fontanet
b87acb47e2 Lots of TODOs in vm.*(). 2016-02-22 18:44:18 +01:00
Julien Fontanet
cb192bf9ea vm.convertToTemplate(): require pool admin permission. 2016-02-22 18:30:28 +01:00
Julien Fontanet
16351ba7f3 Merge pull request #236 from vatesfr/abhamonr-vhd-util-without-binary
Remove dependency on vhd-util for VHD merging
2016-02-22 14:02:40 +01:00
wescoeur
96ba128942 Remove vhd-util dependency. Merging vhd works with a node script. (fix vatesfr/xo-web#646) 2016-02-22 13:59:33 +01:00
Julien Fontanet
76c8d4af25 API: fix various permissions. 2016-02-19 16:37:27 +01:00
Julien Fontanet
3ea2b3cc00 vm.createCloudInitConfigDrive(): disable permission check on SR. 2016-02-19 16:37:27 +01:00
Julien Fontanet
0df0936022 resourceSet: fix getAll() and checkConstraints(). 2016-02-19 16:37:27 +01:00
Julien Fontanet
4fc11a7fd3 resourceSet.{add,remove}Subject(): minor fixes. 2016-02-19 16:37:27 +01:00
Julien Fontanet
8c509271a6 resourceSet.getAll(): code has been refactored around streamToArray(). 2016-02-19 16:37:27 +01:00
Julien Fontanet
67d5b63ef9 vm.create(): works with resource sets. 2016-02-19 16:37:27 +01:00
Julien Fontanet
4f999511a6 resourceSet.getAll(): code has been refactored around lightSet(). 2016-02-19 16:37:27 +01:00
Julien Fontanet
cfbf239175 resourceSet.getAll(): works also for non-admins. 2016-02-19 16:37:27 +01:00
Julien Fontanet
1aedf9bb07 resourceSet: fix deletion. 2016-02-19 16:37:27 +01:00
Julien Fontanet
c2d4423720 Fix: start event instead of starting. 2016-02-19 16:37:27 +01:00
Julien Fontanet
c2f7a2620c api: fix access to private Xo properties. 2016-02-19 16:37:27 +01:00
Julien Fontanet
6f0cda34b4 Xo/Subjects: Fix admin user creation. 2016-02-19 16:37:27 +01:00
Julien Fontanet
1a472fdf1f Xo: subjects managements moved into new mixin. 2016-02-19 16:37:27 +01:00
Julien Fontanet
0551f61228 Resource sets: use store. 2016-02-19 16:37:27 +01:00
Julien Fontanet
b900adfddd Promisify store. 2016-02-19 16:37:27 +01:00
Julien Fontanet
0e339daef5 Typo. 2016-02-19 16:37:27 +01:00
Julien Fontanet
5f5733e8b9 Various fixes and additions to resourceSet API. 2016-02-19 16:37:27 +01:00
Julien Fontanet
1372050a7b LevelDbLogger: avoid increments in keys when not necessary. 2016-02-19 16:37:27 +01:00
Julien Fontanet
1960951c5e Initial resource sets. 2016-02-19 16:37:27 +01:00
Julien Fontanet
bc070407c7 generate-indexes: rewritten in Node
- cross-platform
- properly camel-cases identifiers
2016-02-19 16:37:27 +01:00
Julien Fontanet
0172ee0b6b Remove unused import. 2016-02-19 16:37:27 +01:00
Julien Fontanet
2953bc6bb8 loggers: LevelDbLogger now inherits from AbstractLogger. 2016-02-19 16:37:27 +01:00
Julien Fontanet
c0ed3a9e3c Xo#getStore(namespace). 2016-02-19 16:37:27 +01:00
Julien Fontanet
5456e4fe75 Merge branch 'stable' into next-release 2016-02-19 16:36:59 +01:00
Julien Fontanet
867a1e960e Merge pull request #240 from vatesfr/stream-to-array
util: streamToArray(Stream, filter?: Predicate) => Promise.
2016-02-18 17:02:25 +01:00
Julien Fontanet
48dc68c3fe util: streamToArray(Stream, filter?: Predicate) => Promise. 2016-02-18 16:58:17 +01:00
Julien Fontanet
2c719f326b Xapi: lodash.pick() → lodash.pickBy(). 2016-02-18 15:45:28 +01:00
Julien Fontanet
201f92eb93 system.getMethodsInfo(): Fix breakage due to upgrade to Lodash v4. 2016-02-18 15:39:08 +01:00
Julien Fontanet
46f055b216 Merge pull request #137 from vatesfr/abhamonr-job-schema
Add: Job and crossProduct schemas.
2016-02-17 10:29:40 +01:00
Julien Fontanet
08305e679b http-proxy: agent is now the default export. 2016-02-17 09:53:00 +01:00
Julien Fontanet
e9e0b70199 Merge pull request #238 from bartekrutkowski/next-release
Correct the redis protocol in sample config.
2016-02-16 16:49:50 +01:00
Bartek Rutkowski
441d784027 Correct the redis protocol in sample config
The existing sample configuration file documents the Redis uri string with 'tcp://' prefix string, while xo-server actually expects it to be 'redis://' instead.
2016-02-16 15:47:21 +00:00
Julien Fontanet
558956bf55 Merge pull request #237 from vatesfr/http-proxy
HTTP proxy support (fix vatesfr/xo-web#737).
2016-02-16 15:57:29 +01:00
Julien Fontanet
0d8250a3ac HTTP proxy support (fix vatesfr/xo-web#737). 2016-02-16 15:50:28 +01:00
Julien Fontanet
dc1f5826f8 Lodash 4 iteration: no thisArg argument. 2016-02-16 15:50:10 +01:00
Julien Fontanet
06fb06829b Update lodash.* deps. 2016-02-16 11:53:53 +01:00
Julien Fontanet
bbf52d2611 Update deps. 2016-02-16 11:53:22 +01:00
Julien Fontanet
f55a6617e9 Remove graceful-fs: not used directly by xo-server. 2016-02-16 11:48:52 +01:00
Julien Fontanet
3bd273fbdd Merge pull request #235 from vatesfr/abhamonr-avoid-merge-delta-delta
Avoid merge between two delta vdi backups. (fix vatesfr/xo-web#702)
2016-02-11 17:32:28 +01:00
wescoeur
1b64a543f1 Avoid merge between two delta vdi backups. (fix vatesfr/xo-web#702) 2016-02-11 16:42:50 +01:00
Julien Fontanet
97b07f7d42 The main bug tracker is xo-web. 2016-02-08 15:34:13 +01:00
Olivier Lambert
ebb472b8f6 4.13.2 2016-02-05 13:01:49 +01:00
Olivier Lambert
1a2ef6479e Merge pull request #232 from vatesfr/fix-xapi-updateObjectMapProperty
Xapi#_updateObjectMapProperty(): fix major issue.
2016-02-05 13:00:58 +01:00
Olivier Lambert
876c63fe80 4.13.1 2016-02-05 13:00:02 +01:00
Julien Fontanet
32236962f5 Xapi#_updateObjectMapProperty(): fix major issue. 2016-02-05 12:53:39 +01:00
Julien Fontanet
ba66af922f 4.13.0 2016-02-04 19:43:43 +01:00
Julien Fontanet
28b9bbe54f 4.13.0-0 2016-02-04 18:01:04 +01:00
Julien Fontanet
bf6bd7cbdc Merge pull request #230 from vatesfr/pierre-vm-migration-details
Fix intra-pool migration on different SRs
2016-02-04 17:30:14 +01:00
Pierre
ddcb2468a6 Minor fixes 2016-02-04 17:26:27 +01:00
Pierre
f048b58935 Fix intra-pool migration on different SRs 2016-02-04 17:17:09 +01:00
Julien Fontanet
09f6200c2e Merge pull request #209 from vatesfr/abhamonr-checksum-verification-delta-backup
Create and verify checksums for VDI delta backups
2016-02-04 16:12:54 +01:00
wescoeur
354692fb06 Add checksum verification for delta backup on restore/merge. (fix vatesfr/xo-web#617) 2016-02-04 15:22:14 +01:00
Julien Fontanet
2c5858c2e0 Merge pull request #228 from vatesfr/pierre-vm-migration-details
Fix default migration
2016-02-04 15:18:31 +01:00
Pierre
1f41fd0436 Better handle of undefined maps 2016-02-04 11:36:04 +01:00
Pierre
e0bbefdfae Fix default migration 2016-02-04 11:02:43 +01:00
Julien Fontanet
bc6fbb2797 Xo#registerPlugin(): log errors. 2016-02-04 10:32:36 +01:00
Julien Fontanet
b579cf8128 Merge pull request #227 from vatesfr/https-redirect
Can redirect to HTTPs (fix vatesfr/xo-web#626).
2016-02-04 09:55:12 +01:00
Julien Fontanet
a94ed014b7 sample config: add redirectToHttps. 2016-02-04 09:52:37 +01:00
Julien Fontanet
0db991b668 Can redirect to HTTPs. 2016-02-03 17:39:39 +01:00
Julien Fontanet
347ced6942 Merge pull request #214 from vatesfr/better-https
Better https (fix vatesfr/xo-web#685)
2016-02-03 14:32:25 +01:00
Olivier Lambert
5d7a775b2b Merge pull request #225 from vatesfr/xo-acl-resolver
Use xo-acl-resolver.
2016-02-03 14:29:31 +01:00
Julien Fontanet
df732ab4bf Merge pull request #216 from vatesfr/vdi-snapshot-type
VDI-snapshot type
2016-02-03 14:14:42 +01:00
Fabrice Marsaud
31cd3953d6 Fixing VM object properties 2016-02-03 13:56:15 +01:00
Julien Fontanet
4666b13892 Use xo-acl-resolver. 2016-02-03 11:47:02 +01:00
Julien Fontanet
37d7ddb4b0 Merge pull request #224 from vatesfr/pierre-vm-migration-details
Custom VM migration (See vatesfr/xo-web#567)
2016-02-03 11:39:53 +01:00
Fabrice Marsaud
3abbaeb44b resolving VDI snapshots 2016-02-03 10:38:09 +01:00
Fabrice Marsaud
847ea49042 VDI-snapshot type 2016-02-03 09:57:42 +01:00
Julien Fontanet
779068c2ee HTTP security: use Helmet. 2016-02-02 20:49:33 +01:00
Julien Fontanet
140cd6882d Allows full TLS config. 2016-02-02 20:45:06 +01:00
Julien Fontanet
2e295c2391 Merge pull request #213 from vatesfr/fix-cpu-weight
Fixed cpuWeight removal for default
2016-02-02 10:24:32 +01:00
Fabrice Marsaud
596b0995f4 Prepare object values for xapi 2016-02-02 10:11:39 +01:00
Fabrice Marsaud
b61fe97893 Fixed cpuWeight removal for default 2016-02-02 09:55:40 +01:00
Julien Fontanet
209aa2ebe6 Add a TODO. 2016-02-01 17:14:05 +01:00
Julien Fontanet
c03a0e857e Merge pull request #188 from vatesfr/olivierlambert-cpu-weight
Ability to set vCPU weight
2016-02-01 16:00:21 +01:00
Olivier Lambert
2854d698e6 Implement vCPU weight 2016-02-01 15:56:36 +01:00
Pierre
944163be0e Bug fix: VDIs should be on chosen SRs. 2016-01-29 10:07:02 +01:00
Julien Fontanet
269a9eaff0 Xapi: small but important fix concerning imports. 2016-01-28 17:22:44 +01:00
Olivier Lambert
7f9c49cbc4 Merge pull request #208 from vatesfr/pierre-vm-migration-details
Custom VM migration. (See vatesfr/xo-web#567)
2016-01-28 17:02:28 +01:00
Julien Fontanet
2b6bfeeb15 Merge pull request #212 from vatesfr/contrep-better-snapshot-names
continous replication: clearer VM snapshot names.
2016-01-28 16:05:03 +01:00
Julien Fontanet
fa9742bc92 continous replication: clearer VM snapshot names. 2016-01-28 15:57:25 +01:00
Pierre
472e419abc Using forEach instead of for. Minor fixes. 2016-01-28 13:33:43 +01:00
Pierre
169d11387b Custom VM migration (See vatesfr/xo-web#567)
Optional parameters for migratePool:
- Migration network
- Map each VDI to an SR on destination host
- Map each VIF to a network on destination host
2016-01-28 13:33:43 +01:00
Julien Fontanet
e59ac6d947 Fixes regarding #660. 2016-01-28 11:42:06 +01:00
Olivier Lambert
e193b45562 Merge pull request #207 from vatesfr/abhamonr-avoid-metadata-imp-exp-delta-backups
Avoid metadata import/export in delta backups. (fix vatesfr/xo-web#651)
2016-01-28 11:35:04 +01:00
wescoeur
1ac34f810e Avoid metadata import/export in delta backups. (fix vatesfr/xo-web#651) 2016-01-28 11:12:21 +01:00
Olivier Lambert
e65e5c6e5f Merge pull request #211 from vatesfr/marsaudf-clear-logs#661
Marsaudf clear logs#661
2016-01-28 10:59:37 +01:00
Fabrice Marsaud
af6365c76a logger.delete 2016-01-28 09:00:15 +01:00
Julien Fontanet
8c672b23b5 Merge pull request #159 from vatesfr/marsaudf-smb-mounts#338
Remotes refactoring + SMB implementation.
2016-01-27 11:24:52 +01:00
Fabrice Marsaud
3b53f5ac11 fixes 2016-01-27 10:58:16 +01:00
Fabrice Marsaud
ccdc744748 fixes 2016-01-27 10:08:59 +01:00
Fabrice Marsaud
261f0b4bf0 typo fix 2016-01-27 09:11:45 +01:00
Fabrice Marsaud
495b59c2e5 update dependency 2016-01-26 17:34:00 +01:00
Fabrice Marsaud
d6e1c13c39 Handler and remotes reworked 2016-01-26 17:28:27 +01:00
Fabrice Marsaud
f7f13b9e07 PR feedback 2 2016-01-26 09:47:47 +01:00
Fabrice Marsaud
62564d747f Errors moved from API to core 2016-01-25 17:29:18 +01:00
Fabrice Marsaud
1d5d59c4c0 Remote handler reworked 2016-01-25 17:01:14 +01:00
Fabrice Marsaud
e8380b8a12 PR feedback 2016-01-25 11:45:53 +01:00
Fabrice Marsaud
c304d9cc62 No vdi merge through smb 2016-01-25 11:45:53 +01:00
Fabrice Marsaud
aad4ebf287 Remote handlers refactored, and adding a smb handler 2016-01-25 11:45:53 +01:00
Olivier Lambert
6c2f48181c Merge pull request #210 from vatesfr/handle-objects-conflicts
Properly handle multiple XAPI objects with the same XO id.
2016-01-22 16:02:19 +01:00
Julien Fontanet
480b6ff7d6 Properly handle multiple XAPI objects with the same XO id.
When there is a conflict, the existing object keep the place but when
it is removed, the other object (which is in the waiting list) will
take the new place.
2016-01-22 15:57:44 +01:00
Julien Fontanet
4bdd6f972c Remove node-inspector. 2016-01-21 16:44:42 +01:00
Olivier Lambert
6674d8456a Merge pull request #206 from vatesfr/olivierlambert-fixMigration
Correctly use destination host SR and network
2016-01-20 18:31:33 +01:00
Olivier Lambert
d1478ff694 select the correct migration network 2016-01-20 18:12:37 +01:00
Julien Fontanet
cb20d46b74 Merge pull request #205 from vatesfr/abhamonr-fix-avoid-errors-delta-backups
Delta backups: Fix various issues.
2016-01-20 17:36:33 +01:00
Olivier Lambert
9dd2538043 correctly use destination host SR and network 2016-01-20 17:26:47 +01:00
wescoeur
f25136a512 Avoid errors in delta backups. (fix)
- Wait the task end of vdi export.
- Now, in the error case of vdi backup,
  the current vdi snapshot is removed with catch(noop).
2016-01-20 17:19:41 +01:00
Julien Fontanet
03eb56ad2a Xapi#_updateObjectMapProperty(): do no hide remove errors. 2016-01-20 16:04:23 +01:00
Julien Fontanet
2508840701 4.12.1 2016-01-19 12:49:37 +01:00
Julien Fontanet
6e098f5a4f Merge pull request #203 from vatesfr/fix-scheduling
Scheduler: properly use Xo#runJobSequense() (fix vatesfr/xo-web#657).
2016-01-19 12:45:36 +01:00
Julien Fontanet
31b33406fd Scheduler: properly use Xo#runJobSequense() (fix vatesfr/xo-web#657). 2016-01-19 12:12:29 +01:00
Julien Fontanet
7ab7c763ed startup: ignore non existent paths in plugins lookup. 2016-01-19 11:49:07 +01:00
Julien Fontanet
06258e757a 4.12.0 2016-01-18 10:25:41 +01:00
Julien Fontanet
5919b43a21 @mixin(): compatibility with Node 0.12 (fix #202). 2016-01-18 10:18:02 +01:00
Julien Fontanet
7d4b9521e7 Merge pull request #199 from vatesfr/continuous-replication
Continuous VM replication.
2016-01-17 23:51:29 +01:00
Julien Fontanet
f9d2fd7997 Xapi: Ugly hack seems to be working. 2016-01-17 23:28:45 +01:00
Julien Fontanet
bdbc20c3c6 Xapi: fix private put() when length is known. 2016-01-17 21:05:18 +01:00
Julien Fontanet
69d6d03714 Better debugs in Xapi. 2016-01-17 21:03:19 +01:00
Julien Fontanet
f40e1e55b0 Xapi#importVdiContent(): revert to use Promise.all() instead of Promise.race(). 2016-01-17 12:52:41 +01:00
Julien Fontanet
b9082ed838 Xapi#deleteVm(): Correctly remove VDIs with more than one VBD on the same VM. 2016-01-17 12:52:04 +01:00
Julien Fontanet
4edfefa9a2 Homogenise task names. 2016-01-17 12:52:04 +01:00
Julien Fontanet
0f98ee5407 Xapi#importVdiContent(): better task name. 2016-01-17 12:50:44 +01:00
Julien Fontanet
7fdf119873 Temporarily disable the ugly put hack. 2016-01-17 12:50:44 +01:00
Julien Fontanet
3c054e6ea1 Various changes. 2016-01-17 12:50:42 +01:00
Julien Fontanet
98899ece72 Use $ to prefix injected params names. 2016-01-17 12:49:23 +01:00
Julien Fontanet
2061a006d0 Xapi#createDeltaVdi(): correctly set the source of cloned VDI. 2016-01-17 12:49:23 +01:00
Julien Fontanet
5496c2d7fd Various fixes. 2016-01-17 12:49:22 +01:00
Julien Fontanet
d6b862a4a9 Xapi#_createVif(): Various fixes. 2016-01-17 12:49:22 +01:00
Julien Fontanet
d581f8a852 Xapi#importDeltaVm(): explicit error when base VDI is not found. 2016-01-17 12:49:22 +01:00
Julien Fontanet
3a593ee35a Xapi#_createVm(): clearer type handling. 2016-01-17 12:49:22 +01:00
Julien Fontanet
415d34fdaa Xo#copyDeltaVm(): Cancel exports on failures. 2016-01-17 12:49:22 +01:00
Julien Fontanet
7d28191bb5 Xapi#exportDeltaVm(): full export if the base is not found. 2016-01-17 12:49:20 +01:00
Julien Fontanet
e2c7693370 Xapi#importVdiContent(): do not wait for connection closure. 2016-01-17 12:48:38 +01:00
Julien Fontanet
f17ff02f4d Continuous replication: do not rely on metadata import/export. 2016-01-17 12:48:35 +01:00
Julien Fontanet
225043e01d Properly identify last snapshot as future base. 2016-01-16 19:34:35 +01:00
Julien Fontanet
56f78349f8 Xen expects keys(other_config) to be snake or it will change them itself! 2016-01-16 19:34:35 +01:00
Julien Fontanet
8839d4f55a Delete exportDeltaVm() snapshot on failure. 2016-01-16 19:34:34 +01:00
Julien Fontanet
2562aec1d2 Missing space in utils.pDebug(). 2016-01-16 19:34:34 +01:00
Julien Fontanet
db2361be84 Fix createVbd(). 2016-01-16 19:34:34 +01:00
Julien Fontanet
d08fcbfef3 Various fixes. 2016-01-16 19:34:29 +01:00
Julien Fontanet
7601b93e65 Various fixes. 2016-01-16 19:19:51 +01:00
Julien Fontanet
1103ec40e0 Xapi#importDeltaVm(): clean after failure. 2016-01-16 19:19:51 +01:00
Julien Fontanet
af32c7e3db Properly exports vm.deltaCopy(). 2016-01-16 19:19:51 +01:00
Julien Fontanet
170918eb3b Initial continuous replication. 2016-01-16 19:19:51 +01:00
Julien Fontanet
a91e615a8d @deferrable.onSuccess() 2016-01-16 19:19:51 +01:00
Julien Fontanet
cc92c26fe3 Xapi#_importVdiContent() 2016-01-16 19:19:46 +01:00
Julien Fontanet
937135db32 Xapi#_exportVdi() 2016-01-16 18:57:15 +01:00
Julien Fontanet
01366558b4 Xapi#_deleteVbd() 2016-01-16 18:57:15 +01:00
Julien Fontanet
b0dbd54ea4 Xapi#_disconnectVbd() 2016-01-16 18:57:15 +01:00
Julien Fontanet
f113915307 Xapi#_updateObjectMapProperty() can remove a property. 2016-01-16 18:57:15 +01:00
Julien Fontanet
0a3c3d9bb1 Xapi#remoteCopyVm() falls back on local copy if possible. 2016-01-16 18:50:09 +01:00
Julien Fontanet
ba2e005c3e Merge pull request #201 from vatesfr/custom-http-request
Custom HTTP request implementation instead of got.
2016-01-16 18:31:21 +01:00
Julien Fontanet
b9ea52d65f Add missing space in forbidden operations description. 2016-01-16 18:27:23 +01:00
Julien Fontanet
f1e328d333 Better error handling in patch unzipping. 2016-01-16 18:13:13 +01:00
Julien Fontanet
23f1965398 Custom HTTP request implementation instead of got. 2016-01-16 18:13:04 +01:00
Olivier Lambert
fc82f185cb Merge pull request #200 from vatesfr/abhamonr-forever-forward-incremental-backup-fix
Old vdi bases must be removed at the backup end.
2016-01-15 14:31:04 +01:00
wescoeur
56b25f373f Old vdi bases must be removed at the backup end. 2016-01-15 14:20:11 +01:00
Olivier Lambert
1ac6add122 Merge pull request #196 from vatesfr/abhamonr-forever-forward-incremental-backup
Forever forward incremental backup (fix vatesfr/xo-web#576)
2016-01-15 14:13:02 +01:00
wescoeur
91b1a903f9 Fix rejected backup. 2016-01-15 13:48:15 +01:00
wescoeur
a8d6654ef5 Forever forward incremental backup (fix vatesfr/xo-web#576) 2016-01-15 13:12:05 +01:00
Olivier Lambert
63093b1be6 Merge pull request #198 from vatesfr/abhamonr-vbd-set-bootable-fix-getxapi
vbd.setBootable use xo.getXapi() instead of xo.getXAPI()
2016-01-14 16:28:16 +01:00
wescoeur
60abe8f37e vbd.setBootable use xo.getXapi() instead of xo.getXAPI() 2016-01-14 16:22:16 +01:00
Olivier Lambert
7ba3909aa1 Merge pull request #175 from vatesfr/abhamonr-button-bootable-disk
Add vbd.setBootable api call.
2016-01-14 16:04:30 +01:00
Julien Fontanet
eecdba2d05 Merge pull request #197 from vatesfr/deferrable-decorator
deferrable() decorator.
2016-01-14 14:33:20 +01:00
Julien Fontanet
7bdc005aa7 @deferrable() works with async functions. 2016-01-14 14:24:09 +01:00
Julien Fontanet
d46703fdc4 Cosmetic changes in decorators spec. 2016-01-14 11:58:18 +01:00
Julien Fontanet
e4aa85f603 Cosmetic changes in decorators. 2016-01-14 11:58:18 +01:00
Julien Fontanet
233124ef50 deferrable.onFailure() 2016-01-14 11:58:11 +01:00
Julien Fontanet
36a3012de2 deferrable() decorator. 2016-01-14 11:16:51 +01:00
Olivier Lambert
2b4ee96ed7 Fix issue vatesfr/xo-web/issues/643 2016-01-13 18:55:35 +01:00
Julien Fontanet
85a2afd55c Add --safe-mode which do not registers plugins. 2016-01-13 15:53:55 +01:00
Julien Fontanet
6cd0d8456a Fix plugins (broken by Xo split). 2016-01-13 15:22:14 +01:00
Julien Fontanet
7750a0a773 Integrate api/xo-mixins indexes to the build. 2016-01-13 15:21:03 +01:00
Julien Fontanet
a5364b9257 Camel case: Xo#getXAPI() → Xo#getXapi(). 2016-01-13 14:39:40 +01:00
Julien Fontanet
e0e7b1406d Fix backups listing (broken by Xo split). 2016-01-13 12:12:40 +01:00
Julien Fontanet
38b67a0002 Merge pull request #192 from vatesfr/mixins
Split Xo with mixins.
2016-01-13 11:47:49 +01:00
Julien Fontanet
18dd4f8a52 Print start/stop errors. 2016-01-13 11:40:52 +01:00
Julien Fontanet
879f9b4ea9 Remove listeners after start/stop. 2016-01-13 11:40:51 +01:00
Julien Fontanet
3db0dda67a Fix a race condition in the scheduler. 2016-01-13 11:40:51 +01:00
Julien Fontanet
ed9ee15b90 Expose Xo#scheduler. 2016-01-13 11:40:51 +01:00
Julien Fontanet
44ff85e8e9 Rename Xo {start,stop}{,ing} events. 2016-01-13 11:40:51 +01:00
Julien Fontanet
cb07e9ba11 Split Xo with mixins. 2016-01-13 11:40:48 +01:00
Julien Fontanet
bfe05ce5fc Merge pull request #184 from vatesfr/abhamonr-disable-vm-start-during-delta-import
Disable vm start during delta import and explicit notification.
2016-01-13 11:25:56 +01:00
wescoeur
64ee23cec0 Disable vm start during delta import and explicit notification. (fix vatesfr/xo-web#613) 2016-01-13 11:20:58 +01:00
Julien Fontanet
c022d3c4a4 Merge pull request #182 from vatesfr/abhamonr-properly-remove-vdi-backups
Only delete VDI exports when VM backup is successful.
2016-01-13 10:38:15 +01:00
wescoeur
69c764301f Only delete VDI exports when VM backup is successful (fix vatesfr/xo-web#644). 2016-01-13 10:33:44 +01:00
Julien Fontanet
2f777daef6 Merge pull request #168 from vatesfr/cleaner-xo-stop
xo-server should properly stops on SIGINT/SIGTERM.
2016-01-12 17:38:30 +01:00
Julien Fontanet
a10bf7330e xo-server should properly stops on SIGINT/SIGTERM. 2016-01-12 17:33:32 +01:00
Julien Fontanet
782bb5967d Update level-party to 3.0.4. 2016-01-12 15:17:15 +01:00
Olivier Lambert
aeb2f55f0d Merge pull request #191 from vatesfr/prevent-concurrent-schedule-runs
A schedule cannot have concurrent runs (fix vatesfr/xo-web#642).
2016-01-11 14:43:24 +01:00
Julien Fontanet
ae68749b1b A schedule cannot have concurrent runs (fix vatesfr/xo-web#642). 2016-01-11 14:00:52 +01:00
Julien Fontanet
a3c25d56a0 Update deps. 2016-01-08 18:39:59 +01:00
Julien Fontanet
d2b9cc8df9 Merge pull request #189 from vatesfr/olivierlambert-change-name-during-import
Change VM name during VM delta import. Fix vatesfr/xo-web/issues/641
2016-01-07 14:08:45 +01:00
Olivier Lambert
2027daa75c Change name during VM delta import. Fix vatesfr/xo-web/issues/641 2016-01-07 14:02:53 +01:00
Julien Fontanet
f3493a08bd Api#addMethod() returns a remover function. 2016-01-05 18:16:04 +01:00
Julien Fontanet
f3963269ae Initialize FAT buffer with null bytes. 2016-01-04 14:43:49 +01:00
Julien Fontanet
ae2212c245 Merge pull request #183 from vatesfr/pierre-delete-running-vm
Delete not halted VMs. (vatesfr/xo-web/issues/616)
2015-12-31 09:48:37 +01:00
Julien Fontanet
3a19ac4c93 Merge pull request #187 from vatesfr/olivierlambert-vif-deletion
VIF delete typo. Fix issue vatesfr/xo-web/issues/632
2015-12-30 20:01:49 +01:00
Olivier Lambert
666f546cf0 VIF delete typo. Fix issue vatesfr/xo-web/issues/632 2015-12-30 19:57:48 +01:00
Julien Fontanet
464f57d7da Merge pull request #186 from vatesfr/olivierlambert-custom-templates
add 'install_repository' support for vatesfr/xo-web/issues/627
2015-12-30 17:21:12 +01:00
Olivier Lambert
2a192f33a1 add 'install_repository' support for vatesfr/xo-web/issues/627 2015-12-30 17:12:26 +01:00
Julien Fontanet
9ca2674261 Make unhandled rejected promises visible on exit. 2015-12-29 10:39:26 +01:00
Julien Fontanet
24bc91dc0c Minor optimizations. 2015-12-23 13:57:29 +01:00
Julien Fontanet
cf2d5b502f Do not remove VDIs attached to other VMs. 2015-12-22 16:27:34 +01:00
Julien Fontanet
61450ef602 Typo. 2015-12-22 16:24:50 +01:00
Julien Fontanet
78f1d1738e Properly ignore snapshot deletion failures after export. 2015-12-22 16:24:00 +01:00
Pierre
9f595cf5f7 Delete not halted VMs. (See vatesfr/xo-web#616) 2015-12-22 15:45:13 +01:00
Julien Fontanet
25b8e49975 4.11.0 2015-12-22 13:35:24 +01:00
Julien Fontanet
d40086cd13 Merge branch 'next-release' into stable 2015-12-22 13:34:57 +01:00
Olivier Lambert
8f9d8d93b9 Merge pull request #181 from vatesfr/fix-vbd-state-after-metadata-import
Fix vbd state after metadata import
2015-12-22 12:23:38 +01:00
Julien Fontanet
1080c10004 Call VM.power_state_reset after a metadata import (fix vatesfr/xo-web#615). 2015-12-22 12:18:02 +01:00
Julien Fontanet
866aeca220 Revert "Snapshots running VM for metadata export (see vatesfr/xo-web#615)."
This reverts commit 121b3afc61.

It is not possible to export metadata of a snapshot.
2015-12-22 11:41:04 +01:00
Julien Fontanet
121b3afc61 Snapshots running VM for metadata export (see vatesfr/xo-web#615). 2015-12-22 11:26:03 +01:00
Julien Fontanet
e8406b04b4 Merge pull request #180 from vatesfr/fix-memory-issue-importVmBackup
Fix memory issue on Xo#importVmBackup().
2015-12-21 19:46:06 +01:00
Julien Fontanet
8e7fe81806 Disable async traces for now (fix vatesfr/xo-web#608). 2015-12-21 19:20:48 +01:00
Olivier Lambert
852807b5d7 Merge pull request #178 from vatesfr/abhamonr-incremental-backups-integration
Some corrections for integration.
2015-12-21 19:05:57 +01:00
Olivier Lambert
9928d47fa2 PR comment review 2015-12-21 19:00:36 +01:00
wescoeur
412a1bd62a Some corrections for delta integration in xo-web.
- List delta backups in subfolders.
- Fix unhandled exception. (ENOENT)
- ...
2015-12-21 17:48:34 +01:00
Julien Fontanet
b290520951 vm.import() accepts a SR id. 2015-12-21 11:13:42 +01:00
Olivier Lambert
dde677b6d3 do NOT backup a CD drive 2015-12-18 21:45:03 +01:00
Julien Fontanet
75030847bd Merge pull request #177 from vatesfr/abhamonr-fix-remote-importVm
The vm import call use a sr instead of a host.
2015-12-18 17:23:48 +01:00
wescoeur
e7b9cb76bc The vm import call use a sr instead of a host. 2015-12-18 17:18:39 +01:00
Olivier Lambert
e96c4c0dd3 restore accidently removed code 2015-12-18 16:56:25 +01:00
Julien Fontanet
b553b3fa50 Merge pull request #176 from vatesfr/olivierlambert-xenstorefix
Fix undefined xenstore_data sent to XAPI
2015-12-18 16:06:50 +01:00
Olivier Lambert
c6fb924b8f Fix undefined xenstore_data sent to XAPI 2015-12-18 16:00:06 +01:00
Julien Fontanet
b13844c4a6 Merge pull request #170 from vatesfr/pierre-read-only-connection
Connection to a Xen Server in read-only mode. (Fix vatesfr/xo-web#439)
2015-12-18 12:09:34 +01:00
Pierre
ab6c83a3fc Connection to a Xen Server in read-only mode. (Fix vatesfr/xo-web#439)
`updateXenServer` applies changes in database and also changes the connection's read-only status if the client is connected to this server.
2015-12-18 11:59:54 +01:00
Julien Fontanet
7e0a97973f Merge pull request #163 from vatesfr/abhamonr-incremental-backups
Implement rolling delta VM backup and import. (vatesfr/xo-web#494)
2015-12-18 11:24:31 +01:00
wescoeur
6a8a79bba5 Implement rolling delta VM backup and import. (vatesfr/xo-web#494) 2015-12-18 11:20:13 +01:00
wescoeur
4a0c58c50a Add vbd.setBootable api call. (fix vatesfr/xo-web/#583) 2015-12-17 17:07:35 +01:00
Julien Fontanet
eb0c963332 Coding style. 2015-12-17 16:21:25 +01:00
Julien Fontanet
023fe82932 Merge pull request #150 from vatesfr/olivierlambert-configdrive
Generic CloudConfig Drive
2015-12-16 18:19:51 +01:00
Julien Fontanet
2e1a06c7bf Generic Cloud Config drive. Fix vatesfr/xo-web/issues/549 2015-12-16 18:12:47 +01:00
Julien Fontanet
8b6961d40c VDI.resize{,_online} expect a string contrary to what the doc says. 2015-12-16 16:33:55 +01:00
Julien Fontanet
53351877da Fix typo: size → parseSize. 2015-12-16 15:48:33 +01:00
Julien Fontanet
522445894e Always parse sizes. 2015-12-16 15:36:01 +01:00
Julien Fontanet
550351bb16 Merge pull request #174 from vatesfr/abhamonr-import-vdi-content
Import VDI content is implemented.
2015-12-16 15:06:32 +01:00
wescoeur
328adbb56f Import VDI content is implemented. 2015-12-16 14:59:53 +01:00
Julien Fontanet
44a36bbba3 Use human-format v6 (much nicer with incorrect casing). 2015-12-16 13:17:07 +01:00
Julien Fontanet
4cc4adeda6 disk.{create,resize}() accept integers for size. 2015-12-16 11:34:46 +01:00
Julien Fontanet
c14e6f2a63 disk.resize() accepts human readable size. 2015-12-16 11:34:46 +01:00
Julien Fontanet
cfcb2d54d8 Merge pull request #172 from vatesfr/olivierlambert-vdimove
Allow offline VDI moving. Fix vatesfr/xo-web#591
2015-12-16 10:46:33 +01:00
Julien Fontanet
010d60e504 Coding style. 2015-12-16 09:46:39 +01:00
Julien Fontanet
eabde07ff6 Remove incorrect export. 2015-12-16 09:46:39 +01:00
Olivier Lambert
be19ad5f2a Allow offline VDI moving. Fix https://github.com/vatesfr/xo-web/issues/591 2015-12-15 19:07:20 +01:00
Julien Fontanet
d1d0816961 Merge pull request #171 from vatesfr/olivierlambert-existingdisks
allow edition of existing disks during VM creation
2015-12-15 17:56:38 +01:00
Olivier Lambert
7be7170504 allow edition of existing disks during VM creation 2015-12-15 17:51:03 +01:00
Julien Fontanet
478272f515 Merge pull request #167 from vatesfr/remoteCopyVm-set-nameLabel-asap
Xapi#remoteCopyVm() sets name label ASAP.
2015-12-15 13:49:13 +01:00
Julien Fontanet
09af6958c8 Xapi#remoteCopyVm() sets name label ASAP. 2015-12-15 11:35:03 +01:00
Julien Fontanet
adb3a2b64e Merge pull request #169 from vatesfr/olivierlambert-vdiresize
Clean VDI resize support
2015-12-14 17:09:12 +01:00
Olivier Lambert
1ee7e842dc Clean VDI resize support 2015-12-14 17:05:59 +01:00
Julien Fontanet
b080a57406 Merge pull request #162 from vatesfr/pierre-aborting-vm-export-cancels-operation
VM-export interruption properly transferred to Xen
2015-12-11 18:18:05 +01:00
Julien Fontanet
7c017e345a Minor updates. 2015-12-11 18:02:41 +01:00
Pierre
4b91343155 VM-export interruption properly transferred to Xen. (Fix vatesfr/xo-web#490)
When the connection with the client is lost, the export task is cancelled and the connection is closed.
As the task is over, the snapshot used for the export is deleted.

Cancelling the task is useless as it is cancelled by Xen
2015-12-11 18:02:41 +01:00
Julien Fontanet
02a3df8ad0 Merge pull request #164 from vatesfr/olivierlambert-import-metadata
Support VM metadata import. Fix vatesfr/xo-web#579
2015-12-11 17:01:47 +01:00
Olivier Lambert
6a7080f4ee Support VM metadata import. Fix vatesfr/xo-web#579 2015-12-11 16:56:56 +01:00
Julien Fontanet
4547042577 Fix issue in utils.createRawObject(). 2015-12-10 14:06:22 +01:00
Julien Fontanet
0e39eea7f8 Always use noop from utils. 2015-12-10 14:06:22 +01:00
Olivier Lambert
1e5aefea63 Merge pull request #161 from vatesfr/olivierlambert-set-default-sr
Set default sr
2015-12-10 12:14:27 +01:00
Olivier Lambert
02c4f333b0 minor fixes 2015-12-10 12:08:30 +01:00
Olivier Lambert
1e8fc4020b Add setDefaultSr, fix #572 2015-12-10 11:01:05 +01:00
Olivier Lambert
f969701ac1 Merge pull request #155 from vatesfr/pierre-plugins-autodiscovery
Installed plugins are detected even if the config file does not show …
2015-12-09 20:41:46 +01:00
Olivier Lambert
b236243857 Merge pull request #160 from vatesfr/marsaudf-no-backup-if-unmounted
Rolling backup fails immediatly if remote is disabled. Fix https://github.com/vatesfr/xo-web/issues/561
2015-12-09 19:13:01 +01:00
Fabrice Marsaud
39edc64922 Rolling backup fails immediatly if remote is disabled 2015-12-09 17:18:50 +01:00
Pierre
f22ece403f Installed plugins are automatically detected when the server starts. They are no more loaded from the config file.
Plugins (ie installed modules of which names start with `xo-server-`) are automatically detected and registered from `${__dirname}/node_modules/` and `/usr/local/lib/node_modules/`.
2015-12-09 16:56:48 +01:00
Julien Fontanet
f5423bb314 Merge pull request #158 from vatesfr/olivierlambert-recoveryStart
Generic recovery start (PV or HVM)
2015-12-07 18:11:03 +01:00
Julien Fontanet
b1e5945ebe Xapi#startVm() and Xapi#startVmOnCd() (fix vatesfr/xo-web#563). 2015-12-07 18:02:55 +01:00
Julien Fontanet
76b5be8171 Revert "Freeze config object before configuring plugin."
This reverts commit 789f51bd2a.

The change introduced issues with nodemailer used in xo-server-transport-email.
2015-12-03 16:00:32 +01:00
Julien Fontanet
804bca2041 Merge pull request #148 from vatesfr/abhamonr-purge-plugin-config
The plugins configurations can be cleaned.
2015-12-03 14:37:32 +01:00
Julien Fontanet
10602b47b4 4.10.2 2015-12-03 12:20:48 +01:00
Julien Fontanet
8d7c522596 Merge pull request #154 from vatesfr/julienf-fix-patches-for-6.1
Fix patches handling for XenServer 6.1.
2015-12-03 12:20:05 +01:00
Julien Fontanet
3ac455c5a7 Fix patches handling for XenServer 6.1. 2015-12-03 12:16:56 +01:00
Julien Fontanet
2b19a459df Merge pull request #152 from vatesfr/julienf-handle-xapiToXo-failures
Handle XAPI to XO objects failures.
2015-12-03 12:15:29 +01:00
Julien Fontanet
41ba2d9bf6 Properly schedule retry for the next loop. 2015-12-03 12:10:29 +01:00
Julien Fontanet
a7b5eb69d3 Handle XAPI to XO objects failures. 2015-12-03 11:13:00 +01:00
Julien Fontanet
67c209bb5e Properly handle the case where the pool object is not here yet. 2015-12-03 10:27:23 +01:00
Julien Fontanet
a6d436d9ea 4.10.1 2015-12-02 17:45:30 +01:00
Julien Fontanet
652c784e13 Update xen-api to 0.6.8 (fix vatesfr/xo-web#552). 2015-12-02 17:41:32 +01:00
wescoeur
a0a3b7a158 The plugins configurations can be cleaned. 2015-12-02 16:15:23 +01:00
Julien Fontanet
789f51bd2a Freeze config object before configuring plugin. 2015-12-02 15:20:38 +01:00
Olivier Lambert
c2f1a74f96 Merge pull request #149 from vatesfr/julienf-fix-vm-migration-collision
Use a different id (opaque ref) for VMs which are under migration.
2015-11-30 17:40:31 +01:00
Julien Fontanet
a9ed7a3f3b Use a different id (opaque ref) for VMs which are under migration. 2015-11-30 17:34:12 +01:00
Julien Fontanet
b348e88a5f 4.10.0 2015-11-27 14:24:08 +01:00
Julien Fontanet
1615395866 Merge branch 'next-release' into stable 2015-11-27 14:23:57 +01:00
Julien Fontanet
e483abcad0 Merge pull request #130 from vatesfr/marsaudf-generic-job-schedules
Enhancements to prepare generic job scheduling
2015-11-27 12:03:27 +01:00
Fabrice Marsaud
12b6760f6e Extend job & schedule API with job & schedule names, and job.runSequence 2015-11-27 11:56:37 +01:00
Julien Fontanet
6fde6d7eac Expose plugin config validation errors (vatesfr/xo-web#530). 2015-11-26 16:18:39 +01:00
Julien Fontanet
a7ef891217 Merge pull request #146 from vatesfr/abhamonr-abort-vm-export-import
Start VM export only when necessary.
2015-11-26 16:10:59 +01:00
wescoeur
8f22dfe87b Start VM export only when necessary. 2015-11-26 15:42:07 +01:00
Julien Fontanet
2dc7fab39a Merge pull request #134 from vatesfr/abhamonr-backup-jobs-notifications
Xo event `job:terminated` on job termination.
2015-11-26 15:29:39 +01:00
wescoeur
74cb2e3c63 'job:terminated' signal is emitted after job execution. (with one executionStatus object) 2015-11-26 11:07:37 +01:00
Julien Fontanet
6e763a58f1 Expose UUIDs on all Xapi objects which have one. 2015-11-25 16:53:25 +01:00
Julien Fontanet
a8e72ed410 Merge pull request #140 from vatesfr/julienf-xo-defineProperty
Xo#defineProperty() to properly expose attributes
2015-11-25 14:16:55 +01:00
Julien Fontanet
fcdfd5f936 Merge pull request #139 from vatesfr/abhamonr-remember-disabled-servers
Save enabled state for each server
2015-11-25 13:54:44 +01:00
Julien Fontanet
f1faa463c1 Xo#defineProperty() allows (plugins) to define property on Xo instance 2015-11-25 12:27:09 +01:00
wescoeur
a0f4952b54 Save enabled state for each server 2015-11-25 12:15:15 +01:00
Olivier Lambert
bd82ded07d automatically set autopoweron on the pool 2015-11-25 12:06:16 +01:00
Julien Fontanet
016e17dedb Merge pull request #145 from vatesfr/julienf-pool-autopoweron
Xapi#setPoolProperties() supports autoPowerOn.
2015-11-25 12:03:23 +01:00
Julien Fontanet
5cd3e1b368 Xapi#setPoolProperties() supports autoPowerOn. 2015-11-25 11:03:33 +01:00
Julien Fontanet
b2b39458da Merge pull request #144 from vatesfr/julienf-fix-redis-items-updates
Fix items updates in Redis.
2015-11-25 10:57:54 +01:00
wescoeur
556bbe394d Add: Job and params-vector schemas. 2015-11-25 10:44:40 +01:00
Julien Fontanet
07288b3f26 Fix items updates in Redis. 2015-11-25 10:39:50 +01:00
Julien Fontanet
90f79b7708 Shallow copy the configuration object before configuring a plugin. (fix vatesfr/xo-web#513) 2015-11-24 11:25:52 +01:00
Julien Fontanet
e220786a20 Merge pull request #142 from vatesfr/marsaudf-parameter-fix
Fix: group.getAll() has no params.
2015-11-24 10:49:51 +01:00
Fabrice Marsaud
f16b993294 Removed unwanted API parameter 2015-11-24 10:42:43 +01:00
Julien Fontanet
c241bea3bf Add vendor config file. 2015-11-20 18:28:32 +01:00
Julien Fontanet
084654cd3c Merge branch 'stable' into next-release 2015-11-20 18:17:08 +01:00
Julien Fontanet
d21742afb6 4.9.2 2015-11-20 18:05:26 +01:00
Julien Fontanet
b5259384e8 Merge pull request #138 from vatesfr/julienf-fix-tokens-expiration
Auth tokens expires after one month (side effect: remove old tokens).
2015-11-20 18:05:03 +01:00
Julien Fontanet
bf78ad9fbe Auth tokens expires after one month (side effect: remove old tokens). 2015-11-20 17:42:18 +01:00
Olivier Lambert
ab3577c369 Merge pull request #136 from vatesfr/olivierlambert-cloudconfig
Cloud config management for CoreOS
2015-11-20 17:30:53 +01:00
Olivier Lambert
6efb90c94e Merge pull request #135 from vatesfr/pierre-emergency-host-shutdown
emergencyHostShutdown(hostId) : suspends all the VMs running on the host
2015-11-20 17:30:27 +01:00
Olivier Lambert
cbcc400eb4 Cloud config management for CoreOS 2015-11-20 17:12:12 +01:00
Julien Fontanet
15aec7da7e vm.clone() requires permissions on SRs. 2015-11-20 16:19:33 +01:00
Julien Fontanet
46535e4f56 Utils: pAll() & pReflect() 2015-11-20 15:31:55 +01:00
Julien Fontanet
e3f945c079 Minor fixes. 2015-11-20 14:34:07 +01:00
Julien Fontanet
04239c57fe pSettle() returns an object for an object. 2015-11-20 11:42:49 +01:00
Pierre
ad4439ed55 emergencyHostShutdown(hostId) : suspends all the VMs running on the host and then shuts the host down 2015-11-20 11:26:03 +01:00
Julien Fontanet
9fe3ef430f More tests for pSettle(). 2015-11-20 10:43:56 +01:00
Julien Fontanet
ff30773097 Fix pSettle() to accept non-promises. 2015-11-20 10:43:56 +01:00
Julien Fontanet
f7531d1e18 pSettle() accepts either arrays or objects. 2015-11-20 10:43:56 +01:00
Olivier Lambert
658008ab64 add comment for quiesce 2015-11-19 16:03:28 +01:00
Olivier Lambert
b089d63112 allow snapshots on halted VMs 2015-11-19 16:01:08 +01:00
Julien Fontanet
ee9b1b7f57 Merge pull request #133 from vatesfr/abhamonr-validate-config-plugins-registration
Avoid plugin loading if config is not valid
2015-11-19 14:40:33 +01:00
wescoeur
cd0fc8176f Avoid plugin loading if config is not valid 2015-11-19 13:51:25 +01:00
Julien Fontanet
8e291e3e46 Define CoffeeScript API modules as ES6 (fix default value). 2015-11-19 13:13:53 +01:00
Julien Fontanet
e3024076cd Merge pull request #131 from vatesfr/abhamonr-delete-user-with-tokens
remove tokens on user deletion
2015-11-19 12:55:38 +01:00
wescoeur
6105874abc remove tokens on user deletion 2015-11-19 12:40:31 +01:00
Julien Fontanet
1855f7829d Advanced setting: verboseApiLogsOnErrors. 2015-11-19 11:11:54 +01:00
Julien Fontanet
456e8bd9c0 New FIXME. 2015-11-19 11:11:54 +01:00
Julien Fontanet
d5f2efac26 Merge pull request #132 from vatesfr/abhamonr-add-leveldown-dep
Add leveldown dep
2015-11-19 10:45:17 +01:00
wescoeur
21e692623c Add leveldown dep 2015-11-19 10:40:11 +01:00
Julien Fontanet
80e9589af5 Initial --repair command in xo-server-logs. 2015-11-19 10:11:01 +01:00
Julien Fontanet
b2b9ae0677 Quick fix for groups.getAll() attributes. 2015-11-19 09:26:34 +01:00
Julien Fontanet
63122905e6 Comments. 2015-11-18 18:36:27 +01:00
Julien Fontanet
f99b6f4646 Merge pull request #123 from vatesfr/abhamonr-logs-cli
CLI to explore xo-server's logs.
2015-11-18 17:01:26 +01:00
wescoeur
39090c2a22 Logs CLI:
- Can print logs for one namespace or all namespaces
- Can sort logs since one start timestamp/until one end timestamp
- The sort results can be limited by one value
2015-11-18 16:57:28 +01:00
Julien Fontanet
76baa8c791 Minor fix. 2015-11-18 16:40:08 +01:00
Julien Fontanet
74e4b9d6d2 Merge imports. 2015-11-18 15:24:38 +01:00
Julien Fontanet
bbfc5039f7 Merge pull request #129 from vatesfr/abhamonr-ghost-user
Avoid ghost user and ghost group.
2015-11-18 15:14:31 +01:00
wescoeur
b2fd694483 Avoid ghost user and ghost group. 2015-11-18 15:02:54 +01:00
Julien Fontanet
b03f38ff22 Include user name in console proxy logs. 2015-11-17 16:24:22 +01:00
Julien Fontanet
fe48811047 Include user name in API logs. 2015-11-17 16:24:07 +01:00
Julien Fontanet
bd9396b031 Ability to not create users on first sign in (fix #497). 2015-11-17 15:59:31 +01:00
Julien Fontanet
f0497ec16d Move default configuration to config.json. 2015-11-17 15:49:45 +01:00
Julien Fontanet
7e9e179fa7 Minor fixes. 2015-11-17 15:01:58 +01:00
Julien Fontanet
de62464ad8 Improve security: check token for console access. 2015-11-17 15:01:58 +01:00
Julien Fontanet
f6911ca195 Merge pull request #128 from vatesfr/olivierlambert-setbootorder
Rename vm.bootOrder() to vm.setBootOrder() and ensure VM is HVM.
2015-11-17 15:00:03 +01:00
Olivier Lambert
aec09ed8d2 Rename vm.bootOrder() to vm.setBootOrder() and ensure VM is HVM 2015-11-17 14:56:15 +01:00
Julien Fontanet
51a983e460 Logs clients IPs for WebSocket connections. 2015-11-17 13:24:07 +01:00
Julien Fontanet
0eb46e29c7 Merge pull request #122 from vatesfr/fix-poolPatches-removal
Properly remove objects for which `xo.id !== xapi.$id`.
2015-11-17 11:00:05 +01:00
Julien Fontanet
5ee11c7b6b Properly remove objects for which xo.id !== xapi.$id. 2015-11-17 10:33:56 +01:00
Olivier Lambert
b55accd76f add tag for quiesced snapshots 2015-11-16 12:41:16 +01:00
Julien Fontanet
fef2be1bc7 Merge pull request #125 from vatesfr/olivierlambert-snapquiesce
Add VM snapshot quiesce support
2015-11-16 10:59:39 +01:00
Olivier Lambert
0b3858f91d Add VM snapshot quiesce support 2015-11-16 10:55:51 +01:00
Julien Fontanet
d07ea1b337 Explicit Node versions compatibility. 2015-11-16 10:31:28 +01:00
Julien Fontanet
7e2dbc7358 4.9.1 2015-11-13 17:08:54 +01:00
Julien Fontanet
c676f08a7c Use correct host to import VM without known length. 2015-11-13 16:53:00 +01:00
Julien Fontanet
92f24b5728 Sets a long timeout for vm.import(). 2015-11-13 16:08:44 +01:00
Julien Fontanet
0254e71435 4.9.0 2015-11-13 11:27:40 +01:00
Julien Fontanet
2972fc5814 Merge pull request #124 from vatesfr/abhamonr-logs-avoid-sublevel
avoid sublevel for logs
2015-11-13 11:08:44 +01:00
wescoeur
975c96217c avoid sublevel for logs 2015-11-13 11:03:02 +01:00
Julien Fontanet
c30c1848bc Fix VM naming in vm.rollingDrCopy(). 2015-11-12 18:21:35 +01:00
Julien Fontanet
94615d3b36 Fix optional options in Xapi#remoteCopyVm(). 2015-11-12 18:21:12 +01:00
Julien Fontanet
37a00f0d16 Minor fixes. 2015-11-12 15:25:34 +01:00
Julien Fontanet
0dbe70f5af vm.copy() handles running VMs and compression can be disabled. 2015-11-12 11:02:05 +01:00
Fabrice Marsaud
7584374b0b Merge pull request #119 from vatesfr/marsaudf-DR-API
Disaster recovery feature
2015-11-12 10:24:57 +01:00
Fabrice Marsaud
71ca51dc1a Disaster recovery feature 2015-11-12 10:20:57 +01:00
Fabrice Marsaud
aa81e72e45 Merge pull request #115 from vatesfr/abhamonr-logger-module
Logger is implemented.
2015-11-12 10:16:04 +01:00
wescoeur
9954bb9c15 Logger is implemented.
Logger save jobs events : (start, end, call start, call end)
Logs can be obtained in api.
2015-11-12 09:59:56 +01:00
Julien Fontanet
e5c0250423 Xapi#importVm() crash on failure. 2015-11-10 21:28:01 +01:00
Olivier Lambert
135799ed5e Merge pull request #121 from vatesfr/marsaudf-backup-fix
Fixed rolling tag detection
2015-11-10 16:24:35 +01:00
Fabrice Marsaud
22c3b57960 Fixed backup rolling bug 2015-11-10 16:18:09 +01:00
Fabrice Marsaud
7054dd74a4 Merge pull request #120 from vatesfr/marsaudf-function-move
Moving rollingSnapshot form xapi to xo
2015-11-10 15:34:49 +01:00
Fabrice Marsaud
d4f1e52ef6 fix 2015-11-10 09:04:11 +01:00
Fabrice Marsaud
76a44459cf Moving rollingSnapshot form xapi to xo 2015-11-10 08:49:38 +01:00
Julien Fontanet
a5590b090c Merge pull request #116 from vatesfr/julien-f-vm-copy
vm.copy() can a copy a VM on a remote or local SR.
2015-11-09 15:14:04 +01:00
Julien Fontanet
74c9a57070 Replace lodash.assign() with ES2016 object spread. 2015-11-09 15:07:42 +01:00
Julien Fontanet
06e283e070 vm.copy() can a copy a VM on a remote or local SR. 2015-11-09 14:40:49 +01:00
Julien Fontanet
8ab2ca3f24 Remove unnecessary Bluebird-isms. 2015-11-09 12:05:18 +01:00
Julien Fontanet
0eb949ba39 Move often used lodash utils to utils.js 2015-11-09 12:03:34 +01:00
Julien Fontanet
be35693814 Minor rewrite in utils. 2015-11-09 11:20:41 +01:00
Julien Fontanet
1e5f13795c Merge pull request #117 from vatesfr/promise-settle
Implement utils.pSettle().
2015-11-06 14:51:56 +01:00
Julien Fontanet
cca2265633 Fix PV drivers detection. 2015-11-06 13:23:03 +01:00
Julien Fontanet
0f0d1ac370 Expose vm.xenTools property. 2015-11-06 13:08:03 +01:00
Julien Fontanet
d52d4ac183 Implement utils.pSettle(). 2015-11-06 11:17:35 +01:00
Julien Fontanet
841220fd01 Remove unused variable. 2015-11-05 16:42:17 +01:00
Julien Fontanet
ca5e10784b Only use absolute paths in redirects to ease reverse proxies. 2015-11-05 16:33:07 +01:00
Olivier Lambert
712319974b Merge pull request #114 from vatesfr/marsaudf-import-backup
Import a VM from a remote backup
2015-11-05 15:46:26 +01:00
Fabrice Marsaud
067a6d01bc List backup Files in remotes ans import them for restore 2015-11-05 15:38:09 +01:00
Julien Fontanet
27825f9e2e Unit tests for utils.pFinally(). 2015-11-04 15:36:07 +01:00
Julien Fontanet
425eb115dc Use ES2016 bind syntax for pFinally(). 2015-11-04 14:54:10 +01:00
Julien Fontanet
0a5ce55e2b Expose xapi.parseDateTime(). 2015-11-03 12:03:57 +01:00
Julien Fontanet
dbc8ed9d4c Update deps. 2015-11-03 10:54:14 +01:00
Julien Fontanet
e31e990684 Do not use ES6 method String#endsWith(). 2015-11-03 10:31:21 +01:00
Julien Fontanet
8618f56481 Support vm.import() without knowning the length. 2015-10-30 18:55:04 +01:00
Julien Fontanet
a39fc4667e Do not test on Node 5 for now. 2015-10-30 17:10:25 +01:00
Julien Fontanet
4c369e240b Merge pull request #112 from vatesfr/abhamonr-stats-use-server-time
The local xen orchestra timestamp is never used for get stats.
2015-10-30 14:32:56 +01:00
wescoeur
4e291d01d4 The local xen orchestra timestamp is never used for get stats.
Only the timestamp of one xenServer/rrd is used.
2015-10-30 14:18:37 +01:00
Julien Fontanet
b32fce46cb Merge pull request #111 from vatesfr/abhamonr-fixs-debugs-stats-messages
Fix debugs messages for stats
2015-10-29 18:14:13 +01:00
wescoeur
d1fcd45aac Fix debugs messages for stats 2015-10-29 16:49:13 +01:00
Julien Fontanet
ebdb92c708 4.8.1 2015-10-29 16:31:10 +01:00
Julien Fontanet
112909d35b Merge pull request #110 from vatesfr/julien-f-fix-password-rehash
Fix user password rehash.
2015-10-29 16:30:29 +01:00
Julien Fontanet
c6e2c559f1 Fix user password rehash. 2015-10-29 16:11:45 +01:00
Julien Fontanet
cf8613886a 4.8.0 2015-10-29 10:45:37 +01:00
Olivier Lambert
39c25c2001 moving json5 as a real dep and not a dev dep anymore. Fix #109 2015-10-28 19:21:32 +01:00
Julien Fontanet
9c9721ade5 Fix permissions checking for API -_-" 2015-10-28 17:22:09 +01:00
Julien Fontanet
4b8abe4ce8 Fix test.hasPermission(). 2015-10-28 17:09:33 +01:00
Julien Fontanet
33dfaba276 Remove useless console.log. 2015-10-28 16:56:50 +01:00
Julien Fontanet
dd8bbcf358 VM snapshots require operate permission on all SRs. (fix vatesfr/xo-web#429). 2015-10-28 16:56:41 +01:00
Julien Fontanet
cc3e4369ed Fix some VM-snapshot & VDI ACLs resolution. 2015-10-28 16:51:09 +01:00
Julien Fontanet
548355fce6 Use Xo#getUserByName() where possible. 2015-10-28 16:17:33 +01:00
Julien Fontanet
a4e0e6544b Merge pull request #108 from vatesfr/julien-f-acl-inheritance
ACLs inheritance (fix vatesfr/xo-web#279).
2015-10-28 15:29:31 +01:00
Julien Fontanet
62067e0801 ACLs inheritance (fix vatesfr/xo-web#279). 2015-10-28 15:11:28 +01:00
Olivier Lambert
5eb40d2299 Merge pull request #107 from vatesfr/pierre-remember-me-label
Clicking on 'remember me' label checks the box
2015-10-28 14:49:59 +01:00
Pierre
53990a531b Clicking on 'remember me' label checks the box 2015-10-28 14:46:14 +01:00
Olivier Lambert
d799aea3c4 Merge pull request #105 from vatesfr/abhamonr-intelligent-stats
New parser for host and vms stats.
2015-10-28 14:44:56 +01:00
Julien Fontanet
9af86cbba2 Adds granularity param spec to {host,vm}.stats(). 2015-10-28 14:35:00 +01:00
wescoeur
6fbfece4ff New parser for host and vms stats.
The data are cached for other http requests.
2015-10-28 14:20:48 +01:00
Julien Fontanet
d56cca7873 Use undefined provider for local providers. 2015-10-28 12:38:55 +01:00
Julien Fontanet
fa1096a6ba Remove unused variable. 2015-10-28 12:38:27 +01:00
Julien Fontanet
16ff721331 Throttle authentications tries by user (fix #339). 2015-10-28 12:11:50 +01:00
Julien Fontanet
798ee9dc46 Remove commented code. 2015-10-28 11:45:18 +01:00
Julien Fontanet
bc17c60305 Refactor Xo#authenticateUser(). 2015-10-28 11:44:17 +01:00
Julien Fontanet
432688d577 Remove unused code. 2015-10-28 11:44:17 +01:00
Julien Fontanet
da8d4b47f1 Remove some console logs. 2015-10-28 11:44:17 +01:00
Olivier Lambert
ed1e2e2449 Merge pull request #102 from vatesfr/julien-f-refactor-vm-import
Refactor `vm.import()`
2015-10-27 18:26:29 +01:00
Julien Fontanet
b9744b4688 Refactor vm.import()
- Move logic code to `Xapi#importVm()`
- `vm.import()` following request now returns the id of the imported VM
2015-10-27 18:00:51 +01:00
Julien Fontanet
2f328f8f37 Merge pull request #106 from vatesfr/pierre-patch-name-in-upload-task-description
'Pending tasks' panel: upload task displays patch name
2015-10-27 12:23:59 +01:00
Pierre
ffefd7e50b Added default patch name for uploadPoolPatch to handle handlePatchUpload in pool.js 2015-10-27 11:07:45 +01:00
Pierre
c2445f8a7c 'Pending tasks' panel: upload task displays patch name 2015-10-27 10:05:14 +01:00
Julien Fontanet
54d44079cc Remove unused dep. 2015-10-27 09:16:59 +01:00
Julien Fontanet
87f089a12c Do not proxy data when the connection is closed. 2015-10-26 18:30:45 +01:00
Julien Fontanet
aa2172e4db Use source-map-support-2 from npm (fix old npm). 2015-10-26 18:30:45 +01:00
Julien Fontanet
0a33e94e79 Remove unused variable. 2015-10-26 18:30:44 +01:00
Julien Fontanet
fada54abae Enable XO API HTTP requests without HTTP auth. 2015-10-26 18:30:44 +01:00
Olivier Lambert
802641b719 Merge pull request #103 from vatesfr/pierre-install-all-patches-on-host
Algorithm to install all patches in correct order
2015-10-26 18:24:07 +01:00
Pierre
1da93829d4 Algorithm to install patches in correct order 2015-10-26 17:56:50 +01:00
Julien Fontanet
9e7acbc49a Avoid special chars in date formatting (fix #448). 2015-10-26 16:49:41 +01:00
Julien Fontanet
318765d40b Remove unused dep. 2015-10-26 10:47:07 +01:00
Julien Fontanet
94ba20dfa1 Register objects with the correct key. 2015-10-23 16:26:06 +02:00
Julien Fontanet
2ad3dc4a32 Uniformize pool patches between collection and host.listMissingPatches(). 2015-10-23 15:09:13 +02:00
Julien Fontanet
eef940dd7c Pool patches ids are opaque refs instead of UUIDs. 2015-10-23 15:08:29 +02:00
Julien Fontanet
1b5fc12ac1 Use createRawObject() instead of Object.create(null). 2015-10-21 17:17:15 +02:00
Julien Fontanet
c1c7b8dfcd Minor updates in utils.js 2015-10-21 17:15:49 +02:00
Julien Fontanet
d4510c2afe Put schemas in a dedicated directory. 2015-10-20 18:02:57 +02:00
Julien Fontanet
f241f073a3 Merge pull request #101 from vatesfr/julien-f-hvm-network-install
Set correct boot order when installing HVM from network.
2015-10-19 15:08:50 +02:00
Julien Fontanet
26a6c72611 Set correct boot order when installing HVM from network. 2015-10-19 15:06:08 +02:00
Julien Fontanet
51cee7804b Merge pull request #100 from vatesfr/julien-f-expose-virtualization-mode
Expose virtualization mode in VM object.
2015-10-19 15:02:40 +02:00
Julien Fontanet
52228430f1 Expose virtualization mode in VM object. 2015-10-19 14:30:08 +02:00
Julien Fontanet
4fcd45d8a4 Do not filter out node_modules in stack traces. 2015-10-16 09:53:13 +02:00
Julien Fontanet
ea5736947d No need to parse source maps for ignored call sites. 2015-10-16 09:42:16 +02:00
Julien Fontanet
6b5f36fb7e Move source mapping after async support. 2015-10-15 15:05:42 +02:00
Julien Fontanet
b328d6d95f Augment stack traces limit to 100. 2015-10-15 15:05:42 +02:00
Julien Fontanet
2e06921bf8 Merge pull request #99 from vatesfr/abhamonr-stats-optimization-host
Cache Host stats for 5 secs to avoid unnecessary requests.
2015-10-15 11:30:07 +02:00
Julien Fontanet
fd95e2d711 Use source-map-support-2.
It is based on stack-chain and should give better results with
`trace`.
2015-10-15 10:42:51 +02:00
wescoeur
490e253b79 Cache Host stats for 5 secs to avoid unnecessary requests. 2015-10-14 17:03:53 +02:00
Julien Fontanet
8a10f5cd52 Merge pull request #98 from vatesfr/abhamonr-stats-optimization
Cache VM stats for 5 secs to avoid unnecessary requests.
2015-10-14 16:43:20 +02:00
wescoeur
5cebcc2424 Cache VM stats for 5 secs to avoid unnecessary requests. 2015-10-14 16:34:57 +02:00
Julien Fontanet
7663b89289 Allowed favicon before sign in. 2015-10-13 11:49:38 +02:00
Julien Fontanet
d0d1f9e3c0 4.7.0 2015-10-12 16:34:55 +02:00
Julien Fontanet
4433760e37 Add better-stacks.js to package.json 2015-10-12 13:46:50 +02:00
Julien Fontanet
b63fcd2254 Merge pull request #89 from vatesfr/plugins-api
Plugins API.
2015-10-09 18:41:26 +02:00
Julien Fontanet
eb2aa352ef API really manages plugins. 2015-10-09 18:36:24 +02:00
Julien Fontanet
ec95f198ec Initial plugins API (with mockup data). 2015-10-08 14:00:33 +02:00
Olivier Lambert
f166e480f1 Merge pull request #96 from vatesfr/abhamonr-auth-non-persistent-cookies
Checkbox added on login page for create persistent session (1 year in…
2015-10-08 13:57:04 +02:00
wescoeur
2051f0486c Checkbox added on login page for create persistent session (1 year in practice) only for internal provider.
If checkbox is not checked or if an external provider is used (as Github, Twitter...) the session is not-persistent.
2015-10-08 12:28:11 +02:00
Julien Fontanet
f33fc5d730 Minor fixes. 2015-10-07 15:01:19 +02:00
Julien Fontanet
1603df3503 Avoid listing node_modules in stacks. 2015-10-07 14:42:36 +02:00
Julien Fontanet
6055ac182b Minor fix on pool merge. 2015-10-07 14:42:36 +02:00
Olivier Lambert
4678f60adf fix the pool merge issue #402 2015-10-06 15:15:54 +02:00
Julien Fontanet
b48521950e Always use got instead of request. 2015-10-05 18:41:54 +02:00
Julien Fontanet
45da6fb39f Upgrade must and got. 2015-10-05 18:41:50 +02:00
Julien Fontanet
1604d327da Use redis 2 directly. 2015-10-05 15:32:34 +02:00
Julien Fontanet
98b2b325a1 Reorder some imports. 2015-10-05 13:07:17 +02:00
Julien Fontanet
55b3def630 Avoid direct dependency on Blubird AMAP. 2015-10-05 13:06:53 +02:00
Julien Fontanet
1b5f6bd3eb Only unregister the Xen Server on success. 2015-10-01 16:13:18 +02:00
Julien Fontanet
3472b85345 Minor fix. 2015-10-01 15:46:31 +02:00
Julien Fontanet
1c6ae53656 Remove a xen pool only after successful pool.mergeInto() (fix #403). 2015-10-01 15:45:03 +02:00
Julien Fontanet
a84a56f611 Merge pull request #95 from vatesfr/julien-f-pool-merge
pool.mergeInto(source, target)
2015-09-30 19:06:29 +02:00
Julien Fontanet
69751aa415 Expose XAPI error messages in pool.mergeInto(). 2015-09-30 17:36:25 +02:00
Julien Fontanet
9ef0337807 Correctly export pool.mergeInto(). 2015-09-30 16:39:30 +02:00
Julien Fontanet
a3fdd274c3 pool.mergeInto(source, target) 2015-09-30 16:26:44 +02:00
Julien Fontanet
f069c02153 Only test on Node 0.10, 0.12 and latest stable. 2015-09-25 10:22:09 +02:00
Olivier Lambert
ad0d14c517 Merge pull request #93 from vatesfr/next-release
4.6 release
2015-09-25 00:28:48 +02:00
Olivier Lambert
a72da79743 4.6.0 2015-09-25 00:12:03 +02:00
Julien Fontanet
6bc35217d9 vm.set() supports PV args. 2015-09-24 10:05:20 +02:00
Julien Fontanet
c526e9ac8f Temporary disable incorrect standard reports. 2015-09-23 15:10:53 +02:00
Julien Fontanet
3dfebcadc1 Fix permissions check for networks and SRs (fix #91). 2015-09-23 15:10:53 +02:00
Olivier Lambert
693af532a2 Merge pull request #90 from vatesfr/better-vm-migration
Better VM migration.
2015-09-22 15:42:11 +02:00
Julien Fontanet
4bac57fcd1 Better VM migration. 2015-09-22 15:22:01 +02:00
Julien Fontanet
59291fcac2 VM export is named after the VM name label (fix #370). 2015-09-17 16:57:30 +02:00
Julien Fontanet
1f21b202a5 Merge pull request #87 from vatesfr/pvargs
PV args can be specified at VM creation.
2015-09-17 16:40:30 +02:00
Olivier Lambert
b3ca02a166 add PV args for VM 2015-09-17 16:17:11 +02:00
Julien Fontanet
2462f8f87e Fix Xapi#_setObjectProperties() for uppercase properties. 2015-09-17 16:16:35 +02:00
Olivier Lambert
18268acc1f Merge pull request #83 from vatesfr/tags
Tags
2015-09-16 20:39:11 +02:00
Julien Fontanet
44f4423a9d Fix tag.{add,remove}(). 2015-09-15 16:25:45 +02:00
Olivier Lambert
37250a6f48 Support tags on Xen objects. 2015-09-15 11:59:25 +02:00
Olivier Lambert
fd97541417 Merge pull request #85 from vatesfr/changePassword
Password change API for any user. Related to xo-web issue#362
2015-09-14 16:34:45 +02:00
Fabrice Marsaud
dba3e30d02 Fixes after PR feedback 2015-09-14 16:28:29 +02:00
Julien Fontanet
5962283ad3 Directly use posix path functions. 2015-09-14 14:14:40 +02:00
Fabrice Marsaud
cdd705831b reworked password change 2015-09-14 13:33:44 +02:00
Fabrice Marsaud
a1a7c5e4bb Password change API for any user. Related to xo-web issue#362 2015-09-14 11:28:33 +02:00
Fabrice Marsaud
c1e9061568 Minor code style fix 2015-09-14 11:28:10 +02:00
Julien Fontanet
90ee04de57 Fix bad sign in redirections (fix vatesfr/xo-web#373). 2015-09-14 11:19:38 +02:00
Julien Fontanet
cd24cfbe5c Minor fix in vm.stop(). 2015-09-11 18:03:47 +02:00
Julien Fontanet
5b05668a30 4.5.0 2015-09-11 13:00:24 +02:00
Julien Fontanet
a2eca9589f Avoid crashes on failed VM backups. 2015-09-11 12:48:42 +02:00
Julien Fontanet
a2adbb19bd Avoid crashes on failed VM exports. 2015-09-11 12:48:22 +02:00
Julien Fontanet
012e5c09ed Minor fixes. 2015-09-11 12:31:53 +02:00
Fabrice Marsaud
5f33aa7fd4 Fixed host stats 2015-09-10 17:16:21 +02:00
Fabrice Marsaud
7ea2204dfd Merge pull request #82 from vatesfr/vmMetadata
Only VM metadata handle in various backup/export methods
2015-09-10 17:14:19 +02:00
Julien Fontanet
be8cadf5ac Pass context to ACL checkers. 2015-09-10 17:01:12 +02:00
Fabrice Marsaud
fc7192c748 Only VM metadata handle in various backup/export methods 2015-09-10 16:20:49 +02:00
Julien Fontanet
5c6b6b3919 Implements ACL inheritance for hosts, networks, SRs and VMs. 2015-09-10 14:57:10 +02:00
Julien Fontanet
87553ac685 Use relative paths for sign in (fix vatesfr/xo-web#361). 2015-09-10 14:45:35 +02:00
Julien Fontanet
384f396bbb Revert redis to 0.12.1 for compatibility with then-redis. 2015-09-09 17:33:34 +02:00
Julien Fontanet
d40560bd49 Allow a query string in external auth providers callback. 2015-09-07 17:27:42 +02:00
Julien Fontanet
67952ca246 Update deps. 2015-09-07 16:07:23 +02:00
Julien Fontanet
4ddbefb147 Update standard to 5.2 and fix coding style. 2015-09-04 11:00:36 +02:00
Julien Fontanet
edabc17ac9 onlyMetadata param for vm.export() is optional. 2015-09-04 10:38:45 +02:00
Olivier Lambert
c7e8211fae replace passport placehold by password 2015-09-02 22:00:51 +02:00
Olivier Lambert
dc3f07dd52 add the parameter in the VM method 2015-09-02 17:01:06 +02:00
Olivier Lambert
fae6a5cfb4 add the onlyMetadata parameter 2015-09-02 16:34:31 +02:00
Julien Fontanet
cafd225797 Better logs for the consoles proxy. 2015-09-02 16:34:20 +02:00
Julien Fontanet
bf77d2973e Error handling should be in place before the connection. 2015-09-02 16:22:47 +02:00
Julien Fontanet
dfd8133431 Handle errors in consoles proxy. 2015-09-02 16:15:28 +02:00
Olivier Lambert
e9d78b8990 fix xo-web/#304 2015-09-02 15:43:27 +02:00
Olivier Lambert
13532cdffb Merge pull request #81 from vatesfr/julien-f-direct-consoles
Access directly to consoles without using wsproxy.
2015-09-01 17:58:06 +02:00
Julien Fontanet
c17620efb7 Access directly to consoles without using wsproxy. 2015-09-01 17:51:25 +02:00
Julien Fontanet
d3b5b1080f Correctly handle VM_LACKS_FEATURE_SHUTDOWN. 2015-09-01 17:45:03 +02:00
Julien Fontanet
e4b0c23bb9 Add comment regarding chained certificates. 2015-09-01 15:22:47 +02:00
Julien Fontanet
6c24fb6d79 Minor cleanup. 2015-08-28 18:06:49 +02:00
Julien Fontanet
bd93551c13 Minor fixes for external auth providers. 2015-08-28 18:06:49 +02:00
Fabrice Marsaud
dc0799a56b 4.4.0 2015-08-28 17:43:02 +02:00
Julien Fontanet
e1fca1520b Merge pull request #79 from vatesfr/passport
Passport integration.
2015-08-28 16:12:00 +02:00
Julien Fontanet
c72f3b8efb Fix providers list on sign in page. 2015-08-28 16:03:24 +02:00
Julien Fontanet
afcd172a94 Put signin page in its own file. 2015-08-28 15:52:46 +02:00
Julien Fontanet
5957700699 Better error feedback. 2015-08-28 15:14:41 +02:00
Julien Fontanet
451d023e81 Authentication is now handled by passport. 2015-08-28 15:04:01 +02:00
Julien Fontanet
64d166cebf Enable Travis for iojs v3. 2015-08-28 09:06:01 +02:00
Fabrice Marsaud
51b067def7 host.stats fixed 2 2015-08-27 17:51:29 +02:00
Fabrice Marsaud
de25e8dd6f host.stats fixed 2015-08-27 16:48:42 +02:00
Fabrice Marsaud
39ac291ba2 vm.stats fix 2015-08-27 09:57:53 +02:00
Julien Fontanet
722d26c5b9 Only super admins can interact with a network (should fix vatesfr/xo-web#322). 2015-08-24 17:34:15 +02:00
Julien Fontanet
0346760a75 Hide non managed VDIs (should fix #324). 2015-08-24 16:12:27 +02:00
Julien Fontanet
a2015143f8 Fix coding style. 2015-08-24 13:27:00 +02:00
Julien Fontanet
071d99a7b4 Update deps. 2015-08-24 13:26:24 +02:00
Fabrice Marsaud
e6d8e063c9 stats api funcs now have 4 possible time granularities 2015-08-10 15:41:16 +02:00
Fabrice Marsaud
29526144c9 4.3.2 2015-07-29 15:59:54 +02:00
Fabrice Marsaud
b88bcafddd Merge branch 'next-release' 2015-07-29 15:52:55 +02:00
Fabrice Marsaud
78d844d693 Fixed backup depth 2015-07-29 15:36:18 +02:00
Fabrice Marsaud
49310b53e9 4.3.1 2015-07-29 15:32:57 +02:00
Fabrice Marsaud
4684487ff8 Fixed backup path failure for nfs remotes 2015-07-29 15:32:57 +02:00
Fabrice Marsaud
89250205dc 4.3.1 2015-07-29 15:25:04 +02:00
Fabrice Marsaud
9039919b03 Fixed backup path failure for nfs remotes 2015-07-29 15:15:14 +02:00
Julien Fontanet
df16803f11 Expose host.powerOnMode (fixes #77). 2015-07-27 16:24:38 +02:00
Julien Fontanet
48327cf5f8 A pool’s name defaults to its master’s name. 2015-07-24 13:42:08 +02:00
Varchar38
2ead125b9d Check if the server already exist 2015-07-23 14:24:28 +02:00
Fabrice Marsaud
23807f11f9 4.3.0 2015-07-22 15:46:07 +02:00
Fabrice Marsaud
cad666d07f Merge branch 'next-release' 2015-07-22 15:44:12 +02:00
Varchar38
a0b11051e1 Position can be a string or a number 2015-07-22 09:40:00 +02:00
Julien Fontanet
b6e6fb9de2 Closes WebSockets connections on SIGINT/SIGTERM. 2015-07-21 16:03:20 +02:00
Olivier Lambert
4172fd8aff Merge pull request #75 from vatesfr/marsaud-backup
Scheduled VM Backup feature
2015-07-21 14:35:15 +02:00
Fabrice Marsaud
d60df6ca69 Backup, rolling backup, and remote backup location features 2015-07-21 14:22:31 +02:00
Julien Fontanet
f9ba7377eb Do not create the promise if unnecessary. 2015-07-21 13:58:43 +02:00
Julien Fontanet
5d3c47b8ae Prefer pathname over path with got. 2015-07-21 13:58:25 +02:00
Fabrice Marsaud
ef9cc860a9 removed console output 2015-07-17 14:57:21 +02:00
Julien Fontanet
5a1ec8aa74 Merge pull request #76 from vatesfr/xo-web-issue#301
Deleting a VM also deletes its snapshots.
2015-07-16 17:11:13 +02:00
Fabrice Marsaud
ac435f3699 Deleting a VM also deletes its snapshots. 2015-07-16 17:03:15 +02:00
Julien Fontanet
0374dec79c Use standard Promise instead of Bluebird where possible. 2015-07-15 15:15:54 +02:00
Julien Fontanet
41b6bf38c4 Fix disks deletion in deleteVm(). 2015-07-15 13:44:16 +02:00
Julien Fontanet
ddba251449 Coding style fixes. 2015-07-15 09:42:50 +02:00
Julien Fontanet
bf76e47648 Coding style fixes. 2015-07-15 09:41:54 +02:00
Julien Fontanet
5a3ad35bdd Update standard to 4.5.4. 2015-07-15 09:41:54 +02:00
Julien Fontanet
7da18274ad Use standard Promise instead of Bluebird where possible. 2015-07-15 09:41:54 +02:00
Varchar38
9b66ae05eb Delete set_enable 2015-07-13 15:49:25 +02:00
Fabrice Marsaud
d407964625 Avoid deleting Isos in drive when deleting a VM 2015-07-09 21:59:09 +02:00
Fabrice Marsaud
bc254d5a8d Issue #303, delete unpluggable disks with VMs 2015-07-09 19:09:36 +02:00
Fabrice Marsaud
1d9128a650 Removed unused import 2015-07-02 17:29:20 +02:00
Fabrice Marsaud
a1f2e9e4e2 Fixed a xapi call 2015-07-02 16:50:37 +02:00
Julien Fontanet
8c8188c24f Update deps. 2015-07-01 16:54:24 +02:00
Julien Fontanet
e42eaf73b8 Update xen-api to 0.6.1. 2015-06-30 17:23:20 +02:00
Julien Fontanet
fbd8552c0b Fix error symbol in API traces. 2015-06-30 17:15:38 +02:00
Julien Fontanet
daa26b2e1e Created VBDs should be unpluggable. 2015-06-30 16:23:51 +02:00
Julien Fontanet
18473e6819 Fix inter pool migration. 2015-06-30 13:05:30 +02:00
Julien Fontanet
a51d6b9a74 Better API traces. 2015-06-30 11:06:31 +02:00
Olivier Lambert
706871c12a 4.2.0 2015-06-29 09:17:00 +02:00
Julien Fontanet
16c49e965c Update json-rpc-peer to 0.10 (fixes #71, fixes #72 and maybe vatesfr/xo-web#297) 2015-06-26 20:24:04 +02:00
Fabrice Marsaud
f29e867441 Merge pull request #66 from vatesfr/marsaud-issue176
Marsaud issue176 schedules/jobs
2015-06-26 14:23:16 +02:00
Fabrice Marsaud
4a5f6d8393 Job scheduler feature 2015-06-26 14:13:38 +02:00
Julien Fontanet
39e3025077 Work around babel/babel#1850. 2015-06-26 14:02:11 +02:00
Julien Fontanet
9ee293816d Properly cast booleans from xen-api. 2015-06-26 10:51:09 +02:00
Julien Fontanet
b3c1397753 Fix invalid exception in vdi.set(). 2015-06-25 17:02:25 +02:00
Julien Fontanet
93d63538bb vdi.set() supports size as string 2015-06-25 16:34:10 +02:00
Julien Fontanet
09c28d2311 Update source-map-support to 0.3.2. 2015-06-25 14:10:42 +02:00
Julien Fontanet
5b8ed496db Fix disk.create(). 2015-06-25 13:53:42 +02:00
Julien Fontanet
27b4cf743e Work around for dates from XAPI JSON. 2015-06-25 10:32:30 +02:00
Julien Fontanet
95938a65b6 Minor fix. 2015-06-24 17:47:47 +02:00
Julien Fontanet
70780fc4ef New Xapi#ejectCdFromVm(). 2015-06-24 17:46:34 +02:00
Julien Fontanet
21d265d12f Only set the CD drive bootable on VM creation. 2015-06-24 17:30:02 +02:00
Julien Fontanet
788526d6eb disk.create() requires a sr. 2015-06-24 17:02:25 +02:00
Julien Fontanet
a7203e9a17 Xapi#createVdi() use default_SR if necessary. 2015-06-24 16:27:16 +02:00
Julien Fontanet
eff50daae4 Fix CD insertion for HVM VM creation. 2015-06-23 15:34:04 +02:00
Julien Fontanet
7612db15e0 Do not use babel-plugin-closure-elimination. 2015-06-23 14:59:29 +02:00
Julien Fontanet
0731ee473d Minor fix. 2015-06-23 14:43:51 +02:00
Julien Fontanet
3a6a76fe19 Prefers position (device) 3 for CD drive. 2015-06-23 14:38:16 +02:00
Julien Fontanet
edb0509194 Minor package.json change. 2015-06-23 14:07:58 +02:00
Julien Fontanet
1806be1fd7 Xapi#_createTask() now returns a ref. 2015-06-23 14:04:54 +02:00
Varchar38
7769f25869 Add delete VM-template 2015-06-23 13:57:59 +02:00
Julien Fontanet
f6e5743f20 Update xen-api to v0.6. 2015-06-23 10:47:34 +02:00
Julien Fontanet
1ef6196de6 TODOs. 2015-06-23 10:14:16 +02:00
Julien Fontanet
75ceee23ec Fix attachVdiToVm. 2015-06-22 10:52:56 +02:00
Julien Fontanet
38f2372ee8 Do not use require-tree. 2015-06-22 10:41:36 +02:00
Julien Fontanet
b58826da6e Merge pull request #70 from vatesfr/vm-create-refactoring
vm.create() refactoring
2015-06-19 16:59:36 +02:00
Varchar38
2c05e606c7 Merge branch 'next-release' of https://github.com/vatesfr/xo-server into next-release 2015-06-18 16:55:08 +02:00
Varchar38
92f1c080c4 Add missing await for async unregisterXenServer 2015-06-18 16:54:00 +02:00
Julien Fontanet
8f79699e84 Xapi#_createVbd() correctly finds a default position. 2015-06-18 16:11:37 +02:00
Julien Fontanet
e7b3e28f76 vm.delete() must not remove unpluggable VDIs. 2015-06-18 16:11:08 +02:00
Julien Fontanet
123677b6c6 Fix isVmHvm(). 2015-06-18 16:10:45 +02:00
Julien Fontanet
f393c7173c vm.create() only one VBD is bootable. 2015-06-18 16:10:32 +02:00
Julien Fontanet
0abfb9f1e4 vm.create() can set the description. 2015-06-18 16:09:59 +02:00
Julien Fontanet
379771a5c1 Remove unused import. 2015-06-18 15:26:31 +02:00
Julien Fontanet
2f4a76b3fa Fix handling when vm.create(installation) is undefined. 2015-06-18 15:18:42 +02:00
Julien Fontanet
7d82e199b7 Fix Xapi#_deleteVdi(). 2015-06-18 15:09:28 +02:00
Julien Fontanet
de693a08ad vm.create() seems to work ok. 2015-06-18 15:09:28 +02:00
Julien Fontanet
923091ca62 Various updates. 2015-06-18 15:09:28 +02:00
Julien Fontanet
cb65ddedb6 Remove problematic _waitObject(). 2015-06-18 15:09:27 +02:00
Julien Fontanet
7aa75539c9 Fix VDIs creation. 2015-06-18 15:09:27 +02:00
Julien Fontanet
a1a7cf59b3 vm.create() returns the new VM id. 2015-06-18 15:09:27 +02:00
Julien Fontanet
f572cb5f3e Test utils.formatXml(). 2015-06-18 15:09:27 +02:00
Julien Fontanet
8f1a31ad13 Fix XML generation. 2015-06-18 15:09:26 +02:00
Julien Fontanet
dcf63e3da2 Typo. 2015-06-18 15:09:26 +02:00
Julien Fontanet
153dca8137 More tolerant parsing of VM-template.disks. 2015-06-18 15:09:26 +02:00
Julien Fontanet
db97787b15 Fix other_config.disks XML generation. 2015-06-18 15:09:26 +02:00
Julien Fontanet
7148fec6e1 Object watchers are never rejected. 2015-06-18 15:09:25 +02:00
Julien Fontanet
db6d48f8ca vm.create() refactoring. 2015-06-18 15:09:25 +02:00
Julien Fontanet
6d87a1a604 Update xen-api to 0.5.6. 2015-06-18 13:42:03 +02:00
Julien Fontanet
5c414fc7b4 Test on both io.js v1 & v2. 2015-06-18 11:05:44 +02:00
Julien Fontanet
127f4446ae Log SIGINT & SIGTERM. 2015-06-18 10:48:40 +02:00
Julien Fontanet
49a49e2a2c Use must instead of chai. 2015-06-15 16:41:11 +02:00
Julien Fontanet
8b9389b468 Fix tests execution on Windows. 2015-06-15 16:27:02 +02:00
Julien Fontanet
ee17336d64 Remove unused import. 2015-06-15 16:24:50 +02:00
Julien Fontanet
2b768d2fb5 TODO: an user should be able to delete its tokens. 2015-06-15 13:15:38 +02:00
Julien Fontanet
4fc63010d4 Remove incorrect (and unused) code. 2015-06-15 13:15:24 +02:00
Julien Fontanet
3311ded843 Fix tokens deletion. 2015-06-15 13:15:12 +02:00
Julien Fontanet
135c2e7a71 Better npm test. 2015-06-12 10:59:36 +02:00
Julien Fontanet
a3a8f600b6 Add Travis CI badge on README. 2015-06-12 10:56:23 +02:00
Olivier Lambert
23643b99e2 remove old permissions for snapshot, convert, clone and set 2015-06-11 14:42:15 +02:00
Julien Fontanet
764a334952 Various updates. 2015-06-11 14:21:47 +02:00
Julien Fontanet
6f163f2d01 Use Xapi#createVirtualInterface in vm.create(). 2015-06-11 13:59:39 +02:00
Julien Fontanet
aae65f8235 Fix CD drive creation in vm.create(). 2015-06-11 13:59:20 +02:00
Julien Fontanet
764ea571eb Fix VIFs creation in vm.create(). 2015-06-11 13:42:33 +02:00
Julien Fontanet
aef719b574 Fix stack traces on Node 0.10. 2015-06-11 13:02:16 +02:00
Julien Fontanet
73e52e625b Build on install to fix tests on Travis CI. 2015-06-11 09:16:50 +02:00
Julien Fontanet
a967588fe1 Travis CI integration. 2015-06-10 17:00:46 +02:00
Julien Fontanet
72e530f062 Do not distribute examples & tests. 2015-06-10 17:00:06 +02:00
Julien Fontanet
a0b422f99e Also ignore npm-debug.log.* 2015-06-10 16:59:03 +02:00
Julien Fontanet
6b92f30324 Better stack traces in tests and production. 2015-06-10 16:59:03 +02:00
Julien Fontanet
464cfe701c Add Babel plugin for closure elimination (disabled for now). 2015-06-10 16:59:03 +02:00
Olivier Lambert
070fa03108 4.1.0 2015-06-10 16:32:24 +02:00
Olivier Lambert
51abeabb33 fix issue xo-web/#287 2015-06-10 16:22:13 +02:00
Olivier Lambert
74b0697da1 4.0.3 2015-06-07 16:11:41 +02:00
135 changed files with 23681 additions and 3978 deletions

View File

@@ -1,15 +0,0 @@
{
"comments": false,
"compact": true,
"optional": [
// This options are experimental for now and may (will) break the
// code.
// "minification.deadCodeElimination",
// "minification.inlineExpressions",
"es7.asyncFunctions",
"es7.decorators",
"es7.functionBind",
"runtime"
]
}

6
.gitignore vendored
View File

@@ -1,5 +1,11 @@
/.nyc_output/
/dist/
/node_modules/
/src/api/index.js
/src/xapi/mixins/index.js
/src/xo-mixins/index.js
npm-debug.log
npm-debug.log.*
.xo-server.*

View File

@@ -1,93 +0,0 @@
{
// Julien Fontanet JSHint configuration
// https://gist.github.com/julien-f/8095615
//
// Changes from defaults:
// - all enforcing options (except `++` & `--`) enabled
// - single quotes
// - indentation set to 2 instead of 4
// - almost all relaxing options disabled
// - environments are set to Node.js
//
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : true, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"freeze" : true, // true: Prohibit overwriting prototypes of native objects (Array, Date, ...)
"immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 2, // {int} Number of spaces to use for indentation
"latedef" : true, // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonbsp" : true, // true: Prohibit use of non breakable spaces
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : "single", // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
"strict" : false, // true: Requires all functions run in ES5 Strict Mode
"maxcomplexity" : 7, // {int} Max cyclomatic complexity per function
"maxdepth" : 3, // {int} Max depth of nested blocks (within functions)
"maxlen" : 80, // {int} Max number of characters per line
"maxparams" : 4, // {int} Max number of formal params allowed per function
"maxstatements" : 20, // {int} Max number statements per function
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"multistr" : false, // true: Tolerate multi-line strings
"notypeof" : false, // true: Tolerate typeof comparison with unknown values.
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
"noyield" : false, // true: Tolerate generators without yields
// Environments
"browser" : false, // Web Browser (window, document, etc)
"browserify" : false, // Browserify (node.js code in the browser)
"couch" : false, // CouchDB
"devel" : false, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : false, // jQuery
"mocha" : false, // mocha
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"phantom" : false, // PhantomJS
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : {} // additional predefined global variables
}

3
.mention-bot Normal file
View File

@@ -0,0 +1,3 @@
{
"userBlacklist": [ "greenkeeper", "Wescoeur" ]
}

10
.npmignore Normal file
View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

9
.travis.yml Normal file
View File

@@ -0,0 +1,9 @@
language: node_js
node_js:
- stable
- 6
- 4
# Use containers.
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
sudo: false

3
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,3 @@
# ALL ISSUES SHOULD BE CREATED IN XO-WEB'S TRACKER!
https://github.com/vatesfr/xo-web/issues

View File

@@ -11,6 +11,7 @@ It contains all the logic of XO and handles:
- users authentication and authorizations (work in progress);
- a JSON-RPC based interface for XO clients (i.e. [XO-Web](https://github.com/vatesfr/xo-web)).
[![Build Status](https://travis-ci.org/vatesfr/xo-server.svg?branch=next-release)](https://travis-ci.org/vatesfr/xo-server)
[![Dependency Status](https://david-dm.org/vatesfr/xo-server.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-server)
[![devDependency Status](https://david-dm.org/vatesfr/xo-server/dev-status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-server#info=devDependencies)
@@ -18,7 +19,7 @@ ___
## Installation
Manual install procedure is [available here](https://github.com/vatesfr/xo/blob/master/doc/installation/README.md#installation).
Manual install procedure is [available here](https://xen-orchestra.com/docs/from_the_sources.html).
## Compilation
@@ -36,6 +37,4 @@ $ npm run dev
## How to report a bug?
If you are certain the bug is exclusively related to XO-Server, you may use the [bugtracker of this repository](https://github.com/vatesfr/xo-server/issues).
Otherwise, please consider using the [bugtracker of the general repository](https://github.com/vatesfr/xo/issues).
All bug reports should go into the [bugtracker of xo-web](https://github.com/vatesfr/xo-web/issues).

29
better-stacks.js Normal file
View File

@@ -0,0 +1,29 @@
Error.stackTraceLimit = 100
// Removes internal modules.
try {
var sep = require('path').sep
require('stack-chain').filter.attach(function (_, frames) {
var filtered = frames.filter(function (frame) {
var name = frame && frame.getFileName()
return (
// has a filename
name &&
// contains a separator (no internal modules)
name.indexOf(sep) !== -1
)
})
// depd (used amongst other by express requires at least 3 frames
// in the stack.
return filtered.length > 2
? filtered
: frames
})
} catch (_) {}
// Source maps.
try { require('julien-f-source-map-support/register') } catch (_) {}

View File

@@ -4,4 +4,28 @@
// ===================================================================
// Better stack traces if possible.
require('../better-stacks')
// Use Bluebird for all promises as it provides better performance and
// less memory usage.
global.Promise = require('bluebird')
// Make unhandled rejected promises visible.
process.on('unhandledRejection', function (reason) {
console.warn('[Warn] Possibly unhandled rejection:', reason && reason.stack || reason)
})
;(function (EE) {
var proto = EE.prototype
var emit = proto.emit
proto.emit = function patchedError (event, error) {
if (event === 'error' && !this.listenerCount(event)) {
return console.warn('[Warn] Unhandled error event:', error && error.stack || error)
}
return emit.apply(this, arguments)
}
})(require('events').EventEmitter)
require('exec-promise')(require('../'))

10
bin/xo-server-logs Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env node
'use strict'
// ===================================================================
// Better stack traces if possible.
require('../better-stacks')
require('exec-promise')(require('../dist/logs-cli').default)

40
config.json Normal file
View File

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

View File

@@ -7,25 +7,28 @@ var gulp = require('gulp')
var babel = require('gulp-babel')
var coffee = require('gulp-coffee')
var plumber = require('gulp-plumber')
var rimraf = require('rimraf')
var sourceMaps = require('gulp-sourcemaps')
var watch = require('gulp-watch')
var join = require('path').join
// ===================================================================
var SRC_DIR = __dirname + '/src'
var DIST_DIR = __dirname + '/dist'
var SRC_DIR = join(__dirname, 'src')
var DIST_DIR = join(__dirname, 'dist')
var PRODUCTION = process.argv.indexOf('--production') !== -1
// ===================================================================
function src (patterns) {
return PRODUCTION ?
gulp.src(patterns, {
return PRODUCTION
? gulp.src(patterns, {
base: SRC_DIR,
cwd: SRC_DIR
}) :
watch(patterns, {
})
: watch(patterns, {
base: SRC_DIR,
cwd: SRC_DIR,
ignoreInitial: false,
@@ -36,6 +39,10 @@ function src (patterns) {
// ===================================================================
gulp.task(function clean (cb) {
rimraf(DIST_DIR, cb)
})
gulp.task(function buildCoffee () {
return src('**/*.coffee')
.pipe(sourceMaps.init())
@@ -51,7 +58,7 @@ gulp.task(function buildCoffee () {
})
gulp.task(function buildEs6 () {
return src('**/*.js')
return src([ '**/*.js', '!*.spec.js' ])
.pipe(sourceMaps.init())
.pipe(babel())
.pipe(sourceMaps.write('.'))
@@ -60,4 +67,4 @@ gulp.task(function buildEs6 () {
// ===================================================================
gulp.task('build', gulp.parallel('buildCoffee', 'buildEs6'))
gulp.task('build', gulp.series('clean', gulp.parallel('buildCoffee', 'buildEs6')))

View File

@@ -4,11 +4,8 @@
// Enable xo logs by default.
if (process.env.DEBUG === undefined) {
process.env.DEBUG = 'app-conf,xen-api,xo:*'
process.env.DEBUG = 'app-conf,xo:*,-xo:api'
}
// Enable source maps support for traces.
require('source-map-support').install()
// Import the real main module.
module.exports = require('./dist')
module.exports = require('./dist').default

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server",
"version": "4.0.3-alpha.2",
"version": "5.7.2",
"license": "AGPL-3.0",
"description": "Server part of Xen-Orchestra",
"keywords": [
@@ -11,103 +11,170 @@
],
"homepage": "http://github.com/vatesfr/xo-server/",
"bugs": {
"url": "https://github.com/vatesfr/xo-server/issues"
},
"author": "Julien Fontanet <julien.fontanet@vates.fr>",
"preferGlobal": true,
"files": [
"bin/",
"dist/",
"index.js"
],
"directories": {
"bin": "bin"
"url": "https://github.com/vatesfr/xo-web/issues"
},
"repository": {
"type": "git",
"url": "git://github.com/vatesfr/xo-server.git"
},
"author": "Julien Fontanet <julien.fontanet@vates.fr>",
"preferGlobal": true,
"files": [
"better-stacks.js",
"bin/",
"dist/",
"config.json",
"index.js",
"signin.pug"
],
"directories": {
"bin": "bin"
},
"engines": {
"node": ">=4.5"
},
"dependencies": {
"app-conf": "^0.3.4",
"babel-runtime": "^5",
"base64url": "1.0.4",
"blocked": "^1.1.0",
"bluebird": "^2.9.14",
"connect": "^3.3.5",
"debug": "^2.1.3",
"event-to-promise": "^0.3.2",
"exec-promise": "^0.5.1",
"fs-promise": "^0.3.1",
"got": "^3.2.0",
"graceful-fs": "^3.0.6",
"hashy": "~0.4.2",
"http-server-plus": "^0.5.1",
"human-format": "^0.3.0",
"js-yaml": "^3.2.7",
"json-rpc-peer": "^0.9.2",
"json-rpc-protocol": "^0.9.0",
"@marsaud/smb2-promise": "^0.2.1",
"@nraynaud/struct-fu": "^1.0.1",
"app-conf": "^0.4.1",
"archiver": "^1.3.0",
"arp-a": "^0.5.1",
"babel-runtime": "^6.23.0",
"base64url": "^2.0.0",
"bind-property-descriptor": "^0.0.0",
"blocked": "^1.2.1",
"bluebird": "^3.5.0",
"body-parser": "^1.17.1",
"connect-flash": "^0.1.1",
"cookie": "^0.3.1",
"cookie-parser": "^1.4.3",
"cron": "^1.2.1",
"d3-time-format": "^2.0.5",
"debug": "^2.6.3",
"decorator-synchronized": "^0.2.3",
"escape-string-regexp": "^1.0.5",
"event-to-promise": "^0.8.0",
"exec-promise": "^0.6.1",
"execa": "^0.6.3",
"express": "^4.15.2",
"express-session": "^1.15.1",
"fatfs": "^0.10.4",
"from2": "^2.3.0",
"fs-extra": "^2.1.2",
"fs-promise": "^2.0.1",
"golike-defer": "^0.0.0",
"hashy": "~0.6.1",
"helmet": "^3.5.0",
"highland": "^2.10.5",
"http-proxy": "^1.16.2",
"http-server-plus": "^0.8.0",
"human-format": "^0.8.0",
"is-my-json-valid": "^2.16.0",
"is-redirect": "^1.0.0",
"js-yaml": "^3.8.2",
"json-rpc-peer": "^0.13.1",
"json5": "^0.5.1",
"julien-f-source-map-support": "0.0.0",
"julien-f-unzip": "^0.2.1",
"lodash.assign": "^3.0.0",
"lodash.bind": "^3.0.0",
"lodash.difference": "^3.2.0",
"lodash.endswith": "^3.0.2",
"lodash.filter": "^3.1.0",
"lodash.find": "^3.0.0",
"lodash.findindex": "^3.0.0",
"lodash.foreach": "^3.0.1",
"lodash.has": "^3.0.0",
"lodash.includes": "^3.1.1",
"lodash.isarray": "^3.0.0",
"lodash.isempty": "^3.0.0",
"lodash.isfunction": "^3.0.1",
"lodash.isobject": "^3.0.0",
"lodash.isstring": "^3.0.0",
"lodash.keys": "^3.0.4",
"lodash.map": "^3.0.0",
"lodash.pick": "^3.0.0",
"lodash.result": "^3.0.0",
"lodash.startswith": "^3.0.1",
"kindof": "^2.0.0",
"level": "^1.6.0",
"level-party": "^3.0.4",
"level-sublevel": "^6.6.1",
"leveldown": "^1.6.0",
"lodash": "^4.17.4",
"make-error": "^1",
"multikey-hash": "^1.0.1",
"proxy-http-request": "0.1.0",
"redis": "^0.12.1",
"request": "^2.53.0",
"require-tree": "~1.0.1",
"schema-inspector": "^1.5.1",
"serve-static": "^1.9.2",
"source-map-support": "^0.3.1",
"then-redis": "~1.3.0",
"ws": "~0.7.1",
"xen-api": "^0.5.4",
"xml2js": "~0.4.6",
"xo-collection": "^0.3.2"
"micromatch": "^2.3.11",
"minimist": "^1.2.0",
"moment-timezone": "^0.5.11",
"ms": "^1.0.0",
"multikey-hash": "^1.0.4",
"ndjson": "^1.5.0",
"parse-pairs": "^0.2.2",
"partial-stream": "0.0.0",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"pretty-format": "^19.0.0",
"promise-toolbox": "^0.8.2",
"proxy-agent": "^2.0.0",
"pug": "^2.0.0-beta11",
"redis": "^2.7.1",
"schema-inspector": "^1.6.8",
"semver": "^5.3.0",
"serve-static": "^1.12.1",
"split-lines": "^1.1.0",
"stack-chain": "^1.3.7",
"tar-stream": "^1.5.2",
"through2": "^2.0.3",
"tmp": "^0.0.31",
"uuid": "^3.0.1",
"ws": "^2.2.2",
"xen-api": "^0.10.0-2",
"xml2js": "~0.4.17",
"xo-acl-resolver": "^0.2.3",
"xo-collection": "^0.4.1",
"xo-common": "^0.1.1",
"xo-remote-parser": "^0.3",
"xo-vmdk-to-vhd": "0.0.12"
},
"devDependencies": {
"babel-eslint": "^3.1.9",
"chai": "~2.1.2",
"babel-eslint": "^7.2.1",
"babel-plugin-lodash": "^3.2.11",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.2.2",
"babel-preset-stage-0": "^6.22.0",
"dependency-check": "^2.8.0",
"gulp": "git://github.com/gulpjs/gulp#4.0",
"gulp-babel": "^5",
"gulp-coffee": "^2.3.1",
"gulp-plumber": "^1.0.0",
"gulp-sourcemaps": "^1.5.1",
"gulp-watch": "^4.2.2",
"in-publish": "^1.1.1",
"mocha": "^2.2.1",
"node-inspector": "^0.10.1",
"sinon": "^1.14.1",
"standard": "^4.0.0"
"gulp-babel": "^6",
"gulp-coffee": "^2.3.4",
"gulp-plumber": "^1.1.0",
"gulp-sourcemaps": "^2.4.1",
"gulp-watch": "^4.3.11",
"husky": "^0.13.2",
"index-modules": "^0.3.0",
"jest": "^19.0.2",
"rimraf": "^2.6.1",
"standard": "^9.0.2"
},
"scripts": {
"build": "gulp build --production",
"commitmsg": "npm test",
"dev": "gulp build",
"lint": "standard",
"prepublish": "in-publish && npm run build || in-install",
"dev-test": "jest --bail --watch",
"posttest": "standard && dependency-check -i constant-stream ./package.json",
"prebuild": "index-modules src/api src/xapi/mixins src/xo-mixins",
"predev": "npm run prebuild",
"prepublish": "npm run build",
"start": "node bin/xo-server",
"test": "mocha 'dist/**/*.spec.js'"
"test": "jest"
},
"babel": {
"plugins": [
"lodash",
"transform-decorators-legacy",
"transform-runtime"
],
"presets": [
[
"env",
{
"targets": {
"node": 4
}
}
],
"stage-0"
]
},
"jest": {
"roots": [
"<rootDir>/src"
],
"testRegex": "\\.spec\\.js$"
},
"standard": {
"ignore": [
"dist/**"
"dist"
],
"parser": "babel-eslint"
}

View File

@@ -1,11 +1,17 @@
# Example XO-Server configuration.
# BE *VERY* CAREFUL WHEN EDITING!
# YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
# visit http://www.yamllint.com/ to validate this file as needed
#=====================================================================
# Example XO-Server configuration.
#
# This file is automatically looking for at the following places:
# - `$HOME/.config/xo-server/config.yaml`
# - `/etc/xo-server/config.yaml`
#
# The first entries have priority.
#
# Note: paths are relative to the configuration file.
#=====================================================================
@@ -43,16 +49,17 @@ http:
# Hosts & ports on which to listen.
#
# By default, the server listens on 0.0.0.0:80.
# By default, the server listens on [::]:80.
listen:
# Basic HTTP.
-
# Address on which the server is listening on.
#
# Sets it to '127.0.0.1' to listen only on the local host.
# Sets it to 'localhost' for IP to listen only on the local host.
#
# Default: '0.0.0.0' (all addresses)
#hostname: '127.0.0.1'
# Default: all IPv6 addresses if available, otherwise all IPv4
# addresses.
#hostname: 'localhost'
# Port on which the server is listening on.
#
@@ -66,17 +73,26 @@ http:
#socket: './http.sock'
# Basic HTTPS.
#
# You can find the list of possible options there https://nodejs.org/docs/latest/api/tls.html#tls.createServer
# -
# # The only difference is the presence of the certificate and the
# # key.
# #
# #hostname: '127.0.0.1'
# port: 443
# # File containing the certificate (PEM format).
#
# # If a chain of certificates authorities is needed, you may bundle
# # them directly in the certificate.
# #
# # Note: the order of certificates does matter, your certificate
# # should come first followed by the certificate of the above
# # certificate authority up to the root.
# #
# # Default: undefined
# certificate: './certificate.pem'
# cert: './certificate.pem'
# # File containing the private key (PEM format).
# #
@@ -86,6 +102,10 @@ http:
# # Default: undefined
# key: './key.pem'
# If set to true, all HTTP traffic will be redirected to the first
# HTTPs configuration.
#redirectToHttps: true
# List of files/directories which will be served.
mounts:
#'/': '/path/to/xo-web/dist/'
@@ -94,11 +114,36 @@ http:
proxies:
# '/any/url': 'http://localhost:54722'
# HTTP proxy configuration used by xo-server to fetch resources on the
# Internet.
#
# See: https://github.com/TooTallNate/node-proxy-agent#maps-proxy-protocols-to-httpagent-implementations
#httpProxy: 'http://jsmith:qwerty@proxy.lan:3128'
#=====================================================================
# Connection to the Redis server.
redis:
# Syntax: tcp://[db[:password]@]hostname[:port]
# Unix sockets can be used
#
# Default: tcp://localhost:6379
#uri: ''
# Default: undefined
#socket: /var/run/redis/redis.sock
# Syntax: redis://[db[:password]@]hostname[:port][/db-number]
#
# Default: redis://localhost:6379/0
#uri: redis://redis.company.lan/42
# List of aliased commands.
#
# See http://redis.io/topics/security#disabling-of-specific-commands
#renameCommands:
# del: '3dda29ad-3015-44f9-b13b-fa570de92489'
# srem: '3fd758c9-5610-4e9d-a058-dbf4cb6d8bf0'
# Directory containing the database of XO.
# Currently used for logs.
#
# Default: '/var/lib/xo-server/data'
#datadir: '/var/lib/xo-server/data'

50
signin.pug Normal file
View File

@@ -0,0 +1,50 @@
doctype html
html
head
meta(charset = 'utf-8')
meta(http-equiv = 'X-UA-Compatible' content = 'IE=edge,chrome=1')
meta(name = 'viewport' content = 'width=device-width, initial-scale=1.0')
title Xen Orchestra
meta(name = 'author' content = 'Vates SAS')
link(rel = 'stylesheet' href = 'index.css')
body(style = 'display: flex; height: 100vh;')
div(style = 'margin: auto; width: 20em;')
div.mb-2(style = 'display: flex;')
img(src = 'assets/logo.png' style = 'margin: auto;')
h2.text-xs-center.mb-2 Xen Orchestra
form(action = 'signin/local' method = 'post')
fieldset
if error
p.text-danger #{error}
.input-group.mb-1
span.input-group-addon
i.xo-icon-user.fa-fw
input.form-control(
name = 'username'
type = 'text'
placeholder = 'Username'
required
)
.input-group.mb-1
span.input-group-addon
i.fa.fa-key.fa-fw
input.form-control(
name = 'password'
type = 'password'
placeholder = 'Password'
required
)
.checkbox
label
input(
name = 'remember-me'
type = 'checkbox'
)
| &nbsp;
| Remember me
div
button.btn.btn-block.btn-info
i.fa.fa-sign-in
| Sign in
each label, id in strategies
div: a(href = 'signin/' + id) Sign in with #{label}

View File

@@ -1,51 +0,0 @@
import {JsonRpcError} from 'json-rpc-protocol'
// ===================================================================
// Export standard JSON-RPC errors.
export {
InvalidJson,
InvalidParameters,
InvalidRequest,
MethodNotFound
} from 'json-rpc-protocol'
// -------------------------------------------------------------------
export class NotImplemented extends JsonRpcError {
constructor () {
super('not implemented', 0)
}
}
// -------------------------------------------------------------------
export class NoSuchObject extends JsonRpcError {
constructor (id, type) {
super('no such object', 1, {id, type})
}
}
// -------------------------------------------------------------------
export class Unauthorized extends JsonRpcError {
constructor () {
super('not authenticated or not enough permissions', 2)
}
}
// -------------------------------------------------------------------
export class InvalidCredential extends JsonRpcError {
constructor () {
super('invalid credential', 3)
}
}
// -------------------------------------------------------------------
export class AlreadyAuthenticated extends JsonRpcError {
constructor () {
super('already authenticated', 4)
}
}

View File

@@ -1,318 +0,0 @@
import createDebug from 'debug'
const debug = createDebug('xo:api')
import assign from 'lodash.assign'
import Bluebird from 'bluebird'
import forEach from 'lodash.foreach'
import getKeys from 'lodash.keys'
import isFunction from 'lodash.isfunction'
import map from 'lodash.map'
import requireTree from 'require-tree'
import schemaInspector from 'schema-inspector'
import {
InvalidParameters,
MethodNotFound,
NoSuchObject,
Unauthorized
} from './api-errors'
// ===================================================================
// FIXME: this function is specific to XO and should not be defined in
// this file.
function checkPermission (method) {
/* jshint validthis: true */
const {permission} = method
// No requirement.
if (permission === undefined) {
return
}
const {user} = this
if (!user) {
throw new Unauthorized()
}
// The only requirement is login.
if (!permission) {
return
}
if (!user.hasPermission(permission)) {
throw new Unauthorized()
}
}
// -------------------------------------------------------------------
function checkParams (method, params) {
const schema = method.params
if (!schema) {
return
}
const result = schemaInspector.validate({
type: 'object',
properties: schema
}, params)
if (!result.valid) {
throw new InvalidParameters(result.error)
}
}
// -------------------------------------------------------------------
// Forward declaration.
let checkAuthorization
function authorized () {}
// function forbiddden () {
// // We don't care about an error object.
// /* eslint no-throw-literal: 0 */
// throw null
// }
function checkMemberAuthorization (member) {
return function (userId, object, permission) {
const memberObject = this.getObject(object[member])
return checkAuthorization.call(this, userId, memberObject, permission)
}
}
const checkAuthorizationByTypes = {
// Objects of these types do not requires any authorization.
'network': authorized,
'VM-template': authorized,
message: checkMemberAuthorization('$object'),
task: checkMemberAuthorization('$host'),
VBD: checkMemberAuthorization('VDI'),
// Access to a VDI is granted if the user has access to the
// containing SR or to a linked VM.
VDI (userId, vdi, permission) {
// Check authorization for each of the connected VMs.
const promises = map(this.getObjects(vdi.$VBDs, 'VBD'), vbd => {
const vm = this.getObject(vbd.VM, 'VM')
return checkAuthorization.call(this, userId, vm, permission)
})
// Check authorization for the containing SR.
const sr = this.getObject(vdi.$SR, 'SR')
promises.push(checkAuthorization.call(this, userId, sr, permission))
// We need at least one success
return Bluebird.any(promises)
},
VIF (userId, vif, permission) {
const network = this.getObject(vif.$network)
const vm = this.getObject(vif.$VM)
return Bluebird.any([
checkAuthorization.call(this, userId, network, permission),
checkAuthorization.call(this, userId, vm, permission)
])
},
'VM-snapshot': checkMemberAuthorization('$snapshot_of')
}
function throwIfFail (success) {
if (!success) {
// We don't care about an error object.
/* eslint no-throw-literal: 0 */
throw null
}
}
function defaultCheckAuthorization (userId, object, permission) {
return this.hasPermission(userId, object.id, permission).then(throwIfFail)
}
checkAuthorization = Bluebird.method(function (userId, object, permission) {
const fn = checkAuthorizationByTypes[object.type] || defaultCheckAuthorization
return fn.call(this, userId, object, permission)
})
function resolveParams (method, params) {
const resolve = method.resolve
if (!resolve) {
return params
}
const {user} = this
if (!user) {
throw new Unauthorized()
}
const userId = user.get('id')
const isAdmin = this.user.hasPermission('admin')
const promises = []
forEach(resolve, ([param, types, permission = 'administrate'], key) => {
const id = params[param]
if (id === undefined) {
return
}
const object = this.getObject(params[param], types)
// This parameter has been handled, remove it.
delete params[param]
// Register this new value.
params[key] = object
if (!isAdmin) {
promises.push(checkAuthorization.call(this, userId, object, permission))
}
})
return Bluebird.all(promises).catch(() => {
throw new Unauthorized()
}).return(params)
}
// ===================================================================
function getMethodsInfo () {
const methods = {}
forEach(this.api._methods, function (method, name) {
this[name] = assign({}, {
description: method.description,
params: method.params || {},
permission: method.permission
})
}, methods)
return methods
}
getMethodsInfo.description = 'returns the signatures of all available API methods'
// -------------------------------------------------------------------
const getVersion = () => '0.1'
getVersion.description = 'API version (unstable)'
// -------------------------------------------------------------------
function listMethods () {
return getKeys(this.api._methods)
}
listMethods.description = 'returns the name of all available API methods'
// -------------------------------------------------------------------
function methodSignature ({method: name}) {
const method = this.api.getMethod(name)
if (!method) {
throw new NoSuchObject()
}
// Return an array for compatibility with XML-RPC.
return [
// XML-RPC require the name of the method.
assign({ name }, {
description: method.description,
params: method.params || {},
permission: method.permission
})
]
}
methodSignature.description = 'returns the signature of an API method'
// ===================================================================
export default class Api {
constructor ({context} = {}) {
this._methods = Object.create(null)
this.context = context
this.addMethods({
system: {
getMethodsInfo,
getVersion,
listMethods,
methodSignature
}
})
// FIXME: this too is specific to XO and should be moved out of this file.
this.addMethods(requireTree('./api'))
}
addMethod (name, method) {
this._methods[name] = method
}
addMethods (methods) {
let base = ''
forEach(methods, function addMethod (method, name) {
name = base + name
if (isFunction(method)) {
this.addMethod(name, method)
return
}
const oldBase = base
base = name + '.'
forEach(method, addMethod, this)
base = oldBase
}, this)
}
async call (session, name, params) {
debug('%s(...)', name)
const method = this.getMethod(name)
if (!method) {
throw new MethodNotFound(name)
}
const context = Object.create(this.context)
context.api = this // Used by system.*().
context.session = session
// FIXME: too coupled with XO.
// Fetch and inject the current user.
const userId = session.get('user_id', undefined)
if (userId) {
context.user = await context._getUser(userId)
}
await checkPermission.call(context, method)
checkParams(method, params)
await resolveParams.call(context, method, params)
try {
let result = await method.call(context, params)
// If nothing was returned, consider this operation a success
// and return true.
if (result === undefined) {
result = true
}
debug('%s(...) → %s', name, typeof result)
return result
} catch (error) {
debug('Error: %s(...) → %s', name, error)
throw error
}
}
getMethod (name) {
return this._methods[name]
}
}

0
src/api/.index-modules Normal file
View File

View File

@@ -1,5 +1,5 @@
export async function get () {
return await this.getAllAcls()
return /* await */ this.getAllAcls()
}
get.permission = 'admin'
@@ -8,13 +8,13 @@ get.description = 'get existing ACLs'
// -------------------------------------------------------------------
export async function getCurrent () {
return await this.getAclsForUser(this.session.get('user_id'))
export async function getCurrentPermissions () {
return /* await */ this.getPermissionsForUser(this.session.get('user_id'))
}
getCurrent.permission = ''
getCurrentPermissions.permission = ''
getCurrent.description = 'get existing ACLs concerning current user'
getCurrentPermissions.description = 'get (explicit) permissions by object for the current user'
// -------------------------------------------------------------------

98
src/api/backup.js Normal file
View File

@@ -0,0 +1,98 @@
import archiver from 'archiver'
import { basename } from 'path'
import { format } from 'json-rpc-peer'
import { forEach } from 'lodash'
// ===================================================================
export function list ({ remote }) {
return this.listVmBackups(remote)
}
list.permission = 'admin'
list.params = {
remote: { type: 'string' }
}
// -------------------------------------------------------------------
export function scanDisk ({ remote, disk }) {
return this.scanDiskBackup(remote, disk)
}
scanDisk.permission = 'admin'
scanDisk.params = {
remote: { type: 'string' },
disk: { type: 'string' }
}
// -------------------------------------------------------------------
export function scanFiles ({ remote, disk, partition, path }) {
return this.scanFilesInDiskBackup(remote, disk, partition, path)
}
scanFiles.permission = 'admin'
scanFiles.params = {
remote: { type: 'string' },
disk: { type: 'string' },
partition: { type: 'string', optional: true },
path: { type: 'string' }
}
// -------------------------------------------------------------------
function handleFetchFiles (req, res, { remote, disk, partition, paths, format: archiveFormat }) {
this.fetchFilesInDiskBackup(remote, disk, partition, paths).then(files => {
res.setHeader('content-disposition', 'attachment')
res.setHeader('content-type', 'application/octet-stream')
const nFiles = paths.length
// Send lone file directly
if (nFiles === 1) {
files[0].pipe(res)
return
}
const archive = archiver(archiveFormat)
archive.on('error', error => {
console.error(error)
res.end(format.error(0, error))
})
forEach(files, file => {
archive.append(file, { name: basename(file.path) })
})
archive.finalize()
archive.pipe(res)
}).catch(error => {
console.error(error)
res.writeHead(500)
res.end(format.error(0, error))
})
}
export async function fetchFiles ({ format = 'zip', ...params }) {
const fileName = params.paths.length > 1
? `restore_${new Date().toJSON()}.${format}`
: basename(params.paths[0])
return this.registerHttpRequest(handleFetchFiles, { ...params, format }, {
suffix: encodeURI(`/${fileName}`)
}).then(url => ({ $getFrom: url }))
}
fetchFiles.permission = 'admin'
fetchFiles.params = {
remote: { type: 'string' },
disk: { type: 'string' },
format: { type: 'string', optional: true },
partition: { type: 'string', optional: true },
paths: {
type: 'array',
items: { type: 'string' },
minLength: 1
}
}

View File

@@ -3,29 +3,38 @@ import {parseSize} from '../utils'
// ===================================================================
export async function create ({name, size, sr}) {
const xapi = this.getXAPI(sr)
const ref = await xapi.call('VDI.create', {
const vdi = await this.getXapi(sr).createVdi(parseSize(size), {
name_label: name,
other_config: {},
read_only: false,
sharable: false,
SR: sr.ref,
type: 'user',
virtual_size: String(parseSize(size))
sr: sr._xapiId
})
return (await xapi.call('VDI.get_record', ref)).uuid
return vdi.$id
}
create.description = 'create a new disk on a SR'
create.params = {
name: { type: 'string' },
size: { type: 'string' },
size: { type: ['integer', 'string'] },
sr: { type: 'string' }
}
create.resolve = {
sr: ['sr', 'SR', 'administrate']
}
// -------------------------------------------------------------------
export async function resize ({ vdi, size }) {
await this.getXapi(vdi).resizeVdi(vdi._xapiId, parseSize(size))
}
resize.description = 'resize an existing VDI'
resize.params = {
id: { type: 'string' },
size: { type: ['integer', 'string'] }
}
resize.resolve = {
vdi: ['id', ['VDI', 'VDI-snapshot'], 'administrate']
}

View File

@@ -1,8 +1,6 @@
export async function register ({vm}) {
await this.getXAPI(vm).registerDockerContainer(vm.id)
await this.getXapi(vm).registerDockerContainer(vm._xapiId)
}
register.permission = 'admin'
register.description = 'Register the VM for Docker management'
register.params = {
@@ -16,10 +14,8 @@ register.resolve = {
// -----------------------------------------------------------------------------
export async function deregister ({vm}) {
await this.getXAPI(vm).unregisterDockerContainer(vm.id)
await this.getXapi(vm).unregisterDockerContainer(vm._xapiId)
}
deregister.permission = 'admin'
deregister.description = 'Deregister the VM for Docker management'
deregister.params = {
@@ -33,28 +29,26 @@ deregister.resolve = {
// -----------------------------------------------------------------------------
export async function start ({vm, container}) {
await this.getXAPI(vm).startDockerContainer(vm.id, container)
await this.getXapi(vm).startDockerContainer(vm._xapiId, container)
}
export async function stop ({vm, container}) {
await this.getXAPI(vm).stopDockerContainer(vm.id, container)
await this.getXapi(vm).stopDockerContainer(vm._xapiId, container)
}
export async function restart ({vm, container}) {
await this.getXAPI(vm).restartDockerContainer(vm.id, container)
await this.getXapi(vm).restartDockerContainer(vm._xapiId, container)
}
export async function pause ({vm, container}) {
await this.getXAPI(vm).pauseDockerContainer(vm.id, container)
await this.getXapi(vm).pauseDockerContainer(vm._xapiId, container)
}
export async function unpause ({vm, container}) {
await this.getXAPI(vm).unpauseDockerContainer(vm.id, container)
await this.getXapi(vm).unpauseDockerContainer(vm._xapiId, container)
}
for (let fn of [start, stop, restart, pause, unpause]) {
fn.permission = 'admin'
fn.params = {
vm: { type: 'string' },
container: { type: 'string' }

View File

@@ -27,14 +27,11 @@ delete_.params = {
// -------------------------------------------------------------------
export async function getAll () {
return await this._groups.get()
return /* await */ this.getAllGroups()
}
delete_.description = 'returns all the existing group'
delete_.permission = 'admin'
delete_.params = {
id: {type: 'string'}
}
getAll.description = 'returns all the existing group'
getAll.permission = 'admin'
// -------------------------------------------------------------------

View File

@@ -1,29 +1,32 @@
$debug = (require 'debug') 'xo:api:vm'
$find = require 'lodash.find'
$findIndex = require 'lodash.findindex'
$forEach = require 'lodash.foreach'
$request = require('bluebird').promisify(require('request'))
endsWith = require 'lodash.endswith'
startsWith = require 'lodash.startswith'
$find = require 'lodash/find'
$findIndex = require 'lodash/findIndex'
$forEach = require 'lodash/forEach'
endsWith = require 'lodash/endsWith'
startsWith = require 'lodash/startsWith'
{coroutine: $coroutine} = require 'bluebird'
{parseXml} = require '../utils'
{format} = require 'json-rpc-peer'
{
extractProperty,
mapToArray,
parseXml
} = require '../utils'
#=====================================================================
set = $coroutine (params) ->
{host} = params
xapi = @getXAPI host
set = ({
host,
for param, field of {
'name_label'
'name_description'
'enabled'
}
continue unless param of params
# TODO: use camel case.
name_label: nameLabel,
name_description: nameDescription
}) ->
return @getXapi(host).setHostProperties(host._xapiId, {
nameLabel,
nameDescription
})
yield xapi.call "host.set_#{field}", host.ref, params[param]
return true
set.description = 'changes the properties of an host'
set.params =
id: type: 'string'
@@ -33,9 +36,6 @@ set.params =
name_description:
type: 'string'
optional: true
enabled:
type: 'boolean'
optional: true
set.resolve = {
host: ['id', 'host', 'administrate'],
@@ -45,16 +45,19 @@ exports.set = set
#---------------------------------------------------------------------
restart = $coroutine ({host}) ->
xapi = @getXAPI host
# FIXME: set force to false per default when correctly implemented in
# UI.
restart = ({host, force = true}) ->
return @getXapi(host).rebootHost(host._xapiId, force)
yield xapi.call 'host.disable', host.ref
yield xapi.call 'host.reboot', host.ref
return true
restart.description = 'restart the host'
restart.params = {
id: { type: 'string' }
id: { type: 'string' },
force: {
type: 'boolean',
optional: true
}
}
restart.resolve = {
@@ -65,19 +68,17 @@ exports.restart = restart
#---------------------------------------------------------------------
restartAgent = $coroutine ({host}) ->
xapi = @getXAPI host
restartAgent = ({host}) ->
return @getXapi(host).restartHostAgent(host._xapiId)
yield xapi.call 'host.restart_agent', host.ref
return true
restartAgent.description = 'restart the Xen agent on the host'
restartAgent.params = {
id: { type: 'string' }
}
restartAgent.resolve = {
host: ['id', 'host', 'operate'],
host: ['id', 'host', 'administrate'],
}
# TODO camel case
@@ -85,12 +86,10 @@ exports.restart_agent = restartAgent
#---------------------------------------------------------------------
start = $coroutine ({host}) ->
xapi = @getXAPI host
start = ({host}) ->
return @getXapi(host).powerOnHost(host._xapiId)
yield xapi.call 'host.power_on', host.ref
return true
start.description = 'start the host'
start.params = {
id: { type: 'string' }
@@ -104,13 +103,10 @@ exports.start = start
#---------------------------------------------------------------------
stop = $coroutine ({host}) ->
xapi = @getXAPI host
stop = ({host}) ->
return @getXapi(host).shutdownHost(host._xapiId)
yield xapi.call 'host.disable', host.ref
yield xapi.call 'host.shutdown', host.ref
return true
stop.description = 'stop the host'
stop.params = {
id: { type: 'string' }
@@ -124,12 +120,10 @@ exports.stop = stop
#---------------------------------------------------------------------
detach = $coroutine ({host}) ->
xapi = @getXAPI host
detach = ({host}) ->
return @getXapi(host).ejectHostFromPool(host._xapiId)
yield xapi.call 'pool.eject', host.ref
return true
detach.description = 'eject the host of a pool'
detach.params = {
id: { type: 'string' }
@@ -143,12 +137,10 @@ exports.detach = detach
#---------------------------------------------------------------------
enable = $coroutine ({host}) ->
xapi = @getXAPI host
enable = ({host}) ->
return @getXapi(host).enableHost(host._xapiId)
yield xapi.call 'host.enable', host.ref
return true
enable.description = 'enable to create VM on the host'
enable.params = {
id: { type: 'string' }
@@ -162,12 +154,10 @@ exports.enable = enable
#---------------------------------------------------------------------
disable = $coroutine ({host}) ->
xapi = @getXAPI host
disable = ({host}) ->
return @getXapi(host).disableHost(host._xapiId)
yield xapi.call 'host.disable', host.ref
return true
disable.description = 'disable to create VM on the hsot'
disable.params = {
id: { type: 'string' }
@@ -179,49 +169,13 @@ disable.resolve = {
exports.disable = disable
#---------------------------------------------------------------------
createNetwork = $coroutine ({host, name, description, pif, mtu, vlan}) ->
xapi = @getXAPI host
description = description ? 'Created with Xen Orchestra'
network_ref = yield xapi.call 'network.create', {
name_label: name,
name_description: description,
MTU: mtu ? '1500'
other_config: {}
}
if pif?
vlan = vlan ? '0'
pif = @getObject pif, 'PIF'
yield xapi.call 'pool.create_VLAN_from_PIF', pif.ref, network_ref, vlan
return true
createNetwork.params = {
host: { type: 'string' }
name: { type: 'string' }
description: { type: 'string', optional: true }
pif: { type: 'string', optional: true }
mtu: { type: 'string', optional: true }
vlan: { type: 'string', optional: true }
}
createNetwork.resolve = {
host: ['host', 'host', 'administrate'],
}
createNetwork.permission = 'admin'
exports.createNetwork = createNetwork
#---------------------------------------------------------------------
# Returns an array of missing new patches in the host
# Returns an empty array if up-to-date
# Throws an error if the host is not running the latest XS version
listMissingPatches = ({host}) ->
return @getXAPI(host).listMissingPoolPatchesOnHost(host.id)
return @getXapi(host).listMissingPoolPatchesOnHost(host._xapiId)
listMissingPatches.params = {
host: { type: 'string' }
@@ -233,10 +187,14 @@ listMissingPatches.resolve = {
exports.listMissingPatches = listMissingPatches
listMissingPatches.description = 'return an array of missing new patches in the host'
#---------------------------------------------------------------------
installPatch = ({host, patch: patchUuid}) ->
return @getXAPI(host).installPoolPatchOnHost(patchUuid, host.id)
return @getXapi(host).installPoolPatchOnHost(patchUuid, host._xapiId)
installPatch.description = 'install a patch on an host'
installPatch.params = {
host: { type: 'string' }
@@ -251,89 +209,51 @@ exports.installPatch = installPatch
#---------------------------------------------------------------------
installAllPatches = ({host}) ->
return @getXapi(host).installAllPoolPatchesOnHost(host._xapiId)
stats = $coroutine ({host}) ->
installAllPatches.description = 'install all the missing patches on a host'
xapi = @getXAPI host
installAllPatches.params = {
host: { type: 'string' }
}
[response, body] = yield $request {
method: 'get'
rejectUnauthorized: false
url: 'https://'+host.address+'/host_rrd?session_id='+xapi.sessionId
}
installAllPatches.resolve = {
host: ['host', 'host', 'administrate']
}
if response.statusCode isnt 200
throw new Error('Cannot fetch the RRDs')
exports.installAllPatches = installAllPatches
json = parseXml(body)
#---------------------------------------------------------------------
# Find index of needed objects for getting their values after
cpusIndexes = []
pifsIndexes = []
memoryFreeIndex = []
memoryIndex = []
loadIndex = []
index = 0
emergencyShutdownHost = ({host}) ->
return @getXapi(host).emergencyShutdownHost(host._xapiId)
$forEach(json.rrd.ds, (value, i) ->
if /^cpu[0-9]+$/.test(value.name)
cpusIndexes.push(i)
else if startsWith(value.name, 'pif_eth') && endsWith(value.name, '_tx')
pifsIndexes.push(i)
else if startsWith(value.name, 'pif_eth') && endsWith(value.name, '_rx')
pifsIndexes.push(i)
else if startsWith(value.name, 'loadavg')
loadIndex.push(i)
else if startsWith(value.name, 'memory_free_kib')
memoryFreeIndex.push(i)
else if startsWith(value.name, 'memory_total_kib')
memoryIndex.push(i)
emergencyShutdownHost.description = 'suspend all VMs and shutdown host'
return
)
emergencyShutdownHost.params = {
host: { type: 'string' }
}
memoryFree = []
memoryUsed = []
memory = []
load = []
cpus = []
pifs = []
date = [] #TODO
baseDate = json.rrd.lastupdate
dateStep = json.rrd.step
numStep = json.rrd.rra[0].database.row.length - 1
emergencyShutdownHost.resolve = {
host: ['host', 'host', 'administrate']
}
$forEach json.rrd.rra[0].database.row, (n, key) ->
memoryFree.push(Math.round(parseInt(n.v[memoryFreeIndex])))
memoryUsed.push(Math.round(parseInt(n.v[memoryIndex])-(n.v[memoryFreeIndex])))
memory.push(parseInt(n.v[memoryIndex]))
load.push(n.v[loadIndex])
date.push(baseDate - (dateStep * (numStep - key)))
# build the multi dimensional arrays
$forEach cpusIndexes, (value, key) ->
cpus[key] ?= []
cpus[key].push(n.v[value]*100)
return
$forEach pifsIndexes, (value, key) ->
pifs[key] ?= []
pifs[key].push(if n.v[value] == 'NaN' then null else n.v[value]) # * (if key % 2 then -1 else 1))
return
return
exports.emergencyShutdownHost = emergencyShutdownHost
#---------------------------------------------------------------------
# the final object
return {
memoryFree: memoryFree
memoryUsed: memoryUsed
memory: memory
date: date
cpus: cpus
pifs: pifs
load: load
}
stats = ({host, granularity}) ->
return @getXapiHostStats(host, granularity)
stats.description = 'returns statistic of the host'
stats.params = {
host: { type: 'string' }
host: { type: 'string' },
granularity: {
type: 'string',
optional: true
}
}
stats.resolve = {
@@ -341,3 +261,45 @@ stats.resolve = {
}
exports.stats = stats;
#---------------------------------------------------------------------
handleInstallSupplementalPack = $coroutine (req, res, { hostId }) ->
xapi = @getXapi(hostId)
# Timeout seems to be broken in Node 4.
# See https://github.com/nodejs/node/issues/3319
req.setTimeout(43200000) # 12 hours
req.length = req.headers['content-length']
try
yield xapi.installSupplementalPack(req, { hostId })
res.end(format.response(0))
catch e
res.writeHead(500)
res.end(format.error(0, new Error(e.message)))
return
installSupplementalPack = $coroutine ({host}) ->
return {
$sendTo: yield @registerHttpRequest(handleInstallSupplementalPack, { hostId: host.id })
}
installSupplementalPack.description = 'installs supplemental pack from ISO file'
installSupplementalPack.params = {
host: { type: 'string' }
}
installSupplementalPack.resolve = {
host: ['host', 'host', 'admin']
}
exports.installSupplementalPack = installSupplementalPack;
#=====================================================================
Object.defineProperty(exports, '__esModule', {
value: true
})

44
src/api/ip-pool.js Normal file
View File

@@ -0,0 +1,44 @@
import { unauthorized } from 'xo-common/api-errors'
export function create (props) {
return this.createIpPool(props)
}
create.permission = 'admin'
create.description = 'Creates a new ipPool'
// -------------------------------------------------------------------
function delete_ ({ id }) {
return this.deleteIpPool(id)
}
export { delete_ as delete }
delete_.permission = 'admin'
delete_.description = 'Delete an ipPool'
// -------------------------------------------------------------------
export function getAll (params) {
const { user } = this
if (!user) {
throw unauthorized()
}
return this.getAllIpPools(user.permission === 'admin'
? params && params.userId
: user.id
)
}
getAll.description = 'List all ipPools'
// -------------------------------------------------------------------
export function set ({ id, ...props }) {
return this.updateIpPool(id, props)
}
set.permission = 'admin'
set.description = 'Allow to modify an existing ipPool'

110
src/api/job.js Normal file
View File

@@ -0,0 +1,110 @@
// FIXME so far, no acls for jobs
export async function getAll () {
return /* await */ this.getAllJobs()
}
getAll.permission = 'admin'
getAll.description = 'Gets all available jobs'
export async function get (id) {
return /* await */ this.getJob(id)
}
get.permission = 'admin'
get.description = 'Gets an existing job'
get.params = {
id: {type: 'string'}
}
export async function create ({job}) {
if (!job.userId) {
job.userId = this.session.get('user_id')
}
return (await this.createJob(job)).id
}
create.permission = 'admin'
create.description = 'Creates a new job from description object'
create.params = {
job: {
type: 'object',
properties: {
userId: {type: 'string', optional: true},
name: {type: 'string', optional: true},
timeout: {type: 'number', optional: true},
type: {type: 'string'},
key: {type: 'string'},
method: {type: 'string'},
paramsVector: {
type: 'object',
properties: {
type: {type: 'string'},
items: {
type: 'array',
items: {
type: 'object'
}
}
},
optional: true
}
}
}
}
export async function set ({job}) {
await this.updateJob(job)
}
set.permission = 'admin'
set.description = 'Modifies an existing job from a description object'
set.params = {
job: {
type: 'object',
properties: {
id: {type: 'string'},
name: {type: 'string', optional: true},
timeout: {type: ['number', 'null'], optional: true},
type: {type: 'string', optional: true},
key: {type: 'string', optional: true},
method: {type: 'string', optional: true},
paramsVector: {
type: 'object',
properties: {
type: {type: 'string'},
items: {
type: 'array',
items: {
type: 'object'
}
}
},
optional: true
}
}
}
}
async function delete_ ({id}) {
await this.removeJob(id)
}
delete_.permission = 'admin'
delete_.description = 'Deletes an existing job'
delete_.params = {
id: {type: 'string'}
}
export {delete_ as delete}
export async function runSequence ({idSequence}) {
await this.runJobSequence(idSequence)
}
runSequence.permission = 'admin'
runSequence.description = 'Runs jobs sequentially, in the provided order'
runSequence.params = {
idSequence: {type: 'array', items: {type: 'string'}}
}

38
src/api/log.js Normal file
View File

@@ -0,0 +1,38 @@
export async function get ({namespace}) {
const logger = await this.getLogger(namespace)
return new Promise((resolve, reject) => {
const logs = {}
logger.createReadStream()
.on('data', (data) => {
logs[data.key] = data.value
})
.on('end', () => {
resolve(logs)
})
.on('error', reject)
})
}
get.description = 'returns logs list for one namespace'
get.params = {
namespace: { type: 'string' }
}
get.permission = 'admin'
// -------------------------------------------------------------------
async function delete_ ({namespace, id}) {
const logger = await this.getLogger(namespace)
logger.del(id)
}
delete_.description = 'deletes one or several logs from a namespace'
delete_.params = {
id: { type: [ 'array', 'string' ] },
namespace: { type: 'string' }
}
delete_.permission = 'admin'
export {delete_ as delete}

View File

@@ -1,5 +1,5 @@
async function delete_ ({message}) {
await this.getXAPI(message).call('message.destroy', message.ref)
async function delete_ ({ message }) {
await this.getXapi(message).call('message.destroy', message._xapiRef)
}
export {delete_ as delete}

119
src/api/network.js Normal file
View File

@@ -0,0 +1,119 @@
import { mapToArray } from '../utils'
export function getBondModes () {
return ['balance-slb', 'active-backup', 'lacp']
}
export async function create ({ pool, name, description, pif, mtu = 1500, vlan = 0 }) {
return this.getXapi(pool).createNetwork({
name,
description,
pifId: pif && this.getObject(pif, 'PIF')._xapiId,
mtu: +mtu,
vlan: +vlan
})
}
create.params = {
pool: { type: 'string' },
name: { type: 'string' },
description: { type: 'string', optional: true },
pif: { type: 'string', optional: true },
mtu: { type: ['integer', 'string'], optional: true },
vlan: { type: ['integer', 'string'], optional: true }
}
create.resolve = {
pool: ['pool', 'pool', 'administrate']
}
create.permission = 'admin'
// =================================================================
export async function createBonded ({ pool, name, description, pifs, mtu = 1500, mac, bondMode }) {
return this.getXapi(pool).createBondedNetwork({
name,
description,
pifIds: mapToArray(pifs, pif =>
this.getObject(pif, 'PIF')._xapiId
),
mtu: +mtu,
mac,
bondMode
})
}
createBonded.params = {
pool: { type: 'string' },
name: { type: 'string' },
description: { type: 'string', optional: true },
pifs: {
type: 'array',
items: {
type: 'string'
}
},
mtu: { type: ['integer', 'string'], optional: true },
// RegExp since schema-inspector does not provide a param check based on an enumeration
bondMode: { type: 'string', pattern: new RegExp(`^(${getBondModes().join('|')})$`) }
}
createBonded.resolve = {
pool: ['pool', 'pool', 'administrate']
}
createBonded.permission = 'admin'
createBonded.description = 'Create a bonded network. bondMode can be balance-slb, active-backup or lacp'
// ===================================================================
export async function set ({
network,
name_description: nameDescription,
name_label: nameLabel,
defaultIsLocked,
id
}) {
await this.getXapi(network).setNetworkProperties(network._xapiId, {
nameDescription,
nameLabel,
defaultIsLocked
})
}
set.params = {
id: {
type: 'string'
},
name_label: {
type: 'string',
optional: true
},
name_description: {
type: 'string',
optional: true
},
defaultIsLocked: {
type: 'boolean',
optional: true
}
}
set.resolve = {
network: ['id', 'network', 'administrate']
}
// =================================================================
export async function delete_ ({ network }) {
return this.getXapi(network).deleteNetwork(network._xapiId)
}
export {delete_ as delete}
delete_.params = {
id: { type: 'string' }
}
delete_.resolve = {
network: ['id', 'network', 'administrate']
}

View File

@@ -5,7 +5,7 @@
async function delete_ ({PBD}) {
// TODO: check if PBD is attached before
await this.getXAPI(PBD).call('PBD.destroy', PBD.ref)
await this.getXapi(PBD).call('PBD.destroy', PBD._xapiRef)
}
export {delete_ as delete}
@@ -20,9 +20,8 @@ delete_.resolve = {
// ===================================================================
// Disconnect
export async function disconnect ({PBD}) {
// TODO: check if PBD is attached before
await this.getXAPI(PBD).call('PBD.unplug', PBD.ref)
export async function disconnect ({ pbd }) {
return this.getXapi(pbd).unplugPbd(pbd._xapiId)
}
disconnect.params = {
@@ -30,7 +29,7 @@ disconnect.params = {
}
disconnect.resolve = {
PBD: ['id', 'PBD', 'administrate']
pbd: ['id', 'PBD', 'administrate']
}
// ===================================================================
@@ -38,7 +37,7 @@ disconnect.resolve = {
export async function connect ({PBD}) {
// TODO: check if PBD is attached before
await this.getXAPI(PBD).call('PBD.plug', PBD.ref)
await this.getXapi(PBD).call('PBD.plug', PBD._xapiRef)
}
connect.params = {

View File

@@ -1,9 +1,21 @@
// TODO: too low level, move into host.
import { IPV4_CONFIG_MODES, IPV6_CONFIG_MODES } from '../xapi'
export function getIpv4ConfigurationModes () {
return IPV4_CONFIG_MODES
}
export function getIpv6ConfigurationModes () {
return IPV6_CONFIG_MODES
}
// ===================================================================
// Delete
async function delete_ ({PIF}) {
async function delete_ ({pif}) {
// TODO: check if PIF is attached before
await this.getXAPI(PIF).call('PIF.destroy', PIF.ref)
await this.getXapi(pif).call('PIF.destroy', pif._xapiRef)
}
export {delete_ as delete}
@@ -12,15 +24,15 @@ delete_.params = {
}
delete_.resolve = {
PIF: ['id', 'PIF', 'administrate']
pif: ['id', 'PIF', 'administrate']
}
// ===================================================================
// Disconnect
export async function disconnect ({PIF}) {
export async function disconnect ({pif}) {
// TODO: check if PIF is attached before
await this.getXAPI(PIF).call('PIF.unplug', PIF.ref)
await this.getXapi(pif).call('PIF.unplug', pif._xapiRef)
}
disconnect.params = {
@@ -28,14 +40,14 @@ disconnect.params = {
}
disconnect.resolve = {
PIF: ['id', 'PIF', 'administrate']
pif: ['id', 'PIF', 'administrate']
}
// ===================================================================
// Connect
export async function connect ({PIF}) {
export async function connect ({pif}) {
// TODO: check if PIF is attached before
await this.getXAPI(PIF).call('PIF.plug', PIF.ref)
await this.getXapi(pif).call('PIF.plug', pif._xapiRef)
}
connect.params = {
@@ -43,5 +55,39 @@ connect.params = {
}
connect.resolve = {
PIF: ['id', 'PIF', 'administrate']
pif: ['id', 'PIF', 'administrate']
}
// ===================================================================
// Reconfigure IP
export async function reconfigureIp ({ pif, mode = 'DHCP', ip, netmask, gateway, dns }) {
await this.getXapi(pif).call('PIF.reconfigure_ip', pif._xapiRef, mode, ip, netmask, gateway, dns)
}
reconfigureIp.params = {
id: { type: 'string', optional: true },
mode: { type: 'string', optional: true },
ip: { type: 'string', optional: true },
netmask: { type: 'string', optional: true },
gateway: { type: 'string', optional: true },
dns: { type: 'string', optional: true }
}
reconfigureIp.resolve = {
pif: ['id', 'PIF', 'administrate']
}
// ===================================================================
export async function editPif ({ pif, vlan }) {
await this.getXapi(pif).editPif(pif._xapiId, { vlan })
}
editPif.params = {
id: { type: 'string' },
vlan: { type: ['integer', 'string'] }
}
editPif.resolve = {
pif: ['id', 'PIF', 'administrate']
}

125
src/api/plugin.js Normal file
View File

@@ -0,0 +1,125 @@
export async function get () {
return /* await */ this.getPlugins()
}
get.description = 'returns a list of all installed plugins'
get.permission = 'admin'
// -------------------------------------------------------------------
export async function configure ({ id, configuration }) {
await this.configurePlugin(id, configuration)
}
configure.description = 'sets the configuration of a plugin'
configure.params = {
id: {
type: 'string'
},
configuration: {}
}
configure.permission = 'admin'
// -------------------------------------------------------------------
export async function disableAutoload ({ id }) {
await this.disablePluginAutoload(id)
}
disableAutoload.description = ''
disableAutoload.params = {
id: {
type: 'string'
}
}
disableAutoload.permission = 'admin'
// -------------------------------------------------------------------
export async function enableAutoload ({ id }) {
await this.enablePluginAutoload(id)
}
enableAutoload.description = 'enables a plugin, allowing it to be loaded'
enableAutoload.params = {
id: {
type: 'string'
}
}
enableAutoload.permission = 'admin'
// -------------------------------------------------------------------
export async function load ({ id }) {
await this.loadPlugin(id)
}
load.description = 'loads a plugin'
load.params = {
id: {
type: 'string'
}
}
load.permission = 'admin'
// -------------------------------------------------------------------
export async function unload ({ id }) {
await this.unloadPlugin(id)
}
unload.description = 'unloads a plugin'
unload.params = {
id: {
type: 'string'
}
}
unload.permission = 'admin'
// -------------------------------------------------------------------
export async function purgeConfiguration ({ id }) {
await this.purgePluginConfiguration(id)
}
purgeConfiguration.description = 'removes a plugin configuration'
purgeConfiguration.params = {
id: {
type: 'string'
}
}
purgeConfiguration.permission = 'admin'
// ---------------------------------------------------------------------
export async function test ({ id, data }) {
await this.testPlugin(id, data)
}
test.description = 'Test a plugin with its current configuration'
test.params = {
id: {
type: 'string'
},
data: {
optional: true
}
}
test.permission = 'admin'
// ---------------------------------------------------------------------

View File

@@ -1,10 +1,18 @@
import { format } from 'json-rpc-peer'
// ===================================================================
export async function set (params) {
const {pool} = params
delete params.pool
export async function set ({
pool,
await this.getXAPI(pool).setPoolProperties(params)
// TODO: use camel case.
name_description: nameDescription,
name_label: nameLabel
}) {
await this.getXapi(pool).setPoolProperties({
nameDescription,
nameLabel
})
}
set.params = {
@@ -27,8 +35,27 @@ set.resolve = {
// -------------------------------------------------------------------
export async function setDefaultSr ({ sr }) {
await this.hasPermissions(this.user.id, [ [ sr.$pool, 'administrate' ] ])
await this.getXapi(sr).setDefaultSr(sr._xapiId)
}
setDefaultSr.permission = '' // signed in
setDefaultSr.params = {
sr: {
type: 'string'
}
}
setDefaultSr.resolve = {
sr: ['sr', 'SR']
}
// -------------------------------------------------------------------
export async function installPatch ({pool, patch: patchUuid}) {
await this.getXAPI(pool).installPoolPatchOnAllHosts(patchUuid)
await this.getXapi(pool).installPoolPatchOnAllHosts(patchUuid)
}
installPatch.params = {
@@ -43,18 +70,35 @@ installPatch.params = {
installPatch.resolve = {
pool: ['pool', 'pool', 'administrate']
}
// -------------------------------------------------------------------
export async function installAllPatches ({ pool }) {
await this.getXapi(pool).installAllPoolPatchesOnAllHosts()
}
installAllPatches.params = {
pool: {
type: 'string'
}
}
installAllPatches.resolve = {
pool: ['pool', 'pool', 'administrate']
}
installAllPatches.description = 'Install automatically all patches for every hosts of a pool'
// -------------------------------------------------------------------
async function handlePatchUpload (req, res, {pool}) {
const {headers: {['content-length']: contentLength}} = req
const contentLength = req.headers['content-length']
if (!contentLength) {
res.writeHead(411)
res.end('Content length is mandatory')
return
}
await this.getXAPI(pool).uploadPoolPatch(req, contentLength)
await this.getXapi(pool).uploadPoolPatch(req, contentLength)
}
export async function uploadPatch ({pool}) {
@@ -75,3 +119,74 @@ uploadPatch.resolve = {
//
// TODO: remove when no longer used in xo-web
export {uploadPatch as patch}
// -------------------------------------------------------------------
export async function mergeInto ({ source, target, force }) {
await this.mergeXenPools(source._xapiId, target._xapiId, force)
}
mergeInto.params = {
force: { type: 'boolean', optional: true },
source: { type: 'string' },
target: { type: 'string' }
}
mergeInto.resolve = {
source: ['source', 'pool', 'administrate'],
target: ['target', 'pool', 'administrate']
}
// -------------------------------------------------------------------
export async function getLicenseState ({pool}) {
return this.getXapi(pool).call(
'pool.get_license_state',
pool._xapiId.$ref
)
}
getLicenseState.params = {
pool: {
type: 'string'
}
}
getLicenseState.resolve = {
pool: ['pool', 'pool', 'administrate']
}
// -------------------------------------------------------------------
async function handleInstallSupplementalPack (req, res, { poolId }) {
const xapi = this.getXapi(poolId)
// Timeout seems to be broken in Node 4.
// See https://github.com/nodejs/node/issues/3319
req.setTimeout(43200000) // 12 hours
req.length = req.headers['content-length']
try {
await xapi.installSupplementalPackOnAllHosts(req)
res.end(format.response(0))
} catch (e) {
res.writeHead(500)
res.end(format.error(0, new Error(e.message)))
}
}
export async function installSupplementalPack ({ pool }) {
return {
$sendTo: await this.registerHttpRequest(handleInstallSupplementalPack, { poolId: pool.id })
}
}
installSupplementalPack.description = 'installs supplemental pack from ISO file on all hosts'
installSupplementalPack.params = {
pool: { type: 'string' }
}
installSupplementalPack.resolve = {
pool: ['pool', 'pool', 'admin']
}

72
src/api/remote.js Normal file
View File

@@ -0,0 +1,72 @@
export async function getAll () {
return this.getAllRemotes()
}
getAll.permission = 'admin'
getAll.description = 'Gets all existing fs remote points'
export async function get ({id}) {
return this.getRemote(id)
}
get.permission = 'admin'
get.description = 'Gets an existing fs remote point'
get.params = {
id: {type: 'string'}
}
export async function test ({id}) {
return this.testRemote(id)
}
test.permission = 'admin'
test.description = 'Performs a read/write matching test on a remote point'
test.params = {
id: {type: 'string'}
}
export async function list ({id}) {
return this.listRemoteBackups(id)
}
list.permission = 'admin'
list.description = 'Lists the files found in a remote point'
list.params = {
id: {type: 'string'}
}
export async function create ({name, url}) {
return this.createRemote({name, url})
}
create.permission = 'admin'
create.description = 'Creates a new fs remote point'
create.params = {
name: {type: 'string'},
url: {type: 'string'}
}
export async function set ({id, name, url, enabled}) {
await this.updateRemote(id, {name, url, enabled})
}
set.permission = 'admin'
set.description = 'Modifies an existing fs remote point'
set.params = {
id: {type: 'string'},
name: {type: 'string', optional: true},
url: {type: 'string', optional: true},
enabled: {type: 'boolean', optional: true}
}
async function delete_ ({id}) {
await this.removeRemote(id)
}
delete_.permission = 'admin'
delete_.description = 'Deletes an existing fs remote point'
delete_.params = {
id: {type: 'string'}
}
export {delete_ as delete}

240
src/api/resource-set.js Normal file
View File

@@ -0,0 +1,240 @@
import {
unauthorized
} from 'xo-common/api-errors'
// ===================================================================
export function create ({ name, subjects, objects, limits }) {
return this.createResourceSet(name, subjects, objects, limits)
}
create.permission = 'admin'
create.params = {
name: {
type: 'string'
},
subjects: {
type: 'array',
items: {
type: 'string'
},
optional: true
},
objects: {
type: 'array',
items: {
type: 'string'
},
optional: true
},
limits: {
type: 'object',
optional: true
}
}
// -------------------------------------------------------------------
function delete_ ({ id }) {
return this.deleteResourceSet(id)
}
export { delete_ as delete }
delete_.permission = 'admin'
delete_.params = {
id: {
type: 'string'
}
}
// -------------------------------------------------------------------
export function set ({ id, name, subjects, objects, ipPools, limits }) {
return this.updateResourceSet(id, {
limits,
name,
objects,
ipPools,
subjects
})
}
set.permission = 'admin'
set.params = {
id: {
type: 'string'
},
name: {
type: 'string',
optional: true
},
subjects: {
type: 'array',
items: {
type: 'string'
},
optional: true
},
objects: {
type: 'array',
items: {
type: 'string'
},
optional: true
},
ipPools: {
type: 'array',
items: {
type: 'string'
},
optional: true
},
limits: {
type: 'object',
optional: true
}
}
// -------------------------------------------------------------------
export function get ({ id }) {
return this.getResourceSet(id)
}
get.permission = 'admin'
get.params = {
id: {
type: 'string'
}
}
// -------------------------------------------------------------------
export async function getAll () {
const { user } = this
if (!user) {
throw unauthorized()
}
return this.getAllResourceSets(user.id)
}
getAll.description = 'Get the list of all existing resource set'
// -------------------------------------------------------------------
export function addObject ({ id, object }) {
return this.addObjectToResourceSet(object, id)
}
addObject.permission = 'admin'
addObject.params = {
id: {
type: 'string'
},
object: {
type: 'string'
}
}
// -------------------------------------------------------------------
export function removeObject ({ id, object }) {
return this.removeObjectFromResourceSet(object, id)
}
removeObject.permission = 'admin'
removeObject.params = {
id: {
type: 'string'
},
object: {
type: 'string'
}
}
// -------------------------------------------------------------------
export function addSubject ({ id, subject }) {
return this.addSubjectToResourceSet(subject, id)
}
addSubject.permission = 'admin'
addSubject.params = {
id: {
type: 'string'
},
subject: {
type: 'string'
}
}
// -------------------------------------------------------------------
export function removeSubject ({ id, subject }) {
return this.removeSubjectFromResourceSet(subject, id)
}
removeSubject.permission = 'admin'
removeSubject.params = {
id: {
type: 'string'
},
subject: {
type: 'string'
}
}
// -------------------------------------------------------------------
export function addLimit ({ id, limitId, quantity }) {
return this.addLimitToResourceSet(limitId, quantity, id)
}
addLimit.permission = 'admin'
addLimit.params = {
id: {
type: 'string'
},
limitId: {
type: 'string'
},
quantity: {
type: 'integer'
}
}
// -------------------------------------------------------------------
export function removeLimit ({ id, limitId }) {
return this.removeLimitFromResourceSet(limitId, id)
}
removeLimit.permission = 'admin'
removeLimit.params = {
id: {
type: 'string'
},
limitId: {
type: 'string'
}
}
// -------------------------------------------------------------------
export function recomputeAllLimits () {
return this.recomputeResourceSetsLimits()
}
recomputeAllLimits.permission = 'admin'
recomputeAllLimits.description = 'Recompute manually the current resource set usage'

View File

@@ -1,3 +1,5 @@
export async function getAll () {
return await this.getRoles()
return /* await */ this.getRoles()
}
getAll.description = 'Returns the list of all existing roles'

57
src/api/schedule.js Normal file
View File

@@ -0,0 +1,57 @@
// FIXME so far, no acls for schedules
export async function getAll () {
return /* await */ this.getAllSchedules()
}
getAll.permission = 'admin'
getAll.description = 'Gets all existing schedules'
export async function get (id) {
return /* await */ this.getSchedule(id)
}
get.permission = 'admin'
get.description = 'Gets an existing schedule'
get.params = {
id: {type: 'string'}
}
export async function create ({ jobId, cron, enabled, name, timezone }) {
return /* await */ this.createSchedule(this.session.get('user_id'), { job: jobId, cron, enabled, name, timezone })
}
create.permission = 'admin'
create.description = 'Creates a new schedule'
create.params = {
jobId: {type: 'string'},
cron: {type: 'string'},
enabled: {type: 'boolean', optional: true},
name: {type: 'string', optional: true}
}
export async function set ({ id, jobId, cron, enabled, name, timezone }) {
await this.updateSchedule(id, { job: jobId, cron, enabled, name, timezone })
}
set.permission = 'admin'
set.description = 'Modifies an existing schedule'
set.params = {
id: {type: 'string'},
jobId: {type: 'string', optional: true},
cron: {type: 'string', optional: true},
enabled: {type: 'boolean', optional: true},
name: {type: 'string', optional: true}
}
async function delete_ ({id}) {
await this.removeSchedule(id)
}
delete_.permission = 'admin'
delete_.description = 'Deletes an existing schedule'
delete_.params = {
id: {type: 'string'}
}
export {delete_ as delete}

30
src/api/scheduler.js Normal file
View File

@@ -0,0 +1,30 @@
export async function enable ({id}) {
const schedule = await this.getSchedule(id)
schedule.enabled = true
await this.updateSchedule(id, schedule)
}
enable.permission = 'admin'
enable.description = 'Enables a schedule to run it\'s job as scheduled'
enable.params = {
id: {type: 'string'}
}
export async function disable ({id}) {
const schedule = await this.getSchedule(id)
schedule.enabled = false
await this.updateSchedule(id, schedule)
}
disable.permission = 'admin'
disable.description = 'Disables a schedule'
disable.params = {
id: {type: 'string'}
}
export function getScheduleTable () {
return this.scheduleTable
}
disable.permission = 'admin'
disable.description = 'Get a map of existing schedules enabled/disabled state'

View File

@@ -1,18 +1,21 @@
import {coroutine} from 'bluebird'
// ===================================================================
import {
noop,
pCatch
} from '../utils'
export async function add ({
label,
host,
username,
password,
readOnly,
autoConnect = true
}) {
const server = await this.registerXenServer({host, username, password})
const server = await this.registerXenServer({label, host, username, password, readOnly})
if (autoConnect) {
// Connect asynchronously, ignore any error.
this.connectXenServer(server.id).catch(() => {})
// Connect asynchronously, ignore any errors.
this.connectXenServer(server.id)::pCatch(noop)
}
return server.id
@@ -23,6 +26,10 @@ add.description = 'register a new Xen server'
add.permission = 'admin'
add.params = {
label: {
optional: true,
type: 'string'
},
host: {
type: 'string'
},
@@ -41,7 +48,7 @@ add.params = {
// -------------------------------------------------------------------
export async function remove ({id}) {
this.unregisterXenServer(id)
await this.unregisterXenServer(id)
}
remove.description = 'unregister a Xen server'
@@ -58,15 +65,9 @@ remove.params = {
// TODO: remove this function when users are integrated to the main
// collection.
export const getAll = coroutine(function * () {
const servers = yield this._servers.get()
for (let i = 0, n = servers.length; i < n; ++i) {
servers[i] = this.getServerPublicProperties(servers[i])
}
return servers
})
export function getAll () {
return this.getAllXenServers()
}
getAll.description = 'returns all the registered Xen server'
@@ -74,11 +75,11 @@ getAll.permission = 'admin'
// -------------------------------------------------------------------
export async function set ({id, host, username, password}) {
await this.updateXenServer(id, {host, username, password})
export async function set ({id, label, host, username, password, readOnly}) {
await this.updateXenServer(id, {label, host, username, password, readOnly})
}
set.description = 'changes the propeorties of a Xen server'
set.description = 'changes the properties of a Xen server'
set.permission = 'admin'
@@ -86,6 +87,10 @@ set.params = {
id: {
type: 'string'
},
label: {
type: 'string',
optional: true
},
host: {
type: 'string',
optional: true
@@ -103,6 +108,7 @@ set.params = {
// -------------------------------------------------------------------
export async function connect ({id}) {
this.updateXenServer(id, {enabled: true})::pCatch(noop)
await this.connectXenServer(id)
}
@@ -119,6 +125,7 @@ connect.params = {
// -------------------------------------------------------------------
export async function disconnect ({id}) {
this.updateXenServer(id, {enabled: false})::pCatch(noop)
await this.disconnectXenServer(id)
}

View File

@@ -1,21 +1,18 @@
import {deprecate} from 'util'
import {InvalidCredential, AlreadyAuthenticated} from '../api-errors'
import { getUserPublicProperties } from '../utils'
import {invalidCredentials} from 'xo-common/api-errors'
// ===================================================================
export async function signIn (credentials) {
if (this.session.has('user_id')) {
throw new AlreadyAuthenticated()
}
const user = await this.authenticateUser(credentials)
if (!user) {
throw new InvalidCredential()
throw invalidCredentials()
}
this.session.set('user_id', user.get('id'))
this.session.set('user_id', user.id)
return this.getUserPublicProperties(user)
return getUserPublicProperties(user)
}
signIn.description = 'sign in'
@@ -53,9 +50,9 @@ signOut.permission = ''
export async function getUser () {
const userId = this.session.get('user_id')
return userId === undefined ?
null :
this.getUserPublicProperties(await this.getUser(userId))
return userId === undefined
? null
: getUserPublicProperties(await this.getUser(userId))
}
getUser.description = 'return the currently connected user'

View File

@@ -1,13 +1,23 @@
import forEach from 'lodash.foreach'
import {ensureArray, parseXml} from '../utils'
import { asInteger } from '../xapi/utils'
import {
ensureArray,
forEach,
parseXml
} from '../utils'
// ===================================================================
export async function set (params) {
const {sr} = params
delete params.sr
export async function set ({
sr,
await this.getXAPI(sr).setSrProperties(sr.id, params)
// TODO: use camel case.
name_description: nameDescription,
name_label: nameLabel
}) {
await this.getXapi(sr).setSrProperties(sr._xapiId, {
nameDescription,
nameLabel
})
}
set.params = {
@@ -24,8 +34,8 @@ set.resolve = {
// -------------------------------------------------------------------
export async function scan ({SR}) {
await this.getXAPI(SR).call('SR.scan', SR.ref)
export async function scan ({ SR }) {
await this.getXapi(SR).call('SR.scan', SR._xapiRef)
}
scan.params = {
@@ -39,8 +49,16 @@ scan.resolve = {
// -------------------------------------------------------------------
// TODO: find a way to call this "delete" and not destroy
export async function destroy ({SR}) {
await this.getXAPI(SR).call('SR.destroy', SR.ref)
export async function destroy ({ sr }) {
const xapi = this.getXapi(sr)
if (sr.SR_type === 'xosan') {
const config = xapi.xo.getData(sr, 'xosan_config')
// we simply forget because the hosted disks are been destroyed with the VMs
await xapi.forgetSr(sr._xapiId)
await Promise.all(config.nodes.map(node => xapi.deleteVm(node.vm.id, true)))
return xapi.deleteNetwork(config.network)
}
await xapi.destroySr(sr._xapiId)
}
destroy.params = {
@@ -48,13 +66,13 @@ destroy.params = {
}
destroy.resolve = {
SR: ['id', 'SR', 'administrate']
sr: ['id', 'SR', 'administrate']
}
// -------------------------------------------------------------------
export async function forget ({SR}) {
await this.getXAPI(SR).call('SR.forget', SR.ref)
export async function forget ({ SR }) {
await this.getXapi(SR).forgetSr(SR._xapiId)
}
forget.params = {
@@ -67,31 +85,67 @@ forget.resolve = {
// -------------------------------------------------------------------
export async function connectAllPbds ({ SR }) {
await this.getXapi(SR).connectAllSrPbds(SR._xapiId)
}
connectAllPbds.params = {
id: { type: 'string' }
}
connectAllPbds.resolve = {
SR: ['id', 'SR', 'administrate']
}
// -------------------------------------------------------------------
export async function disconnectAllPbds ({ SR }) {
await this.getXapi(SR).disconnectAllSrPbds(SR._xapiId)
}
disconnectAllPbds.params = {
id: { type: 'string' }
}
disconnectAllPbds.resolve = {
SR: ['id', 'SR', 'administrate']
}
// -------------------------------------------------------------------
export async function createIso ({
host,
nameLabel,
nameDescription,
path
path,
type,
user,
password
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
// FIXME: won't work for IPv6
// Detect if NFS or local path for ISO files
const deviceConfig = {location: path}
if (path.indexOf(':') === -1) { // not NFS share
// TODO: legacy will be removed in XAPI soon by FileSR
const deviceConfig = {}
if (type === 'local') {
deviceConfig.legacy_mode = 'true'
} else if (type === 'smb') {
path = path.replace(/\\/g, '/')
deviceConfig.type = 'cifs'
deviceConfig.username = user
deviceConfig.cifspassword = password
}
deviceConfig.location = path
const srRef = await xapi.call(
'SR.create',
host.ref,
host._xapiRef,
deviceConfig,
'0', // SR size 0 because ISO
nameLabel,
nameDescription,
'iso', // SR type ISO
'iso', // SR content type ISO
true,
type !== 'local',
{}
)
@@ -103,7 +157,10 @@ createIso.params = {
host: { type: 'string' },
nameLabel: { type: 'string' },
nameDescription: { type: 'string' },
path: { type: 'string' }
path: { type: 'string' },
type: { type: 'string' },
user: { type: 'string', optional: true },
password: { type: 'string', optional: true }
}
createIso.resolve = {
@@ -123,7 +180,7 @@ export async function createNfs ({
serverPath,
nfsVersion
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
const deviceConfig = {
server,
@@ -137,7 +194,7 @@ export async function createNfs ({
const srRef = await xapi.call(
'SR.create',
host.ref,
host._xapiRef,
deviceConfig,
'0',
nameLabel,
@@ -165,6 +222,51 @@ createNfs.resolve = {
host: ['host', 'host', 'administrate']
}
// -------------------------------------------------------------------
// HBA SR
// This functions creates an HBA SR
export async function createHba ({
host,
nameLabel,
nameDescription,
scsiId
}) {
const xapi = this.getXapi(host)
const deviceConfig = {
scsiId
}
const srRef = await xapi.call(
'SR.create',
host._xapiRef,
deviceConfig,
'0',
nameLabel,
nameDescription,
'lvmoohba', // SR LVM over HBA
'user', // recommended by Citrix
true,
{}
)
const sr = await xapi.call('SR.get_record', srRef)
return sr.uuid
}
createHba.params = {
host: { type: 'string' },
nameLabel: { type: 'string' },
nameDescription: { type: 'string' },
scsiId: { type: 'string' }
}
createHba.resolve = {
host: ['host', 'host', 'administrate']
}
// -------------------------------------------------------------------
// Local LVM SR
@@ -176,7 +278,7 @@ export async function createLvm ({
nameDescription,
device
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
const deviceConfig = {
device
@@ -184,7 +286,7 @@ export async function createLvm ({
const srRef = await xapi.call(
'SR.create',
host.ref,
host._xapiRef,
deviceConfig,
'0',
nameLabel,
@@ -218,7 +320,7 @@ export async function probeNfs ({
host,
server
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
const deviceConfig = {
server
@@ -229,7 +331,7 @@ export async function probeNfs ({
try {
await xapi.call(
'SR.probe',
host.ref,
host._xapiRef,
deviceConfig,
'nfs',
{}
@@ -264,6 +366,55 @@ probeNfs.resolve = {
host: ['host', 'host', 'administrate']
}
// -------------------------------------------------------------------
// This function helps to detect all HBA devices on the host
export async function probeHba ({
host
}) {
const xapi = this.getXapi(host)
let xml
try {
await xapi.call(
'SR.probe',
host._xapiRef,
'type',
{}
)
throw new Error('the call above should have thrown an error')
} catch (error) {
if (error.code !== 'SR_BACKEND_FAILURE_107') {
throw error
}
xml = parseXml(error.params[2])
}
const hbaDevices = []
forEach(ensureArray(xml.Devlist.BlockDevice), hbaDevice => {
hbaDevices.push({
hba: hbaDevice.hba.trim(),
path: hbaDevice.path.trim(),
scsciId: hbaDevice.SCSIid.trim(),
size: hbaDevice.size.trim(),
vendor: hbaDevice.vendor.trim()
})
})
return hbaDevices
}
probeHba.params = {
host: { type: 'string' }
}
probeHba.resolve = {
host: ['host', 'host', 'administrate']
}
// -------------------------------------------------------------------
// ISCSI SR
@@ -281,7 +432,7 @@ export async function createIscsi ({
chapUser,
chapPassword
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
const deviceConfig = {
target,
@@ -297,12 +448,12 @@ export async function createIscsi ({
// if we give another port than default iSCSI
if (port) {
deviceConfig.port = port
deviceConfig.port = asInteger(port)
}
const srRef = await xapi.call(
'SR.create',
host.ref,
host._xapiRef,
deviceConfig,
'0',
nameLabel,
@@ -322,7 +473,7 @@ createIscsi.params = {
nameLabel: { type: 'string' },
nameDescription: { type: 'string' },
target: { type: 'string' },
port: { type: 'integer', optional: true},
port: { type: 'integer', optional: true },
targetIqn: { type: 'string' },
scsiId: { type: 'string' },
chapUser: { type: 'string', optional: true },
@@ -344,7 +495,7 @@ export async function probeIscsiIqns ({
chapUser,
chapPassword
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
const deviceConfig = {
target: targetIp
@@ -358,7 +509,7 @@ export async function probeIscsiIqns ({
// if we give another port than default iSCSI
if (port) {
deviceConfig.port = port
deviceConfig.port = asInteger(port)
}
let xml
@@ -366,7 +517,7 @@ export async function probeIscsiIqns ({
try {
await xapi.call(
'SR.probe',
host.ref,
host._xapiRef,
deviceConfig,
'lvmoiscsi',
{}
@@ -421,7 +572,7 @@ export async function probeIscsiLuns ({
chapUser,
chapPassword
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
const deviceConfig = {
target: targetIp,
@@ -436,7 +587,7 @@ export async function probeIscsiLuns ({
// if we give another port than default iSCSI
if (port) {
deviceConfig.port = port
deviceConfig.port = asInteger(port)
}
let xml
@@ -444,7 +595,7 @@ export async function probeIscsiLuns ({
try {
await xapi.call(
'SR.probe',
host.ref,
host._xapiRef,
deviceConfig,
'lvmoiscsi',
{}
@@ -476,7 +627,7 @@ export async function probeIscsiLuns ({
probeIscsiLuns.params = {
host: { type: 'string' },
target: { type: 'string' },
port: { type: 'integer', optional: true},
port: { type: 'integer', optional: true },
targetIqn: { type: 'string' },
chapUser: { type: 'string', optional: true },
chapPassword: { type: 'string', optional: true }
@@ -499,7 +650,7 @@ export async function probeIscsiExists ({
chapUser,
chapPassword
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
const deviceConfig = {
target: targetIp,
@@ -515,15 +666,15 @@ export async function probeIscsiExists ({
// if we give another port than default iSCSI
if (port) {
deviceConfig.port = port
deviceConfig.port = asInteger(port)
}
const xml = parseXml(await xapi.call('SR.probe', host.ref, deviceConfig, 'lvmoiscsi', {}))
const xml = parseXml(await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'lvmoiscsi', {}))
const srs = []
forEach(ensureArray(xml['SRlist'].SR), sr => {
// get the UUID of SR connected to this LUN
srs.push({uuid: sr.UUID.trim()})
srs.push({ uuid: sr.UUID.trim() })
})
return srs
@@ -550,22 +701,22 @@ probeIscsiExists.resolve = {
export async function probeNfsExists ({
host,
server,
serverPath,
serverPath
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
const deviceConfig = {
server,
serverpath: serverPath
}
const xml = parseXml(await xapi.call('SR.probe', host.ref, deviceConfig, 'nfs', {}))
const xml = parseXml(await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'nfs', {}))
const srs = []
forEach(ensureArray(xml['SRlist'].SR), sr => {
// get the UUID of SR connected to this LUN
srs.push({uuid: sr.UUID.trim()})
srs.push({ uuid: sr.UUID.trim() })
})
return srs
@@ -589,9 +740,9 @@ export async function reattach ({
uuid,
nameLabel,
nameDescription,
type,
type
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
if (type === 'iscsi') {
type = 'lvmoiscsi' // the internal XAPI name
@@ -632,9 +783,9 @@ export async function reattachIso ({
uuid,
nameLabel,
nameDescription,
type,
type
}) {
const xapi = this.getXAPI(host)
const xapi = this.getXapi(host)
if (type === 'iscsi') {
type = 'lvmoiscsi' // the internal XAPI name

67
src/api/system.js Normal file
View File

@@ -0,0 +1,67 @@
import forEach from 'lodash/forEach'
import getKeys from 'lodash/keys'
import moment from 'moment-timezone'
import { noSuchObject } from 'xo-common/api-errors'
import { version as xoServerVersion } from '../../package.json'
// ===================================================================
export function getMethodsInfo () {
const methods = {}
forEach(this.apiMethods, (method, name) => {
methods[name] = {
description: method.description,
params: method.params || {},
permission: method.permission
}
})
return methods
}
getMethodsInfo.description = 'returns the signatures of all available API methods'
// -------------------------------------------------------------------
export const getServerTimezone = (tz => () => tz)(moment.tz.guess())
getServerTimezone.description = 'return the timezone server'
// -------------------------------------------------------------------
export const getServerVersion = () => xoServerVersion
getServerVersion.description = 'return the version of xo-server'
// -------------------------------------------------------------------
export const getVersion = () => '0.1'
getVersion.description = 'API version (unstable)'
// -------------------------------------------------------------------
export function listMethods () {
return getKeys(this.apiMethods)
}
listMethods.description = 'returns the name of all available API methods'
// -------------------------------------------------------------------
export function methodSignature ({method: name}) {
const method = this.apiMethods[name]
if (!method) {
throw noSuchObject()
}
// Return an array for compatibility with XML-RPC.
return [
// XML-RPC require the name of the method.
{
name,
description: method.description,
params: method.params || {},
permission: method.permission
}
]
}
methodSignature.description = 'returns the signature of an API method'

31
src/api/tag.js Normal file
View File

@@ -0,0 +1,31 @@
export async function add ({tag, object}) {
await this.getXapi(object).addTag(object._xapiId, tag)
}
add.description = 'add a new tag to an object'
add.resolve = {
object: ['id', null, 'administrate']
}
add.params = {
tag: { type: 'string' },
id: { type: 'string' }
}
// -------------------------------------------------------------------
export async function remove ({tag, object}) {
await this.getXapi(object).removeTag(object._xapiId, tag)
}
remove.description = 'remove an existing tag from an object'
remove.resolve = {
object: ['id', null, 'administrate']
}
remove.params = {
tag: { type: 'string' },
id: { type: 'string' }
}

View File

@@ -1,5 +1,5 @@
export async function cancel ({task}) {
await this.getXAPI(task).call('task.cancel', task.ref)
await this.getXapi(task).call('task.cancel', task._xapiRef)
}
cancel.params = {
@@ -13,7 +13,7 @@ cancel.resolve = {
// -------------------------------------------------------------------
export async function destroy ({task}) {
await this.getXAPI(task).call('task.destroy', task.ref)
await this.getXapi(task).call('task.destroy', task._xapiRef)
}
destroy.params = {

View File

@@ -1,9 +1,21 @@
import {delay} from 'bluebird'
export function getPermissionsForUser ({ userId }) {
return this.getPermissionsForUser(userId)
}
// ===================================================================
getPermissionsForUser.permission = 'admin'
export function hasPermission ({userId, objectId, permission}) {
return this.hasPermission(userId, objectId, permission)
getPermissionsForUser.params = {
userId: {
type: 'string'
}
}
// -------------------------------------------------------------------
export function hasPermission ({ userId, objectId, permission }) {
return this.hasPermissions(userId, [
[ objectId, permission ]
])
}
hasPermission.permission = 'admin'
@@ -23,7 +35,11 @@ hasPermission.params = {
// -------------------------------------------------------------------
export function wait ({duration, returnValue}) {
return delay(returnValue, +duration)
return new Promise(resolve => {
setTimeout(() => {
resolve(returnValue)
}, +duration)
})
}
wait.params = {

View File

@@ -1,14 +1,6 @@
import {Unauthorized} from '../api-errors'
// ===================================================================
// TODO: Prevent token connections from creating tokens.
// TODO: Token permission.
export async function create () {
// The user MUST not be signed with a token
if (this.session.has('token_id')) {
throw new Unauthorized()
}
const userId = this.session.get('user_id')
return (await this.createAuthenticationToken({userId})).id
}
@@ -19,6 +11,7 @@ create.permission = '' // sign in
// -------------------------------------------------------------------
// TODO: an user should be able to delete its own tokens.
async function delete_ ({token: id}) {
await this.deleteAuthenticationToken(id)
}

View File

@@ -1,6 +1,5 @@
import map from 'lodash.map'
import {InvalidParameters} from '../api-errors'
import {invalidParameters} from 'xo-common/api-errors'
import { getUserPublicProperties, mapToArray } from '../utils'
// ===================================================================
@@ -15,7 +14,7 @@ create.permission = 'admin'
create.params = {
email: { type: 'string' },
password: { type: 'string' },
permission: { type: 'string', optional: true}
permission: { type: 'string', optional: true }
}
// -------------------------------------------------------------------
@@ -23,7 +22,7 @@ create.params = {
// Deletes an existing user.
async function delete_ ({id}) {
if (id === this.session.get('user_id')) {
throw new InvalidParameters('an user cannot delete itself')
throw invalidParameters('a user cannot delete itself')
}
await this.deleteUser(id)
@@ -46,10 +45,10 @@ delete_.params = {
// collection.
export async function getAll () {
// Retrieves the users.
const users = await this._users.get()
const users = await this.getAllUsers()
// Filters out private properties.
return map(users, this.getUserPublicProperties)
return mapToArray(users, getUserPublicProperties)
}
getAll.description = 'returns all the existing users'
@@ -58,17 +57,43 @@ getAll.permission = 'admin'
// -------------------------------------------------------------------
export async function set ({id, email, password, permission}) {
await this.updateUser(id, {email, password, permission})
export async function set ({id, email, password, permission, preferences}) {
const isAdmin = this.user && this.user.permission === 'admin'
if (isAdmin) {
if (permission && id === this.session.get('user_id')) {
throw invalidParameters('a user cannot change its own permission')
}
} else if (email || password || permission) {
throw invalidParameters('this properties can only changed by an administrator')
}
await this.updateUser(id, {email, password, permission, preferences})
}
set.description = 'changes the properties of an existing user'
set.permission = 'admin'
set.permission = ''
set.params = {
id: { type: 'string' },
email: { type: 'string', optional: true },
password: { type: 'string', optional: true },
permission: { type: 'string', optional: true }
permission: { type: 'string', optional: true },
preferences: { type: 'object', optional: true }
}
// -------------------------------------------------------------------
export async function changePassword ({oldPassword, newPassword}) {
const id = this.session.get('user_id')
await this.changeUserPassword(id, oldPassword, newPassword)
}
changePassword.description = 'change password after checking old password (user function)'
changePassword.permission = ''
changePassword.params = {
oldPassword: {type: 'string'},
newPassword: {type: 'string'}
}

View File

@@ -5,10 +5,10 @@
#=====================================================================
delete_ = $coroutine ({vbd}) ->
xapi = @getXAPI vbd
xapi = @getXapi vbd
# TODO: check if VBD is attached before
yield xapi.call 'VBD.destroy', vbd.ref
yield xapi.call 'VBD.destroy', vbd._xapiRef
return true
@@ -25,12 +25,9 @@ exports.delete = delete_
#---------------------------------------------------------------------
disconnect = $coroutine ({vbd}) ->
xapi = @getXAPI vbd
# TODO: check if VBD is attached before
yield xapi.call 'VBD.unplug_force', vbd.ref
return true
xapi = @getXapi vbd
yield xapi.disconnectVbd(vbd._xapiRef)
return
disconnect.params = {
id: { type: 'string' }
@@ -45,12 +42,9 @@ exports.disconnect = disconnect
#---------------------------------------------------------------------
connect = $coroutine ({vbd}) ->
xapi = @getXAPI vbd
# TODO: check if VBD is attached before
yield xapi.call 'VBD.plug', vbd.ref
return true
xapi = @getXapi vbd
yield xapi.connectVbd(vbd._xapiRef)
return
connect.params = {
id: { type: 'string' }
@@ -66,19 +60,19 @@ exports.connect = connect
set = $coroutine (params) ->
{vbd} = params
xapi = @getXAPI vbd
xapi = @getXapi vbd
{ref} = vbd
{ _xapiRef: ref } = vbd
# VBD position
if 'position' of params
yield xapi.call 'VBD.set_userdevice', ref, params.position
yield xapi.call 'VBD.set_userdevice', ref, String(params.position)
set.params = {
# Identifier of the VBD to update.
id: { type: 'string' }
position: { type: 'string', optional: true }
position: { type: ['string', 'number'], optional: true }
}
@@ -87,3 +81,29 @@ set.resolve = {
}
exports.set = set
#---------------------------------------------------------------------
setBootable = $coroutine ({vbd, bootable}) ->
xapi = @getXapi vbd
{ _xapiRef: ref } = vbd
yield xapi.call 'VBD.set_bootable', ref, bootable
return
setBootable.params = {
vbd: { type: 'string' }
bootable: { type: 'boolean' }
}
setBootable.resolve = {
vbd: ['vbd', 'VBD', 'administrate'],
}
exports.setBootable = setBootable
#=====================================================================
Object.defineProperty(exports, '__esModule', {
value: true
})

View File

@@ -1,27 +1,25 @@
# FIXME: rename to disk.*
$isArray = require 'lodash.isarray'
#---------------------------------------------------------------------
{coroutine: $coroutine} = require 'bluebird'
{format} = require 'json-rpc-peer'
{invalidParameters} = require 'xo-common/api-errors'
{isArray: $isArray, parseSize} = require '../utils'
{JsonRpcError} = require 'json-rpc-peer'
#=====================================================================
delete_ = $coroutine ({vdi}) ->
xapi = @getXAPI vdi
yield @getXapi(vdi).deleteVdi(vdi._xapiId)
# TODO: check if VDI is attached before
yield xapi.call 'VDI.destroy', vdi.ref
return true
return
delete_.params = {
id: { type: 'string' },
}
delete_.resolve = {
vdi: ['id', 'VDI', 'administrate'],
vdi: ['id', ['VDI', 'VDI-snapshot'], 'administrate'],
}
exports.delete = delete_
@@ -31,21 +29,19 @@ exports.delete = delete_
# FIXME: human readable strings should be handled.
set = $coroutine (params) ->
{vdi} = params
xapi = @getXAPI vdi
xapi = @getXapi vdi
{ref} = vdi
{_xapiRef: ref} = vdi
# Size.
if 'size' of params
{size} = params
size = parseSize(params.size)
if size < vdi.size
@throw(
'INVALID_SIZE'
"cannot set new size below the current size (#{vdi.size})"
throw invalidParameters(
"cannot set new size (#{size}) below the current size (#{vdi.size})"
)
yield xapi.call 'VDI.resize_online', ref, "#{size}"
yield xapi.resizeVdi(ref, size)
# Other fields.
for param, fields of {
@@ -68,11 +64,11 @@ set.params = {
name_description: { type: 'string', optional: true }
# size of VDI
size: { type: 'integer', optional: true }
size: { type: ['integer', 'string'], optional: true }
}
set.resolve = {
vdi: ['id', 'VDI', 'administrate'],
vdi: ['id', ['VDI', 'VDI-snapshot'], 'administrate'],
}
exports.set = set
@@ -80,10 +76,9 @@ exports.set = set
#---------------------------------------------------------------------
migrate = $coroutine ({vdi, sr}) ->
xapi = @getXAPI vdi
xapi = @getXapi vdi
# TODO: check if VDI is attached before
yield xapi.call 'VDI.pool_migrate', vdi.ref, sr.ref, {}
yield xapi.moveVdi(vdi._xapiRef, sr._xapiRef)
return true
@@ -93,8 +88,14 @@ migrate.params = {
}
migrate.resolve = {
vdi: ['id', 'VDI', 'administrate'],
vdi: ['id', ['VDI', 'VDI-snapshot'], 'administrate'],
sr: ['sr_id', 'SR', 'administrate'],
}
exports.migrate = migrate
#=====================================================================
Object.defineProperty(exports, '__esModule', {
value: true
})

View File

@@ -1,6 +1,20 @@
import {
diffItems,
noop,
pCatch
} from '../utils'
// ===================================================================
// TODO: move into vm and rename to removeInterface
async function delete_ ({vif}) {
// TODO: check if VIF is attached before
await this.getXAPI(vif).call('VIF.destroy', vif.ref)
this.allocIpAddresses(
vif.id,
null,
vif.allowedIpv4Addresses.concat(vif.allowedIpv6Addresses)
)::pCatch(noop)
await this.getXapi(vif).deleteVif(vif._xapiId)
}
export {delete_ as delete}
@@ -14,9 +28,10 @@ delete_.resolve = {
// -------------------------------------------------------------------
// TODO: move into vm and rename to disconnectInterface
export async function disconnect ({vif}) {
// TODO: check if VIF is attached before
await this.getXAPI(vif).call('VIF.unplug_force', vif.ref)
await this.getXapi(vif).disconnectVif(vif._xapiId)
}
disconnect.params = {
@@ -28,10 +43,10 @@ disconnect.resolve = {
}
// -------------------------------------------------------------------
// TODO: move into vm and rename to connectInterface
export async function connect ({vif}) {
// TODO: check if VIF is attached before
await this.getXAPI(vif).call('VIF.plug', vif.ref)
await this.getXapi(vif).connectVif(vif._xapiId)
}
connect.params = {
@@ -41,3 +56,86 @@ connect.params = {
connect.resolve = {
vif: ['id', 'VIF', 'operate']
}
// -------------------------------------------------------------------
export async function set ({
vif,
network,
mac,
allowedIpv4Addresses,
allowedIpv6Addresses,
attached
}) {
const oldIpAddresses = vif.allowedIpv4Addresses.concat(vif.allowedIpv6Addresses)
const newIpAddresses = []
{
const { push } = newIpAddresses
push.apply(newIpAddresses, allowedIpv4Addresses || vif.allowedIpv4Addresses)
push.apply(newIpAddresses, allowedIpv6Addresses || vif.allowedIpv6Addresses)
}
if (network || mac) {
const xapi = this.getXapi(vif)
const vm = xapi.getObject(vif.$VM)
mac == null && (mac = vif.MAC)
network = xapi.getObject((network && network.id) || vif.$network)
attached == null && (attached = vif.attached)
await this.allocIpAddresses(vif.id, null, oldIpAddresses)
await xapi.deleteVif(vif._xapiId)
// create new VIF with new parameters
const newVif = await xapi.createVif(vm.$id, network.$id, {
mac,
currently_attached: attached,
ipv4_allowed: newIpAddresses
})
await this.allocIpAddresses(newVif.$id, newIpAddresses)
return
}
const [ addAddresses, removeAddresses ] = diffItems(
newIpAddresses,
oldIpAddresses
)
await this.allocIpAddresses(
vif.id,
addAddresses,
removeAddresses
)
return this.getXapi(vif).editVif(vif._xapiId, {
ipv4Allowed: allowedIpv4Addresses,
ipv6Allowed: allowedIpv6Addresses
})
}
set.params = {
id: { type: 'string' },
network: { type: 'string', optional: true },
mac: { type: 'string', optional: true },
allowedIpv4Addresses: {
type: 'array',
items: {
type: 'string'
},
optional: true
},
allowedIpv6Addresses: {
type: 'array',
items: {
type: 'string'
},
optional: true
},
attached: { type: 'boolean', optional: true }
}
set.resolve = {
vif: ['id', 'VIF', 'operate'],
network: ['network', 'network', 'operate']
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,50 @@
import { streamToBuffer } from '../utils'
// ===================================================================
export function clean () {
return this.clean()
}
clean.permission = 'admin'
// -------------------------------------------------------------------
export async function exportConfig () {
return {
$getFrom: await this.registerHttpRequest((req, res) => {
res.writeHead(200, 'OK', {
'content-disposition': 'attachment'
})
return this.exportConfig()
},
undefined,
{ suffix: '/config.json' })
}
}
exportConfig.permission = 'admin'
// -------------------------------------------------------------------
export function getAllObjects () {
return this.getObjects()
}
getAllObjects.permission = ''
getAllObjects.description = 'Returns all XO objects'
// -------------------------------------------------------------------
export async function importConfig () {
return {
$sendTo: await this.registerHttpRequest(async (req, res) => {
await this.importConfig(JSON.parse(await streamToBuffer(req)))
res.end('config successfully imported')
})
}
}
importConfig.permission = 'admin'

478
src/api/xosan.js Normal file
View File

@@ -0,0 +1,478 @@
import arp from 'arp-a'
import createLogger from 'debug'
import defer from 'golike-defer'
import execa from 'execa'
import fromPairs from 'lodash/fromPairs'
import fs from 'fs-promise'
import map from 'lodash/map'
import splitLines from 'split-lines'
import {
filter,
includes
} from 'lodash'
import {
noop,
pCatch,
pFromCallback,
splitFirst
} from '../utils'
const debug = createLogger('xo:xosan')
const SSH_KEY_FILE = 'id_rsa_xosan'
const NETWORK_PREFIX = '172.31.100.'
const XOSAN_VM_SYSTEM_DISK_SIZE = 10 * 1024 * 1024 * 1024
const XOSAN_DATA_DISK_USEAGE_RATIO = 0.99
const XOSAN_MAX_DISK_SIZE = 2093050 * 1024 * 1024 // a bit under 2To
const CURRENTLY_CREATING_SRS = {}
export async function getVolumeInfo ({ sr }) {
const xapi = this.getXapi(sr)
const giantIPtoVMDict = {}
const data = xapi.xo.getData(sr, 'xosan_config')
if (!data || !data.nodes) {
return null
}
const nodes = data.nodes
nodes.forEach(conf => {
giantIPtoVMDict[conf.vm.ip] = xapi.getObject(conf.vm.id)
})
const oneHostAndVm = nodes[0]
const resultCmd = await remoteSsh(xapi, {
host: xapi.getObject(oneHostAndVm.host),
address: oneHostAndVm.vm.ip
}, 'gluster volume info xosan')
const result = resultCmd['stdout']
/*
Volume Name: xosan
Type: Disperse
Volume ID: 1d4d0e57-8b6b-43f9-9d40-c48be1df7548
Status: Started
Snapshot Count: 0
Number of Bricks: 1 x (2 + 1) = 3
Transport-type: tcp
Bricks:
Brick1: 192.168.0.201:/bricks/brick1/xosan1
Brick2: 192.168.0.202:/bricks/brick1/xosan1
Brick3: 192.168.0.203:/bricks/brick1/xosan1
Options Reconfigured:
client.event-threads: 16
server.event-threads: 16
performance.client-io-threads: on
nfs.disable: on
performance.readdir-ahead: on
transport.address-family: inet
features.shard: on
features.shard-block-size: 64MB
network.remote-dio: enable
cluster.eager-lock: enable
performance.io-cache: off
performance.read-ahead: off
performance.quick-read: off
performance.stat-prefetch: on
performance.strict-write-ordering: off
cluster.server-quorum-type: server
cluster.quorum-type: auto
*/
const info = fromPairs(
splitLines(result.trim()).map(line =>
splitFirst(line, ':').map(val => val.trim())
)
)
const getNumber = item => +item.substr(5)
const brickKeys = filter(Object.keys(info), key => key.match(/^Brick[1-9]/)).sort((i1, i2) => getNumber(i1) - getNumber(i2))
// expected brickKeys : [ 'Brick1', 'Brick2', 'Brick3' ]
info['Bricks'] = brickKeys.map(key => {
const ip = info[key].split(':')[0]
return { config: info[key], ip: ip, vm: giantIPtoVMDict[ip] }
})
const entry = await pFromCallback(cb => arp.table(cb))
if (entry) {
const brick = info['Bricks'].find(element => element.config.split(':')[0] === entry.ip)
if (brick) {
brick.mac = entry.mac
}
}
return info
}
getVolumeInfo.description = 'info on gluster volume'
getVolumeInfo.permission = 'admin'
getVolumeInfo.params = {
sr: {
type: 'string'
}
}
getVolumeInfo.resolve = {
sr: ['sr', 'SR', 'administrate']
}
function floor2048 (value) {
return 2048 * Math.floor(value / 2048)
}
async function copyVm (xapi, originalVm, params) {
return { vm: await xapi.copyVm(originalVm, params.sr), params }
}
async function prepareGlusterVm (xapi, vmAndParam, xosanNetwork, increaseDataDisk = true) {
let vm = vmAndParam.vm
// refresh the object so that sizes are correct
const params = vmAndParam.params
const ip = params.xenstore_data['vm-data/ip']
const sr = xapi.getObject(params.sr.$id)
await xapi._waitObjectState(sr.$id, sr => Boolean(sr.$PBDs))
const host = sr.$PBDs[0].$host
const firstVif = vm.$VIFs[0]
if (xosanNetwork.$id !== firstVif.$network.$id) {
await xapi.call('VIF.move', firstVif.$ref, xosanNetwork.$ref)
}
await xapi.editVm(vm, {
name_label: params.name_label,
name_description: params.name_description
})
await xapi.call('VM.set_xenstore_data', vm.$ref, params.xenstore_data)
if (increaseDataDisk) {
const dataDisk = vm.$VBDs.map(vbd => vbd.$VDI).find(vdi => vdi && vdi.name_label === 'xosan_data')
const srFreeSpace = sr.physical_size - sr.physical_utilisation
// we use a percentage because it looks like the VDI overhead is proportional
const newSize = floor2048((srFreeSpace + dataDisk.virtual_size) * XOSAN_DATA_DISK_USEAGE_RATIO)
await xapi._resizeVdi(dataDisk, Math.min(newSize, XOSAN_MAX_DISK_SIZE))
}
await xapi.startVm(vm)
debug('waiting for boot of ', ip)
// wait until we find the assigned IP in the networks, we are just checking the boot is complete
const vmIsUp = vm => Boolean(vm.$guest_metrics && includes(vm.$guest_metrics.networks, ip))
vm = await xapi._waitObjectState(vm.$id, vmIsUp)
debug('booted ', ip)
return { address: ip, host, vm }
}
async function callPlugin (xapi, host, command, params) {
debug('calling plugin', host.address, command)
return JSON.parse(await xapi.call('host.call_plugin', host.$ref, 'xosan.py', command, params))
}
async function remoteSsh (xapi, hostAndAddress, cmd) {
const result = await callPlugin(xapi, hostAndAddress.host, 'run_ssh', {
destination: 'root@' + hostAndAddress.address,
cmd: cmd
})
if (result.exit !== 0) {
throw new Error('ssh error: ' + JSON.stringify(result))
}
debug(result)
return result
}
async function setPifIp (xapi, pif, address) {
await xapi.call('PIF.reconfigure_ip', pif.$ref, 'Static', address, '255.255.255.0', NETWORK_PREFIX + '1', '')
}
const createNetworkAndInsertHosts = defer.onFailure(async function ($onFailure, xapi, pif, vlan) {
let hostIpLastNumber = 1
const xosanNetwork = await xapi.createNetwork({
name: 'XOSAN network',
description: 'XOSAN network',
pifId: pif._xapiId,
mtu: 9000,
vlan: +vlan
})
$onFailure(() => xapi.deleteNetwork(xosanNetwork)::pCatch(noop))
await Promise.all(xosanNetwork.$PIFs.map(pif => setPifIp(xapi, pif, NETWORK_PREFIX + (hostIpLastNumber++))))
return xosanNetwork
})
async function getOrCreateSshKey (xapi) {
let sshKey = xapi.xo.getData(xapi.pool, 'xosan_ssh_key')
if (!sshKey) {
const readKeys = async () => {
sshKey = {
private: await fs.readFile(SSH_KEY_FILE, 'ascii'),
public: await fs.readFile(SSH_KEY_FILE + '.pub', 'ascii')
}
xapi.xo.setData(xapi.pool, 'xosan_ssh_key', sshKey)
}
try {
await readKeys()
} catch (e) {
await execa('ssh-keygen', ['-q', '-f', SSH_KEY_FILE, '-t', 'rsa', '-b', '4096', '-N', ''])
await readKeys()
}
}
return sshKey
}
async function configureGluster (redundancy, ipAndHosts, xapi, firstIpAndHost, glusterType, arbiter = null) {
const configByType = {
replica_arbiter: {
creation: 'replica 3 arbiter 1',
extra: []
},
replica: {
creation: 'replica ' + redundancy + ' ',
extra: ['gluster volume set xosan cluster.data-self-heal on']
},
disperse: {
creation: 'disperse ' + ipAndHosts.length + ' redundancy ' + redundancy + ' ',
extra: []
}
}
let brickVms = arbiter ? ipAndHosts.concat(arbiter) : ipAndHosts
for (let i = 1; i < brickVms.length; i++) {
await remoteSsh(xapi, firstIpAndHost, 'gluster peer probe ' + brickVms[i].address)
}
const creation = configByType[glusterType].creation
const volumeCreation = 'gluster volume create xosan ' + creation +
' ' + brickVms.map(ipAndHost => (ipAndHost.address + ':/bricks/xosan/xosandir')).join(' ')
debug('creating volume: ', volumeCreation)
await remoteSsh(xapi, firstIpAndHost, volumeCreation)
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan network.remote-dio enable')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan cluster.eager-lock enable')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan performance.io-cache off')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan performance.read-ahead off')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan performance.quick-read off')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan performance.strict-write-ordering off')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan client.event-threads 8')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan server.event-threads 8')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan performance.io-thread-count 64')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan performance.stat-prefetch on')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan features.shard on')
await remoteSsh(xapi, firstIpAndHost, 'gluster volume set xosan features.shard-block-size 512MB')
for (const confChunk of configByType[glusterType].extra) {
await remoteSsh(xapi, firstIpAndHost, confChunk)
}
await remoteSsh(xapi, firstIpAndHost, 'gluster volume start xosan')
}
export const createSR = defer.onFailure(async function ($onFailure, { template, pif, vlan, srs, glusterType, redundancy }) {
if (!this.requestResource) {
throw new Error('requestResource is not a function')
}
if (srs.length < 1) {
return // TODO: throw an error
}
let vmIpLastNumber = 101
const xapi = this.getXapi(srs[0])
if (CURRENTLY_CREATING_SRS[xapi.pool.$id]) {
throw new Error('createSR is already running for this pool')
}
CURRENTLY_CREATING_SRS[xapi.pool.$id] = true
try {
const xosanNetwork = await createNetworkAndInsertHosts(xapi, pif, vlan)
$onFailure(() => xapi.deleteNetwork(xosanNetwork)::pCatch(noop))
const sshKey = await getOrCreateSshKey(xapi)
const srsObjects = map(srs, srId => xapi.getObject(srId))
const vmParameters = map(srs, srId => {
const sr = xapi.getObject(srId)
const host = sr.$PBDs[0].$host
return {
sr,
host,
name_label: `XOSAN - ${sr.name_label} - ${host.name_label}`,
name_description: 'Xosan VM storing data on volume ' + sr.name_label,
// the values of the xenstore_data object *have* to be string, don't forget.
xenstore_data: {
'vm-data/hostname': 'XOSAN' + sr.name_label,
'vm-data/sshkey': sshKey.public,
'vm-data/ip': NETWORK_PREFIX + (vmIpLastNumber++),
'vm-data/mtu': String(xosanNetwork.MTU),
'vm-data/vlan': String(vlan)
}
}
})
await Promise.all(vmParameters.map(vmParam => callPlugin(xapi, vmParam.host, 'receive_ssh_keys', {
private_key: sshKey.private,
public_key: sshKey.public,
force: 'true'
})))
const firstVM = await xapi.importVm(
await this.requestResource('xosan', template.id, template.version),
{ srId: vmParameters[0].sr.$ref, type: 'xva' }
)
$onFailure(() => xapi.deleteVm(firstVM, true)::pCatch(noop))
await xapi.editVm(firstVM, {
autoPoweron: true
})
const copiedVms = await Promise.all(vmParameters.slice(1).map(param => copyVm(xapi, firstVM, param)))
// TODO: Promise.all() is certainly not the right operation to execute all the given promises whether they fulfill or reject.
$onFailure(() => Promise.all(copiedVms.map(vm => xapi.deleteVm(vm.vm, true)))::pCatch(noop))
const vmsAndParams = [{
vm: firstVM,
params: vmParameters[0]
}].concat(copiedVms)
let arbiter = null
if (srs.length === 2) {
const sr = vmParameters[0].sr
const arbiterConfig = {
sr: sr,
host: vmParameters[0].host,
name_label: vmParameters[0].name_label + ' arbiter',
name_description: 'Xosan VM storing data on volume ' + sr.name_label,
xenstore_data: {
'vm-data/hostname': 'XOSAN' + sr.name_label + '_arb',
'vm-data/sshkey': sshKey.public,
'vm-data/ip': NETWORK_PREFIX + (vmIpLastNumber++),
'vm-data/mtu': String(xosanNetwork.MTU),
'vm-data/vlan': String(vlan)
}
}
const arbiterVm = await copyVm(xapi, firstVM, arbiterConfig)
$onFailure(() => xapi.deleteVm(arbiterVm.vm, true)::pCatch(noop))
arbiter = await prepareGlusterVm(xapi, arbiterVm, xosanNetwork, false)
}
const ipAndHosts = await Promise.all(map(vmsAndParams, vmAndParam => prepareGlusterVm(xapi, vmAndParam, xosanNetwork)))
const firstIpAndHost = ipAndHosts[0]
await configureGluster(redundancy, ipAndHosts, xapi, firstIpAndHost, glusterType, arbiter)
debug('xosan gluster volume started')
const config = { server: firstIpAndHost.address + ':/xosan', backupserver: ipAndHosts[1].address }
const xosanSr = await xapi.call('SR.create', srsObjects[0].$PBDs[0].$host.$ref, config, 0, 'XOSAN', 'XOSAN', 'xosan', '', true, {})
if (arbiter) {
ipAndHosts.push(arbiter)
}
// we just forget because the cleanup actions will be executed before.
$onFailure(() => xapi.forgetSr(xosanSr)::pCatch(noop))
await xapi.xo.setData(xosanSr, 'xosan_config', {
nodes: ipAndHosts.map(param => ({
host: param.host.$id,
vm: { id: param.vm.$id, ip: param.address }
})),
network: xosanNetwork.$id
})
} finally {
delete CURRENTLY_CREATING_SRS[xapi.pool.$id]
}
})
createSR.description = 'create gluster VM'
createSR.permission = 'admin'
createSR.params = {
srs: {
type: 'array',
items: {
type: 'string'
}
},
pif: {
type: 'string'
},
vlan: {
type: 'string'
},
glusterType: {
type: 'string'
},
redundancy: {
type: 'number'
}
}
createSR.resolve = {
srs: ['sr', 'SR', 'administrate'],
pif: ['pif', 'PIF', 'administrate']
}
export function checkSrIsBusy ({ poolId }) {
return !!CURRENTLY_CREATING_SRS[poolId]
}
checkSrIsBusy.description = 'checks if there is a xosan SR curently being created on the given pool id'
checkSrIsBusy.permission = 'admin'
checkSrIsBusy.params = { poolId: { type: 'string' } }
const POSSIBLE_CONFIGURATIONS = {}
POSSIBLE_CONFIGURATIONS[2] = [{ layout: 'replica_arbiter', redundancy: 3, capacity: 1 }]
POSSIBLE_CONFIGURATIONS[3] = [
{ layout: 'disperse', redundancy: 1, capacity: 2 },
{ layout: 'replica', redundancy: 3, capacity: 1 }]
POSSIBLE_CONFIGURATIONS[4] = [{ layout: 'replica', redundancy: 2, capacity: 1 }]
POSSIBLE_CONFIGURATIONS[5] = [{ layout: 'disperse', redundancy: 1, capacity: 4 }]
POSSIBLE_CONFIGURATIONS[6] = [
{ layout: 'disperse', redundancy: 2, capacity: 4 },
{ layout: 'replica', redundancy: 2, capacity: 3 },
{ layout: 'replica', redundancy: 3, capacity: 2 }]
POSSIBLE_CONFIGURATIONS[7] = [{ layout: 'disperse', redundancy: 3, capacity: 4 }]
POSSIBLE_CONFIGURATIONS[8] = [{ layout: 'replica', redundancy: 2, capacity: 4 }]
POSSIBLE_CONFIGURATIONS[9] = [
{ layout: 'disperse', redundancy: 1, capacity: 8 },
{ layout: 'replica', redundancy: 3, capacity: 3 }]
POSSIBLE_CONFIGURATIONS[10] = [
{ layout: 'disperse', redundancy: 2, capacity: 8 },
{ layout: 'replica', redundancy: 2, capacity: 5 }]
POSSIBLE_CONFIGURATIONS[11] = [{ layout: 'disperse', redundancy: 3, capacity: 8 }]
POSSIBLE_CONFIGURATIONS[12] = [
{ layout: 'disperse', redundancy: 4, capacity: 8 },
{ layout: 'replica', redundancy: 2, capacity: 6 }]
POSSIBLE_CONFIGURATIONS[13] = [{ layout: 'disperse', redundancy: 5, capacity: 8 }]
POSSIBLE_CONFIGURATIONS[14] = [
{ layout: 'disperse', redundancy: 6, capacity: 8 },
{ layout: 'replica', redundancy: 2, capacity: 7 }]
POSSIBLE_CONFIGURATIONS[15] = [
{ layout: 'disperse', redundancy: 7, capacity: 8 },
{ layout: 'replica', redundancy: 3, capacity: 5 }]
POSSIBLE_CONFIGURATIONS[16] = [{ layout: 'replica', redundancy: 2, capacity: 8 }]
export async function computeXosanPossibleOptions ({ lvmSrs }) {
const count = lvmSrs.length
const configurations = POSSIBLE_CONFIGURATIONS[count]
if (!configurations) {
return null
}
if (count > 0) {
const xapi = this.getXapi(lvmSrs[0])
const srs = map(lvmSrs, srId => xapi.getObject(srId))
const srSizes = map(srs, sr => sr.physical_size - sr.physical_utilisation)
const minSize = Math.min.apply(null, srSizes)
const brickSize = (minSize - XOSAN_VM_SYSTEM_DISK_SIZE) * XOSAN_DATA_DISK_USEAGE_RATIO
return configurations.map(conf => ({ ...conf, availableSpace: brickSize * conf.capacity }))
}
}
computeXosanPossibleOptions.params = {
lvmSrs: {
type: 'array',
items: {
type: 'string'
}
}
}
// ---------------------------------------------------------------------
export async function downloadAndInstallXosanPack ({ id, version, pool }) {
if (!this.requestResource) {
throw new Error('requestResource is not a function')
}
const xapi = this.getXapi(pool.id)
const res = await this.requestResource('xosan', id, version)
return xapi.installSupplementalPackOnAllHosts(res)
}
downloadAndInstallXosanPack.description = 'Register a resource via cloud plugin'
downloadAndInstallXosanPack.params = {
id: { type: 'string' },
version: { type: 'string' },
pool: { type: 'string' }
}
downloadAndInstallXosanPack.resolve = {
pool: ['pool', 'pool', 'administrate']
}
downloadAndInstallXosanPack.permission = 'admin'

View File

@@ -1,15 +1,16 @@
import Bluebird from 'bluebird'
import isArray from 'lodash.isarray'
import isObject from 'lodash.isobject'
import Model from './model'
import {BaseError} from 'make-error'
import {EventEmitter} from 'events'
import {mapInPlace} from './utils'
import {
isArray,
isObject,
map
} from './utils'
// ===================================================================
export class ModelAlreadyExists extends BaseError {
constructor (id) {
constructor (id) {
super('this model already exists: ' + id)
}
}
@@ -32,19 +33,14 @@ export default class Collection extends EventEmitter {
})
}
constructor () {
super()
}
add (models, opts) {
async add (models, opts) {
const array = isArray(models)
if (!array) {
models = [models]
}
const {Model} = this
mapInPlace(models, model => {
map(models, model => {
if (!(model instanceof Model)) {
model = new Model(model)
}
@@ -56,56 +52,56 @@ export default class Collection extends EventEmitter {
}
return model.properties
})
}, models)
return Bluebird.try(this._add, [models, opts], this).then(models => {
this.emit('add', models)
models = await this._add(models, opts)
this.emit('add', models)
return array ? models : new this.Model(models[0])
})
return array
? models
: new this.Model(models[0])
}
first (properties) {
async first (properties) {
if (!isObject(properties)) {
properties = (properties !== undefined) ?
{ id: properties } :
{}
properties = (properties !== undefined)
? { id: properties }
: {}
}
return Bluebird.try(this._first, [properties], this).then(
model => model && new this.Model(model)
)
const model = await this._first(properties)
return model && new this.Model(model)
}
get (properties) {
async get (properties) {
if (!isObject(properties)) {
properties = (properties !== undefined) ?
{ id: properties } :
{}
properties = (properties !== undefined)
? { id: properties }
: {}
}
return Bluebird.try(this._get, [properties], this)
return /* await */ this._get(properties)
}
remove (ids) {
async remove (ids) {
if (!isArray(ids)) {
ids = [ids]
}
return Bluebird.try(this._remove, [ids], this).then(() => {
this.emit('remove', ids)
return true
})
await this._remove(ids)
this.emit('remove', ids)
return true
}
update (models) {
async update (models) {
const array = isArray(models)
if (!isArray(models)) {
models = [models]
}
const {Model} = this
mapInPlace(models, model => {
map(models, model => {
if (!(model instanceof Model)) {
// TODO: Problems, we may be mixing in some default
// properties which will overwrite existing ones.
@@ -127,13 +123,14 @@ export default class Collection extends EventEmitter {
}
return model.properties
})
}, models)
return Bluebird.try(this._update, [models], this).then(models => {
this.emit('update', models)
models = await this._update(models)
this.emit('update', models)
return array ? models : new this.Model(models[0])
})
return array
? models
: new this.Model(models[0])
}
// Methods to override in implementations.
@@ -165,9 +162,11 @@ export default class Collection extends EventEmitter {
return this.first(properties).then(model => model != null)
}
_first (properties) {
return Bluebird.try(this.get, [properties], this).then(
models => models.length ? models[0] : null
)
async _first (properties) {
const models = await this.get(properties)
return models.length
? models[0]
: null
}
}

View File

@@ -1,12 +1,18 @@
import Bluebird, {coroutine} from 'bluebird'
import Collection, {ModelAlreadyExists} from '../collection'
import difference from 'lodash.difference'
import filter from 'lodash.filter'
import forEach from 'lodash.foreach'
import getKey from 'lodash.keys'
import isEmpty from 'lodash.isempty'
import map from 'lodash.map'
import thenRedis from 'then-redis'
import difference from 'lodash/difference'
import filter from 'lodash/filter'
import getKey from 'lodash/keys'
import {createClient as createRedisClient} from 'redis'
import {v4 as generateUuid} from 'uuid'
import {
forEach,
isEmpty,
mapToArray,
promisifyAll
} from '../utils'
// ===================================================================
// ///////////////////////////////////////////////////////////////////
// Data model:
@@ -30,13 +36,13 @@ export default class Redis extends Collection {
connection,
indexes = [],
prefix,
uri = 'tcp://localhost:6379',
uri
}) {
super()
this.indexes = indexes
this.prefix = prefix
this.redis = connection || thenRedis.createClient(uri)
this.redis = promisifyAll(connection || createRedisClient(uri))
}
_extract (ids) {
@@ -44,7 +50,7 @@ export default class Redis extends Collection {
const {redis} = this
const models = []
return Bluebird.map(ids, id => {
return Promise.all(mapToArray(ids, id => {
return redis.hgetall(prefix + id).then(model => {
// If empty, consider it a no match.
if (isEmpty(model)) {
@@ -56,22 +62,22 @@ export default class Redis extends Collection {
models.push(model)
})
}).return(models)
})).then(() => models)
}
_add (models, {replace = false} = {}) {
// TODO: remove “replace” which is a temporary measure, implement
// “set()” instead.
const {indexes, prefix, redis, idPrefix = ''} = this
const {indexes, prefix, redis} = this
return Bluebird.map(models, coroutine(function * (model) {
return Promise.all(mapToArray(models, async model => {
// Generate a new identifier if necessary.
if (model.id === undefined) {
model.id = idPrefix + String(yield redis.incr(prefix + '_id'))
model.id = generateUuid()
}
const success = yield redis.sadd(prefix + '_ids', model.id)
const success = await redis.sadd(prefix + '_ids', model.id)
// The entry already exists an we are not in replace mode.
if (!success && !replace) {
@@ -90,8 +96,10 @@ export default class Redis extends Collection {
params.push(name, value)
})
const key = `${prefix}:${model.id}`
const promises = [
redis.hmset(prefix + ':' + model.id, ...params)
redis.del(key),
redis.hmset(key, ...params)
]
// Update indexes.
@@ -105,7 +113,7 @@ export default class Redis extends Collection {
promises.push(redis.sadd(key, model.id))
})
yield Bluebird.all(promises)
await Promise.all(promises)
return model
}))
@@ -123,10 +131,9 @@ export default class Redis extends Collection {
if (id !== undefined) {
delete properties.id
return this._extract([id]).then(models => {
return (models.length && !isEmpty(properties)) ?
filter(models) :
models
return (models.length && !isEmpty(properties))
? filter(models)
: models
})
}
@@ -138,21 +145,25 @@ export default class Redis extends Collection {
throw new Error('fields not indexed: ' + unfit.join())
}
const keys = map(properties, (value, index) => prefix + '_' + index + ':' + value)
const keys = mapToArray(properties, (value, index) => `${prefix}_${index}:${value}`)
return redis.sinter(...keys).then(ids => this._extract(ids))
}
_remove (ids) {
if (isEmpty(ids)) {
return
}
const {prefix, redis} = this
// TODO: handle indexes.
return Bluebird.all([
return Promise.all([
// Remove the identifiers from the main index.
redis.srem(prefix + '_ids', ...ids),
// Remove the models.
redis.del(map(ids, id => prefix + ':' + id))
redis.del(mapToArray(ids, id => `${prefix}:${id}`))
])
}

View File

@@ -1,8 +1,6 @@
import {EventEmitter} from 'events'
// ===================================================================
// const noop = () => {}
import {createRawObject, noop} from './utils'
// ===================================================================
@@ -10,13 +8,13 @@ export default class Connection extends EventEmitter {
constructor () {
super()
this._data = Object.create(null)
this._data = createRawObject()
}
// Close the connection.
close () {
// Prevent errors when the connection is closed more than once.
// this.close = noop
this.close = noop
this.emit('close')
}

View File

@@ -1,69 +1,33 @@
import bind from 'lodash.bind'
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
import {
isArray,
isFunction
} from './utils'
// ===================================================================
const {defineProperty} = Object
const {
defineProperties,
getOwnPropertyDescriptor
} = Object
// ===================================================================
// See: https://github.com/jayphelps/core-decorators.js#autobind
export function autobind (target, key, {
configurable,
enumerable,
value: fn,
writable
}) {
return {
configurable,
enumerable,
get () {
const bounded = bind(fn, this)
defineProperty(this, key, {
configurable: true,
enumerable: false,
value: bounded,
writable: true
})
return bounded
},
set (newValue) {
if (this === target) {
// New value directly set on the prototype.
delete this[key]
this[key] = newValue
} else {
// New value set on a child object.
// Cannot use assignment because it will call the setter on
// the prototype.
defineProperty(this, key, {
configurable: true,
enumerable: true,
value: newValue,
writable: true
})
}
}
}
}
// -------------------------------------------------------------------
// Debounce decorator for methods.
//
// See: https://github.com/wycats/javascript-decorators
export const debounce = (duration) => (target, name, descriptor) => {
const {value: fn} = descriptor
//
// TODO: make it work for single functions.
export const debounce = duration => (target, name, descriptor) => {
const fn = descriptor.value
// This symbol is used to store the related data directly on the
// current object.
const s = Symbol()
const s = Symbol(`debounced ${name} data`)
function debounced () {
let data = this[s] || (this[s] = {
const data = this[s] || (this[s] = {
lastCall: 0,
wrapper: null
})
@@ -80,8 +44,132 @@ export const debounce = (duration) => (target, name, descriptor) => {
}
return data.wrapper()
}
debounced.reset = (obj) => { delete obj[s] }
debounced.reset = obj => { delete obj[s] }
descriptor.value = debounced
return descriptor
}
// -------------------------------------------------------------------
const _ownKeys = (
(typeof Reflect !== 'undefined' && Reflect.ownKeys) ||
(({
getOwnPropertyNames: names,
getOwnPropertySymbols: symbols
}) => symbols
? obj => names(obj).concat(symbols(obj))
: names
)(Object)
)
const _isIgnoredProperty = name => (
name[0] === '_' ||
name === 'constructor'
)
const _IGNORED_STATIC_PROPERTIES = {
__proto__: null,
arguments: true,
caller: true,
length: true,
name: true,
prototype: true
}
const _isIgnoredStaticProperty = name => _IGNORED_STATIC_PROPERTIES[name]
export const mixin = MixIns => Class => {
if (!isArray(MixIns)) {
MixIns = [ MixIns ]
}
const { name } = Class
// Copy properties of plain object mix-ins to the prototype.
{
const allMixIns = MixIns
MixIns = []
const { prototype } = Class
const descriptors = { __proto__: null }
for (const MixIn of allMixIns) {
if (isFunction(MixIn)) {
MixIns.push(MixIn)
continue
}
for (const prop of _ownKeys(MixIn)) {
if (prop in prototype) {
throw new Error(`${name}#${prop} is already defined`)
}
(
descriptors[prop] = getOwnPropertyDescriptor(MixIn, prop)
).enumerable = false // Object methods are enumerable but class methods are not.
}
}
defineProperties(prototype, descriptors)
}
function Decorator (...args) {
const instance = new Class(...args)
for (const MixIn of MixIns) {
const { prototype } = MixIn
const mixinInstance = new MixIn(instance)
const descriptors = { __proto__: null }
for (const prop of _ownKeys(prototype)) {
if (_isIgnoredProperty(prop)) {
continue
}
if (prop in instance) {
throw new Error(`${name}#${prop} is already defined`)
}
descriptors[prop] = getBoundPropertyDescriptor(
prototype,
prop,
mixinInstance
)
}
defineProperties(instance, descriptors)
}
return instance
}
// Copy original and mixed-in static properties on Decorator class.
const descriptors = { __proto__: null }
for (const prop of _ownKeys(Class)) {
let descriptor
if (!(
// Special properties are not defined...
_isIgnoredStaticProperty(prop) &&
// if they already exist...
(descriptor = getOwnPropertyDescriptor(Decorator, prop)) &&
// and are not configurable.
!descriptor.configurable
)) {
descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
}
}
for (const MixIn of MixIns) {
for (const prop of _ownKeys(MixIn)) {
if (_isIgnoredStaticProperty(prop)) {
continue
}
if (prop in descriptors) {
throw new Error(`${name}.${prop} is already defined`)
}
descriptors[prop] = getOwnPropertyDescriptor(MixIn, prop)
}
}
defineProperties(Decorator, descriptors)
return Decorator
}

View File

@@ -1,49 +1,10 @@
/* eslint-env mocha */
/* eslint-env jest */
import {expect} from 'chai'
import {debounce} from './decorators'
// ===================================================================
import {autobind, debounce} from './decorators'
// ===================================================================
describe('autobind', function () {
class Foo {
@autobind
getFoo () {
return this
}
}
it('returns a bound instance for a method', function () {
const foo = new Foo()
const {getFoo} = foo
expect(getFoo()).to.equal(foo)
})
it('returns the same bound instance each time', function () {
const foo = new Foo()
expect(foo.getFoo).to.equal(foo.getFoo)
})
it('works with multiple instances of the same class', function () {
const foo1 = new Foo()
const foo2 = new Foo()
const {getFoo: getFoo1} = foo1
const {getFoo: getFoo2} = foo2
expect(getFoo1()).to.equal(foo1)
expect(getFoo2()).to.equal(foo2)
})
})
// -------------------------------------------------------------------
describe('debounce', function () {
describe('debounce()', () => {
let i
class Foo {
@@ -53,24 +14,24 @@ describe('debounce', function () {
}
}
beforeEach(function () {
beforeEach(() => {
i = 0
})
it('works', function (done) {
it('works', done => {
const foo = new Foo()
expect(i).to.equal(0)
expect(i).toBe(0)
foo.foo()
expect(i).to.equal(1)
expect(i).toBe(1)
foo.foo()
expect(i).to.equal(1)
expect(i).toBe(1)
setTimeout(function () {
setTimeout(() => {
foo.foo()
expect(i).to.equal(2)
expect(i).toBe(2)
done()
}, 2e1)

85
src/fatfs-buffer.js Normal file
View File

@@ -0,0 +1,85 @@
// Buffer driver for [fatfs](https://github.com/natevw/fatfs).
//
// Usage:
//
// ```js
// import fatfs from 'fatfs'
// import fatfsBuffer, { init as fatfsBufferInit } from './fatfs-buffer'
//
// const buffer = fatfsBufferinit()
//
// const fs = fatfs.createFileSystem(fatfsBuffer(buffer))
//
// fs.writeFile('/foo', 'content of foo', function (err, content) {
// if (err) {
// console.error(err)
// }
// })
import { boot16 as fat16 } from 'fatfs/structs'
const SECTOR_SIZE = 512
const TEN_MIB = 10 * 1024 * 1024
// Creates a 10MB buffer and initializes it as a FAT 16 volume.
export function init () {
const buf = Buffer.alloc(TEN_MIB)
// https://github.com/natevw/fatfs/blob/master/structs.js
fat16.pack({
jmpBoot: Buffer.from('eb3c90', 'hex'),
OEMName: 'mkfs.fat',
BytsPerSec: SECTOR_SIZE,
SecPerClus: 4,
ResvdSecCnt: 1,
NumFATs: 2,
RootEntCnt: 512,
TotSec16: 20480,
Media: 248,
FATSz16: 20,
SecPerTrk: 32,
NumHeads: 64,
HiddSec: 0,
TotSec32: 0,
DrvNum: 128,
Reserved1: 0,
BootSig: 41,
VolID: 895111106,
VolLab: 'NO NAME ',
FilSysType: 'FAT16 '
}, buf)
// End of sector.
buf[0x1fe] = 0x55
buf[0x1ff] = 0xaa
// Mark sector as reserved.
buf[0x200] = 0xf8
buf[0x201] = 0xff
buf[0x202] = 0xff
buf[0x203] = 0xff
// Mark sector as reserved.
buf[0x2a00] = 0xf8
buf[0x2a01] = 0xff
buf[0x2a02] = 0xff
buf[0x2a03] = 0xff
return buf
}
export default buffer => {
return {
sectorSize: SECTOR_SIZE,
numSectors: Math.floor(buffer.length / SECTOR_SIZE),
readSectors: (i, target, cb) => {
buffer.copy(target, 0, i * SECTOR_SIZE)
cb()
},
writeSectors: (i, source, cb) => {
source.copy(buffer, i * SECTOR_SIZE, 0)
cb()
}
}
}

54
src/glob-matcher.js Normal file
View File

@@ -0,0 +1,54 @@
// See: https://gist.github.com/julien-f/5b9a3537eb82a34b04e2
var matcher = require('micromatch').matcher
module.exports = function globMatcher (patterns, opts) {
if (!Array.isArray(patterns)) {
if (patterns[0] === '!') {
var m = matcher(patterns.slice(1), opts)
return function (string) {
return !m(string)
}
} else {
return matcher(patterns, opts)
}
}
var noneMustMatch = []
var anyMustMatch = []
// TODO: could probably be optimized by combining all positive patterns (and all negative patterns) as a single matcher.
for (var i = 0, n = patterns.length; i < n; ++i) {
var pattern = patterns[i]
if (pattern[0] === '!') {
noneMustMatch.push(matcher(pattern.slice(1), opts))
} else {
anyMustMatch.push(matcher(pattern, opts))
}
}
var nNone = noneMustMatch.length
var nAny = anyMustMatch.length
return function (string) {
var i
for (i = 0; i < nNone; ++i) {
if (noneMustMatch[i](string)) {
return false
}
}
if (nAny === 0) {
return true
}
for (i = 0; i < nAny; ++i) {
if (anyMustMatch[i](string)) {
return true
}
}
return false
}
}

13
src/http-proxy.js Normal file
View File

@@ -0,0 +1,13 @@
import ProxyAgent from 'proxy-agent'
let agent
export { agent as default }
export function setup (uri) {
agent = uri != null
? new ProxyAgent(uri)
: undefined
}
const { env } = process
setup(env.http_proxy || env.HTTP_PROXY)

144
src/http-request.js Normal file
View File

@@ -0,0 +1,144 @@
import isRedirect from 'is-redirect'
import { assign, isString, startsWith } from 'lodash'
import { cancellable } from 'promise-toolbox'
import { request as httpRequest } from 'http'
import { request as httpsRequest } from 'https'
import { stringify as formatQueryString } from 'querystring'
import {
format as formatUrl,
parse as parseUrl,
resolve as resolveUrl
} from 'url'
import { streamToBuffer } from './utils'
// -------------------------------------------------------------------
const raw = opts => {
let req
const pResponse = new Promise((resolve, reject) => {
const {
body,
headers: { ...headers } = {},
protocol,
query,
...rest
} = opts
if (headers['content-length'] == null && body != null) {
let tmp
if (isString(body)) {
headers['content-length'] = Buffer.byteLength(body)
} else if (
(
(tmp = body.headers) &&
(tmp = tmp['content-length']) != null
) ||
(tmp = body.length) != null
) {
headers['content-length'] = tmp
}
}
if (query) {
rest.path = `${rest.pathname || rest.path || '/'}?${
isString(query)
? query
: formatQueryString(query)
}`
}
// Some headers can be explicitly removed by setting them to null.
const headersToRemove = []
for (const header in headers) {
if (headers[header] === null) {
delete headers[header]
headersToRemove.push(header)
}
}
const secure = protocol && startsWith(protocol.toLowerCase(), 'https')
let requestFn
if (secure) {
requestFn = httpsRequest
} else {
requestFn = httpRequest
delete rest.rejectUnauthorized
}
req = requestFn({
...rest,
headers
})
for (let i = 0, length = headersToRemove.length; i < length; ++i) {
req.removeHeader(headersToRemove[i])
}
if (body) {
if (typeof body.pipe === 'function') {
body.pipe(req)
} else {
req.end(body)
}
} else {
req.end()
}
req.on('error', reject)
req.once('response', resolve)
}).then(response => {
response.cancel = () => {
req.abort()
}
response.readAll = () => streamToBuffer(response)
const length = response.headers['content-length']
if (length) {
response.length = length
}
const code = response.statusCode
const { location } = response.headers
if (isRedirect(code) && location) {
assign(opts, parseUrl(resolveUrl(formatUrl(opts), location)))
return raw(opts)
}
if (code < 200 || code >= 300) {
const error = new Error(response.statusMessage)
error.code = code
Object.defineProperty(error, 'response', {
configurable: true,
value: response,
writable: true
})
throw error
}
return response
})
pResponse.request = req
return pResponse
}
const httpRequestPlus = ($cancelToken, ...args) => {
const opts = {}
for (let i = 0, length = args.length; i < length; ++i) {
const arg = args[i]
assign(opts, isString(arg) ? parseUrl(arg) : arg)
}
const pResponse = raw(opts)
$cancelToken.promise.then(() => {
const { request } = pResponse
request.emit('error', new Error('HTTP request canceled!'))
request.abort()
})
pResponse.readAll = () => pResponse.then(response => response.readAll())
return pResponse
}
export default cancellable(httpRequestPlus)

View File

@@ -1,44 +1,53 @@
import createLogger from 'debug'
const debug = createLogger('xo:main')
import Bluebird from 'bluebird'
Bluebird.longStackTraces()
import appConf from 'app-conf'
import assign from 'lodash.assign'
import bind from 'lodash.bind'
import bind from 'lodash/bind'
import blocked from 'blocked'
import createConnectApp from 'connect'
import createExpress from 'express'
import createLogger from 'debug'
import eventToPromise from 'event-to-promise'
import forEach from 'lodash.foreach'
import has from 'lodash.has'
import isArray from 'lodash.isarray'
import isFunction from 'lodash.isfunction'
import map from 'lodash.map'
import pick from 'lodash.pick'
import proxyRequest from 'proxy-http-request'
import has from 'lodash/has'
import helmet from 'helmet'
import includes from 'lodash/includes'
import proxyConsole from './proxy-console'
import serveStatic from 'serve-static'
import startsWith from 'lodash/startsWith'
import WebSocket from 'ws'
import {
AlreadyAuthenticated,
InvalidCredential,
InvalidParameters,
NoSuchObject,
NotImplemented
} from './api-errors'
import JsonRpcPeer from 'json-rpc-peer'
import {readFile} from 'fs-promise'
import { compile as compilePug } from 'pug'
import { createServer as createProxyServer } from 'http-proxy'
import { join as joinPath } from 'path'
import JsonRpcPeer from 'json-rpc-peer'
import { invalidCredentials } from 'xo-common/api-errors'
import {
ensureDir,
readdir,
readFile
} from 'fs-promise'
import Api from './api'
import WebServer from 'http-server-plus'
import wsProxy from './ws-proxy'
import Xo from './xo'
import {
setup as setupHttpProxy
} from './http-proxy'
import {
createRawObject,
forEach,
isArray,
isFunction,
mapToArray,
pFromCallback
} from './utils'
import bodyParser from 'body-parser'
import connectFlash from 'connect-flash'
import cookieParser from 'cookie-parser'
import expressSession from 'express-session'
import passport from 'passport'
import { parse as parseCookies } from 'cookie'
import { Strategy as LocalStrategy } from 'passport-local'
// ===================================================================
const info = (...args) => {
console.info('[Info]', ...args)
}
const debug = createLogger('xo:main')
const warn = (...args) => {
console.warn('[Warn]', ...args)
@@ -46,15 +55,6 @@ const warn = (...args) => {
// ===================================================================
const DEFAULTS = {
http: {
listen: [
{ port: 80 }
],
mounts: {}
}
}
const DEPRECATED_ENTRIES = [
'users',
'servers'
@@ -62,7 +62,6 @@ const DEPRECATED_ENTRIES = [
async function loadConfiguration () {
const config = await appConf.load('xo-server', {
defaults: DEFAULTS,
ignoreUnknownFormats: true
})
@@ -80,42 +79,233 @@ async function loadConfiguration () {
// ===================================================================
const debugPlugin = createLogger('xo:plugin')
function createExpressApp () {
const app = createExpress()
const loadPlugin = Bluebird.method(function (pluginConf, pluginName) {
debugPlugin('loading %s', pluginName)
app.use(helmet())
const pluginPath = (function (name) {
try {
return require.resolve('xo-server-' + name)
} catch (e) {
return require.resolve(name)
// Registers the cookie-parser and express-session middlewares,
// necessary for connect-flash.
app.use(cookieParser())
app.use(expressSession({
resave: false,
saveUninitialized: false,
// TODO: should be in the config file.
secret: 'CLWguhRZAZIXZcbrMzHCYmefxgweItKnS'
}))
// Registers the connect-flash middleware, necessary for Passport to
// display error messages.
app.use(connectFlash())
// Registers the body-parser middleware, necessary for Passport to
// access the username and password from the sign in form.
app.use(bodyParser.urlencoded({ extended: false }))
// Registers Passport's middlewares.
app.use(passport.initialize())
return app
}
async function setUpPassport (express, xo) {
const strategies = createRawObject()
xo.registerPassportStrategy = strategy => {
passport.use(strategy)
const {name} = strategy
if (name !== 'local') {
strategies[name] = strategy.label || name
}
})(pluginName)
let plugin = require(pluginPath)
if (isFunction(plugin)) {
plugin = plugin(pluginConf)
}
return plugin.load(this)
})
const loadPlugins = function (plugins, xo) {
return Bluebird.all(map(plugins, loadPlugin, xo)).then(() => {
debugPlugin('all plugins loaded')
// Registers the sign in form.
const signInPage = compilePug(
await readFile(joinPath(__dirname, '..', 'signin.pug'))
)
express.get('/signin', (req, res, next) => {
res.send(signInPage({
error: req.flash('error')[0],
strategies
}))
})
express.get('/signout', (req, res) => {
res.clearCookie('token')
res.redirect('/')
})
const SIGNIN_STRATEGY_RE = /^\/signin\/([^/]+)(\/callback)?(:?\?.*)?$/
express.use(async (req, res, next) => {
const { url } = req
const matches = url.match(SIGNIN_STRATEGY_RE)
if (matches) {
return passport.authenticate(matches[1], async (err, user, info) => {
if (err) {
return next(err)
}
if (!user) {
req.flash('error', info ? info.message : 'Invalid credentials')
return res.redirect('/signin')
}
// The cookie will be set in via the next request because some
// browsers do not save cookies on redirect.
req.flash(
'token',
(await xo.createAuthenticationToken({userId: user.id})).id
)
// The session is only persistent for internal provider and if 'Remember me' box is checked
req.flash(
'session-is-persistent',
matches[1] === 'local' && req.body['remember-me'] === 'on'
)
res.redirect(req.flash('return-url')[0] || '/')
})(req, res, next)
}
const token = req.flash('token')[0]
if (token) {
const isPersistent = req.flash('session-is-persistent')[0]
if (isPersistent) {
// Persistent cookie ? => 1 year
res.cookie('token', token, { maxAge: 1000 * 60 * 60 * 24 * 365 })
} else {
// Non-persistent : external provider as Github, Twitter...
res.cookie('token', token)
}
next()
} else if (req.cookies.token) {
next()
} else if (/favicon|fontawesome|images|styles|\.(?:css|jpg|png)$/.test(url)) {
next()
} else {
req.flash('return-url', url)
return res.redirect('/signin')
}
})
// Install the local strategy.
xo.registerPassportStrategy(new LocalStrategy(
async (username, password, done) => {
try {
const user = await xo.authenticateUser({username, password})
done(null, user)
} catch (error) {
done(null, false, { message: error.message })
}
}
))
}
// ===================================================================
async function makeWebServerListen (opts) {
// Read certificate and key if necessary.
const {certificate, key} = opts
if (certificate && key) {
[opts.certificate, opts.key] = await Bluebird.all([
readFile(certificate),
async function registerPlugin (pluginPath, pluginName) {
const plugin = require(pluginPath)
const { version = 'unknown' } = (() => {
try {
return require(pluginPath + '/package.json')
} catch (_) {
return {}
}
})()
// Supports both “normal” CommonJS and Babel's ES2015 modules.
const {
default: factory = plugin,
configurationSchema,
configurationPresets,
testSchema
} = plugin
// The default export can be either a factory or directly a plugin
// instance.
const instance = isFunction(factory)
? factory({
xo: this,
getDataDir: () => {
const dir = `${this._config.datadir}/${pluginName}`
return ensureDir(dir).then(() => dir)
}})
: factory
await this.registerPlugin(
pluginName,
instance,
configurationSchema,
configurationPresets,
testSchema,
version
)
}
const debugPlugin = createLogger('xo:plugin')
function registerPluginWrapper (pluginPath, pluginName) {
debugPlugin('register %s', pluginName)
return registerPlugin.call(this, pluginPath, pluginName).then(
() => {
debugPlugin(`successfully register ${pluginName}`)
},
error => {
debugPlugin(`failed register ${pluginName}`)
debugPlugin(error)
}
)
}
const PLUGIN_PREFIX = 'xo-server-'
const PLUGIN_PREFIX_LENGTH = PLUGIN_PREFIX.length
async function registerPluginsInPath (path) {
const files = await readdir(path).catch(error => {
if (error.code === 'ENOENT') {
return []
}
throw error
})
await Promise.all(mapToArray(files, name => {
if (startsWith(name, PLUGIN_PREFIX)) {
return registerPluginWrapper.call(
this,
`${path}/${name}`,
name.slice(PLUGIN_PREFIX_LENGTH)
)
}
}))
}
async function registerPlugins (xo) {
await Promise.all(mapToArray([
`${__dirname}/../node_modules/`,
'/usr/local/lib/node_modules/'
], xo::registerPluginsInPath))
}
// ===================================================================
async function makeWebServerListen ({
certificate,
// The properties was called `certificate` before.
cert = certificate,
key,
...opts
}) {
if (cert && key) {
[opts.cert, opts.key] = await Promise.all([
readFile(cert),
readFile(key)
])
}
@@ -124,55 +314,79 @@ async function makeWebServerListen (opts) {
const niceAddress = await this.listen(opts)
debug(`Web server listening on ${niceAddress}`)
} catch (error) {
warn(`Web server could not listen on ${error.niceAddress}`)
if (error.niceAddress) {
warn(`Web server could not listen on ${error.niceAddress}`)
const {code} = error
if (code === 'EACCES') {
warn(' Access denied.')
warn(' Ports < 1024 are often reserved to privileges users.')
} else if (code === 'EADDRINUSE') {
warn(' Address already in use.')
const {code} = error
if (code === 'EACCES') {
warn(' Access denied.')
warn(' Ports < 1024 are often reserved to privileges users.')
} else if (code === 'EADDRINUSE') {
warn(' Address already in use.')
}
} else {
warn('Web server could not listen:', error.message)
}
}
}
const createWebServer = opts => {
async function createWebServer (opts) {
const webServer = new WebServer()
return Bluebird
.bind(webServer).return(opts).map(makeWebServerListen)
.return(webServer)
await Promise.all(mapToArray(opts, webServer::makeWebServerListen))
return webServer
}
// ===================================================================
const setUpProxies = (connect, opts) => {
const setUpProxies = (express, opts, xo) => {
if (!opts) {
return
}
const proxy = createProxyServer({
ignorePath: true
}).on('error', (error) => console.error(error))
// TODO: sort proxies by descending prefix length.
// HTTP request proxy.
forEach(opts, (target, url) => {
connect.use(url, (req, res) => {
proxyRequest(target + req.url, req, res)
})
express.use((req, res, next) => {
const { url } = req
for (const prefix in opts) {
if (startsWith(url, prefix)) {
const target = opts[prefix]
proxy.web(req, res, {
target: target + url.slice(prefix.length)
})
return
}
}
next()
})
// WebSocket proxy.
const webSocketServer = new WebSocket.Server({
noServer: true
})
connect.on('upgrade', (req, socket, head) => {
const {url} = req
xo.on('stop', () => pFromCallback(cb => webSocketServer.close(cb)))
for (let prefix in opts) {
if (url.lastIndexOf(prefix, 0) !== -1) {
const target = opts[prefix] + url.slice(prefix.length)
webSocketServer.handleUpgrade(req, socket, head, socket => {
wsProxy(socket, target)
express.on('upgrade', (req, socket, head) => {
const { url } = req
for (const prefix in opts) {
if (startsWith(url, prefix)) {
const target = opts[prefix]
proxy.ws(req, socket, head, {
target: target + url.slice(prefix.length)
})
return
}
}
@@ -181,7 +395,7 @@ const setUpProxies = (connect, opts) => {
// ===================================================================
const setUpStaticFiles = (connect, opts) => {
const setUpStaticFiles = (express, opts) => {
forEach(opts, (paths, url) => {
if (!isArray(paths)) {
paths = [paths]
@@ -190,62 +404,23 @@ const setUpStaticFiles = (connect, opts) => {
forEach(paths, path => {
debug('Setting up %s → %s', url, path)
connect.use(url, serveStatic(path))
express.use(url, serveStatic(path))
})
})
}
// ===================================================================
const errorClasses = {
ALREADY_AUTHENTICATED: AlreadyAuthenticated,
INVALID_CREDENTIAL: InvalidCredential,
INVALID_PARAMS: InvalidParameters,
NO_SUCH_OBJECT: NoSuchObject,
NOT_IMPLEMENTED: NotImplemented
}
const apiHelpers = {
getUserPublicProperties (user) {
// Handles both properties and wrapped models.
const properties = user.properties || user
return pick(properties, 'id', 'email', 'groups', 'permission')
},
getServerPublicProperties (server) {
// Handles both properties and wrapped models.
const properties = server.properties || server
server = pick(properties, 'id', 'host', 'username')
// Injects connection status.
const xapi = this._xapis[server.id]
server.status = xapi ? xapi.status : 'disconnected'
return server
},
throw (errorId, data) {
throw new (errorClasses[errorId])(data)
}
}
const setUpApi = (webServer, xo) => {
const context = Object.create(xo)
assign(xo, apiHelpers)
const api = new Api({
context
})
const setUpApi = (webServer, xo, verboseLogsOnErrors) => {
const webSocketServer = new WebSocket.Server({
server: webServer,
path: '/api/'
noServer: true
})
xo.on('stop', () => pFromCallback(cb => webSocketServer.close(cb)))
webSocketServer.on('connection', socket => {
debug('+ WebSocket connection')
const onConnection = socket => {
const { remoteAddress } = socket.upgradeReq.socket
debug('+ WebSocket connection (%s)', remoteAddress)
// Create the abstract XO object for this connection.
const connection = xo.createUserConnection()
@@ -256,14 +431,14 @@ const setUpApi = (webServer, xo) => {
// Create the JSON-RPC server for this connection.
const jsonRpc = new JsonRpcPeer(message => {
if (message.type === 'request') {
return api.call(connection, message.method, message.params)
return xo.callApiMethod(connection, message.method, message.params)
}
})
connection.notify = bind(jsonRpc.notify, jsonRpc)
// Close the XO connection with this WebSocket.
socket.once('close', () => {
debug('- WebSocket connection')
debug('- WebSocket connection (%s)', remoteAddress)
connection.close()
})
@@ -285,6 +460,11 @@ const setUpApi = (webServer, xo) => {
socket.send(data, onSend)
}
})
}
webServer.on('upgrade', (req, socket, head) => {
if (req.url === '/api/') {
webSocketServer.handleUpgrade(req, socket, head, onConnection)
}
})
}
@@ -296,8 +476,9 @@ const setUpConsoleProxy = (webServer, xo) => {
const webSocketServer = new WebSocket.Server({
noServer: true
})
xo.on('stop', () => pFromCallback(cb => webSocketServer.close(cb)))
webServer.on('upgrade', (req, socket, head) => {
webServer.on('upgrade', async (req, socket, head) => {
const matches = CONSOLE_PROXY_PATH_RE.exec(req.url)
if (!matches) {
return
@@ -305,74 +486,49 @@ const setUpConsoleProxy = (webServer, xo) => {
const [, id] = matches
try {
const url = xo.getXAPI(id, ['VM', 'VM-controller']).getVmConsoleUrl(id)
// TODO: factorize permissions checking in an Express middleware.
{
const { token } = parseCookies(req.headers.cookie)
const user = await xo.authenticateUser({ token })
if (!await xo.hasPermissions(user.id, [ [ id, 'operate' ] ])) {
throw invalidCredentials()
}
const { remoteAddress } = socket
debug('+ Console proxy (%s - %s)', user.name, remoteAddress)
socket.on('close', () => {
debug('- Console proxy (%s - %s)', user.name, remoteAddress)
})
}
const xapi = xo.getXapi(id, ['VM', 'VM-controller'])
const vmConsole = xapi.getVmConsole(id)
// FIXME: lost connection due to VM restart is not detected.
webSocketServer.handleUpgrade(req, socket, head, connection => {
wsProxy(connection, url, {
rejectUnauthorized: false
})
proxyConsole(connection, vmConsole, xapi.sessionId)
})
} catch (_) {}
} catch (error) {
console.error((error && error.stack) || error)
}
})
}
// ===================================================================
const registerPasswordAuthenticationProvider = (xo) => {
async function passwordAuthenticationProvider ({
email,
password,
}) {
/* eslint no-throw-literal: 0 */
const USAGE = (({
name,
version
}) => `Usage: ${name} [--safe-mode]
if (email === undefined || password === undefined) {
throw null
}
// TODO: this is deprecated and should be removed.
const user = await xo._users.first({email})
if (!user || !(await user.checkPassword(password))) {
throw null
}
return user
}
xo.registerAuthenticationProvider(passwordAuthenticationProvider)
}
const registerTokenAuthenticationProvider = (xo) => {
async function tokenAuthenticationProvider ({
token: tokenId,
}) {
/* eslint no-throw-literal: 0 */
if (!tokenId) {
throw null
}
try {
return (await xo.getAuthenticationToken(tokenId)).user_id
} catch (e) {
// It is not an error if the token does not exists.
throw null
}
}
xo.registerAuthenticationProvider(tokenAuthenticationProvider)
}
// ===================================================================
const help = (function ({name, version}) {
return () => `${name} v${version}`
})(require('../package.json'))
${name} v${version}`)(require('../package.json'))
// ===================================================================
export default async function main (args) {
if (args.indexOf('--help') !== -1 || args.indexOf('-h') !== -1) {
return help()
if (includes(args, '--help') || includes(args, '-h')) {
return USAGE
}
{
@@ -401,58 +557,94 @@ export default async function main (args) {
warn('Failed to change user/group:', error)
}
// Create the main object which will connects to Xen servers and
// manages all the models.
const xo = new Xo()
await xo.start({
redis: {
uri: config.redis && config.redis.uri
}
})
// Loads default authentication providers.
registerPasswordAuthenticationProvider(xo)
registerTokenAuthenticationProvider(xo)
if (config.plugins) {
await loadPlugins(config.plugins, xo)
if (config.httpProxy) {
setupHttpProxy(config.httpProxy)
}
// Connect is used to manage non WebSocket connections.
const connect = createConnectApp()
webServer.on('request', connect)
webServer.on('upgrade', (req, socket, head) => {
connect.emit('upgrade', req, socket, head)
})
// Creates main object.
const xo = new Xo(config)
// Register web server close on XO stop.
xo.on('stop', () => pFromCallback(cb => webServer.close(cb)))
// Connects to all registered servers.
await xo.start()
// Express is used to manage non WebSocket connections.
const express = createExpressApp()
if (config.http.redirectToHttps) {
let port
forEach(config.http.listen, listen => {
if (
listen.port &&
(listen.cert || listen.certificate)
) {
port = listen.port
return false
}
})
if (port === undefined) {
warn('Could not setup HTTPs redirection: no HTTPs port found')
} else {
express.use((req, res, next) => {
if (req.secure) {
return next()
}
res.redirect(`https://${req.hostname}:${port}${req.originalUrl}`)
})
}
}
// Must be set up before the API.
setUpConsoleProxy(webServer, xo)
// Must be set up before the API.
connect.use(bind(xo._handleHttpRequest, xo))
express.use(bind(xo._handleHttpRequest, xo))
// TODO: remove when no longer necessary.
connect.use(bind(xo._handleProxyRequest, xo))
// Everything above is not protected by the sign in, allowing xo-cli
// to work properly.
await setUpPassport(express, xo)
// Attaches express to the web server.
webServer.on('request', express)
webServer.on('upgrade', (req, socket, head) => {
express.emit('upgrade', req, socket, head)
})
// Must be set up before the static files.
setUpApi(webServer, xo)
setUpApi(webServer, xo, config.verboseApiLogsOnErrors)
setUpProxies(connect, config.http.proxies)
setUpProxies(express, config.http.proxies, xo)
setUpStaticFiles(connect, config.http.mounts)
setUpStaticFiles(express, config.http.mounts)
if (!(await xo._users.exists())) {
const email = 'admin@admin.net'
const password = 'admin'
await xo.createUser({email, password, permission: 'admin'})
info('Default user created:', email, ' with password', password)
if (!includes(args, '--safe-mode')) {
await registerPlugins(xo)
}
// Handle gracefully shutdown.
const closeWebServer = () => { webServer.close() }
process.on('SIGINT', closeWebServer)
process.on('SIGTERM', closeWebServer)
// Gracefully shutdown on signals.
//
// TODO: implements a timeout? (or maybe it is the services launcher
// responsibility?)
forEach([ 'SIGINT', 'SIGTERM' ], signal => {
let alreadyCalled = false
return eventToPromise(webServer, 'close')
process.on(signal, () => {
if (alreadyCalled) {
warn('forced exit')
process.exit(1)
}
alreadyCalled = true
debug('%s caught, closing…', signal)
xo.stop()
})
})
await eventToPromise(xo, 'stopped')
debug('bye :-)')
}

205
src/job-executor.js Normal file
View File

@@ -0,0 +1,205 @@
import assign from 'lodash/assign'
import Bluebird from 'bluebird'
import every from 'lodash/every'
import filter from 'lodash/filter'
import isArray from 'lodash/isArray'
import isPlainObject from 'lodash/isPlainObject'
import map from 'lodash/map'
import mapValues from 'lodash/mapValues'
import size from 'lodash/size'
import some from 'lodash/some'
import { BaseError } from 'make-error'
import { timeout } from 'promise-toolbox'
import { crossProduct } from './math'
import {
serializeError,
thunkToArray
} from './utils'
export class JobExecutorError extends BaseError {}
export class UnsupportedJobType extends JobExecutorError {
constructor (job) {
super('Unknown job type: ' + job.type)
}
}
export class UnsupportedVectorType extends JobExecutorError {
constructor (vector) {
super('Unknown vector type: ' + vector.type)
}
}
// ===================================================================
const match = (pattern, value) => {
if (isPlainObject(pattern)) {
if (size(pattern) === 1) {
if (pattern.__or) {
return some(pattern.__or, subpattern => match(subpattern, value))
}
if (pattern.__not) {
return !match(pattern.__not, value)
}
}
return isPlainObject(value) && every(pattern, (subpattern, key) => (
value[key] !== undefined && match(subpattern, value[key])
))
}
if (isArray(pattern)) {
return isArray(value) && every(pattern, subpattern =>
some(value, subvalue => match(subpattern, subvalue))
)
}
return pattern === value
}
const paramsVectorActionsMap = {
extractProperties ({ mapping, value }) {
return mapValues(mapping, key => value[key])
},
crossProduct ({ items }) {
return thunkToArray(crossProduct(
map(items, value => resolveParamsVector.call(this, value))
))
},
fetchObjects ({ pattern }) {
return filter(this.xo.getObjects(), object => match(pattern, object))
},
map ({ collection, iteratee, paramName = 'value' }) {
return map(resolveParamsVector.call(this, collection), value => {
return resolveParamsVector.call(this, {
...iteratee,
[paramName]: value
})
})
},
set: ({ values }) => values
}
export function resolveParamsVector (paramsVector) {
const visitor = paramsVectorActionsMap[paramsVector.type]
if (!visitor) {
throw new Error(`Unsupported function '${paramsVector.type}'.`)
}
return visitor.call(this, paramsVector)
}
// ===================================================================
export default class JobExecutor {
constructor (xo) {
this.xo = xo
this._extractValueCb = {
'set': items => items.values
}
// The logger is not available until Xo has started.
xo.on('start', () => xo.getLogger('jobs').then(logger => {
this._logger = logger
}))
}
async exec (job) {
const runJobId = this._logger.notice(`Starting execution of ${job.id}.`, {
event: 'job.start',
userId: job.userId,
jobId: job.id,
key: job.key
})
try {
if (job.type === 'call') {
const execStatus = await this._execCall(job, runJobId)
this.xo.emit('job:terminated', execStatus)
} else {
throw new UnsupportedJobType(job)
}
this._logger.notice(`Execution terminated for ${job.id}.`, {
event: 'job.end',
runJobId
})
} catch (error) {
this._logger.error(`The execution of ${job.id} has failed.`, {
event: 'job.end',
runJobId,
error: serializeError(error)
})
throw error
}
}
async _execCall (job, runJobId) {
const { paramsVector } = job
const paramsFlatVector = paramsVector
? resolveParamsVector.call(this, paramsVector)
: [{}] // One call with no parameters
const connection = this.xo.createUserConnection()
connection.set('user_id', job.userId)
const execStatus = {
runJobId,
start: Date.now(),
calls: {}
}
await Bluebird.map(paramsFlatVector, params => {
const runCallId = this._logger.notice(`Starting ${job.method} call. (${job.id})`, {
event: 'jobCall.start',
runJobId,
method: job.method,
params
})
const call = execStatus.calls[runCallId] = {
method: job.method,
params,
start: Date.now()
}
let promise = this.xo.callApiMethod(connection, job.method, assign({}, params))
if (job.timeout) {
promise = promise::timeout(job.timeout)
}
return promise.then(
value => {
this._logger.notice(`Call ${job.method} (${runCallId}) is a success. (${job.id})`, {
event: 'jobCall.end',
runJobId,
runCallId,
returnedValue: value
})
call.returnedValue = value
call.end = Date.now()
},
reason => {
this._logger.notice(`Call ${job.method} (${runCallId}) has failed. (${job.id})`, {
event: 'jobCall.end',
runJobId,
runCallId,
error: serializeError(reason)
})
call.error = reason
call.end = Date.now()
}
)
}, {
concurrency: 2
})
connection.close()
execStatus.end = Date.now()
return execStatus
}
}

100
src/job-executor.spec.js Normal file
View File

@@ -0,0 +1,100 @@
/* eslint-env jest */
import { forEach } from 'lodash'
import { resolveParamsVector } from './job-executor'
describe('resolveParamsVector', function () {
forEach({
'cross product with three sets': [
// Expected result.
[ { id: 3, value: 'foo', remote: 'local' },
{ id: 7, value: 'foo', remote: 'local' },
{ id: 10, value: 'foo', remote: 'local' },
{ id: 3, value: 'bar', remote: 'local' },
{ id: 7, value: 'bar', remote: 'local' },
{ id: 10, value: 'bar', remote: 'local' } ],
// Entry.
{
type: 'crossProduct',
items: [{
type: 'set',
values: [ { id: 3 }, { id: 7 }, { id: 10 } ]
}, {
type: 'set',
values: [ { value: 'foo' }, { value: 'bar' } ]
}, {
type: 'set',
values: [ { remote: 'local' } ]
}]
}
],
'cross product with `set` and `map`': [
// Expected result.
[
{ remote: 'local', id: 'vm:2' },
{ remote: 'smb', id: 'vm:2' }
],
// Entry.
{
type: 'crossProduct',
items: [{
type: 'set',
values: [ { remote: 'local' }, { remote: 'smb' } ]
}, {
type: 'map',
collection: {
type: 'fetchObjects',
pattern: {
$pool: { __or: [ 'pool:1', 'pool:8', 'pool:12' ] },
power_state: 'Running',
tags: [ 'foo' ],
type: 'VM'
}
},
iteratee: {
type: 'extractProperties',
mapping: { id: 'id' }
}
}]
},
// Context.
{
xo: {
getObjects: function () {
return [{
id: 'vm:1',
$pool: 'pool:1',
tags: [],
type: 'VM',
power_state: 'Halted'
}, {
id: 'vm:2',
$pool: 'pool:1',
tags: [ 'foo' ],
type: 'VM',
power_state: 'Running'
}, {
id: 'host:1',
type: 'host',
power_state: 'Running'
}, {
id: 'vm:3',
$pool: 'pool:8',
tags: [ 'foo' ],
type: 'VM',
power_state: 'Halted'
}]
}
}
}
]
}, ([ expectedResult, entry, context ], name) => {
describe(`with ${name}`, () => {
it('Resolves params vector', () => {
expect(resolveParamsVector.call(context, entry)).toEqual(expectedResult)
})
})
})
})

22
src/loggers/abstract.js Normal file
View File

@@ -0,0 +1,22 @@
export default class AbstractLogger {}
// See: https://en.wikipedia.org/wiki/Syslog#Severity_level
const LEVELS = [
'emergency',
'alert',
'critical',
'error',
'warning',
'notice',
'informational',
'debug'
]
// Create high level log methods.
for (const level of LEVELS) {
Object.defineProperty(AbstractLogger.prototype, level, {
value (message, data) {
return this._add(level, message, data)
}
})
}

59
src/loggers/leveldb.js Normal file
View File

@@ -0,0 +1,59 @@
import highland from 'highland'
import AbstractLogger from './abstract'
import { forEach, noop } from '../utils'
let lastDate = 0
let increment = 0
function generateUniqueKey (date) {
if (date === lastDate) {
return `${date}:${increment++}`
}
increment = 0
return String(lastDate = date)
}
export default class LevelDbLogger extends AbstractLogger {
constructor (db, namespace) {
super()
this._db = db
this._namespace = namespace
}
_add (level, message, data) {
const time = Date.now()
const log = {
level,
message,
data,
namespace: this._namespace,
time
}
const key = generateUniqueKey(time)
this._db.putSync(key, log)
return key
}
createReadStream () {
return highland(this._db.createReadStream())
.filter(({value}) => value.namespace === this._namespace)
}
del (id) {
if (!Array.isArray(id)) {
id = [id]
}
forEach(id, id => {
this._db.get(id).then(value => {
if (value.namespace === this._namespace) {
this._db.delSync(id, noop)
}
})
})
}
}

202
src/logs-cli.js Normal file
View File

@@ -0,0 +1,202 @@
import appConf from 'app-conf'
import get from 'lodash/get'
import highland from 'highland'
import levelup from 'level-party'
import ndjson from 'ndjson'
import parseArgs from 'minimist'
import sublevel from 'level-sublevel'
import util from 'util'
import { repair as repairDb } from 'leveldown'
import {forEach} from './utils'
import globMatcher from './glob-matcher'
// ===================================================================
async function printLogs (db, args) {
let stream = highland(db.createReadStream({reverse: true}))
if (args.since) {
stream = stream.filter(({value}) => (value.time >= args.since))
}
if (args.until) {
stream = stream.filter(({value}) => (value.time <= args.until))
}
const fields = Object.keys(args.matchers)
if (fields.length > 0) {
stream = stream.filter(({value}) => {
for (const field of fields) {
const fieldValue = get(value, field)
if (fieldValue === undefined || !args.matchers[field](fieldValue)) {
return false
}
}
return true
})
}
stream = stream.take(args.limit)
if (args.json) {
stream = highland(stream.pipe(ndjson.serialize()))
.each(value => {
process.stdout.write(value)
})
} else {
stream = stream.each(value => {
console.log(util.inspect(value, { depth: null }))
})
}
return new Promise(resolve => {
stream.done(resolve)
})
}
// ===================================================================
function helper () {
console.error(`
xo-server-logs --help, -h
Display this help message.
xo-server-logs [--json] [--limit=<limit>] [--since=<date>] [--until=<date>] [<pattern>...]
Prints the logs.
--json
Display the results as new line delimited JSON for consumption
by another program.
--limit=<limit>, -n <limit>
Limit the number of results to be displayed (default 100)
--since=<date>, --until=<date>
Start showing entries on or newer than the specified date, or on
or older than the specified date.
<date> should use the format \`YYYY-MM-DD\`.
<pattern>
Patterns can be used to filter the entries.
Patterns have the following format \`<field>=<value>\`/\`<field>\`.
xo-server-logs --repair
Repair/compact the database.
This is an advanced operation and should be used only when necessary and offline (xo-server should be stopped).
`)
}
// ===================================================================
function getArgs () {
const stringArgs = ['since', 'until', 'limit']
const args = parseArgs(process.argv.slice(2), {
string: stringArgs,
boolean: ['help', 'json', 'repair'],
default: {
limit: 100,
json: false,
help: false
},
alias: {
limit: 'n',
help: 'h'
}
})
const patterns = {}
for (let value of args._) {
value = String(value)
const i = value.indexOf('=')
if (i !== -1) {
const field = value.slice(0, i)
const pattern = value.slice(i + 1)
patterns[pattern]
? patterns[field].push(pattern)
: patterns[field] = [ pattern ]
} else if (!patterns[value]) {
patterns[value] = null
}
}
const trueFunction = () => true
args.matchers = {}
for (const field in patterns) {
const values = patterns[field]
args.matchers[field] = (values === null) ? trueFunction : globMatcher(values)
}
// Warning: minimist makes one array of values if the same option is used many times.
// (But only for strings args, not boolean)
forEach(stringArgs, arg => {
if (args[arg] instanceof Array) {
throw new Error(`error: too many values for ${arg} argument`)
}
})
;['since', 'until'].forEach(arg => {
if (args[arg] !== undefined) {
args[arg] = Date.parse(args[arg])
if (isNaN(args[arg])) {
throw new Error(`error: bad ${arg} timestamp format`)
}
}
})
if (isNaN(args.limit = +args.limit)) {
throw new Error('error: limit is not a valid number')
}
return args
}
// ===================================================================
export default async function main () {
const args = getArgs()
if (args.help) {
helper()
return
}
const config = await appConf.load('xo-server', {
ignoreUnknownFormats: true
})
if (args.repair) {
await new Promise((resolve, reject) => {
repairDb(`${config.datadir}/leveldb`, error => {
if (error) {
reject(error)
} else {
resolve()
}
})
})
return
}
const db = sublevel(levelup(
`${config.datadir}/leveldb`,
{ valueEncoding: 'json' }
)).sublevel('logs')
return printLogs(db, args)
}

33
src/lvm.js Normal file
View File

@@ -0,0 +1,33 @@
import execa from 'execa'
import splitLines from 'split-lines'
import { createParser } from 'parse-pairs'
import { isArray, map } from 'lodash'
// ===================================================================
const parse = createParser({
keyTransform: key => key.slice(5).toLowerCase()
})
const makeFunction = command => (fields, ...args) =>
execa.stdout(command, [
'--noheading',
'--nosuffix',
'--nameprefixes',
'--unbuffered',
'--units',
'b',
'-o',
String(fields),
...args
]).then(stdout => map(
splitLines(stdout),
isArray(fields)
? parse
: line => {
const data = parse(line)
return data[fields]
}
))
export const lvs = makeFunction('lvs')
export const pvs = makeFunction('pvs')

48
src/math.js Normal file
View File

@@ -0,0 +1,48 @@
import assign from 'lodash/assign'
const _combine = (vectors, n, cb) => {
if (!n) {
return
}
const nLast = n - 1
const vector = vectors[nLast]
const m = vector.length
if (n === 1) {
for (let i = 0; i < m; ++i) {
cb([ vector[i] ]) // eslint-disable-line standard/no-callback-literal
}
return
}
for (let i = 0; i < m; ++i) {
const value = vector[i]
_combine(vectors, nLast, (vector) => {
vector.push(value)
cb(vector)
})
}
}
// Compute all combinations from vectors.
//
// Ex: combine([[2, 3], [5, 7]])
// => [ [ 2, 5 ], [ 3, 5 ], [ 2, 7 ], [ 3, 7 ] ]
export const combine = vectors => cb => _combine(vectors, vectors.length, cb)
// Merge the properties of an objects set in one object.
//
// Ex: mergeObjects([ { a: 1 }, { b: 2 } ]) => { a: 1, b: 2 }
export const mergeObjects = objects => assign({}, ...objects)
// Compute a cross product between vectors.
//
// Ex: crossProduct([ [ { a: 2 }, { b: 3 } ], [ { c: 5 }, { d: 7 } ] ] )
// => [ { a: 2, c: 5 }, { b: 3, c: 5 }, { a: 2, d: 7 }, { b: 3, d: 7 } ]
export const crossProduct = (vectors, mergeFn = mergeObjects) => cb => (
combine(vectors)(vector => {
cb(mergeFn(vector))
})
)

74
src/math.spec.js Normal file
View File

@@ -0,0 +1,74 @@
/* eslint-env jest */
import { forEach } from 'lodash'
import { thunkToArray } from './utils'
import {
crossProduct,
mergeObjects
} from './math'
describe('mergeObjects', function () {
forEach({
'Two sets of one': [
{a: 1, b: 2}, {a: 1}, {b: 2}
],
'Two sets of two': [
{a: 1, b: 2, c: 3, d: 4}, {a: 1, b: 2}, {c: 3, d: 4}
],
'Three sets': [
{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}, {a: 1}, {b: 2, c: 3}, {d: 4, e: 5, f: 6}
],
'One set': [
{a: 1, b: 2}, {a: 1, b: 2}
],
'Empty set': [
{a: 1}, {a: 1}, {}
],
'All empty': [
{}, {}, {}
],
'No set': [
{}
]
}, ([ resultSet, ...sets ], name) => {
describe(`with ${name}`, () => {
it('Assembles all given param sets in on set', function () {
expect(mergeObjects(sets)).toEqual(resultSet)
})
})
})
})
describe('crossProduct', function () {
// Gives the sum of all args
const addTest = args => args.reduce((prev, curr) => prev + curr, 0)
// Gives the product of all args
const multiplyTest = args => args.reduce((prev, curr) => prev * curr, 1)
forEach({
'2 sets of 2 items to multiply': [
[10, 14, 15, 21], [[2, 3], [5, 7]], multiplyTest
],
'3 sets of 2 items to multiply': [
[110, 130, 154, 182, 165, 195, 231, 273], [[2, 3], [5, 7], [11, 13]], multiplyTest
],
'2 sets of 3 items to multiply': [
[14, 22, 26, 21, 33, 39, 35, 55, 65], [[2, 3, 5], [7, 11, 13]], multiplyTest
],
'2 sets of 2 items to add': [
[7, 9, 8, 10], [[2, 3], [5, 7]], addTest
],
'3 sets of 2 items to add': [
[18, 20, 20, 22, 19, 21, 21, 23], [[2, 3], [5, 7], [11, 13]], addTest
],
'2 sets of 3 items to add': [
[9, 13, 15, 10, 14, 16, 12, 16, 18], [[2, 3, 5], [7, 11, 13]], addTest
]
}, ([ product, items, cb ], name) => {
describe(`with ${name}`, () => {
it('Crosses sets of values with a crossProduct callback', function () {
expect(thunkToArray(crossProduct(items, cb)).sort()).toEqual(product.sort())
})
})
})
})

View File

@@ -1,15 +1,18 @@
import assign from 'lodash.assign'
import forEach from 'lodash.foreach'
import isEmpty from 'lodash.isempty'
import {EventEmitter} from 'events'
import {
forEach,
isEmpty,
isString
} from './utils'
// ===================================================================
export default class Model extends EventEmitter {
constructor (properties) {
super()
this.properties = assign({}, this.default)
this.properties = { ...this.default }
if (properties) {
this.set(properties)
@@ -39,7 +42,7 @@ export default class Model extends EventEmitter {
set (properties, value) {
// This method can also be used with two arguments to set a single
// property.
if (value !== undefined) {
if (isString(properties)) {
properties = { [properties]: value }
}

View File

@@ -1,9 +1,10 @@
import forEach from 'lodash.foreach'
import map from 'lodash.map'
import Collection from '../collection/redis'
import Model from '../model'
import {multiKeyHash} from '../utils'
import {
forEach,
mapToArray,
multiKeyHash
} from '../utils'
// ===================================================================
@@ -58,11 +59,11 @@ export class Acls extends Collection {
})
if (toUpdate.length) {
// Removes all existing entries.
await this.remove(map(toUpdate, 'id'))
await this.remove(mapToArray(toUpdate, 'id'))
// Compute the new ids (new hashes).
const {hash} = Acl
await Promise.all(map(
await Promise.all(mapToArray(
toUpdate,
(acl) => hash(acl.subject, acl.object, acl.action).then(id => {
acl.id = id

View File

@@ -1,8 +1,12 @@
import forEach from 'lodash.foreach'
import isEmpty from 'lodash/isEmpty'
import Collection from '../collection/redis'
import Model from '../model'
import { forEach } from '../utils'
import { parseProp } from './utils'
// ===================================================================
export default class Group extends Model {}
@@ -14,22 +18,18 @@ export class Groups extends Collection {
return Group
}
get idPrefix () {
return 'group:'
}
create (name) {
return this.add(new Group({
name,
users: '[]'
}))
return this.add(new Group({ name }))
}
async save (group) {
// Serializes.
group.users = JSON.stringify(group.users)
let tmp
group.users = isEmpty(tmp = group.users)
? undefined
: JSON.stringify(tmp)
return await this.update(group)
return /* await */ this.update(group)
}
async get (properties) {
@@ -37,13 +37,7 @@ export class Groups extends Collection {
// Deserializes.
forEach(groups, group => {
const {users} = group
try {
group.users = JSON.parse(users)
} catch (error) {
console.warn('cannot parse group.users:', users)
group.users = []
}
group.users = parseProp('group', group, 'users', [])
})
return groups

42
src/models/job.js Normal file
View File

@@ -0,0 +1,42 @@
import Collection from '../collection/redis'
import Model from '../model'
import { forEach } from '../utils'
// ===================================================================
export default class Job extends Model {}
export class Jobs extends Collection {
get Model () {
return Job
}
async create (job) {
// Serializes.
job.paramsVector = JSON.stringify(job.paramsVector)
return /* await */ this.add(new Job(job))
}
async save (job) {
// Serializes.
job.paramsVector = JSON.stringify(job.paramsVector)
return /* await */ this.update(job)
}
async get (properties) {
const jobs = await super.get(properties)
// Deserializes.
forEach(jobs, job => {
const {paramsVector} = job
try {
job.paramsVector = JSON.parse(paramsVector)
} catch (error) {
console.warn('cannot parse job.paramsVector:', paramsVector) // FIXME this is a warning as I copy/paste acl.js, but...
job.paramsVector = {}
}
})
return jobs
}
}

View File

@@ -0,0 +1,53 @@
import Collection from '../collection/redis'
import Model from '../model'
import { forEach } from '../utils'
// ===================================================================
export default class PluginMetadata extends Model {}
// ===================================================================
export class PluginsMetadata extends Collection {
get Model () {
return PluginMetadata
}
async save ({ id, autoload, configuration }) {
return /* await */ this.update({
id,
autoload: autoload ? 'true' : 'false',
configuration: configuration && JSON.stringify(configuration)
})
}
async merge (id, data) {
const pluginMetadata = await this.first(id)
if (!pluginMetadata) {
throw new Error('no such plugin metadata')
}
return /* await */ this.save({
...pluginMetadata.properties,
...data
})
}
async get (properties) {
const pluginsMetadata = await super.get(properties)
// Deserializes.
forEach(pluginsMetadata, pluginMetadata => {
const { autoload, configuration } = pluginMetadata
pluginMetadata.autoload = autoload === 'true'
try {
pluginMetadata.configuration = configuration && JSON.parse(configuration)
} catch (error) {
console.warn('cannot parse pluginMetadata.configuration:', configuration)
pluginMetadata.configuration = []
}
})
return pluginsMetadata
}
}

36
src/models/remote.js Normal file
View File

@@ -0,0 +1,36 @@
import Collection from '../collection/redis'
import Model from '../model'
import {
forEach
} from '../utils'
// ===================================================================
export default class Remote extends Model {}
export class Remotes extends Collection {
get Model () {
return Remote
}
create (name, url) {
return this.add(new Remote({
name,
url,
enabled: false,
error: ''
}))
}
async save (remote) {
return /* await */ this.update(remote)
}
async get (properties) {
const remotes = await super.get(properties)
forEach(remotes, remote => {
remote.enabled = (remote.enabled === 'true')
})
return remotes
}
}

36
src/models/schedule.js Normal file
View File

@@ -0,0 +1,36 @@
import Collection from '../collection/redis'
import Model from '../model'
import { forEach } from '../utils'
// ===================================================================
export default class Schedule extends Model {}
export class Schedules extends Collection {
get Model () {
return Schedule
}
create (userId, job, cron, enabled, name = undefined, timezone = undefined) {
return this.add(new Schedule({
userId,
job,
cron,
enabled,
name,
timezone
}))
}
async save (schedule) {
return /* await */ this.update(schedule)
}
async get (properties) {
const schedules = await super.get(properties)
forEach(schedules, schedule => {
schedule.enabled = (schedule.enabled === 'true')
})
return schedules
}
}

View File

@@ -1,5 +1,8 @@
import Collection from '../collection/redis'
import Model from '../model'
import { forEach } from '../utils'
import { parseProp } from './utils'
// ===================================================================
@@ -11,4 +14,27 @@ export class Servers extends Collection {
get Model () {
return Server
}
async create ({label, host, username, password, readOnly}) {
if (await this.exists({host})) {
throw new Error('server already exists')
}
return /* await */ this.add({label, host, username, password, readOnly})
}
async get (properties) {
const servers = await super.get(properties)
// Deserializes
forEach(servers, server => {
if (server.error) {
server.error = parseProp('server', server, 'error', '')
} else {
delete server.error
}
})
return servers
}
}

View File

@@ -1,26 +1,10 @@
import Collection from '../collection/redis'
import Model from '../model'
import {generateToken} from '../utils'
// ===================================================================
export default class Token extends Model {}
Token.generate = (userId) => {
return generateToken().then(token => new Token({
id: token,
user_id: userId
}))
}
// -------------------------------------------------------------------
export class Tokens extends Collection {
get Model () {
return Token
}
generate (userId) {
return Token.generate(userId).then(token => this.add(token))
}
}
export class Tokens extends Collection {}

View File

@@ -1,47 +1,14 @@
import forEach from 'lodash.foreach'
import {hash, needsRehash, verify} from 'hashy'
import isEmpty from 'lodash/isEmpty'
import Collection from '../collection/redis'
import Model from '../model'
import { forEach } from '../utils'
import { parseProp } from './utils'
// ===================================================================
const PERMISSIONS = {
none: 0,
read: 1,
write: 2,
admin: 3
}
// ===================================================================
export default class User extends Model {
async checkPassword (password) {
const hash = this.get('pw_hash')
if (!(hash && await verify(password, hash))) {
return false
}
// There might be no hash if the user authenticate with another
// method (e.g. LDAP).
if (needsRehash(hash)) {
await this.setPassword(password)
}
return true
}
hasPermission (permission) {
return PERMISSIONS[this.get('permission')] >= PERMISSIONS[permission]
}
setPassword (password) {
return hash(password).then(hash => {
return this.set('pw_hash', hash)
})
}
}
export default class User extends Model {}
User.prototype.default = {
permission: 'none'
@@ -54,24 +21,32 @@ export class Users extends Collection {
return User
}
async create (email, password, permission = 'none') {
const user = new User({
email,
permission
})
async create (properties) {
const { email } = properties
if (password != null) {
await user.setPassword(password)
// Avoid duplicates.
if (await this.exists({email})) {
throw new Error(`the user ${email} already exists`)
}
return this.add(user)
// Create the user object.
const user = new User(properties)
// Adds the user to the collection.
return /* await */ this.add(user)
}
async save (user) {
// Serializes.
user.groups = JSON.stringify(user.groups)
let tmp
user.groups = isEmpty(tmp = user.groups)
? undefined
: JSON.stringify(tmp)
user.preferences = isEmpty(tmp = user.preferences)
? undefined
: JSON.stringify(tmp)
return await this.update(user)
return /* await */ this.update(user)
}
async get (properties) {
@@ -79,13 +54,8 @@ export class Users extends Collection {
// Deserializes
forEach(users, user => {
const {groups} = user
try {
user.groups = groups ? JSON.parse(groups) : []
} catch (_) {
console.warn('cannot parse user.groups:', groups)
user.groups = []
}
user.groups = parseProp('user', user, 'groups', [])
user.preferences = parseProp('user', user, 'preferences', {})
})
return users

16
src/models/utils.js Normal file
View File

@@ -0,0 +1,16 @@
export const parseProp = (type, obj, name, defaultValue) => {
const value = obj[name]
if (
value == null ||
value === '' // do not warn on this trivial and minor error
) {
return defaultValue
}
try {
return JSON.parse(value)
} catch (error) {
// do not display the error because it can occurs a lot and fill
// up log files
return defaultValue
}
}

42
src/node_modules/constant-stream.js generated vendored Normal file
View File

@@ -0,0 +1,42 @@
import from2 from 'from2'
const constantStream = (data, n = 1) => {
if (!Buffer.isBuffer(data)) {
data = Buffer.from(data)
}
const { length } = data
if (!length) {
throw new Error('data should not be empty')
}
n *= length
let currentLength = length
return from2((size, next) => {
if (n <= 0) {
return next(null, null)
}
if (n < size) {
size = n
}
if (size < currentLength) {
const m = Math.floor(size / length) * length || length
n -= m
return next(null, data.slice(0, m))
}
// if more than twice the data length is requested, repeat the data
if (size > currentLength * 2) {
currentLength = Math.floor(size / length) * length
data = Buffer.alloc(currentLength, data)
}
n -= currentLength
return next(null, data)
})
}
export { constantStream as default }

76
src/proxy-console.js Normal file
View File

@@ -0,0 +1,76 @@
import createDebug from 'debug'
import partialStream from 'partial-stream'
import {connect} from 'tls'
import {parse} from 'url'
const debug = createDebug('xo:proxy-console')
export default function proxyConsole (ws, vmConsole, sessionId) {
const url = parse(vmConsole.location)
let closed = false
const socket = connect({
host: url.host,
port: url.port || 443,
rejectUnauthorized: false
}, () => {
// Write headers.
socket.write([
`CONNECT ${url.path} HTTP/1.0`,
`Host: ${url.hostname}`,
`Cookie: session_id=${sessionId}`,
'', ''
].join('\r\n'))
const onSend = (error) => {
if (error) {
debug('error sending to the XO client: %s', error.stack || error.message || error)
}
}
socket.pipe(partialStream('\r\n\r\n', headers => {
// TODO: check status code 200.
debug('connected')
})).on('data', data => {
if (!closed) {
// Encode to base 64.
ws.send(data.toString('base64'), onSend)
}
}).on('end', () => {
if (!closed) {
closed = true
debug('disconnected from the console')
}
ws.close()
})
ws
.on('error', error => {
closed = true
debug('error from the XO client: %s', error.stack || error.message || error)
socket.close()
})
.on('message', data => {
if (!closed) {
// Decode from base 64.
socket.write(Buffer.from(data, 'base64'))
}
})
.on('close', () => {
if (!closed) {
closed = true
debug('disconnected from the XO client')
}
socket.end()
})
}).on('error', error => {
closed = true
debug('error from the console: %s', error.stack || error.message || error)
ws.close()
})
}

View File

@@ -0,0 +1,226 @@
import eventToPromise from 'event-to-promise'
import through2 from 'through2'
import {
parse
} from 'xo-remote-parser'
import {
addChecksumToReadStream,
getPseudoRandomBytes,
noop,
pCatch,
streamToBuffer,
validChecksumOfReadStream
} from '../utils'
export default class RemoteHandlerAbstract {
constructor (remote) {
this._remote = {...remote, ...parse(remote.url)}
if (this._remote.type !== this.type) {
throw new Error('Incorrect remote type')
}
}
get type () {
throw new Error('Not implemented')
}
/**
* Asks the handler to sync the state of the effective remote with its' metadata
*/
async sync () {
return this._sync()
}
async _sync () {
throw new Error('Not implemented')
}
/**
* Free the resources possibly dedicated to put the remote at work, when it is no more needed
*/
async forget () {
return this._forget()
}
async _forget () {
throw new Error('Not implemented')
}
async test () {
const testFileName = `${Date.now()}.test`
const data = getPseudoRandomBytes(1024 * 1024)
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 {
this.unlink(testFileName).catch(noop)
}
}
async outputFile (file, data, options) {
return this._outputFile(file, data, {
flags: 'wx',
...options
})
}
async _outputFile (file, data, options) {
const stream = await this.createOutputStream(file, options)
const promise = eventToPromise(stream, 'finish')
stream.end(data)
return promise
}
async readFile (file, options) {
return this._readFile(file, options)
}
_readFile (file, options) {
return this.createReadStream(file, options).then(streamToBuffer)
}
async rename (oldPath, newPath) {
return this._rename(oldPath, newPath)
}
async _rename (oldPath, newPath) {
throw new Error('Not implemented')
}
async list (dir = '.') {
return this._list(dir)
}
async _list (dir) {
throw new Error('Not implemented')
}
createReadStream (file, {
checksum = false,
ignoreMissingChecksum = false,
...options
} = {}) {
const streamP = this._createReadStream(file, options).then(stream => {
// detect early errors
let promise = eventToPromise(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, this.getSize(file).then(size => {
stream.length = size
}, noop) ])
}
return promise.then(() => stream)
})
if (!checksum) {
return streamP
}
// avoid a unhandled rejection warning
streamP.catch(noop)
return this.readFile(`${file}.checksum`).then(
checksum => streamP.then(stream => {
const { length } = stream
stream = validChecksumOfReadStream(stream, String(checksum).trim())
stream.length = length
return stream
}),
error => {
if (ignoreMissingChecksum && error && error.code === 'ENOENT') {
return streamP
}
throw error
}
)
}
async _createReadStream (file, options) {
throw new Error('Not implemented')
}
async refreshChecksum (path) {
const stream = addChecksumToReadStream(await this.createReadStream(path))
stream.resume() // start reading the whole file
const checksum = await stream.checksum
await this.outputFile(`${path}.checksum`, checksum)
}
async createOutputStream (file, {
checksum = false,
...options
} = {}) {
const streamP = this._createOutputStream(file, {
flags: 'wx',
...options
})
if (!checksum) {
return streamP
}
const connectorStream = through2()
const forwardError = error => {
connectorStream.emit('error', error)
}
const streamWithChecksum = addChecksumToReadStream(connectorStream)
streamWithChecksum.pipe(await streamP)
streamWithChecksum.checksum
.then(value => this.outputFile(`${file}.checksum`, value))
.catch(forwardError)
return connectorStream
}
async _createOutputStream (file, options) {
throw new Error('Not implemented')
}
async unlink (file, {
checksum = false
} = {}) {
if (checksum) {
this._unlink(`${file}.checksum`)::pCatch(noop)
}
return this._unlink(file)
}
async _unlink (file) {
throw new Error('Not implemented')
}
async getSize (file) {
return this._getSize(file)
}
async _getSize (file) {
throw new Error('Not implemented')
}
}

View File

@@ -0,0 +1,89 @@
import fs from 'fs-promise'
import startsWith from 'lodash/startsWith'
import {
dirname,
resolve
} from 'path'
import RemoteHandlerAbstract from './abstract'
import {
noop
} from '../utils'
export default class LocalHandler extends RemoteHandlerAbstract {
get type () {
return 'file'
}
_getRealPath () {
return this._remote.path
}
_getFilePath (file) {
const realPath = this._getRealPath()
const parts = [realPath]
if (file) {
parts.push(file)
}
const path = resolve.apply(null, parts)
if (!startsWith(path, realPath)) {
throw new Error('Remote path is unavailable')
}
return path
}
async _sync () {
if (this._remote.enabled) {
try {
const path = this._getRealPath()
await fs.ensureDir(path)
await fs.access(path, fs.R_OK | fs.W_OK)
} catch (exc) {
this._remote.enabled = false
this._remote.error = exc.message
}
}
return this._remote
}
async _forget () {
return noop()
}
async _outputFile (file, data, options) {
const path = this._getFilePath(file)
await fs.ensureDir(dirname(path))
await fs.writeFile(path, data, options)
}
async _readFile (file, options) {
return fs.readFile(this._getFilePath(file), options)
}
async _rename (oldPath, newPath) {
return fs.rename(this._getFilePath(oldPath), this._getFilePath(newPath))
}
async _list (dir = '.') {
return fs.readdir(this._getFilePath(dir))
}
async _createReadStream (file, options) {
return fs.createReadStream(this._getFilePath(file), options)
}
async _createOutputStream (file, options) {
const path = this._getFilePath(file)
await fs.ensureDir(dirname(path))
return fs.createWriteStream(path, options)
}
async _unlink (file) {
return fs.unlink(this._getFilePath(file))
}
async _getSize (file) {
const stats = await fs.stat(this._getFilePath(file))
return stats.size
}
}

View File

@@ -0,0 +1,84 @@
import execa from 'execa'
import fs from 'fs-promise'
import LocalHandler from './local'
import {
forEach
} from '../utils'
export default class NfsHandler extends LocalHandler {
get type () {
return 'nfs'
}
_getRealPath () {
return `/run/xo-server/mounts/${this._remote.id}`
}
async _loadRealMounts () {
let stdout
const mounted = {}
try {
stdout = await execa.stdout('findmnt', ['-P', '-t', 'nfs,nfs4', '--output', 'SOURCE,TARGET', '--noheadings'])
const regex = /^SOURCE="([^:]*):(.*)" TARGET="(.*)"$/
forEach(stdout.split('\n'), m => {
if (m) {
const match = regex.exec(m)
mounted[match[3]] = {
host: match[1],
share: match[2]
}
}
})
} catch (exc) {
// When no mounts are found, the call pretends to fail...
if (exc.stderr !== '') {
throw exc
}
}
this._realMounts = mounted
return mounted
}
_matchesRealMount () {
return this._getRealPath() in this._realMounts
}
async _mount () {
await fs.ensureDir(this._getRealPath())
return execa('mount', ['-t', 'nfs', '-o', 'vers=3', `${this._remote.host}:${this._remote.path}`, this._getRealPath()])
}
async _sync () {
await this._loadRealMounts()
if (this._matchesRealMount() && !this._remote.enabled) {
try {
await this._umount(this._remote)
} catch (exc) {
this._remote.enabled = true
this._remote.error = exc.message
}
} else if (!this._matchesRealMount() && this._remote.enabled) {
try {
await this._mount()
} catch (exc) {
this._remote.enabled = false
this._remote.error = exc.message
}
}
return this._remote
}
async _forget () {
try {
await this._umount(this._remote)
} catch (_) {
// We have to go on...
}
}
async _umount (remote) {
await execa('umount', [this._getRealPath()])
}
}

191
src/remote-handlers/smb.js Normal file
View File

@@ -0,0 +1,191 @@
import Smb2 from '@marsaud/smb2-promise'
import RemoteHandlerAbstract from './abstract'
import {
noop,
pFinally
} from '../utils'
// Normalize the error code for file not found.
const normalizeError = error => {
const { code } = error
return (
code === 'STATUS_OBJECT_NAME_NOT_FOUND' ||
code === 'STATUS_OBJECT_PATH_NOT_FOUND'
)
? Object.create(error, {
code: {
configurable: true,
readable: true,
value: 'ENOENT',
writable: true
}
})
: error
}
export default class SmbHandler extends RemoteHandlerAbstract {
constructor (remote) {
super(remote)
this._forget = noop
}
get type () {
return 'smb'
}
_getClient (remote) {
return new Smb2({
share: `\\\\${remote.host}`,
domain: remote.domain,
username: remote.username,
password: remote.password,
autoCloseTimeout: 0
})
}
_getFilePath (file) {
if (file === '.') {
file = undefined
}
let path = (this._remote.path !== '')
? this._remote.path
: ''
// Ensure remote path is a directory.
if (path !== '' && path[path.length - 1] !== '\\') {
path += '\\'
}
if (file) {
path += file.replace(/\//g, '\\')
}
return path
}
_dirname (file) {
const parts = file.split('\\')
parts.pop()
return parts.join('\\')
}
async _sync () {
if (this._remote.enabled) {
try {
// Check access (smb2 does not expose connect in public so far...)
await this.list()
} catch (error) {
this._remote.enabled = false
this._remote.error = error.message
}
}
return this._remote
}
async _outputFile (file, data, options = {}) {
const client = this._getClient(this._remote)
const path = this._getFilePath(file)
const dir = this._dirname(path)
if (dir) {
await client.ensureDir(dir)
}
return client.writeFile(path, data, options)::pFinally(() => { client.close() })
}
async _readFile (file, options = {}) {
const client = this._getClient(this._remote)
let content
try {
content = await client.readFile(this._getFilePath(file), options)::pFinally(() => { client.close() })
} catch (error) {
throw normalizeError(error)
}
return content
}
async _rename (oldPath, newPath) {
const client = this._getClient(this._remote)
try {
await client.rename(this._getFilePath(oldPath), this._getFilePath(newPath))::pFinally(() => { client.close() })
} catch (error) {
throw normalizeError(error)
}
}
async _list (dir = '.') {
const client = this._getClient(this._remote)
let list
try {
list = await client.readdir(this._getFilePath(dir))::pFinally(() => { client.close() })
} catch (error) {
throw normalizeError(error)
}
return list
}
async _createReadStream (file, options = {}) {
const client = this._getClient(this._remote)
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.close())
} catch (error) {
throw normalizeError(error)
}
return stream
}
async _createOutputStream (file, options = {}) {
const client = this._getClient(this._remote)
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.close()
throw err
}
stream.on('finish', () => client.close())
return stream
}
async _unlink (file) {
const client = this._getClient(this._remote)
try {
await client.unlink(this._getFilePath(file))::pFinally(() => { client.close() })
} catch (error) {
throw normalizeError(error)
}
}
async _getSize (file) {
const client = await this._getClient(this._remote)
let size
try {
size = await client.getSize(this._getFilePath(file))::pFinally(() => { client.close() })
} catch (error) {
throw normalizeError(error)
}
return size
}
}

28
src/schemas/acl.js Normal file
View File

@@ -0,0 +1,28 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
id: {
type: 'string',
description: 'unique identifier for this ACL'
},
action: {
type: 'string',
description: 'permission (or role)'
},
object: {
type: 'string',
description: 'item (or set)'
},
subject: {
type: 'string',
description: 'user (or group)'
}
},
required: [
'id',
'action',
'object',
'subject'
]
}

43
src/schemas/job.js Normal file
View File

@@ -0,0 +1,43 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
type: {
enum: ['call']
},
id: {
type: 'string',
description: 'job identifier'
},
name: {
type: 'string',
description: 'human readable name'
},
userId: {
type: 'string',
description: 'identifier of the user who have created the job (the permissions of the user are used by the job)'
},
key: {
type: 'string'
// TODO description
},
method: {
type: 'string',
description: 'called method'
},
paramsVector: {
type: 'object'
},
timeout: {
type: 'number',
description: 'number of milliseconds after which the job is considered failed'
}
},
required: [
'type',
'id',
'userId',
'key',
'method'
]
}

29
src/schemas/log.js Normal file
View File

@@ -0,0 +1,29 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
id: {
type: 'string',
description: 'unique identifier for this log'
},
time: {
type: 'string',
description: 'timestamp (in milliseconds) of this log'
},
message: {
type: 'string',
description: 'human readable (short) description of this log'
},
namespace: {
type: 'string',
description: 'space to store logs'
},
data: {}
},
required: [
'id',
'time',
'message',
'namespace'
]
}

View File

@@ -0,0 +1,33 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
event: {
enum: ['jobCall.end']
},
runJobId: {
type: 'string',
description: 'instance id of this job'
},
runCallId: {
type: 'string',
description: 'instance id of this call'
},
error: {
type: 'object',
description: 'describe one failure, exists if the call has failed'
},
returnedValue: {
description: 'call\'s result, exists if the call is a success'
}
},
required: [
'event',
'runJobId',
'runCallId'
],
oneOf: [
{ required: ['error'] },
{ required: ['returnedValue'] }
]
}

View File

@@ -0,0 +1,27 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
event: {
enum: ['jobCall.start']
},
runJobId: {
type: 'string',
description: 'instance id of this job'
},
method: {
type: 'string',
description: 'method linked to this call'
},
params: {
type: 'object',
description: 'params of the called method'
}
},
required: [
'event',
'runJobId',
'method',
'params'
]
}

21
src/schemas/log/jobEnd.js Normal file
View File

@@ -0,0 +1,21 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
event: {
enum: ['job.end']
},
runJobId: {
type: 'string',
description: 'instance id of this job'
},
error: {
type: 'object',
description: 'describe one failure, exists if no call has been made'
}
},
required: [
'event',
'runJobId'
]
}

View File

@@ -0,0 +1,26 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
event: {
enum: ['job.start']
},
userId: {
type: 'string',
description: 'user who executes this job'
},
jobId: {
type: 'string',
description: 'identifier of this job'
},
key: {
type: 'string'
}
},
required: [
'event',
'userId',
'jobId',
'key'
]
}

49
src/schemas/plugin.js Normal file
View File

@@ -0,0 +1,49 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
id: {
type: 'string',
description: 'unique identifier for this plugin'
},
name: {
type: 'string',
description: 'unique human readable name for this plugin'
},
autoload: {
type: 'boolean',
description: 'whether this plugin is loaded on startup'
},
loaded: {
type: 'boolean',
description: 'whether or not this plugin is currently loaded'
},
unloadable: {
type: 'boolean',
default: true,
description: 'whether or not this plugin can be unloaded'
},
configuration: {
type: 'object',
description: 'current configuration of this plugin (not present if none)'
},
configurationSchema: {
$ref: 'http://json-schema.org/draft-04/schema#',
description: 'configuration schema for this plugin (not present if not configurable)'
},
testable: {
type: 'boolean',
description: 'whether or not this plugin can be tested'
},
testSchema: {
$ref: 'http://json-schema.org/draft-04/schema#',
description: 'test schema for this plugin'
}
},
required: [
'id',
'name',
'autoload',
'loaded'
]
}

50
src/schemas/user.js Normal file
View File

@@ -0,0 +1,50 @@
export default {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
id: {
type: 'string',
description: 'unique identifier for this user'
},
email: {
type: 'string',
description: 'email address of this user'
},
groups: {
type: 'array',
items: {
type: 'string'
},
description: 'identifier of groups this user belong to'
},
permission: {
enum: ['none', 'read', 'write', 'admin'],
description: 'root permission for this user, none and admin are the only significant ones'
},
preferences: {
type: 'object',
properties: {
lang: { type: 'string' },
sshKeys: {
type: 'array',
items: {
type: 'object',
properties: {
key: { type: 'string' },
title: { type: 'string' }
},
required: [
'key',
'title'
]
}
}
},
description: 'various user preferences'
}
},
required: [
'id',
'email'
]
}

View File

@@ -0,0 +1,44 @@
import assert from 'assert'
const streamToExistingBuffer = (
stream,
buffer,
offset = 0,
end = buffer.length
) => new Promise((resolve, reject) => {
assert(offset >= 0)
assert(end > offset)
assert(end <= buffer.length)
let i = offset
const onData = chunk => {
const prev = i
i += chunk.length
if (i > end) {
return onError(new Error('too much data'))
}
chunk.copy(buffer, prev)
}
stream.on('data', onData)
const clean = () => {
stream.removeListener('data', onData)
stream.removeListener('end', onEnd)
stream.removeListener('error', onError)
}
const onEnd = () => {
resolve(i - offset)
clean()
}
stream.on('end', onEnd)
const onError = error => {
reject(error)
clean()
}
stream.on('error', onError)
})
export { streamToExistingBuffer as default }

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