Compare commits

..

429 Commits

Author SHA1 Message Date
badrAZ
62a8b94221 feat(@xen-orchestra/cron): 1.0.5 2019-10-25 13:22:23 +02:00
Julien Fontanet
21faaeb33d fix(server/scheduling): start/stop schedules after import (#4624) 2019-10-25 10:44:36 +02:00
Julien Fontanet
0525fc5909 fix(cron): prevent early runs (#4626)
Fixes #4625
2019-10-25 10:22:24 +02:00
badrAZ
a1a53bb285 feat(backup-ng): offline snapshotless backup (#4470)
Fixes #3449
2019-10-25 09:45:45 +02:00
Rajaa.BARHTAOUI
0c453c4415 feat(xo-web/backup): hide legacy backup menu entry (#4476)
Fixes #4467
2019-10-24 17:19:16 +02:00
HamadaBrest
d0406f9736 feat(xo-web/hub): update existing template (#4613)
See xoa#35
2019-10-24 15:11:22 +02:00
BenjiReis
ba74b8603d feat(xo-server-transport-icinga2): Icinga2 support (#4573)
Fixes #4563
2019-10-24 14:35:02 +02:00
Julien Fontanet
c675a4d61d chore(yarn.lock): remove unnecessary entry 2019-10-24 14:16:40 +02:00
Julien Fontanet
965c45bc70 chore(vhd-lib): use @xen-orchestra/log 2019-10-24 12:18:01 +02:00
Rajaa.BARHTAOUI
139a22602a feat(xo-web/xoa/support): expose support tunnel (#4616)
Fixes #4513
2019-10-24 09:51:22 +02:00
Julien Fontanet
e0e4969198 fix(backups-cli): should be compatible with Node 7.10 2019-10-23 17:53:07 +02:00
Julien Fontanet
08d69d95b3 chore(server/backup): explicit use of SR UUID (#4622) 2019-10-23 15:38:51 +02:00
HamadaBrest
4e6c507ba9 feat(xo-web/hub): extra hub template info (#4593)
See xoa#35
2019-10-23 12:09:11 +02:00
HamadaBrest
fd06374365 feat(xo-server-cloud): remove (#4615)
All its features were irrelevant for the open source version and have been merged into `xo-server-xoa`.

See xoa#39
2019-10-22 16:03:51 +02:00
Julien Fontanet
a07ebc636a fix(docs/from sources): show Node 8.16.2 2019-10-20 12:43:06 +02:00
BenjiReis
4c151ac9aa chore(xo-server-sdn-controller): complete refactor (#4570) 2019-10-18 17:04:26 +02:00
HamadaBrest
05c425698f feat(xo-web/hub): ability to select SR in hub VM installation (#4571) 2019-10-16 15:34:43 +02:00
Julien Fontanet
2a961979e6 chore: use String#trim instead of lodash/trim (#4602)
Support is good enough: https://kangax.github.io/compat-table/es5/#test-String_properties_and_methods
2019-10-15 09:43:20 +02:00
Pierre Donias
211ede92cc fix(xo-web/new/sr): reattach SR (#4550)
See #4546
2019-10-14 14:57:24 +02:00
Julien Fontanet
256af03772 chore: use typeof instead of lodash/isString (#4603) 2019-10-14 13:58:58 +02:00
Julien Fontanet
654fd5a4f9 chore: use typeof instead of lodash/isBoolean (#4604) 2019-10-14 13:51:45 +02:00
Julien Fontanet
541d90e49f fix(xo-server-recover-account): explicit appDir 2019-10-12 17:07:14 +02:00
Julien Fontanet
974e7038e7 chore(backups-cli): dont fail on missing dirs 2019-10-12 00:49:16 +02:00
Pierre Donias
e2f5b30aa9 fix(xo-server/patching): install XCP-ng patches host by host (#4532)
Fixes #4468

To prevent issues where 2 hosts try to modify the XAPI DB at the same time
2019-10-12 00:39:17 +02:00
Julien Fontanet
3483e7d9e0 chore: update dependencies 2019-10-11 17:15:25 +02:00
Julien Fontanet
56cb20a1af chore: use typeof instead of lodash/isFunction (#4587) 2019-10-11 15:16:37 +02:00
Rajaa.BARHTAOUI
64929653dd feat(xo-server): upgrade to execa@2.0.5 (#4584) 2019-10-11 15:01:56 +02:00
Julien Fontanet
c955da9bc6 feat(backups-cli): lowlevel tool to help with backups (#4556) 2019-10-11 14:48:37 +02:00
Julien Fontanet
291354fa8e chore: use Object.assign instead of lodash/assign (#4585)
Support is good enough:
- https://node.green/#ES2015-built-in-extensions-Object-static-methods-Object-assign
- https://kangax.github.io/compat-table/es6/#test-Object_static_methods
2019-10-11 14:43:38 +02:00
Julien Fontanet
905d736512 chore: use Array.isArray instead of lodash/isArray (#4586)
Support is good enough:
- https://node.green/#ES2015-built-ins-Proxy-Array-isArray-support
- https://kangax.github.io/compat-table/es5/#test-Array_methods
2019-10-11 14:29:01 +02:00
Rajaa.BARHTAOUI
3406d6e2a9 chore(CHANGELOG): 5.39.1 (#4597) 2019-10-11 10:33:18 +02:00
BARHTAOUI
fc10b5ffb9 chore(CHANGELOG): update next 2019-10-10 16:03:20 +02:00
BARHTAOUI
f89c313166 feat(xo-web): 5.50.3 2019-10-10 16:03:20 +02:00
Rajaa.BARHTAOUI
7c734168d0 feat(xo-web/xoa): expose 'xoa check' on the UI (#4574)
See #4513
2019-10-10 13:49:17 +02:00
HamadaBrest
1e7bfec2ce feat(xo-web/hub): delete template by namespace instead of ID (#4594) 2019-10-10 10:36:07 +02:00
badrAZ
1eb0603b4e chore(xo-server-test/backup-ng): consolidate default values (#4544)
Required for #4470
2019-10-08 14:34:11 +02:00
badrAZ
4b32730ce8 feat(xo-web/vm): improve invalid cores per socket feedback (#4187)
Fixes #4120
2019-10-08 11:05:11 +02:00
BenjiReis
ad083c1d9b chore(xo-server-sdn-controller): better cert creation code (#4582) 2019-10-07 12:12:01 +02:00
BenjiReis
b4f84c2de2 chore(xo-server-sdn-controller): arrow functions when possible (#4583) 2019-10-07 11:29:30 +02:00
badrAZ
fc17443ce4 fix(xo-web/vm/advanced): error on displaying ACL users (#4578) 2019-10-07 11:08:11 +02:00
Julien Fontanet
342ae06b21 chore(xo-sdn-controller): minor formatting fix 2019-10-07 10:19:34 +02:00
Julien Fontanet
093fb7f959 fix(xo-server-logs): explicit appDir
May fix #4576
2019-10-03 16:02:10 +02:00
Julien Fontanet
f6472424ad fix(eslint): disable lines-between-class-members rule 2019-10-02 15:52:51 +02:00
Julien Fontanet
31ed3767c6 chore: fix some lint
Mainly: `obj['prop']` → `obj.prop`
2019-10-02 15:45:32 +02:00
marcpezin
366acb65ea doc(updater): release channels (#4572) 2019-10-02 15:24:45 +02:00
Pierre Donias
7c6946931b chore(xo-web): PascalCase NotFound component (#4567)
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
2019-10-01 17:21:00 +02:00
HamadaBrest
5d971433a5 fix(xo-web/new-vm/self): template selection (#4568)
Fixes #4565
Introduced by 9efc3dd1fb
2019-10-01 16:59:59 +02:00
Rajaa.BARHTAOUI
05264b326b chore(CHANGELOG): 5.39.0 (#4561) 2019-09-30 16:08:56 +02:00
BARHTAOUI
fdd5c6bfd8 chore(CHANGELOG): update next 2019-09-30 15:13:05 +02:00
BARHTAOUI
42c3528c2f feat(xo-web): 5.50.2 2019-09-30 15:13:05 +02:00
HamadaBrest
18640714f1 fix(xo-web/hub/resource): "remove" button icon (#4559) 2019-09-30 14:43:31 +02:00
HamadaBrest
cda4d3399b fix(xo-web/hub): responsive hub VM templates (#4558)
Fixes #4557
2019-09-30 14:42:01 +02:00
Julien Fontanet
4da8af6e69 fix(log/configure): process nested transport 2019-09-27 14:55:26 +02:00
Julien Fontanet
b535565612 feat(@xen-orchestra/log): 0.2.0 2019-09-27 14:29:34 +02:00
Julien Fontanet
bef39b8a96 fix(log): support DEBUG and NODE_DEBUG when both defined 2019-09-27 14:26:19 +02:00
Julien Fontanet
fb2502a031 feat(log): support env var LOG_LEVEL 2019-09-27 14:23:27 +02:00
Julien Fontanet
0b90befda1 feat(log): also export createLogger symbol 2019-09-27 13:59:44 +02:00
Julien Fontanet
f9800f104a feat(log/console): display data inline 2019-09-27 13:59:03 +02:00
Julien Fontanet
99134cc381 Merge pull request #4554 from vatesfr/release
Technical release
2019-09-27 10:22:49 +02:00
BARHTAOUI
66ca08da6d chore(CHANGELOG): update next 2019-09-27 10:15:46 +02:00
BARHTAOUI
5eb7ece6ba feat(xo-web): 5.50.1 2019-09-27 09:59:05 +02:00
BARHTAOUI
6b3d334e76 feat(xo-server): 5.50.1 2019-09-27 09:58:51 +02:00
badrAZ
14f5fd8f73 feat(xen-api): 0.27.2 2019-09-27 09:54:47 +02:00
BARHTAOUI
5f73aee0df feat(xo-server-cloud): 0.3.0 2019-09-27 09:47:16 +02:00
HamadaBrest
f8666ba367 fix(xo-web/hub): properly display community info (#4552) 2019-09-26 16:57:53 +02:00
badrAZ
9e80f76dd8 fix(xo-server-test/backup-ng): force fetching uptodate logs (#4551)
See a6d182e

The issue:

xo-server-test execute sequentially multiple backups and validate their execution using logs. The issue is that the logs are not up-to-date in the second fetch because the method returns the previous cached response.

The solution:

Add _forceRefresh param to backupNg.getLogs to be able to clear the method cache. It's just a work-around which will be removed once the consolidated logs will be stored in the DB.
2019-09-26 16:42:12 +02:00
HamadaBrest
c76a5eaf67 feat: hub: XVA templates store (#4442)
Fixes #1918
2019-09-26 16:36:32 +02:00
Julien Fontanet
cd378f0168 chore(PULL_REQUEST_TEMPLATE): strikethrough not relevant items 2019-09-26 14:24:54 +02:00
badrAZ
7d51ff0cf5 feat(xo-server/_pDebounceWithKey): ability to clear cache entry (#4549)
Fixes #4548
2019-09-26 14:17:33 +02:00
Julien Fontanet
47819ea956 chore(PULL_REQUEST_TEMPLATE): better checklist + 4 agreements (#4547) 2019-09-25 17:41:32 +02:00
Julien Fontanet
c7e3560c98 chore(normalize-packages): add --access public to npm publish
Only for non-private packages without existing `postversion` script.
2019-09-25 17:36:36 +02:00
Julien Fontanet
b24400b21d feat(xen-api): support IPv6 addresses (#4521)
Fixes #4520
2019-09-24 13:59:49 +02:00
Pierre Donias
6c1d651687 fix(xo-server/host.isHostServerTimeConsistent): dont return false on check failure (#4540) 2019-09-24 10:46:43 +02:00
Pierre Donias
e7757b53e7 feat(xo-web/new-vm): remove cloud init plan limitation (#4543) 2019-09-24 10:24:27 +02:00
Julien Fontanet
a6d182e92d feat(xo-server/getBackupNgLogs): implement debounce (#4509) (#4541)
Similar to #4509

Fixes xoa-support#1676

For now, the delay is set to 10s which is the duration used by xo-web's
subscription, which makes it enough to make it independent of the number of
clients.

In the future, this could be configurable, but we may simply do the
consolidation only once during the backup execution.
2019-09-23 16:20:17 +02:00
Julien Fontanet
925eca1463 chore(xo-server/api): context has methods bound to XO
This makes sure the correct context is used, which is necessary for properties write and for debouncing.
2019-09-23 15:59:45 +02:00
Julien Fontanet
8b454f0d39 chore(xo-server/MultiKeyMap): add basic tests 2019-09-23 15:14:54 +02:00
Pierre Donias
7c4d110353 feat(xo-web/settings/logs): identify XAPI errors (#4385)
Fixes #4101
2019-09-23 10:31:34 +02:00
Julien Fontanet
6df55523b6 feat(xo-web): display node in list of packages 2019-09-20 17:22:21 +02:00
badrAZ
3ec6a24634 chore(CHANGELOG): update next 2019-09-20 16:36:04 +02:00
badrAZ
164b4218c4 feat(xo-web): 5.50.0 2019-09-20 16:21:38 +02:00
badrAZ
56df8a6477 feat(xo-server): 5.50.0 2019-09-20 16:21:22 +02:00
badrAZ
47a83b312d feat(@xen-orchestra/template): 0.1.0 2019-09-20 16:20:26 +02:00
badrAZ
41a28ae088 feat(xo-server-sdn-controller): 0.3.0 2019-09-20 16:05:27 +02:00
badrAZ
436a8755ae feat(@xen-orchestra/cron): 1.0.4 2019-09-20 16:04:31 +02:00
Julien Fontanet
960b179d95 chore(xen-api/examples): update dependencies 2019-09-20 12:22:43 +02:00
badrAZ
0f0d0e1076 fix(xo-server-test/backupNg): bypass VDI chains check (#4538) 2019-09-20 12:15:49 +02:00
Julien Fontanet
a8bd0d8075 feat(xo-server/listVmBackupsNg): implement debounce (#4509)
Fixes xoa-support#1676

For now, the delay is set to 10s which is the duration used by xo-web's
subscription, which makes it enough to make it independent of the number of
clients.

In the future, this should probably be longer and configurable, but this
requires invalidating the cache in case of changes (creation/deletion of
backups) and explicit requests from the users, which will be implemented in
another PR.
2019-09-20 12:01:58 +02:00
Julien Fontanet
986d3af685 feat(xo-server/vm.export): prefix filename with datetime
Fixes #4503
2019-09-20 11:26:12 +02:00
BenjiReis
1833f9ffdf feat(xo-server-sdn-controller): MTU configurable for private networks (#4491) 2019-09-20 11:19:18 +02:00
badrAZ
30a6877f8a fix(xo-server/jobs): report backups as failed if already running (#4534)
Fixes #4497
2019-09-19 17:27:16 +02:00
badrAZ
aaae2583c7 fix(xo-web/new-vm): ability to escape cloud config template variables (#4501)
Fixes #4486

Fixes xoa-support#1721
2019-09-19 16:19:18 +02:00
Pierre Donias
7f24afc2e7 fix(xo-web/xoa): remove "Updates" & "Licenses" tabs for non admins (#4526)
Fixes support#1753
2019-09-19 16:00:10 +02:00
BenjiReis
0040923e12 chore(xo-server-sdn-controller): move default values to public API (#4536) 2019-09-19 15:48:01 +02:00
Julien Fontanet
844efb88d8 fix(cron): fix 2 race conditions (#4533)
Should fix xoa-support#344, xoa-support#1186 & xoa-support#1755.

These could lead to:
- job not properly stopped
- job run twice
2019-09-19 13:52:29 +02:00
HamadaBrest
9efc3dd1fb feat(xo-web/new-vm): populate form with template in URL query (#4500)
Fixes #4494
2019-09-19 12:03:46 +02:00
Julien Fontanet
67853bad8e chore(cron): extract Schedule#_nextDelay() 2019-09-18 17:44:10 +02:00
BenjiReis
faa8e1441a chore(xo-server-sdn-controller): clearer maps (#4531) 2019-09-18 13:46:36 +02:00
BenjiReis
5c54611d1b feat(xo-server-sdn-controller): encryption for private networks (#4441) 2019-09-17 15:26:19 +02:00
badrAZ
dcf55e4385 fix(xo-server/network.set): missing param definition (#4510)
Fixes #4514

See https://xcp-ng.org/forum/topic/962/why-are-you-using-xcp-ng-center/96
2019-09-17 12:05:11 +02:00
Nicolas Raynaud
2b0f1b6aab feat(xo-web/vm/console): direct connection to SSH button (#4415) 2019-09-17 12:02:33 +02:00
Julien Fontanet
ae6cc8eea3 feat(xo-web/xoa/updater): true submit button for proxy
This allows using "Enter" key to submit.
2019-09-16 17:40:49 +02:00
marcpezin
5279fa49a7 chore(installation): make screenshot a link (#4524) 2019-09-16 14:49:13 +02:00
badrAZ
dcd8a62784 fix(xo-server/network.create): return id XAPI record (#4523)
It was a mistake to return the record, and it was not used.
2019-09-16 14:45:17 +02:00
marcpezin
8c197b0e1a docs(installation): use deploy form (#4522) 2019-09-16 12:12:59 +02:00
Julien Fontanet
aed824b200 fix(xo-web): dont reset state on registration failure 2019-09-13 13:59:25 +02:00
Julien Fontanet
036b30212e feat(xo-cli): special handling of invalid params error
Before:
```
✖ invalid parameters
JsonRpcError: invalid parameters
    at Peer._callee$ (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/dist/index.js:137:44)
    at tryCatch (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/node_modules/regenerator-runtime/runtime.js:62:40)
    at Generator.invoke [as _invoke] (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/node_modules/regenerator-runtime/runtime.js:288:22)
    at Generator.prototype.(anonymous function) [as next] (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/node_modules/regenerator-runtime/runtime.js:114:21)
    at asyncGeneratorStep (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:24)
    at _next (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/node_modules/@babel/runtime/helpers/asyncToGenerator.js:25:9)
    at /usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/node_modules/@babel/runtime/helpers/asyncToGenerator.js:32:7
    at new Promise (<anonymous>)
    at Peer.<anonymous> (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/node_modules/@babel/runtime/helpers/asyncToGenerator.js:21:12)
    at Peer.exec (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/dist/index.js:180:20)
    at Peer.write (/usr/local/lib/node_modules/xen-api/node_modules/json-rpc-peer/dist/index.js:284:10)
    at Xo.<anonymous> (/usr/local/lib/node_modules/xen-api/node_modules/jsonrpc-websocket-client/dist/index.js:77:12)
    at emitOne (events.js:116:13)
    at Xo.emit (events.js:211:7)
    at WebSocket.<anonymous> (/usr/local/lib/node_modules/xen-api/node_modules/jsonrpc-websocket-client/dist/websocket-client.js:206:18)
    at WebSocket.onMessage (/usr/local/lib/node_modules/xen-api/node_modules/ws/lib/event-target.js:120:16)
```

After:
```
✖ invalid parameters
  property @: should not contains property ["foo"]
  property @.id: is missing and not optional
```
2019-09-13 12:02:29 +02:00
badrAZ
3451ab3f50 fix(xo-server/editVm): null support for CoresPerSocket and cpuCap (#4507)
Introduced by 3196c7c#diff-a20130cea265330a92852ddcd3a425ebR286
2019-09-12 12:09:01 +02:00
BenjiReis
0d0a92c2b1 fix(xo-server-sdn-controller): avoid unhandled promises (#4518) 2019-09-12 11:44:11 +02:00
BenjiReis
aa19bc7bf5 chore(xo-server-sdn-controller): add missing awaits (#4516) 2019-09-12 11:06:19 +02:00
BenjiReis
347759b2e7 chore(xo-server-sdn-controller): safer error testing (#4517) 2019-09-12 09:43:09 +02:00
badrAZ
352230446c chore(xo-server-test/createTempVm): returns a record instead of an id (#4508) 2019-09-11 16:19:51 +02:00
Pierre Donias
3eff8102e1 fix(xo-server/patching): bad semver check for update system (#4511)
So far, in order to know if we needed to use the "new" patching system, we were checking that the pool master's `software_version.platform_version` satisfied `^2.1.1` (instead of `>=2.1.1` like [XenCenter does](f3a64fc54b/XenModel/Utils/Helpers.cs (L420))). This broke the installation and the display of missing patches for CH 8.0 whose `software_version.platform_version` is `3.0.0`.
2019-09-11 14:12:21 +02:00
Rajaa.BARHTAOUI
6693d845d9 feat(xo-web/vm/disks): show duplicated disks (#4414)
Fixes #4400
2019-09-10 15:34:16 +02:00
Julien Fontanet
4d79c462db fix(xo-server): use encodeURIComponent for filenames
This makes sure special characters like `?` or `#` are correctly handled.
2019-09-10 14:44:48 +02:00
badrAZ
c44ef6a1dc fix(xo-web/backup-ng): display user errors in the form (#4131)
Fixes #3831
2019-09-10 14:23:20 +02:00
badrAZ
f0996fcfa7 feat(xo-server/backup-ng): emit warning task when zstd is chosen but not supported (#4375)
See #3892
2019-09-10 11:55:57 +02:00
badrAZ
54bc384d37 fix(xo-server/vm): fix "vm.set_domain_type" is not a function on XS < 7.5 (#4504)
Fixes #4348
Introduced by 3196c7ca09 (diff-a20130cea265330a92852ddcd3a425ebR286)
2019-09-09 17:05:12 +02:00
BenjiReis
504fc1efe8 doc(xo-server-sdn-controller): hosts must be able to reach each other (#4498) 2019-09-06 11:49:10 +02:00
BenjiReis
f4179b93fb chore(sdn-controller): remove other_config from interfaces (#4479)
Only needed in ports
2019-09-06 11:32:24 +02:00
badrAZ
564252c198 chore(CHANGELOG): update next 2019-09-05 16:32:47 +02:00
badrAZ
802a7a4463 feat(xo-web): 5.49.0 2019-09-05 16:17:00 +02:00
badrAZ
3b3d6ba13c feat(xo-server): 5.49.0 2019-09-05 16:16:52 +02:00
badrAZ
7350bf58e2 feat(xo-server-sdn-controller): 0.2.1 2019-09-05 16:16:00 +02:00
badrAZ
d37e29afc6 fix(xo-web/home): wait initial fetch before state.objects.fetched (#4456)
Fixes #4420
2019-09-05 15:42:13 +02:00
Julien Fontanet
40de8c9e23 fix(xo-server/createVdi): ignore sm_config (#4484)
Fixes #4482
2019-09-05 14:42:30 +02:00
badrAZ
c81eac13c8 fix(xo-server/cr): handle undefined vdis (#4417)
Fixes #4416
2019-09-05 14:31:12 +02:00
badrAZ
a6e1860f0d fix(xo-web/network): fix inability to create bonded network (#4489)
Fixes xoa-support#1725
2019-09-05 13:39:31 +02:00
Nicolas Raynaud
03eb2d81f0 feat(xo-server/patching): fewer XCP-ng updater plugin requests (#4477)
Fixes #4358
2019-09-05 11:58:54 +02:00
badrAZ
171710b5e8 feat(xo-web/backup-ng/new): warning if zstd is not supported (#4411)
Fixes #3892
2019-09-05 11:44:48 +02:00
BenjiReis
bed76429c2 fix(xo-server-sdn-controller):don't add host to network when no tunnel available (#4480) 2019-09-05 08:50:42 +02:00
Pierre Donias
d19f9b5062 fix(xo-web/build): disable uglify inline optimization (#4485)
Fixes #4377

See mishoo/UglifyJS2#2842
2019-09-04 17:49:55 +02:00
badrAZ
38081d9822 fix(xo-server/api/xosan): missing params definition (#4478)
Fixes xoa-support #1724
2019-09-04 16:23:14 +02:00
BenjiReis
54e278d3f7 doc(xo-server-sdn-controller): enhance documentation (#4461) 2019-09-04 08:48:27 +02:00
BenjiReis
181ed1b1a5 chore(xo-server-sdn-controller): namespace 'other_config' entries (#4473) 2019-09-03 14:39:44 +02:00
Pierre Donias
fb2d325ccb fix(xo-web/xo): missing Promise.alls (#4469) 2019-09-02 16:34:23 +02:00
badrAZ
5f94a52537 fix(xo-server/xapi-object-to-xo): fix incorrect PBD entry name (#4466)
Fixes #4465

Introduced by 77c62d6e7d
2019-09-02 10:33:40 +02:00
Julien Fontanet
c69b50c5d2 chore: update dependencies 2019-09-02 09:45:38 +02:00
BenjiReis
1c72f89178 fix(xo-server-sdn-controller): same VNI for nodes of cross pool network (#4464) 2019-08-30 14:30:28 +02:00
HamadaBrest
14bd16da14 feat(xo-web/new/sr): clarify address formats (#4460)
Fixes #4450
2019-08-30 11:08:37 +02:00
badrAZ
11a57f4618 chore(CHANGELOG): 5.38.0 2019-08-29 17:06:56 +02:00
badrAZ
57f35aff90 chore(CHANGELOG): update next 2019-08-29 13:39:26 +02:00
badrAZ
60e63a307f feat(xo-web): 5.48.1 2019-08-29 13:16:10 +02:00
badrAZ
175e878ea6 chore(CHANGELOG): update next 2019-08-28 17:27:10 +02:00
badrAZ
5c960a3213 feat(xo-server-sdn-controller): 0.2.0 2019-08-28 16:59:28 +02:00
BenjiReis
5dfb299e37 chore(xo-server-sdn-controller): use ?. to simplify tests (#4459) 2019-08-28 16:49:38 +02:00
BenjiReis
3890d4d9d1 feat(xo-server-sdn-controller): cross-pool private networks (#4405) 2019-08-28 16:31:33 +02:00
badrAZ
77c62d6e7d fix(xo-server/xapi-object-to-xo): obfuscate cifs password from PBD device_config (#4401)
Fixes #4384
2019-08-27 17:37:57 +02:00
badrAZ
ba54b53194 fix(xo-server-test/backup-ng): reduce timeout for delta backup scenario (#4454)
Introduced by 1ce3368530
2019-08-27 10:08:18 +02:00
BenjiReis
b4ef7352f2 chore(xo-server-sdn-controller): Do not specify test when comparing boolean (#4452) 2019-08-27 09:48:53 +02:00
badrAZ
1ce3368530 fix(xo-server-test/backup-ng): large timeout for delta backup scenario (#4453) 2019-08-27 09:45:32 +02:00
BenjiReis
a4b32f3cb7 chore(xo-server-sdn-controller): better name for port & iface in OVS (#4451) 2019-08-27 09:03:55 +02:00
badrAZ
ee9cc05ae0 feat(PULL_REQUEST_TEMPLATE): xo-server-test integration (#4447) 2019-08-26 13:59:40 +02:00
BenjiReis
b8ccf2b0d6 chore(xo-server-sdn-controller): parallelize adding port and interface (#4448) 2019-08-26 13:15:36 +02:00
badrAZ
886b499b94 feat(xo-server-test): update documentation 2019-08-26 11:54:48 +02:00
heafalan
07924d5621 feat(xo-server-test): delta backup test (#4393)
See #4307
2019-08-26 11:20:39 +02:00
BenjiReis
43f3367ae4 chore(xo-server-sdn-controller): use xen-api helpers (#4445) 2019-08-26 09:23:45 +02:00
Julien Fontanet
454c73f42f chore(xo-server/registerHttpRequest): simplify code 2019-08-23 17:54:54 +02:00
BenjiReis
041df698d5 chore(xo-server-sdn-controller): unify (in/de)crement style (#4444) 2019-08-23 10:42:51 +02:00
BenjiReis
97081f1219 chore(xo-server-sdn-controller): use destructuring (#4443) 2019-08-23 10:03:04 +02:00
Pierre Donias
f6792bf080 fix(CHANGELOG): entry indentation 2019-08-22 14:41:32 +02:00
Pierre Donias
88635f31d6 chore(CHANGELOG): update next 2019-08-22 14:36:30 +02:00
Pierre Donias
abd0f115fc feat(xo-web): 5.48.0 2019-08-22 14:28:39 +02:00
Pierre Donias
e9766c76c1 feat(xo-server): 5.48.0 2019-08-22 14:28:30 +02:00
Pierre Donias
570506b324 feat(xo-server-sdn-controller): 0.1.3 2019-08-22 14:27:50 +02:00
BenjiReis
11889880eb feat(xo-server-sdn-controller): use VNI (#4435) 2019-08-22 14:22:01 +02:00
Pierre Donias
a86abde893 fix(xo-web/createBondedNetwork): remove vlan param (#4429)
Fixes #4425
Introduced by 7a2a88b7ad
2019-08-22 09:25:19 +02:00
Julien Fontanet
2cfe3360d8 feat(xo-server/config): enable guessVhdSizeOnImport by default (#4436) 2019-08-21 17:29:43 +02:00
Pierre Donias
60d75cb8ee chore(xo-web): improve display of XOA related pages for sources users (#4434)
- XOSAN
- Updater
2019-08-21 14:51:26 +02:00
BenjiReis
68838e310a feat(xo-web,sdn-controller): tip on network creation (#4431)
"Warning: a pool can have 1 GRE and 1 VxLAN network max"
2019-08-21 09:41:05 +02:00
Jon Sands
161de6cb7c fix(docs/troubleshooting): fix sudo usage (#4427) 2019-08-20 17:28:36 +02:00
Pierre Donias
af5a9b644b feat(xo-web/about): XOA support message (#4433) 2019-08-20 16:22:51 +02:00
Pierre Donias
785426eab5 feat(xo-web/config): unlock import/export config for everyone (#4432) 2019-08-20 12:21:31 +02:00
Jon Sands
9267aef498 clarify that ssl proxy changes require sudo (#4424) 2019-08-17 01:28:11 +02:00
Rajaa.BARHTAOUI
ae27a07578 feat(xo-web/usage): show the item if there's only 1 'other' (#4419)
Fixes #4392
2019-08-14 09:45:01 +02:00
Rajaa.BARHTAOUI
131b2a35aa fix(xo-server/vm/revert): set admin ACL on created snapshot (#4391)
Fixes #4331
2019-08-13 16:39:52 +02:00
Olivier Lambert
5a89601b24 update doc for web deploy 2019-08-11 19:58:24 +02:00
badrAZ
2528bbc552 chore(CHANGELOG): update next 2019-08-08 16:26:04 +02:00
badrAZ
7c3a480003 feat(xo-web): 5.47.0 2019-08-08 16:08:16 +02:00
badrAZ
80eac8443d feat(xo-server): 5.47.0 2019-08-08 16:07:38 +02:00
badrAZ
a97234c48d feat(xo-server-usage-report): 0.7.3 2019-08-08 16:06:39 +02:00
badrAZ
53ea58c2f6 chore(CHANGELOG): v5.37.1 2019-08-06 14:22:11 +02:00
badrAZ
d867524c6b feat(xo-server-sdn-controller): 0.1.2 2019-08-06 13:51:43 +02:00
badrAZ
5edf9bde78 fix(xo-web/copyVms): handle undefined container (#4402)
Introduced by 770ea55872
2019-08-02 17:01:06 +02:00
badrAZ
770ea55872 feat(xo-web#copyVms): warning if some VMs don't support ZSTD (#4346)
See #3892
2019-08-02 15:51:08 +02:00
Rajaa.BARHTAOUI
4eb0101c5b feat(xo-web/vm/attachDisk): warning when VDI already attached (#4366)
Fixes #3381
2019-08-02 15:43:15 +02:00
badrAZ
5d7af94abf fix(xo-server/xen-servers): error on connecting a connected server (#4396) 2019-08-02 14:38:49 +02:00
Rajaa.BARHTAOUI
b729b8f7c8 fix(xo-web/vm/attachDisk): VBDs mode check (#4373) 2019-08-02 14:37:15 +02:00
BenjiReis
064e69d943 chore(xo-server-sdn-controller): simplify _objectsUpdated method (#4394) 2019-08-02 11:58:05 +02:00
heafalan
d880931951 feat(xo-server-test/xoConnection): add a TOFIX (#4399) 2019-08-02 11:56:07 +02:00
BenjiReis
f24741cd32 chore(sdn-controller): use simpler syntax to clearify the code (#4398) 2019-08-02 11:26:42 +02:00
BenjiReis
45c7017e83 chore(xo-server-sdn-controller): keep arrays immutable (#4397) 2019-08-02 11:05:23 +02:00
heafalan
7cfb891e6b feat(xo-server-test): add required servers specified in the config (#4381)
Fixes #4355
2019-08-02 09:37:02 +02:00
BenjiReis
fc8604e896 fix(sdn-controller): add new hosts to pool's private networks (#4382) 2019-08-01 11:57:43 +02:00
badrAZ
6b5e94103d fix(usage-report): throw error when transport-email is disabled (#4389)
See support#1652
2019-07-31 14:25:08 +02:00
badrAZ
aee4679ae5 fix(xo-web/exportVm,copyVm): handle undefined container (#4386)
Introduced by a00e3e6f41 and 10d5228eb2
2019-07-30 17:11:16 +02:00
Pierre Donias
2c2c930fce fix(xo-server/patching): log → log.debug (#4387)
See support#1652
2019-07-30 16:36:18 +02:00
BenjiReis
3f309e4db5 fix(xo-server-sdn-controller): fix certificate generator (#4383) 2019-07-30 15:42:04 +02:00
BenjiReis
d26be402db feat(xo-server-sdn-controller/new network): choose PIF (#4379) 2019-07-30 15:05:17 +02:00
Pierre Donias
a571e83005 feat(xo-web): make source disclaimer banner dismissible (#4278) 2019-07-30 14:27:36 +02:00
badrAZ
10d5228eb2 feat(xo-web#exportVm): only show zstd option when it's supported (#4368)
See #3892
2019-07-30 10:20:42 +02:00
Rajaa.BARHTAOUI
7ed49b476f fix(xo-web/sr/general): display VDI VM name in SR usage graph (#4370) 2019-07-30 10:14:30 +02:00
BenjiReis
5396b90695 chore(xo-server-sdn-controller): unify and specify tests (#4376) 2019-07-26 11:22:03 +02:00
BenjiReis
a6983d4e7b feat(xo-server-sdn-controller): use tunnel status (#4322)
Use XAPI `tunnel.status` as described here:
https://xapi-project.github.io/xapi/design/tunnelling.html
2019-07-25 15:32:44 +02:00
heafalan
a3d1c76f67 fix(xo-server-test): extend the tests timeouts (#4374)
See #4356
2019-07-25 15:31:52 +02:00
badrAZ
15fab226b7 chore(CHANGELOG): 5.37.0 2019-07-25 14:21:58 +02:00
BenjiReis
5a065d5a05 chore(xo-server-sdn-controller): expose ovsdbClient's host (#4369) 2019-07-25 10:03:17 +02:00
badrAZ
de81f3ffbb chore(xo-server-test/README): add known issue 2019-07-24 14:20:43 +02:00
heafalan
9103369cf6 chore(xo-server-test/README.md): add warning (#4371) 2019-07-24 13:49:52 +02:00
heafalan
7be36e6d0d feat(xo-server-test): use temp resources instead of create resources for each test suite (#4359) 2019-07-24 11:31:17 +02:00
badrAZ
a00e3e6f41 feat(xo-web#copyVm): only show zstd option when it's supported (#4326)
See #3892
2019-07-23 15:00:55 +02:00
BenjiReis
82ba02b4f3 chore(xo-server-sdn-controller): rework logs (#4365) 2019-07-23 11:45:51 +02:00
badrAZ
d70ae6ebe3 feat(xo-web/backup-ng/new): create schedule without mode (#4357)
Fixes #4098
2019-07-22 15:22:01 +02:00
BenjiReis
f6c411a261 fix(xo-server-sdn-controller): better monitor of host life cycle (#4314) 2019-07-22 14:53:42 +02:00
badrAZ
b606eaf9ee chore(CHANGELOG): update next 2019-07-22 10:42:04 +02:00
badrAZ
516edd1b09 feat(xo-web): 5.46.0 2019-07-22 10:29:05 +02:00
badrAZ
e31c3b1f27 feat(xo-server): 5.46.0 2019-07-22 10:28:54 +02:00
Pierre Donias
619818f968 feat(xo-web/{host,vm}): state icons improvements (#4363)
- Always use .xo-status-* classes
- Show host busy state in the host view as well (previously only in home/hosts view)
- Differentiate "disabled" state from "busy" state
- Host view state icon tooltip
- Homogenize state display between hosts and VMs
2019-07-19 17:07:47 +02:00
Julien Fontanet
79a80a1adf fix(xo-server/vm.set): setting VCPUs_live (#4360)
Caused by 3196c7ca09

Fixes xoa-support#1632
2019-07-18 17:00:01 +02:00
Julien Fontanet
7cef48b995 fix(xo-server/vm.set): setting affinity (#4361)
Caused by 3196c7ca09

Fixes xoa-support#1625
2019-07-18 16:41:02 +02:00
Rajaa.BARHTAOUI
7d3d1b1544 feat(xo-web/vm/console): copy/paste multiple lines (#4341)
Fixes #4261
2019-07-18 16:31:20 +02:00
Rajaa.BARHTAOUI
3f935f271d feat(xo-web/pool): ability to add multiple hosts on the pool (#3716)
Fixes #2402
2019-07-18 14:24:56 +02:00
HamadaBrest
89935a1517 feat(xo-web/backup-ng): ability to bypass VDI chain check (#4340)
Fixes #4324
2019-07-18 10:46:22 +02:00
Rajaa.BARHTAOUI
c67af4fb2f feat(xo-web/host/advanced): ability to edit host ISCSI IQN (#4208)
Fixes #4048
2019-07-17 17:32:05 +02:00
Julien Fontanet
0b4adc36a0 chore(xo-server-test): remove unused deps 2019-07-17 16:49:53 +02:00
Julien Fontanet
44776b795f fix(xo-server-test/.babelrc.js): look into pro deps 2019-07-17 16:49:10 +02:00
Julien Fontanet
bec73a1c43 feat(normalize-packages): further clean package.json 2019-07-17 16:31:34 +02:00
Julien Fontanet
6ce35fdfa8 fix(import-packages): force merge (--no-ff) 2019-07-17 16:30:54 +02:00
Julien Fontanet
dabc2d0442 fix(xo-server-test): adapt to monorepo 2019-07-17 15:54:26 +02:00
Julien Fontanet
0527d3bc2b Merge branch 'xo-server-test/master' 2019-07-17 15:54:10 +02:00
Julien Fontanet
a7cfb71070 feat(xo-server-test): move all files to packages/xo-server-test 2019-07-17 15:54:10 +02:00
Pierre Donias
52003bedb4 feat(xo-web/Usage): use same color for "Others" block (#4347)
Fixes #3608

And show number of others in tooltip
2019-07-17 15:41:40 +02:00
Rajaa.BARHTAOUI
a02fb8e739 fix(xo-web/task): fix hidden notification by search field (#4305)
Fixes #3874
2019-07-16 17:07:51 +02:00
badrAZ
60fad187a2 feat(xo-web/backup-ng/new): generate default schedule if no sch… (#4183)
…edules specified

Fixes #4036
2019-07-16 16:20:43 +02:00
BenjiReis
e8cd1e070f fixme(xo-server/xapi): missing vgpu_map param to VM.migrate_send (#4351) 2019-07-16 16:15:25 +02:00
badrAZ
de6620be12 chore(CHANGELOG): update next 2019-07-16 16:05:25 +02:00
badrAZ
72dee73faa fix(xo-server): 5.45.3 2019-07-16 16:01:54 +02:00
badrAZ
d8ce27907d fix(@xen-orchestra/fs): 0.10.1 2019-07-16 15:57:00 +02:00
badrAZ
3d8891d518 chore(CHANGELOG): update next 2019-07-16 14:50:02 +02:00
badrAZ
97742ccdc2 fix(xo-server): 5.45.2 2019-07-16 14:00:06 +02:00
badrAZ
82fec86179 fix(xen-api): 0.27.1 2019-07-16 13:39:16 +02:00
badrAZ
be83b53875 chore(CHANGELOG): update next 2019-07-16 11:12:05 +02:00
badrAZ
85fda0c18b feat(xo-web): 5.45.1 2019-07-16 10:50:17 +02:00
badrAZ
a89f8fbd9c feat(xo-server): 5.45.1 2019-07-16 10:50:06 +02:00
Julien Fontanet
efdfa1f2f7 chore: update dependencies
Main goal is to update Babel to latest fix release which fix object rest spread on Node 6
2019-07-16 09:59:03 +02:00
badrAZ
5bd61e3fb0 fix(xo-server/vm#set): fix virtualization switch (#4349) 2019-07-15 14:57:16 +02:00
badrAZ
a45f83b646 fix(README): fix test command 2019-07-12 16:59:49 +02:00
Rajaa.BARHTAOUI
16135b8e37 feat(xo-web/sr/general): improve SR usage graph (#3830)
See #3608
2019-07-12 16:56:06 +02:00
heafalan
b011e8656f fix(user#user.changePassword): delete useless tests (#118) 2019-07-12 11:00:05 +02:00
badrAZ
215432be6c chore(CHANGELOG): update next 2019-07-11 16:12:48 +02:00
badrAZ
d373760412 feat(xo-web): 5.45.0 2019-07-11 16:12:48 +02:00
badrAZ
a1de04e285 feat(xo-server): 5.45.0 2019-07-11 16:12:48 +02:00
badrAZ
23e16732fd feat(xen-api): 0.27.0 2019-07-11 16:12:48 +02:00
badrAZ
5efac84b8b feat(xo-server-sdn-controller): 0.1.1 2019-07-11 16:12:48 +02:00
badrAZ
2cbc7b7d7d feat(@xen-orchestra/fs): 0.10.0 2019-07-11 16:12:48 +02:00
Julien Fontanet
b1acbaecc2 fix(log): remove some lints 2019-07-10 15:40:25 +02:00
Julien Fontanet
6d61e8efff chore: use native String#{end,start}sWith
Supported since Node 4
2019-07-09 16:00:16 +02:00
Julien Fontanet
482e6b3cb3 feat(xo-server/backupNg.runJob): new param settings (#4321)
Can be used to override certain job settings, the format is the same as the `settings` field in the job.

Related to xoa-support#1583
2019-07-08 14:54:09 +02:00
heafalan
445b13ec29 feat: create temp remote with remote configuration (#117) 2019-07-08 13:01:40 +02:00
HamadaBrest
116af372dc fix(fs): EIO errors due to massive parallel operations (#4330)
Fixes #4323
2019-07-08 11:42:25 +02:00
badrAZ
970952783c fix(xo-web/vm/advanced): fix CPUs/max CPUs edition (#4337) 2019-07-08 10:44:16 +02:00
Pierre Donias
e59cf13456 feat(xo-web/VM): show related pending tasks (#3982)
And their progress.

Fixes #3811
2019-07-05 17:15:44 +02:00
BenjiReis
d0cfddce19 chore(xo-server-sdn-controller): _getOrWaitObject → getObjectByRef (#4336)
When possible.
2019-07-05 13:48:37 +02:00
Julien Fontanet
30b2a8dd8d feat(log/README): document data param 2019-07-05 12:13:54 +02:00
BenjiReis
b811ee7e7e chore(xo-server-sdn-controller): use optionnal chaining (#4335) 2019-07-05 11:54:07 +02:00
Julien Fontanet
ebe7f6784a chore: re-format with Prettier
Related to c8aa058ede

Mainly due to https://prettier.io/blog/2019/06/06/1.18.0.html#stop-breaking-simple-template-literals-5979-by-jwbay
2019-07-05 11:34:15 +02:00
BenjiReis
e40792378f chore(xo-server-sdn-controller): simplify for loops (#4334) 2019-07-05 11:08:57 +02:00
Julien Fontanet
cc9c8fb891 feat(CHANGELOG.unreleased): add instructions for devs (#4333) 2019-07-05 10:05:02 +02:00
Enishowk
ca06c4d403 fix(xo-web/backup-ng/restore): display correct size of full VM backup (#4332)
Fixes #4316
2019-07-05 09:31:32 +02:00
Julien Fontanet
c8aa058ede chore: update dependencies 2019-07-04 16:01:07 +02:00
badrAZ
34169d685e fix(xo-server/xen-servers): handle pool UUID change (#4302)
Fixes #4299
2019-07-04 14:52:30 +02:00
HamadaBrest
d5a9d36815 feat(xo-server/vm): VM operators can revert any snapshot (#4247)
Fixes #3928

- VM operators were only able to revert the snapshots they created. They can now revert any snapshot
- VM operators can still only delete the snapshots they created
- VM admins still have full control over the snapshots
2019-07-04 11:21:54 +02:00
HamadaBrest
c7aaeca530 feat(xo-web/settings/servers): display connection issues (#4310)
Fixes #4300
2019-07-04 10:19:44 +02:00
Julien Fontanet
863e4f0c19 feat(xo-server/backup NG): new setting bypassVdiChainsCheck (#4320)
Related to xoa-support#1583
2019-07-04 10:07:28 +02:00
BenjiReis
0226e0553d fix(xo-server-sdn-controller): dont pick PIF if ip_configuration_mode = None (#4319)
Fixes xoa-support#1572
2019-07-03 17:07:30 +02:00
Julien Fontanet
02995d278f fix(xo-server/server.set): declare readOnly in params
Related to b8524732ce
2019-07-02 17:31:44 +02:00
Julien Fontanet
78a2104bcc fix(xo-server/api/server): use correct types
Booleans should be booleans, not strings.
2019-07-02 17:09:57 +02:00
heafalan
4e9d143996 fix: remove VM ids from snapshots (#116) 2019-07-01 09:39:41 +02:00
badrAZ
0811e5c765 feat(xo-web/stats): ability to display last day stats (#4168)
Fixes #4160
2019-06-28 14:35:50 +02:00
heafalan
b2cf2edd43 fix(backup-ng): update snapshots (#115) 2019-06-28 09:34:48 +02:00
heafalan
db493f6887 fix(backup-ng): defaultSr -> default (#114) 2019-06-28 09:09:38 +02:00
heafalan
2cd0dec480 fix: delete_disks -> deleteDisks (#113) 2019-06-27 16:27:07 +02:00
badrAZ
29024888fb chore(CHANGELOG): v5.36.0 2019-06-27 15:07:29 +02:00
badrAZ
dbcaab2bc1 chore(CHANGELOG): update next 2019-06-27 14:52:16 +02:00
badrAZ
28d445ae1c feat(xo-web): 5.44.0 2019-06-27 14:08:37 +02:00
badrAZ
530360f859 feat(xo-server): 5.44.0 2019-06-27 14:08:26 +02:00
badrAZ
738c55bad0 feat(xo-server-backup-reports): 0.16.2 2019-06-27 14:08:11 +02:00
badrAZ
4b09bc85f5 feat(xo-server-auth-saml): 0.6.0 2019-06-27 14:07:44 +02:00
badrAZ
5bc67d3570 feat(xo-server-sdn-controller): 0.1.0 2019-06-27 14:07:23 +02:00
heafalan
dfe5f412eb feat: config.toml -> sample.config.toml and update documentation (#111) 2019-06-26 14:52:34 +02:00
heafalan
003eadc8fd fix(backup-ng): use generic test for retentions (#103) 2019-06-24 14:15:24 +02:00
badrAZ
254fa36c01 chore: structured "config.toml" (#110)
See #108
2019-06-21 15:25:29 +02:00
Julien Fontanet
814fee4f47 chore(package): update deps 2019-06-06 09:47:04 +02:00
heafalan
8924a64622 chore: delete useless test (#105) 2019-06-05 16:59:10 +02:00
heafalan
114d521636 feat: test backupNg.runJob with srs without copyRetention (#87)
See #76
2019-06-03 08:58:34 +02:00
heafalan
85d55e97e7 feat: test run backup job with remotes without export retention (#86)
See #76
2019-05-29 16:47:50 +02:00
heafalan
abad2944fb feat: test backupNg.runJob 3 times with retention 2 and revert (#93)
See #76
2019-05-27 10:53:34 +02:00
heafalan
2827544409 chore: backupNg.create() -> backupNg.createJob() (#96) 2019-05-22 11:55:30 +02:00
heafalan
db0a399da1 chore: use getSchedule() (#99) 2019-05-22 08:58:01 +02:00
heafalan
87d2096ed7 chore: createUser() -> createTempUser() (#100) 2019-05-15 14:21:11 +02:00
heafalan
d47f66548d chore: use snapshot property matchers instead of 'omit' (#98) 2019-05-10 13:23:47 +02:00
heafalan
fb9425e503 feat(backupNg): test running a job with a VM without disks (#92)
See #76
2019-04-18 17:18:15 +02:00
heafalan
d75580e11d feat: add doc for tests (#90) 2019-04-15 13:55:06 +02:00
badrAZ
a64960ddd0 chore(xoConnection): various changes (#95) 2019-04-15 09:22:53 +02:00
heafalan
876850a7a7 chore(_xoWithTestHelpers): refactoring (#94) 2019-04-10 15:23:45 +02:00
heafalan
0b689d99fa feat: test backupNg.runJob with non-existent vm (#91)
See #76
2019-03-26 16:39:06 +01:00
heafalan
cd0064d19c feat: fails trying to test backupNg.runJob with no matching VMs (#84)
See #76
2019-03-21 14:06:06 +01:00
heafalan
b4baa6cd7b feat: test backupNg.runJob without schedule (#83)
See #76
2019-03-15 10:18:42 +01:00
heafalan
1ab2cdeed3 feat: test backupNg.deleteJob (#82)
See #76
2019-03-12 09:44:45 +01:00
badrAZ
83c0281a33 chore: various changes (#89) 2019-03-07 16:58:54 +01:00
heafalan
437b0b0240 feat: test backupNg.createJob with schedules (#81)
See #76
2019-03-05 13:27:02 +01:00
heafalan
5c48697eda feat: improve test job.runSequence() (#71)
Fixes #65
2019-03-01 12:13:21 +01:00
heafalan
0feea5b7a6 feat: test "backupNg.createJob" without defining schedules (#77)
See #76
2019-02-27 16:27:15 +01:00
heafalan
9eb27fdd5e Chore(old-tests/user): delete old file (#80) 2019-02-18 09:32:52 +01:00
heafalan
6e4a64232a fix(job.create): job not properly deleted after the tests (#79) 2019-02-18 09:31:06 +01:00
heafalan
4bbedeeea9 fix: improve test job.delete (#75)
See #65
2019-02-15 13:43:14 +01:00
heafalan
b5c004e870 fix: change vmIdXoTest (#78) 2019-02-14 17:13:28 +01:00
heafalan
a0ef1ab4f4 fix: improve test job.set (#73)
See #65
2019-02-11 15:32:39 +01:00
heafalan
c9172a11a8 fix: improve test job.get (#72)
See #65
2019-02-07 16:44:32 +01:00
badrAZ
a0feee912e chore(util): remove useless console.logs 2019-02-07 16:24:44 +01:00
heafalan
8e42b7b891 fix: improve test job.getAll (#69)
See #65
2019-02-07 16:16:10 +01:00
heafalan
147d7e773f fix: improve test job.create (#68)
See #65
2019-02-07 14:23:59 +01:00
heafalan
759ab1c5ee fix: all-uppercase format for global constants (#70) 2019-02-04 16:04:17 +01:00
heafalan
4c1581d845 feat: improve test user.changePassword (#66)
Fixes #53
2019-02-01 15:23:01 +01:00
heafalan
e1c6e4347a fix(user.delete): create a new admin user for tests (#67)
Introduced by #59
2019-01-29 17:25:17 +01:00
heafalan
256f117bbf feat: improve test user.set (#63)
See #53
2019-01-22 15:11:58 +01:00
heafalan
3b0acf82c7 fix: change the titles of tests (#61) 2019-01-11 16:35:52 +01:00
heafalan
3a12f3d6c7 feat: improve test user.getAll (#62)
See #53
2019-01-11 15:54:59 +01:00
heafalan
335ac5a595 chore(package): remove jest-extended (#60) 2019-01-11 09:35:17 +01:00
heafalan
d0e2e97007 fix: improve test user.delete (#59)
Fixes #53
2019-01-10 17:03:05 +01:00
heafalan
85e1baa2dc fix: improve test user.create (#56)
See #53
2019-01-08 12:02:59 +01:00
Julien Fontanet
0c66c39211 WiP: chore: use app-conf (#58)
chore: use app-conf

Fixes #54
2018-12-14 14:44:09 +01:00
Julien Fontanet
250afa38ca chore(package): lint-staged integration 2018-12-12 11:25:47 +01:00
Julien Fontanet
b7e58eeb3f chore(eslint): use plugin:node/recommended 2018-12-12 11:17:42 +01:00
Julien Fontanet
6f024d78a6 chore(package): update dependencies 2018-12-12 10:58:06 +01:00
Julien Fontanet
1e48096f36 chore(package): remove incorrect posttest script 2018-12-07 09:48:35 +01:00
Julien Fontanet
ccf6a1bedb chore(package): ESLint on commit 2018-12-04 11:22:12 +01:00
Julien Fontanet
3639edb4db chore(jest): some config 2018-12-04 11:20:47 +01:00
badrAZ
d3bbe0b3b6 chore: move old tests to a dedicated directory (#52) 2018-11-29 16:10:14 +01:00
Julien Fontanet
e8ab101993 chore: update dependencies 2018-11-26 15:59:47 +01:00
badrAZ
ef98b10063 feat: PCI (#47) 2017-03-10 16:13:54 +01:00
badrAZ
84943e7fe6 feat: VM insert/eject CD (#34) 2017-03-10 16:10:08 +01:00
badrAZ
d0fa5ff385 feat: snapshotting (#40) 2017-03-10 16:09:31 +01:00
badrAZ
3609559ced feat: VM lifecycle (#36) 2017-03-10 16:08:11 +01:00
badrAZ
950c780122 fix(package): register jasmine as a global (#51) 2017-03-10 14:56:28 +01:00
greenkeeper[bot]
32b510ef40 fix(package): update husky to version 0.13.2 (#50)
https://greenkeeper.io/
2017-03-02 22:04:59 +01:00
greenkeeper[bot]
4cc33ed29b fix(package): update standard to version 9.0.0 (#49)
https://greenkeeper.io/
2017-03-01 00:19:00 +01:00
badrAZ
d72906a6ba fix(package): fix for Jest 19 (#46) 2017-02-21 11:12:28 +01:00
greenkeeper[bot]
d577b51a86 fix(package): update jest to version 19.0.0 (#45)
https://greenkeeper.io/
2017-02-21 10:54:43 +01:00
badrAZ
63d4865427 fix: server tests (#31) 2017-01-13 14:35:16 +01:00
badrAZ
1355477e37 chore(user): use rejectionOf() (#32) 2017-01-13 11:30:50 +01:00
badrAZ
d50e1b4e02 fix: role tests (#30) 2017-01-12 17:07:29 +01:00
badrAZ
606ae41698 fix(user): use utils/testConnection (#29) 2017-01-12 14:20:54 +01:00
badrAZ
b6ee5ae779 fix: token tests (#28) 2017-01-12 13:58:12 +01:00
badrAZ
aeb1b2c30f fix: group tests (#26) 2017-01-11 15:50:51 +01:00
badrAZ
35ace281cc fix: user tests (#27) 2017-01-11 11:47:59 +01:00
badrAZ
6cd056eee5 fix: user tests (#25) 2017-01-02 13:42:49 +01:00
Julien Fontanet
6c664bfaa7 chore: update yarn.lock 2016-12-23 17:52:47 +01:00
Julien Fontanet
8890d445dc feat(utils): export xo connection 2016-12-23 17:51:17 +01:00
Julien Fontanet
7a7db1ea08 fix: various fixes 2016-12-23 11:55:38 +01:00
Julien Fontanet
e585a3e5c4 chore(package): update all dependencies 2016-12-22 17:20:21 +01:00
greenkeeper[bot]
7336032009 chore(package): update dependencies (#24)
https://greenkeeper.io/
2016-11-28 15:27:36 +01:00
Greenkeeper
d29bc63b24 chore: drop support for Node.js 0.10 (#22)
BREAKING CHANGE: This module no longer supports Node.js 0.10
2016-11-01 15:59:11 +01:00
Greenkeeper
2a9bd1d4cb chore(package): update babel-eslint to version 7.0.0 (#6)
https://greenkeeper.io/
2016-09-27 23:27:01 +02:00
Greenkeeper
6578c14292 chore(package): update dependencies (#2)
https://greenkeeper.io/
2016-09-22 07:49:04 +02:00
Julien Fontanet
ceee93883f Remove only specifiers. 2015-08-26 15:26:21 +02:00
Julien Fontanet
dae8fd2370 Remove console.log(). 2015-08-26 15:16:44 +02:00
Julien Fontanet
48f8322390 Coding style fixes. 2015-08-26 14:38:26 +02:00
Julien Fontanet
7df833bd9f Upgrade standard & babel-eslint. 2015-08-26 14:32:47 +02:00
Varchar38
2d639e191a Add functions on host 2015-08-12 16:33:26 +02:00
Varchar38
db758c6806 Add test on vm 2015-08-12 16:33:12 +02:00
Varchar38
6822e4ac0c Add function on disk 2015-08-11 10:08:03 +02:00
Varchar38
14b1b07ecd Add test on server.spec 2015-08-11 10:06:41 +02:00
Varchar38
3c71a20bb2 Add tests and move functions to util 2015-07-29 15:25:33 +02:00
Varchar38
8f73619ba1 Add tests on job and schedule 2015-07-28 17:09:09 +02:00
Varchar38
0ee6e5a35f Tests job.spec 2015-07-27 17:13:25 +02:00
Varchar38
22692757e6 Add test on server.spec 2015-07-24 11:00:35 +02:00
Varchar38
ed9584270d Add getIsoId 2015-07-24 10:59:46 +02:00
Varchar38
5a5c35a1c9 Add getPoolId 2015-07-24 10:45:35 +02:00
Varchar38
1f842e4fe4 Add tests 2015-07-22 17:08:56 +02:00
Varchar38
9275c4a6d6 getMainConnection 2015-07-21 11:49:03 +02:00
Varchar38
9c7e61cbf3 Reduce timeout 2015-07-17 17:13:22 +02:00
Varchar38
69a6066fd8 Add vif's tests 2015-07-16 15:36:13 +02:00
Varchar38
47d2d09e50 Add tests on pool.spec and vif.spec 2015-07-15 17:22:52 +02:00
Varchar38
da648e0a78 Move a function from vm.spc to util 2015-07-15 17:22:31 +02:00
Varchar38
9e1c526d51 Reorganization of user.spec and group.spec's code 2015-07-15 10:42:23 +02:00
Varchar38
d81998f91c Add test on VM.spec 2015-07-13 16:22:06 +02:00
Varchar38
a717d9b8f3 Reorganization of server.spec and host.spec code 2015-07-13 16:21:46 +02:00
Varchar38
31d1243a14 Add tests on vbd.spec 2015-07-13 15:00:07 +02:00
Varchar38
2424222964 Add vm.migrate test 2015-07-09 15:49:26 +02:00
Varchar38
370b245d65 Add test to vm.spec 2015-07-08 16:42:40 +02:00
Varchar38
c4dfcc27e3 Add tests and reorganization of vm.spec code 2015-07-07 16:39:11 +02:00
Varchar38
dfa870a777 Add vm test 2015-07-06 17:51:31 +02:00
Varchar38
572375fff4 Add test on vm.spec 2015-07-03 14:52:59 +02:00
Varchar38
ed1caee9f8 Change host IP 2015-07-03 13:13:34 +02:00
Varchar38
6f7757c81b Tests vbd.spec 2015-07-03 13:13:14 +02:00
Varchar38
4c92965313 Reorganization host.spec code 2015-07-02 10:41:45 +02:00
Varchar38
bbce96eb67 Add pvVm to getConfig 2015-06-30 17:06:20 +02:00
Varchar38
e3cb7bd4c7 Reorganization disk.spec code 2015-06-30 16:07:38 +02:00
Varchar38
79599bf831 Add test on VM 2015-06-26 16:56:15 +02:00
Varchar38
1ab67bc225 Tests disk 2015-06-26 10:58:03 +02:00
Varchar38
37df213771 Tests VM 2015-06-24 17:27:18 +02:00
Varchar38
d48ffdb14f Add getConfig 2015-06-24 17:26:44 +02:00
Varchar38
766cdc9f59 Tests group (delete group) 2015-06-24 17:25:54 +02:00
Varchar38
21a40c9d14 Add tests on VM 2015-06-23 14:02:33 +02:00
Varchar38
9275e9d006 console.log delete 2015-06-22 17:15:09 +02:00
Varchar38
ef9fe025e0 Tests VM 2015-06-22 17:14:03 +02:00
Varchar38
05694a8cda Delete items created for test modified 2015-06-19 17:21:40 +02:00
Varchar38
e6304cb028 Variables nerver used in comment 2015-06-19 17:20:46 +02:00
Varchar38
b2d00784a4 Add missing event-to-promise 2015-06-18 17:04:48 +02:00
Varchar38
ae31ebdc33 Tests host and vm 2015-06-18 17:03:48 +02:00
Varchar38
a2d50b380f Modified presentation in token.spec 2015-06-18 17:03:23 +02:00
Varchar38
654e8fd13f Add waitObjet function 2015-06-18 17:02:30 +02:00
Varchar38
bcd44e4b2d Tests for disk and docker 2015-06-18 16:58:01 +02:00
Varchar38
5200793744 Test host 2015-06-17 17:22:28 +02:00
Varchar38
abcb29391c Read me 2015-06-17 15:04:06 +02:00
Varchar38
6a682dc143 Tests server 2015-06-17 14:07:48 +02:00
Varchar38
d93d30537f Coding style fix 2015-06-16 16:00:04 +02:00
Varchar38
377e88ff36 Code mutualisation 2015-06-16 15:58:17 +02:00
Varchar38
1733290c02 Tests role 2015-06-16 11:10:22 +02:00
Varchar38
e702ccc48a Tests for groups 2015-06-16 10:37:26 +02:00
Varchar38
ba729c493b Tests for token.*() 2015-06-15 13:20:07 +02:00
Varchar38
1c55950b7e Fix tests on Windows. 2015-06-15 13:19:43 +02:00
Olivier Lambert
18c8282bac initial work on VM tests 2015-06-12 21:37:45 +02:00
Julien Fontanet
1d20456853 Initial test suite for vm.*(). 2015-06-12 20:54:42 +02:00
Varchar38
7e32d0ae10 More tests for user.*(). 2015-06-12 17:32:07 +02:00
Julien Fontanet
5d33e45eae Spec for another test. 2015-06-12 11:37:31 +02:00
Julien Fontanet
1590930ef9 First test! 2015-06-12 11:34:59 +02:00
Julien Fontanet
8186d34f4e Initial commit. 2015-06-11 11:01:46 +02:00
296 changed files with 17229 additions and 7920 deletions

View File

@@ -21,7 +21,7 @@ module.exports = {
overrides: [
{
files: ['cli.js', '*-cli.js', 'packages/*cli*/**/*.js'],
files: ['cli.js', '*-cli.js', '**/*cli*/**/*.js'],
rules: {
'no-console': 'off',
},
@@ -38,6 +38,15 @@ module.exports = {
// disabled because XAPI objects are using camel case
camelcase: ['off'],
'react/jsx-handler-names': 'off',
// disabled because not always relevant, we might reconsider in the future
//
// enabled by https://github.com/standard/eslint-config-standard/commit/319b177750899d4525eb1210686f6aca96190b2f
//
// example: https://github.com/vatesfr/xen-orchestra/blob/31ed3767c67044ca445658eb6b560718972402f2/packages/xen-api/src/index.js#L156-L157
'lines-between-class-members': 'off',
'no-console': ['error', { allow: ['warn', 'error'] }],
'no-var': 'error',
'node/no-extraneous-import': 'error',

View File

@@ -36,8 +36,8 @@
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -0,0 +1,378 @@
#!/usr/bin/env node
const args = process.argv.slice(2)
if (
args.length === 0 ||
/^(?:-h|--help)$/.test(args[0]) ||
args[0] !== 'clean-vms'
) {
console.log('Usage: xo-backups clean-vms [--force] xo-vm-backups/*')
// eslint-disable-next-line no-process-exit
return process.exit(1)
}
// remove `clean-vms` arg which is the only available command ATM
args.splice(0, 1)
// only act (ie delete files) if `--force` is present
const force = args[0] === '--force'
if (force) {
args.splice(0, 1)
}
// -----------------------------------------------------------------------------
const assert = require('assert')
const lockfile = require('proper-lockfile')
const { default: Vhd } = require('vhd-lib')
const { curryRight, flatten } = require('lodash')
const { dirname, resolve } = require('path')
const { DISK_TYPE_DIFFERENCING } = require('vhd-lib/dist/_constants')
const { pipe, promisifyAll } = require('promise-toolbox')
const fs = promisifyAll(require('fs'))
const handler = require('@xen-orchestra/fs').getHandler({ url: 'file://' })
// -----------------------------------------------------------------------------
const asyncMap = curryRight((iterable, fn) =>
Promise.all(
Array.isArray(iterable) ? iterable.map(fn) : Array.from(iterable, fn)
)
)
const filter = (...args) => thisArg => thisArg.filter(...args)
// TODO: better check?
// our heuristic is not good enough, there has been some false positives
// (detected as invalid by us but valid by `tar` and imported with success),
// either:
// - these files were normal but the check is incorrect
// - these files were invalid but without data loss
// - these files were invalid but with silent data loss
//
// FIXME: the heuristic does not work if the XVA is compressed, we need to
// implement a specific test for it
//
// maybe reading the end of the file looking for a file named
// /^Ref:\d+/\d+\.checksum$/ and then validating the tar structure from it
//
// https://github.com/npm/node-tar/issues/234#issuecomment-538190295
const isValidTar = async path => {
try {
const fd = await fs.open(path, 'r')
try {
const { size } = await fs.fstat(fd)
if (size <= 1024 || size % 512 !== 0) {
return false
}
const buf = Buffer.allocUnsafe(1024)
assert.strictEqual(
await fs.read(fd, buf, 0, buf.length, size - buf.length),
buf.length
)
return buf.every(_ => _ === 0)
} finally {
fs.close(fd).catch(noop)
}
} catch (error) {
// never throw, log and report as valid to avoid side effects
console.error('isValidTar', path, error)
return true
}
}
const noop = Function.prototype
const readDir = path =>
fs.readdir(path).then(
entries => {
entries.forEach((entry, i) => {
entries[i] = `${path}/${entry}`
})
return entries
},
error => {
// a missing dir is by definition empty
if (error != null && error.code === 'ENOENT') {
return []
}
throw error
}
)
// -----------------------------------------------------------------------------
// chain is an array of VHDs from child to parent
//
// the whole chain will be merged into parent, parent will be renamed to child
// and all the others will deleted
async function mergeVhdChain(chain) {
assert(chain.length >= 2)
const child = chain[0]
const parent = chain[chain.length - 1]
const children = chain.slice(0, -1).reverse()
console.warn('Unused parents of VHD', child)
chain
.slice(1)
.reverse()
.forEach(parent => {
console.warn(' ', parent)
})
force && console.warn(' merging…')
console.warn('')
if (force) {
// `mergeVhd` does not work with a stream, either
// - make it accept a stream
// - or create synthetic VHD which is not a stream
return console.warn('TODO: implement merge')
// await mergeVhd(
// handler,
// parent,
// handler,
// children.length === 1
// ? child
// : await createSyntheticStream(handler, children)
// )
}
await Promise.all([
force && fs.rename(parent, child),
asyncMap(children.slice(0, -1), child => {
console.warn('Unused VHD', child)
force && console.warn(' deleting…')
console.warn('')
return force && handler.unlink(child)
}),
])
}
const listVhds = pipe([
vmDir => vmDir + '/vdis',
readDir,
asyncMap(readDir),
flatten,
asyncMap(readDir),
flatten,
filter(_ => _.endsWith('.vhd')),
])
async function handleVm(vmDir) {
const vhds = new Set()
const vhdParents = { __proto__: null }
const vhdChildren = { __proto__: null }
// remove broken VHDs
await asyncMap(await listVhds(vmDir), async path => {
try {
const vhd = new Vhd(handler, path)
await vhd.readHeaderAndFooter()
vhds.add(path)
if (vhd.footer.diskType === DISK_TYPE_DIFFERENCING) {
const parent = resolve(dirname(path), vhd.header.parentUnicodeName)
vhdParents[path] = parent
if (parent in vhdChildren) {
const error = new Error(
'this script does not support multiple VHD children'
)
error.parent = parent
error.child1 = vhdChildren[parent]
error.child2 = path
throw error // should we throw?
}
vhdChildren[parent] = path
}
} catch (error) {
console.warn('Error while checking VHD', path)
console.warn(' ', error)
if (error != null && error.code === 'ERR_ASSERTION') {
force && console.warn(' deleting…')
console.warn('')
force && (await handler.unlink(path))
}
}
})
// remove VHDs with missing ancestors
{
const deletions = []
// return true if the VHD has been deleted or is missing
const deleteIfOrphan = vhd => {
const parent = vhdParents[vhd]
if (parent === undefined) {
return
}
// no longer needs to be checked
delete vhdParents[vhd]
deleteIfOrphan(parent)
if (!vhds.has(parent)) {
vhds.delete(vhd)
console.warn('Error while checking VHD', vhd)
console.warn(' missing parent', parent)
force && console.warn(' deleting…')
console.warn('')
force && deletions.push(handler.unlink(vhd))
}
}
// > A property that is deleted before it has been visited will not be
// > visited later.
// >
// > -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#Deleted_added_or_modified_properties
for (const child in vhdParents) {
deleteIfOrphan(child)
}
await Promise.all(deletions)
}
const [jsons, xvas] = await readDir(vmDir).then(entries => [
entries.filter(_ => _.endsWith('.json')),
new Set(entries.filter(_ => _.endsWith('.xva'))),
])
await asyncMap(xvas, async path => {
// check is not good enough to delete the file, the best we can do is report
// it
if (!(await isValidTar(path))) {
console.warn('Potential broken XVA', path)
console.warn('')
}
})
const unusedVhds = new Set(vhds)
const unusedXvas = new Set(xvas)
// compile the list of unused XVAs and VHDs, and remove backup metadata which
// reference a missing XVA/VHD
await asyncMap(jsons, async json => {
const metadata = JSON.parse(await fs.readFile(json))
const { mode } = metadata
if (mode === 'full') {
const linkedXva = resolve(vmDir, metadata.xva)
if (xvas.has(linkedXva)) {
unusedXvas.delete(linkedXva)
} else {
console.warn('Error while checking backup', json)
console.warn(' missing file', linkedXva)
force && console.warn(' deleting…')
console.warn('')
force && (await handler.unlink(json))
}
} else if (mode === 'delta') {
const linkedVhds = (() => {
const { vhds } = metadata
return Object.keys(vhds).map(key => resolve(vmDir, vhds[key]))
})()
// FIXME: find better approach by keeping as much of the backup as
// possible (existing disks) even if one disk is missing
if (linkedVhds.every(_ => vhds.has(_))) {
linkedVhds.forEach(_ => unusedVhds.delete(_))
} else {
console.warn('Error while checking backup', json)
const missingVhds = linkedVhds.filter(_ => !vhds.has(_))
console.warn(
' %i/%i missing VHDs',
missingVhds.length,
linkedVhds.length
)
missingVhds.forEach(vhd => {
console.warn(' ', vhd)
})
force && console.warn(' deleting…')
console.warn('')
force && (await handler.unlink(json))
}
}
})
// TODO: parallelize by vm/job/vdi
const unusedVhdsDeletion = []
{
// VHD chains (as list from child to ancestor) to merge indexed by last
// ancestor
const vhdChainsToMerge = { __proto__: null }
const toCheck = new Set(unusedVhds)
const getUsedChildChainOrDelete = vhd => {
if (vhd in vhdChainsToMerge) {
const chain = vhdChainsToMerge[vhd]
delete vhdChainsToMerge[vhd]
return chain
}
if (!unusedVhds.has(vhd)) {
return [vhd]
}
// no longer needs to be checked
toCheck.delete(vhd)
const child = vhdChildren[vhd]
if (child !== undefined) {
const chain = getUsedChildChainOrDelete(child)
if (chain !== undefined) {
chain.push(vhd)
return chain
}
}
console.warn('Unused VHD', vhd)
force && console.warn(' deleting…')
console.warn('')
force && unusedVhdsDeletion.push(handler.unlink(vhd))
}
toCheck.forEach(vhd => {
vhdChainsToMerge[vhd] = getUsedChildChainOrDelete(vhd)
})
Object.keys(vhdChainsToMerge).forEach(key => {
const chain = vhdChainsToMerge[key]
if (chain !== undefined) {
unusedVhdsDeletion.push(mergeVhdChain(chain))
}
})
}
await Promise.all([
unusedVhdsDeletion,
asyncMap(unusedXvas, path => {
console.warn('Unused XVA', path)
force && console.warn(' deleting…')
console.warn('')
return force && handler.unlink(path)
}),
])
}
// -----------------------------------------------------------------------------
asyncMap(args, async vmDir => {
vmDir = resolve(vmDir)
// TODO: implement this in `xo-server`, not easy because not compatible with
// `@xen-orchestra/fs`.
const release = await lockfile.lock(vmDir)
try {
await handleVm(vmDir)
} catch (error) {
console.error('handleVm', vmDir, error)
} finally {
await release()
}
}).catch(error => console.error('main', error))

View File

@@ -0,0 +1,27 @@
{
"bin": {
"xo-backups": "index.js"
},
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"dependencies": {
"@xen-orchestra/fs": "^0.10.1",
"lodash": "^4.17.15",
"promise-toolbox": "^0.14.0",
"proper-lockfile": "^4.1.1",
"vhd-lib": "^0.7.0"
},
"engines": {
"node": ">=7.10.1"
},
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/backups-cli",
"name": "@xen-orchestra/backups-cli",
"repository": {
"directory": "@xen-orchestra/backups-cli",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"scripts": {
"postversion": "npm publish --access public"
},
"version": "0.0.0"
}

View File

@@ -16,7 +16,7 @@
},
"dependencies": {
"golike-defer": "^0.4.1",
"xen-api": "^0.26.0"
"xen-api": "^0.27.2"
},
"scripts": {
"postversion": "npm publish"

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/cron",
"version": "1.0.3",
"version": "1.0.5",
"license": "ISC",
"description": "Focused, well maintained, cron parser/scheduler",
"keywords": [
@@ -46,8 +46,8 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -7,7 +7,21 @@ const MAX_DELAY = 2 ** 31 - 1
class Job {
constructor(schedule, fn) {
let scheduledDate
const wrapper = () => {
const now = Date.now()
if (scheduledDate > now) {
// we're early, delay
//
// no need to check _isEnabled, we're just delaying the existing timeout
//
// see https://github.com/vatesfr/xen-orchestra/issues/4625
this._timeout = setTimeout(wrapper, scheduledDate - now)
return
}
this._isRunning = true
let result
try {
result = fn()
@@ -22,23 +36,36 @@ class Job {
}
}
const scheduleNext = () => {
const delay = schedule._nextDelay()
this._timeout =
delay < MAX_DELAY
? setTimeout(wrapper, delay)
: setTimeout(scheduleNext, MAX_DELAY)
this._isRunning = false
if (this._isEnabled) {
const now = Date.now()
scheduledDate = +schedule._createDate()
const delay = scheduledDate - now
this._timeout =
delay < MAX_DELAY
? setTimeout(wrapper, delay)
: setTimeout(scheduleNext, MAX_DELAY)
}
}
this._isEnabled = false
this._isRunning = false
this._scheduleNext = scheduleNext
this._timeout = undefined
}
start() {
this.stop()
this._scheduleNext()
this._isEnabled = true
if (!this._isRunning) {
this._scheduleNext()
}
}
stop() {
this._isEnabled = false
clearTimeout(this._timeout)
}
}
@@ -68,11 +95,6 @@ class Schedule {
return dates
}
_nextDelay() {
const now = this._createDate()
return next(this._schedule, now) - now
}
startJob(fn) {
const job = this.createJob(fn)
job.start()

View File

@@ -0,0 +1,62 @@
/* eslint-env jest */
import { createSchedule } from './'
describe('issues', () => {
test('stop during async execution', async () => {
let nCalls = 0
let resolve, promise
const job = createSchedule('* * * * *').createJob(() => {
++nCalls
// eslint-disable-next-line promise/param-names
promise = new Promise(r => {
resolve = r
})
return promise
})
job.start()
jest.runAllTimers()
expect(nCalls).toBe(1)
job.stop()
resolve()
await promise
jest.runAllTimers()
expect(nCalls).toBe(1)
})
test('stop then start during async job execution', async () => {
let nCalls = 0
let resolve, promise
const job = createSchedule('* * * * *').createJob(() => {
++nCalls
// eslint-disable-next-line promise/param-names
promise = new Promise(r => {
resolve = r
})
return promise
})
job.start()
jest.runAllTimers()
expect(nCalls).toBe(1)
job.stop()
job.start()
resolve()
await promise
jest.runAllTimers()
expect(nCalls).toBe(2)
})
})

View File

@@ -34,8 +34,8 @@
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -33,8 +33,8 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/fs",
"version": "0.9.0",
"version": "0.10.1",
"license": "AGPL-3.0",
"description": "The File System for Xen Orchestra backups.",
"keywords": [],
@@ -28,8 +28,9 @@
"execa": "^1.0.0",
"fs-extra": "^8.0.1",
"get-stream": "^4.0.0",
"limit-concurrency-decorator": "^0.4.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.12.1",
"promise-toolbox": "^0.14.0",
"readable-stream": "^3.0.6",
"through2": "^3.0.0",
"tmp": "^0.1.0",
@@ -40,14 +41,15 @@
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.1.6",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"async-iterator-to-stream": "^1.1.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"cross-env": "^6.0.3",
"dotenv": "^8.0.0",
"index-modules": "^0.3.0",
"rimraf": "^2.6.2"
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -4,6 +4,7 @@
import getStream from 'get-stream'
import asyncMap from '@xen-orchestra/async-map'
import limit from 'limit-concurrency-decorator'
import path from 'path'
import synchronized from 'decorator-synchronized'
import { fromCallback, fromEvent, ignoreErrors, timeout } from 'promise-toolbox'
@@ -31,6 +32,7 @@ const computeRate = (hrtime: number[], size: number) => {
}
const DEFAULT_TIMEOUT = 6e5 // 10 min
const DEFAULT_MAX_PARALLEL_OPERATIONS = 10
const ignoreEnoent = error => {
if (error == null || error.code !== 'ENOENT') {
@@ -83,6 +85,25 @@ export default class RemoteHandlerAbstract {
}
}
;({ timeout: this._timeout = DEFAULT_TIMEOUT } = options)
const sharedLimit = limit(
options.maxParallelOperations ?? DEFAULT_MAX_PARALLEL_OPERATIONS
)
this.closeFile = sharedLimit(this.closeFile)
this.getInfo = sharedLimit(this.getInfo)
this.getSize = sharedLimit(this.getSize)
this.list = sharedLimit(this.list)
this.mkdir = sharedLimit(this.mkdir)
this.openFile = sharedLimit(this.openFile)
this.outputFile = sharedLimit(this.outputFile)
this.read = sharedLimit(this.read)
this.readFile = sharedLimit(this.readFile)
this.rename = sharedLimit(this.rename)
this.rmdir = sharedLimit(this.rmdir)
this.truncate = sharedLimit(this.truncate)
this.unlink = sharedLimit(this.unlink)
this.write = sharedLimit(this.write)
this.writeFile = sharedLimit(this.writeFile)
}
// Public members

View File

@@ -24,6 +24,19 @@ log.info('this information is relevant to the user')
log.warn('something went wrong but did not prevent current action')
log.error('something went wrong')
log.fatal('service/app is going down')
// you can add contextual info
log.debug('new API request', {
method: 'foo',
params: [ 'bar', 'baz' ]
user: 'qux'
})
// by convention, errors go into the `error` field
log.error('could not join server', {
error,
server: 'example.org',
})
```
Then, at application level, configure the logs are handled:

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/log",
"version": "0.1.4",
"version": "0.2.0",
"license": "ISC",
"description": "",
"keywords": [],
@@ -31,16 +31,16 @@
},
"dependencies": {
"lodash": "^4.17.4",
"promise-toolbox": "^0.12.1"
"promise-toolbox": "^0.14.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"cross-env": "^6.0.3",
"index-modules": "^0.3.0",
"rimraf": "^2.6.2"
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -19,7 +19,8 @@ const createTransport = config => {
}
}
let { filter, transport } = config
let { filter } = config
let transport = createTransport(config.transport)
const level = resolve(config.level)
if (filter !== undefined) {
@@ -51,11 +52,12 @@ const symbol =
? Symbol.for('@xen-orchestra/log')
: '@@@xen-orchestra/log'
const { env } = process
global[symbol] = createTransport({
// display warnings or above, and all that are enabled via DEBUG or
// NODE_DEBUG env
filter: process.env.DEBUG || process.env.NODE_DEBUG,
level: LEVELS.INFO,
filter: [env.DEBUG, env.NODE_DEBUG].filter(Boolean).join(','),
level: resolve(env.LOG_LEVEL, LEVELS.INFO),
transport: createConsoleTransport(),
})

View File

@@ -1,5 +1,5 @@
import createTransport from './transports/console'
import LEVELS from './levels'
import LEVELS, { resolve } from './levels'
const symbol =
typeof Symbol !== 'undefined'
@@ -9,7 +9,8 @@ if (!(symbol in global)) {
// the default behavior, without requiring `configure` is to avoid
// logging anything unless it's a real error
const transport = createTransport()
global[symbol] = log => log.level > LEVELS.WARN && transport(log)
const level = resolve(process.env.LOG_LEVEL, LEVELS.WARN)
global[symbol] = log => log.level >= level && transport(log)
}
// -------------------------------------------------------------------
@@ -72,5 +73,5 @@ prototype.wrap = function(message, fn) {
}
}
const createLogger = namespace => new Logger(namespace)
export const createLogger = namespace => new Logger(namespace)
export { createLogger as default }

View File

@@ -13,11 +13,22 @@ for (const name in LEVELS) {
NAMES[LEVELS[name]] = name
}
export const resolve = level => {
if (typeof level === 'string') {
level = LEVELS[level.toUpperCase()]
// resolves to the number representation of a level
//
// returns `defaultLevel` if invalid
export const resolve = (level, defaultLevel) => {
const type = typeof level
if (type === 'number') {
if (level in NAMES) {
return level
}
} else if (type === 'string') {
const nLevel = LEVELS[level.toUpperCase()]
if (nLevel !== undefined) {
return nLevel
}
}
return level
return defaultLevel
}
Object.freeze(LEVELS)

View File

@@ -1,24 +1,23 @@
import LEVELS, { NAMES } from '../levels'
// Bind console methods (necessary for browsers)
const debugConsole = console.log.bind(console)
const infoConsole = console.info.bind(console)
const warnConsole = console.warn.bind(console)
const errorConsole = console.error.bind(console)
const { ERROR, INFO, WARN } = LEVELS
const consoleTransport = ({ data, level, namespace, message, time }) => {
const fn =
/* eslint-disable no-console */
level < INFO
? debugConsole
? console.log
: level < WARN
? infoConsole
? console.info
: level < ERROR
? warnConsole
: errorConsole
? console.warn
: console.error
/* eslint-enable no-console */
fn('%s - %s - [%s] %s', time.toISOString(), namespace, NAMES[level], message)
data != null && fn(data)
const args = [time.toISOString(), namespace, NAMES[level], message]
if (data != null) {
args.push(data)
}
fn.apply(console, args)
}
export default () => consoleTransport

View File

@@ -1,7 +1,6 @@
import fromCallback from 'promise-toolbox/fromCallback'
import splitHost from 'split-host' // eslint-disable-line node/no-extraneous-import node/no-missing-import
import startsWith from 'lodash/startsWith'
import { createClient, Facility, Severity, Transport } from 'syslog-client' // eslint-disable-line node/no-extraneous-import node/no-missing-import
import splitHost from 'split-host'
import { createClient, Facility, Severity, Transport } from 'syslog-client'
import LEVELS from '../levels'
@@ -19,10 +18,10 @@ const facility = Facility.User
export default target => {
const opts = {}
if (target !== undefined) {
if (startsWith(target, 'tcp://')) {
if (target.startsWith('tcp://')) {
target = target.slice(6)
opts.transport = Transport.Tcp
} else if (startsWith(target, 'udp://')) {
} else if (target.startsWith('udp://')) {
target = target.slice(6)
opts.transport = Transport.Udp
}

View File

@@ -36,8 +36,8 @@
"@babel/preset-env": "^7.0.0",
"babel-plugin-dev": "^1.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -0,0 +1,62 @@
# @xen-orchestra/template [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
## Install
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/template):
```
> npm install --save @xen-orchestra/template
```
## Usage
Create a string replacer based on a pattern and a list of rules.
```js
const myReplacer = compileTemplate('{name}_COPY_\{name}_{id}_%\%', {
'{name}': vm => vm.name_label,
'{id}': vm => vm.id,
'%': (_, i) => i
})
const newString = myReplacer({
name_label: 'foo',
id: 42,
}, 32)
newString === 'foo_COPY_{name}_42_32%' // true
```
## Development
```
# Install dependencies
> yarn
# Run the tests
> yarn test
# Continuously compile
> yarn dev
# Continuously run the tests
> yarn dev-test
# Build for production (automatically called by npm install)
> yarn build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
ISC © [Vates SAS](https://vates.fr)

View File

@@ -1,46 +1,35 @@
{
"name": "xo-server-cloud",
"version": "0.2.4",
"name": "@xen-orchestra/template",
"version": "0.1.0",
"license": "ISC",
"description": "",
"keywords": [
"cloud",
"orchestra",
"plugin",
"xen",
"xen-orchestra",
"xo-server"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-cloud",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/template",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/xo-server-cloud",
"directory": "@xen-orchestra/template",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Pierre Donias",
"email": "pierre.donias@gmail.com"
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"dependencies": {
"http-request-plus": "^0.8.0",
"jsonrpc-websocket-client": "^0.5.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"cross-env": "^5.1.3",
"rimraf": "^2.5.4"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
@@ -48,7 +37,10 @@
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
"prepublishOnly": "yarn run build",
"postversion": "npm publish --access public"
},
"private": true
"dependencies": {
"lodash": "^4.17.15"
}
}

View File

@@ -0,0 +1,19 @@
import escapeRegExp from 'lodash/escapeRegExp'
const compareLengthDesc = (a, b) => b.length - a.length
export function compileTemplate(pattern, rules) {
const matches = Object.keys(rules)
.sort(compareLengthDesc)
.map(escapeRegExp)
.join('|')
const regExp = new RegExp(`\\\\(?:\\\\|${matches})|${matches}`, 'g')
return (...params) =>
pattern.replace(regExp, match => {
if (match[0] === '\\') {
return match.slice(1)
}
const rule = rules[match]
return typeof rule === 'function' ? rule(...params) : rule
})
}

View File

@@ -0,0 +1,14 @@
/* eslint-env jest */
import { compileTemplate } from '.'
it("correctly replaces the template's variables", () => {
const replacer = compileTemplate(
'{property}_\\{property}_\\\\{property}_{constant}_%_FOO',
{
'{property}': obj => obj.name,
'{constant}': 1235,
'%': (_, i) => i,
}
)
expect(replacer({ name: 'bar' }, 5)).toBe('bar_{property}_\\bar_1235_5_FOO')
})

View File

@@ -4,22 +4,204 @@
### Enhancements
### Bug fixes
### Released packages
## **5.39.1** (2019-10-11)
![Channel: latest](https://badgen.net/badge/channel/latest/yellow)
### Enhancements
- [Support] Ability to check the XOA on the user interface [#4513](https://github.com/vatesfr/xen-orchestra/issues/4513) (PR [#4574](https://github.com/vatesfr/xen-orchestra/pull/4574))
### Bug fixes
- [VM/new-vm] Fix template selection on creating new VM for resource sets [#4565](https://github.com/vatesfr/xen-orchestra/issues/4565) (PR [#4568](https://github.com/vatesfr/xen-orchestra/pull/4568))
- [VM] Clearer invalid cores per socket error [#4120](https://github.com/vatesfr/xen-orchestra/issues/4120) (PR [#4187](https://github.com/vatesfr/xen-orchestra/pull/4187))
### Released packages
- xo-web v5.50.3
## **5.39.0** (2019-09-30)
### Highlights
- [VM/console] Add a button to connect to the VM via the local SSH client (PR [#4415](https://github.com/vatesfr/xen-orchestra/pull/4415))
- [SDN Controller] Add possibility to encrypt private networks (PR [#4441](https://github.com/vatesfr/xen-orchestra/pull/4441))
- [Backups] Improve performance by caching VM backups listing (PR [#4509](https://github.com/vatesfr/xen-orchestra/pull/4509))
- [HUB] VM template store [#1918](https://github.com/vatesfr/xen-orchestra/issues/1918) (PR [#4442](https://github.com/vatesfr/xen-orchestra/pull/4442))
### Enhancements
- [SR/new] Clarify address formats [#4450](https://github.com/vatesfr/xen-orchestra/issues/4450) (PR [#4460](https://github.com/vatesfr/xen-orchestra/pull/4460))
- [Backup NG/New] Show warning if zstd compression is not supported on a VM [#3892](https://github.com/vatesfr/xen-orchestra/issues/3892) (PRs [#4411](https://github.com/vatesfr/xen-orchestra/pull/4411))
- [VM/disks] Don't hide disks that are attached to the same VM twice [#4400](https://github.com/vatesfr/xen-orchestra/issues/4400) (PR [#4414](https://github.com/vatesfr/xen-orchestra/pull/4414))
- [SDN Controller] Ability to configure MTU for private networks (PR [#4491](https://github.com/vatesfr/xen-orchestra/pull/4491))
- [VM Export] Filenames are now prefixed with datetime [#4503](https://github.com/vatesfr/xen-orchestra/issues/4503)
- [Settings/Logs] Differenciate XS/XCP-ng errors from XO errors [#4101](https://github.com/vatesfr/xen-orchestra/issues/4101) (PR [#4385](https://github.com/vatesfr/xen-orchestra/pull/4385))
- [Backups] Improve performance by caching logs consolidation (PR [#4541](https://github.com/vatesfr/xen-orchestra/pull/4541))
- [New VM] Cloud Init available for all plans (PR [#4543](https://github.com/vatesfr/xen-orchestra/pull/4543))
- [Servers] IPv6 addresses can be used [#4520](https://github.com/vatesfr/xen-orchestra/issues/4520) (PR [#4521](https://github.com/vatesfr/xen-orchestra/pull/4521)) \
Note: They must enclosed in brackets to differentiate with the port, e.g.: `[2001:db8::7334]` or `[ 2001:db8::7334]:4343`
### Bug fixes
- [PBD] Obfuscate cifs password from device config [#4384](https://github.com/vatesfr/xen-orchestra/issues/4384) (PR [#4401](https://github.com/vatesfr/xen-orchestra/pull/4401))
- [XOSAN] Fix "invalid parameters" error on creating a SR (PR [#4478](https://github.com/vatesfr/xen-orchestra/pull/4478))
- [Patching] Avoid overloading XCP-ng by reducing the frequency of yum update checks [#4358](https://github.com/vatesfr/xen-orchestra/issues/4358) (PR [#4477](https://github.com/vatesfr/xen-orchestra/pull/4477))
- [Network] Fix inability to create a bonded network (PR [#4489](https://github.com/vatesfr/xen-orchestra/pull/4489))
- [Backup restore & Replication] Don't copy `sm_config` to new VDIs which might leads to useless coalesces [#4482](https://github.com/vatesfr/xen-orchestra/issues/4482) (PR [#4484](https://github.com/vatesfr/xen-orchestra/pull/4484))
- [Home] Fix intermediary "no results" display showed on filtering items [#4420](https://github.com/vatesfr/xen-orchestra/issues/4420) (PR [#4456](https://github.com/vatesfr/xen-orchestra/pull/4456)
- [Backup NG/New schedule] Properly show user errors in the form [#3831](https://github.com/vatesfr/xen-orchestra/issues/3831) (PR [#4131](https://github.com/vatesfr/xen-orchestra/pull/4131))
- [VM/Advanced] Fix `"vm.set_domain_type" is not a function` error on switching virtualization mode (PV/HVM) [#4348](https://github.com/vatesfr/xen-orchestra/issues/4348) (PR [#4504](https://github.com/vatesfr/xen-orchestra/pull/4504))
- [Backup NG/logs] Show warning when zstd compression is selected but not supported [#3892](https://github.com/vatesfr/xen-orchestra/issues/3892) (PR [#4375](https://github.com/vatesfr/xen-orchestra/pull/4375)
- [Patches] Fix patches installation for CH 8.0 (PR [#4511](https://github.com/vatesfr/xen-orchestra/pull/4511))
- [Network] Fix inability to set a network name [#4514](https://github.com/vatesfr/xen-orchestra/issues/4514) (PR [4510](https://github.com/vatesfr/xen-orchestra/pull/4510))
- [Backup NG] Fix race conditions that could lead to disabled jobs still running (PR [4510](https://github.com/vatesfr/xen-orchestra/pull/4510))
- [XOA] Remove "Updates" and "Licenses" tabs for non admin users (PR [#4526](https://github.com/vatesfr/xen-orchestra/pull/4526))
- [New VM] Ability to escape [cloud config template](https://xen-orchestra.com/blog/xen-orchestra-5-21/#cloudconfigtemplates) variables [#4486](https://github.com/vatesfr/xen-orchestra/issues/4486) (PR [#4501](https://github.com/vatesfr/xen-orchestra/pull/4501))
- [Backup NG] Properly log and report if job is already running [#4497](https://github.com/vatesfr/xen-orchestra/issues/4497) (PR [4534](https://github.com/vatesfr/xen-orchestra/pull/4534))
- [Host] Fix an issue where host was wrongly reporting time inconsistency (PR [#4540](https://github.com/vatesfr/xen-orchestra/pull/4540))
### Released packages
- xen-api v0.27.2
- xo-server-cloud v0.3.0
- @xen-orchestra/cron v1.0.4
- xo-server-sdn-controller v0.3.0
- @xen-orchestra/template v0.1.0
- xo-server v5.50.1
- xo-web v5.50.2
## **5.38.0** (2019-08-29)
![Channel: stable](https://badgen.net/badge/channel/stable/green)
### Enhancements
- [VM/Attach disk] Display confirmation modal when VDI is already attached [#3381](https://github.com/vatesfr/xen-orchestra/issues/3381) (PR [#4366](https://github.com/vatesfr/xen-orchestra/pull/4366))
- [Zstd]
- [VM/copy, VM/export] Only show zstd option when it's supported [#3892](https://github.com/vatesfr/xen-orchestra/issues/3892) (PRs [#4326](https://github.com/vatesfr/xen-orchestra/pull/4326) [#4368](https://github.com/vatesfr/xen-orchestra/pull/4368))
- [VM/Bulk copy] Show warning if zstd compression is not supported on a VM [#3892](https://github.com/vatesfr/xen-orchestra/issues/3892) (PR [#4346](https://github.com/vatesfr/xen-orchestra/pull/4346))
- [VM import & Continuous Replication] Enable `guessVhdSizeOnImport` by default, this fix some `VDI_IO_ERROR` with XenServer 7.1 and XCP-ng 8.0 (PR [#4436](https://github.com/vatesfr/xen-orchestra/pull/4436))
- [SDN Controller] Add possibility to create multiple GRE networks and VxLAN networks within a same pool (PR [#4435](https://github.com/vatesfr/xen-orchestra/pull/4435))
- [SDN Controller] Add possibility to create cross-pool private networks (PR [#4405](https://github.com/vatesfr/xen-orchestra/pull/4405))
### Bug fixes
- [SR/General] Display VDI VM name in SR usage graph (PR [#4370](https://github.com/vatesfr/xen-orchestra/pull/4370))
- [VM/Attach disk] Fix checking VDI mode (PR [#4373](https://github.com/vatesfr/xen-orchestra/pull/4373))
- [VM revert] Snapshot before: add admin ACLs on created snapshot [#4331](https://github.com/vatesfr/xen-orchestra/issues/4331) (PR [#4391](https://github.com/vatesfr/xen-orchestra/pull/4391))
- [Network] Fixed "invalid parameters" error when creating bonded network [#4425](https://github.com/vatesfr/xen-orchestra/issues/4425) (PR [#4429](https://github.com/vatesfr/xen-orchestra/pull/4429))
### Released packages
- xo-server-sdn-controller v0.2.0
- xo-server-usage-report v0.7.3
- xo-server v5.48.0
- xo-web v5.48.1
## **5.37.1** (2019-08-06)
### Enhancements
- [SDN Controller] Let the user choose on which PIF to create a private network (PR [#4379](https://github.com/vatesfr/xen-orchestra/pull/4379))
### Bug fixes
- [SDN Controller] Better detect host shutting down to adapt network topology (PR [#4314](https://github.com/vatesfr/xen-orchestra/pull/4314))
- [SDN Controller] Add new hosts to pool's private networks (PR [#4382](https://github.com/vatesfr/xen-orchestra/pull/4382))
### Released packages
- xo-server-sdn-controller v0.1.2
## **5.37.0** (2019-07-25)
### Highlights
- [Pool] Ability to add multiple hosts on the pool [#2402](https://github.com/vatesfr/xen-orchestra/issues/2402) (PR [#3716](https://github.com/vatesfr/xen-orchestra/pull/3716))
- [SR/General] Improve SR usage graph [#3608](https://github.com/vatesfr/xen-orchestra/issues/3608) (PR [#3830](https://github.com/vatesfr/xen-orchestra/pull/3830))
- [VM] Permission to revert to any snapshot for VM operators [#3928](https://github.com/vatesfr/xen-orchestra/issues/3928) (PR [#4247](https://github.com/vatesfr/xen-orchestra/pull/4247))
- [Backup NG] Ability to bypass unhealthy VDI chains check [#4324](https://github.com/vatesfr/xen-orchestra/issues/4324) (PR [#4340](https://github.com/vatesfr/xen-orchestra/pull/4340))
- [VM/console] Multiline copy/pasting [#4261](https://github.com/vatesfr/xen-orchestra/issues/4261) (PR [#4341](https://github.com/vatesfr/xen-orchestra/pull/4341))
### Enhancements
- [Stats] Ability to display last day stats [#4160](https://github.com/vatesfr/xen-orchestra/issues/4160) (PR [#4168](https://github.com/vatesfr/xen-orchestra/pull/4168))
- [Settings/servers] Display servers connection issues [#4300](https://github.com/vatesfr/xen-orchestra/issues/4300) (PR [#4310](https://github.com/vatesfr/xen-orchestra/pull/4310))
- [VM] Show current operations and progress [#3811](https://github.com/vatesfr/xen-orchestra/issues/3811) (PR [#3982](https://github.com/vatesfr/xen-orchestra/pull/3982))
- [Backup NG/New] Generate default schedule if no schedule is specified [#4036](https://github.com/vatesfr/xen-orchestra/issues/4036) (PR [#4183](https://github.com/vatesfr/xen-orchestra/pull/4183))
- [Host/Advanced] Ability to edit iSCSI IQN [#4048](https://github.com/vatesfr/xen-orchestra/issues/4048) (PR [#4208](https://github.com/vatesfr/xen-orchestra/pull/4208))
- [VM,host] Improved state icons/pills (colors and tooltips) (PR [#4363](https://github.com/vatesfr/xen-orchestra/pull/4363))
### Bug fixes
- [Settings/Servers] Fix read-only setting toggling
- [SDN Controller] Do not choose physical PIF without IP configuration for tunnels. (PR [#4319](https://github.com/vatesfr/xen-orchestra/pull/4319))
- [Xen servers] Fix `no connection found for object` error if pool master is reinstalled [#4299](https://github.com/vatesfr/xen-orchestra/issues/4299) (PR [#4302](https://github.com/vatesfr/xen-orchestra/pull/4302))
- [Backup-ng/restore] Display correct size for full VM backup [#4316](https://github.com/vatesfr/xen-orchestra/issues/4316) (PR [#4332](https://github.com/vatesfr/xen-orchestra/pull/4332))
- [VM/tab-advanced] Fix CPU limits edition (PR [#4337](https://github.com/vatesfr/xen-orchestra/pull/4337))
- [Remotes] Fix `EIO` errors due to massive parallel fs operations [#4323](https://github.com/vatesfr/xen-orchestra/issues/4323) (PR [#4330](https://github.com/vatesfr/xen-orchestra/pull/4330))
- [VM/Advanced] Fix virtualization mode switch (PV/HVM) (PR [#4349](https://github.com/vatesfr/xen-orchestra/pull/4349))
- [Task] fix hidden notification by search field [#3874](https://github.com/vatesfr/xen-orchestra/issues/3874) (PR [#4305](https://github.com/vatesfr/xen-orchestra/pull/4305)
- [VM] Fail to change affinity (PR [#4361](https://github.com/vatesfr/xen-orchestra/pull/4361)
- [VM] Number of CPUs not correctly changed on running VMs (PR [#4360](https://github.com/vatesfr/xen-orchestra/pull/4360)
### Released packages
- @xen-orchestra/fs v0.10.1
- xo-server-sdn-controller v0.1.1
- xen-api v0.27.1
- xo-server v5.46.0
- xo-web v5.46.0
## **5.36.0** (2019-06-27)
### Highlights
- [SR/new] Create ZFS storage [#4260](https://github.com/vatesfr/xen-orchestra/issues/4260) (PR [#4266](https://github.com/vatesfr/xen-orchestra/pull/4266))
- [Host/advanced] Fix host CPU hyperthreading detection [#4262](https://github.com/vatesfr/xen-orchestra/issues/4262) (PR [#4285](https://github.com/vatesfr/xen-orchestra/pull/4285))
- [VM/Advanced] Ability to use UEFI instead of BIOS [#4264](https://github.com/vatesfr/xen-orchestra/issues/4264) (PR [#4268](https://github.com/vatesfr/xen-orchestra/pull/4268))
- [Backup-ng/restore] Display size for full VM backup [#4009](https://github.com/vatesfr/xen-orchestra/issues/4009) (PR [#4245](https://github.com/vatesfr/xen-orchestra/pull/4245))
- [Sr/new] Ability to select NFS version when creating NFS storage [#3951](https://github.com/vatesfr/xen-orchestra/issues/3951) (PR [#4277](https://github.com/vatesfr/xen-orchestra/pull/4277))
- [Host/storages, SR/hosts] Display PBD details [#4264](https://github.com/vatesfr/xen-orchestra/issues/4161) (PR [#4268](https://github.com/vatesfr/xen-orchestra/pull/4284))
- [auth-saml] Improve compatibility with Microsoft Azure Active Directory (PR [#4294](https://github.com/vatesfr/xen-orchestra/pull/4294))
### Enhancements
- [Host] Display warning when "Citrix Hypervisor" license has restrictions [#4251](https://github.com/vatesfr/xen-orchestra/issues/4164) (PR [#4235](https://github.com/vatesfr/xen-orchestra/pull/4279))
- [VM/Backup] Create backup bulk action [#2573](https://github.com/vatesfr/xen-orchestra/issues/2573) (PR [#4257](https://github.com/vatesfr/xen-orchestra/pull/4257))
- [Host] Display warning when host's time differs too much from XOA's time [#4113](https://github.com/vatesfr/xen-orchestra/issues/4113) (PR [#4173](https://github.com/vatesfr/xen-orchestra/pull/4173))
- [VM/network] Display and set bandwidth rate-limit of a VIF [#4215](https://github.com/vatesfr/xen-orchestra/issues/4215) (PR [#4293](https://github.com/vatesfr/xen-orchestra/pull/4293))
- [SDN Controller] New plugin which enables creating pool-wide private networks [xcp-ng/xcp#175](https://github.com/xcp-ng/xcp/issues/175) (PR [#4269](https://github.com/vatesfr/xen-orchestra/pull/4269))
### Bug fixes
- [XOA] Don't require editing the _email_ field in case of re-registration (PR [#4259](https://github.com/vatesfr/xen-orchestra/pull/4259))
- [Metadata backup] Missing XAPIs should trigger a failure job [#4281](https://github.com/vatesfr/xen-orchestra/issues/4281) (PR [#4283](https://github.com/vatesfr/xen-orchestra/pull/4283))
- [iSCSI] Fix fibre channel paths display [#4291](https://github.com/vatesfr/xen-orchestra/issues/4291) (PR [#4303](https://github.com/vatesfr/xen-orchestra/pull/4303))
- [New VM] Fix tooltips not displayed on disabled elements in some browsers (e.g. Google Chrome) [#4304](https://github.com/vatesfr/xen-orchestra/issues/4304) (PR [#4309](https://github.com/vatesfr/xen-orchestra/pull/4309))
### Released packages
- xen-api v0.25.2
- xo-server v5.43.0
- xo-web v5.43.0
- xo-server-auth-ldap v0.6.5
- xen-api v0.26.0
- xo-server-sdn-controller v0.1
- xo-server-auth-saml v0.6.0
- xo-server-backup-reports v0.16.2
- xo-server v5.44.0
- xo-web v5.44.0
## **5.35.0** (2019-05-29)
![Channel: latest](https://badgen.net/badge/channel/latest/yellow)
### Enhancements
- [VM/general] Display 'Started... ago' instead of 'Halted... ago' for paused state [#3750](https://github.com/vatesfr/xen-orchestra/issues/3750) (PR [#4170](https://github.com/vatesfr/xen-orchestra/pull/4170))
@@ -53,8 +235,6 @@
## **5.34.0** (2019-04-30)
![Channel: stable](https://badgen.net/badge/channel/stable/green)
### Highlights
- [Self/New VM] Add network config box to custom cloud-init [#3872](https://github.com/vatesfr/xen-orchestra/issues/3872) (PR [#4150](https://github.com/vatesfr/xen-orchestra/pull/4150))

View File

@@ -1,32 +1,45 @@
> This file contains all changes that have not been released yet.
>
> Keep in mind the changelog is addressed to **users** and should be
> understandable by them.
### Breaking changes
- `xo-server` requires Node 8.
### Enhancements
- [Backup-ng/restore] Display size for full VM backup [#4009](https://github.com/vatesfr/xen-orchestra/issues/4009) (PR [#4245](https://github.com/vatesfr/xen-orchestra/pull/4245))
- [Sr/new] Ability to select NFS version when creating NFS storage [#3951](https://github.com/vatesfr/xen-orchestra/issues/3951) (PR [#4277](https://github.com/vatesfr/xen-orchestra/pull/4277))
- [auth-saml] Improve compatibility with Microsoft Azure Active Directory (PR [#4294](https://github.com/vatesfr/xen-orchestra/pull/4294))
- [Host] Display warning when "Citrix Hypervisor" license has restrictions [#4251](https://github.com/vatesfr/xen-orchestra/issues/4164) (PR [#4235](https://github.com/vatesfr/xen-orchestra/pull/4279))
- [VM/Backup] Create backup bulk action [#2573](https://github.com/vatesfr/xen-orchestra/issues/2573) (PR [#4257](https://github.com/vatesfr/xen-orchestra/pull/4257))
- [Sr/new] Ability to select NFS version when creating NFS storage [#3951](https://github.com/vatesfr/xen-orchestra/issues/#3951) (PR [#4277](https://github.com/vatesfr/xen-orchestra/pull/4277))
- [SR/new] Create ZFS storage [#4260](https://github.com/vatesfr/xen-orchestra/issues/4260) (PR [#4266](https://github.com/vatesfr/xen-orchestra/pull/4266))
- [Host] Display warning when host's time differs too much from XOA's time [#4113](https://github.com/vatesfr/xen-orchestra/issues/4113) (PR [#4173](https://github.com/vatesfr/xen-orchestra/pull/4173))
- [Host/storages, SR/hosts] Display PBD details [#4264](https://github.com/vatesfr/xen-orchestra/issues/4161) (PR [#4268](https://github.com/vatesfr/xen-orchestra/pull/4284))
- [VM/network] Display and set bandwidth rate-limit of a VIF [#4215](https://github.com/vatesfr/xen-orchestra/issues/4215) (PR [#4293](https://github.com/vatesfr/xen-orchestra/pull/4293))
- [SDN Controller] New plugin which enables creating pool-wide private networks [xcp-ng/xcp#175](https://github.com/xcp-ng/xcp/issues/175) (PR [#4269](https://github.com/vatesfr/xen-orchestra/pull/4269))
> Users must be able to say: “Nice enhancement, I'm eager to test it”
- [Hub] Ability to select SR in hub VM installation (PR [#4571](https://github.com/vatesfr/xen-orchestra/pull/4571))
- [Hub] Display more info about downloadable templates (PR [#4593](https://github.com/vatesfr/xen-orchestra/pull/4593))
- [Support] Ability to open and close support tunnel from the user interface [#4513](https://github.com/vatesfr/xen-orchestra/issues/4513) (PR [#4616](https://github.com/vatesfr/xen-orchestra/pull/4616))
- [xo-server-transport-icinga2] Add support of [icinga2](https://icinga.com/docs/icinga2/latest/doc/12-icinga2-api/) for reporting services status [#4563](https://github.com/vatesfr/xen-orchestra/issues/4563) (PR [#4573](https://github.com/vatesfr/xen-orchestra/pull/4573))
- [Hub] Ability to update existing template (PR [#4613](https://github.com/vatesfr/xen-orchestra/pull/4613))
- [Menu] Remove legacy backup entry [#4467](https://github.com/vatesfr/xen-orchestra/issues/4467) (PR [#4476](https://github.com/vatesfr/xen-orchestra/pull/4476))
- [Backup NG] Offline backup feature [#3449](https://github.com/vatesfr/xen-orchestra/issues/3449) (PR [#4470](https://github.com/vatesfr/xen-orchestra/pull/4470))
### Bug fixes
- [Metadata backup] Missing XAPIs should trigger a failure job [#4281](https://github.com/vatesfr/xen-orchestra/issues/4281) (PR [#4283](https://github.com/vatesfr/xen-orchestra/pull/4283))
- [Host/advanced] Fix host CPU hyperthreading detection [#4262](https://github.com/vatesfr/xen-orchestra/issues/4262) (PR [#4285](https://github.com/vatesfr/xen-orchestra/pull/4285))
- [iSCSI] Fix fibre channel paths display [#4291](https://github.com/vatesfr/xen-orchestra/issues/4291) (PR [#4303](https://github.com/vatesfr/xen-orchestra/pull/4303))
- [New VM] Fix tooltips not displayed on disabled elements in some browsers (e.g. Google Chrome) [#4304](https://github.com/vatesfr/xen-orchestra/issues/4304) (PR [#4309](https://github.com/vatesfr/xen-orchestra/pull/4309))
> Users must be able to say: “I had this issue, happy to know it's fixed”
- [SR] Fix `[object HTMLInputElement]` name after re-attaching a SR [#4546](https://github.com/vatesfr/xen-orchestra/issues/4546) (PR [#4550](https://github.com/vatesfr/xen-orchestra/pull/4550))
- [Schedules] Prevent double runs [#4625](https://github.com/vatesfr/xen-orchestra/issues/4625) (PR [#4626](https://github.com/vatesfr/xen-orchestra/pull/4626))
- [Schedules] Properly enable/disable on config import (PR [#4624](https://github.com/vatesfr/xen-orchestra/pull/4624))
### Released packages
- xo-server-auth-ldap v0.6.5
- xen-api v0.26.0
- xo-server-sdn-controller v0.1
- xo-server-auth-saml v0.6.0
- xo-server-backup-reports v0.16.2
- xo-server v5.44.0
- xo-web v5.44.0
> Packages will be released in the order they are here, therefore, they should
> be listed by inverse order of dependency.
>
> Rule of thumb: add packages on top.
- @xen-orchestra/cron v1.0.5
- xo-server-transport-icinga2 v0.1.0
- xo-server-sdn-controller v0.3.1
- xo-server v5.51.0
- xo-web v5.51.0
### Dropped packages
- xo-server-cloud : this package was useless for OpenSource installations because it required a complete XOA environment

View File

@@ -1,14 +1,19 @@
### Check list
> Check items when done or if not relevant
> Check if done.
>
> Strikethrough if not relevant: ~~example~~ ([doc](https://help.github.com/en/articles/basic-writing-and-formatting-syntax)).
- [ ] PR reference the relevant issue (e.g. `Fixes #007`)
- [ ] PR reference the relevant issue (e.g. `Fixes #007` or `See xoa-support#42`)
- [ ] if UI changes, a screenshot has been added to the PR
- [ ] `CHANGELOG.unreleased.md`:
- enhancement/bug fix entry added
- list of packages to release updated (`${name} v${new version}`)
- [ ] documentation updated
- [ ] **I have tested added/updated features** (and impacted code)
- `CHANGELOG.unreleased.md`:
- [ ] enhancement/bug fix entry added
- [ ] list of packages to release updated (`${name} v${new version}`)
- **I have tested added/updated features** (and impacted code)
- [ ] unit tests (e.g. [`cron/parse.spec.js`](https://github.com/vatesfr/xen-orchestra/blob/b24400b21de1ebafa1099c56bac1de5c988d9202/%40xen-orchestra/cron/src/parse.spec.js))
- [ ] if `xo-server` API changes, the corresponding test has been added to/updated on [`xo-server-test`](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-test)
- [ ] at least manual testing
### Process
@@ -16,3 +21,10 @@
1. mark it as `WiP:` (Work in Progress) if not ready to be merged
1. when you want a review, add a reviewer (and only one)
1. if necessary, update your PR, and re- add a reviewer
From [_the Four Agreements_](https://en.wikipedia.org/wiki/Don_Miguel_Ruiz#The_Four_Agreements):
1. Be impeccable with your word.
1. Don't take anything personally.
1. Don't make assumptions.
1. Always do your best.

View File

@@ -13,11 +13,11 @@ It aims to be easy to use on any device supporting modern web technologies (HTML
## XOA quick deploy
SSH to your XenServer, and execute the following:
Log in to your account and use the deploy form available on [this page](https://xen-orchestra.com/#!/xoa)
```
bash -c "$(curl -s http://xoa.io/deploy)"
```
> **Note:** no data will be sent to our servers, it's running only between your browser and your host!
[![](./assets/deploy_form.png)](https://xen-orchestra.com/#!/xoa)
### XOA credentials

View File

@@ -55,6 +55,7 @@
* [Emergency Shutdown](emergency_shutdown.md)
* [Auto scalability](auto_scalability.md)
* [Forecaster](forecaster.md)
* [SDN Controller](sdn_controller.md)
* [Recipes](recipes.md)
* [Reverse proxy](reverse_proxy.md)
* [How to contribute?](contributing.md)

BIN
docs/assets/deploy_form.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -15,5 +15,6 @@ We've made multiple categories to help you to find what you need:
* [Job Manager](scheduler.html)
* [Alerts](alerts.html)
* [Load balancing](load_balancing.html)
* [SDN Controller](sdn_controller.html)
![](./assets/xo5tablet.jpg)

View File

@@ -20,7 +20,7 @@ We'll consider at this point that you've got a working node on your box. E.g:
```
$ node -v
v8.12.0
v8.16.2
```
If not, see [this page](https://nodejs.org/en/download/package-manager/) for instructions on how to install Node.

View File

@@ -1,14 +1,10 @@
# Installation
SSH to your XenServer/XCP-ng host and execute the following:
Log in to your account and use the deploy form available on [this page](https://xen-orchestra.com/#!/xoa)
```
bash -c "$(curl -s http://xoa.io/deploy)"
```
![](./assets/deploy_form.png)
This will automatically download/import/start the XOA appliance. Nothing is changed on your host itself, it's 100% safe.
## [More on XOA](xoa.md)
## [More on XOA and alternate deploy](xoa.md)
![](https://xen-orchestra.com/assets/xoa1.png)

60
docs/sdn_controller.md Normal file
View File

@@ -0,0 +1,60 @@
# SDN Controller
> SDN Controller is available in XOA 5.44 and higher
The SDN Controller enables a user to **create pool-wide and cross-pool** (since XOA 5.48.1) **private networks**.
![](./assets/sdn-controller.png)
## How does it work?
Please read the [dedicated devblog on the SDN Controller](https://xen-orchestra.com/blog/xo-sdn-controller/) and its [extension for cross-pool private networks](https://xen-orchestra.com/blog/devblog-3-extending-the-sdn-controller/).
## Usage
### Network creation
In the network creation view:
- Select a `pool`
- Select `Private network`
- Select an interface on which to create the network's tunnels
- Select the encapsulation: a choice is offered between `GRE` and `VxLAN`, if `VxLAN` is chosen, then port 4789 must be open for UDP traffic on all the network's hosts (see [the requirements](#vxlan))
- Choose if the network should be encrypted or not (see [the requirements](#encryption) to use encryption)
- Select other `pool`s to add them to the network if desired
- For each added `pool`: select an interface on which to create the tunnels
- Create the network
- Have fun! ☺
***NB:***
- All hosts in a private network must be able to reach the other hosts' management interface.
> The term management interface is used to indicate the IP-enabled NIC that carries the management traffic.
- Only 1 encrypted GRE network and 1 encrypted VxLAN network per pool can exist at a time due to Open vSwitch limitation.
### Configuration
Like all other xo-server plugins, it can be configured directly via
the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
The plugin's configuration contains:
- `cert-dir`: The path where the plugin will look for the certificates to create SSL connections with the hosts.
If none is provided, the plugin will create its own self-signed certificates.
- `override-certs`: Enable to uninstall the existing SDN controller CA certificate in order to replace it with the plugin's one.
## Requirements
### VxLAN
- On XCP-ng prior to 7.6:
- To be able to use `VxLAN`, the following line needs to be added, if not already present, in `/etc/sysconfig/iptables` of all the hosts where `VxLAN` is wanted: `-A xapi-INPUT -p udp -m conntrack --ctstate NEW -m udp --dport 4789 -j ACCEPT`
### Encryption
> Encryption is not available prior to 8.0.
- On XCP-ng 8.0:
- To be able to encrypt the networks, `openvswitch-ipsec` package must be installed on all the hosts:
- `yum install openvswitch-ipsec --enablerepo=xcp-ng-testing`
- `systemctl enable ipsec`
- `systemctl enable openvswitch-ipsec`
- `systemctl start ipsec`
- `systemctl start openvswitch-ipsec`

View File

@@ -110,16 +110,17 @@ $ systemctl restart xo-server
### Behind a transparent proxy
If your are behind a transparent proxy, you'll probably have issues with the updater (SSL/TLS issues).
If you're behind a transparent proxy, you'll probably have issues with the updater (SSL/TLS issues).
First, run the following commands:
Run the following commands to allow the updater to work:
```
$ sudo -s
$ echo NODE_TLS_REJECT_UNAUTHORIZED=0 >> /etc/xo-appliance/env
$ npm config -g set strict-ssl=false
$ systemctl restart xoa-updater
```
Then, restart the updater with `systemctl restart xoa-updater`.
Now try running an update again.
### Updating SSL self-signed certificate

View File

@@ -41,6 +41,20 @@ However, if you want to start a manual check, you can do it by clicking on the "
![](./assets/xo5updatebutton.png)
#### Release channel
In Xen Orchestra, you can make a choice between two different release channels.
##### Stable
The stable channel is intended to be a version of Xen Orchestra that is already **one month old** (and therefore will benefit from one month of community feedback and various fixes). This way, users more concerned with the stability of their appliance will have the option to stay on a slightly older (and tested) version of XO (still supported by our pro support).
##### Latest
The latest channel will include all the latest improvements available in Xen Orchestra. The version available in latest has already been QA'd by our team, but issues may still occur once deployed in vastly varying environments, such as our user base has.
> To select the release channel of your choice, go to the XOA > Updates view.
![](./assets/release-channels.png)
#### Upgrade
If a new version is found, you'll have an upgrade button and its tooltip displayed:

View File

@@ -22,26 +22,36 @@ For use on huge infrastructure (more than 500+ VMs), feel free to increase the R
### The quickest way
The fastest way to install Xen Orchestra is to use our appliance deploy script. You can deploy it by connecting to your XenServer host and executing the following:
The **fastest and most secure way** to install Xen Orchestra is to use our web deploy page. Go on https://xen-orchestra.com/#!/xoa and follow instructions.
> **Note:** no data will be sent to our servers, it's running only between your browser and your host!
![](./assets/deploy_form.png)
### Via a bash script
Alternatively, you can deploy it by connecting to your XenServer host and executing the following:
```
bash -c "$(curl -s http://xoa.io/deploy)"
```
**Note:** This won't write or modify anything on your XenServer host: it will just import the XOA VM into your default storage repository.
Now follow the instructions:
> **Note:** This won't write or modify anything on your XenServer host: it will just import the XOA VM into your default storage repository.
Follow the instructions:
* Your IP configuration will be requested: it's set to **DHCP by default**, otherwise you can enter a fixed IP address (eg `192.168.0.10`)
* If DHCP is selected, the script will continue automatically. Otherwise a netmask, gateway, and DNS should be provided.
* XOA will be deployed on your default storage repository. You can move it elsewhere anytime after.
### The alternative
### Via download the XVA
Download XOA from xen-orchestra.com. Once you've got the XVA file, you can import it with `xe vm-import filename=xoa_unified.xva` or via XenCenter.
After the VM is imported, you just need to start it with `xe vm-start vm="XOA"` or with XenCenter.
## First Login
Once you have started the VM, you can access the web UI by putting the IP you configured during deployment into your web browser. If you did not configure an IP or are unsure, try one of the following methods to find it:
* Run `xe vm-list params=name-label,networks | grep -A 1 XOA` on your host

View File

@@ -6,24 +6,24 @@
"babel-eslint": "^10.0.1",
"babel-jest": "^24.1.0",
"benchmark": "^2.1.4",
"eslint": "^5.1.0",
"eslint-config-prettier": "^4.1.0",
"eslint-config-standard": "12.0.0",
"eslint-config-standard-jsx": "^6.0.2",
"eslint": "^6.0.1",
"eslint-config-prettier": "^6.0.0",
"eslint-config-standard": "14.1.0",
"eslint-config-standard-jsx": "^8.1.0",
"eslint-plugin-eslint-comments": "^3.1.1",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^9.0.1",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.0.0",
"eslint-plugin-react": "^7.6.1",
"eslint-plugin-standard": "^4.0.0",
"exec-promise": "^0.7.0",
"flow-bin": "^0.100.0",
"globby": "^9.0.0",
"husky": "^2.2.0",
"flow-bin": "^0.109.0",
"globby": "^10.0.0",
"husky": "^3.0.0",
"jest": "^24.1.0",
"lodash": "^4.17.4",
"prettier": "^1.10.2",
"promise-toolbox": "^0.12.1",
"promise-toolbox": "^0.14.0",
"sorted-object": "^2.0.1"
},
"engines": {
@@ -42,9 +42,11 @@
"testEnvironment": "node",
"testPathIgnorePatterns": [
"/dist/",
"/xo-server-test/",
"/xo-web/"
],
"testRegex": "\\.spec\\.js$",
"timers": "fake",
"transform": {
"\\.jsx?$": "babel-jest"
}

View File

@@ -35,8 +35,8 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.1",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -33,8 +33,8 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -27,8 +27,8 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/fs": "^0.9.0",
"cli-progress": "^2.0.0",
"@xen-orchestra/fs": "^0.10.1",
"cli-progress": "^3.1.0",
"exec-promise": "^0.7.0",
"getopts": "^2.2.3",
"struct-fu": "^1.2.0",
@@ -39,11 +39,11 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",
"cross-env": "^6.0.3",
"execa": "^2.0.2",
"index-modules": "^0.3.0",
"promise-toolbox": "^0.12.1",
"rimraf": "^2.6.1",
"promise-toolbox": "^0.14.0",
"rimraf": "^3.0.0",
"tmp": "^0.1.0"
},
"scripts": {

View File

@@ -21,12 +21,13 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/log": "^0.2.0",
"async-iterator-to-stream": "^1.0.2",
"core-js": "^3.0.0",
"from2": "^2.3.0",
"fs-extra": "^8.0.1",
"limit-concurrency-decorator": "^0.4.0",
"promise-toolbox": "^0.12.1",
"promise-toolbox": "^0.14.0",
"struct-fu": "^1.2.0",
"uuid": "^3.0.1"
},
@@ -35,15 +36,15 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"@xen-orchestra/fs": "^0.9.0",
"@xen-orchestra/fs": "^0.10.1",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",
"cross-env": "^6.0.3",
"execa": "^2.0.2",
"fs-promise": "^2.0.0",
"get-stream": "^5.1.0",
"index-modules": "^0.3.0",
"readable-stream": "^3.0.6",
"rimraf": "^2.6.2",
"rimraf": "^3.0.0",
"tmp": "^0.1.0"
},
"scripts": {

View File

@@ -1,4 +1,5 @@
import asyncIteratorToStream from 'async-iterator-to-stream'
import { createLogger } from '@xen-orchestra/log'
import resolveRelativeFromFile from './_resolveRelativeFromFile'
@@ -13,18 +14,23 @@ import {
import { fuFooter, fuHeader, checksumStruct } from './_structs'
import { test as mapTestBit } from './_bitmap'
export default async function createSyntheticStream(handler, path) {
const { warn } = createLogger('vhd-lib:createSyntheticStream')
export default async function createSyntheticStream(handler, paths) {
const fds = []
const cleanup = () => {
for (let i = 0, n = fds.length; i < n; ++i) {
handler.closeFile(fds[i]).catch(error => {
console.warn('createReadStream, closeFd', i, error)
warn('error while closing file', {
error,
fd: fds[i],
})
})
}
}
try {
const vhds = []
while (true) {
const open = async path => {
const fd = await handler.openFile(path, 'r')
fds.push(fd)
const vhd = new Vhd(handler, fd)
@@ -32,11 +38,18 @@ export default async function createSyntheticStream(handler, path) {
await vhd.readHeaderAndFooter()
await vhd.readBlockAllocationTable()
if (vhd.footer.diskType === DISK_TYPE_DYNAMIC) {
break
return vhd
}
if (typeof paths === 'string') {
let path = paths
let vhd
while ((vhd = await open(path)).footer.diskType !== DISK_TYPE_DYNAMIC) {
path = resolveRelativeFromFile(path, vhd.header.parentUnicodeName)
}
} else {
for (const path of paths) {
await open(path)
}
path = resolveRelativeFromFile(path, vhd.header.parentUnicodeName)
}
const nVhds = vhds.length

View File

@@ -1,4 +1,5 @@
import assert from 'assert'
import { createLogger } from '@xen-orchestra/log'
import checkFooter from './_checkFooter'
import checkHeader from './_checkHeader'
@@ -15,10 +16,7 @@ import {
SECTOR_SIZE,
} from './_constants'
const VHD_UTIL_DEBUG = 0
const debug = VHD_UTIL_DEBUG
? str => console.log(`[vhd-merge]${str}`)
: () => null
const { debug } = createLogger('vhd-lib:Vhd')
// ===================================================================
//
@@ -364,9 +362,7 @@ export default class Vhd {
const offset = blockAddr + this.sectorsOfBitmap + beginSectorId
debug(
`writeBlockSectors at ${offset} block=${
block.id
}, sectors=${beginSectorId}...${endSectorId}`
`writeBlockSectors at ${offset} block=${block.id}, sectors=${beginSectorId}...${endSectorId}`
)
for (let i = beginSectorId; i < endSectorId; ++i) {

View File

@@ -41,15 +41,15 @@
"human-format": "^0.10.0",
"lodash": "^4.17.4",
"pw": "^0.0.4",
"xen-api": "^0.26.0"
"xen-api": "^0.27.2"
},
"devDependencies": {
"@babel/cli": "^7.1.5",
"@babel/core": "^7.1.5",
"@babel/preset-env": "^7.1.5",
"babel-plugin-lodash": "^3.2.11",
"cross-env": "^5.1.4",
"rimraf": "^2.6.1"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -0,0 +1,189 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"event-loop-delay": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/event-loop-delay/-/event-loop-delay-1.0.0.tgz",
"integrity": "sha512-8YtyeIWHXrvTqlAhv+fmtaGGARmgStbvocERYzrZ3pwhnQULe5PuvMUTjIWw/emxssoaftfHZsJtkeY8xjiXCg==",
"requires": {
"napi-macros": "^1.8.2",
"node-gyp-build": "^3.7.0"
}
},
"getopts": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz",
"integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA=="
},
"golike-defer": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/golike-defer/-/golike-defer-0.4.1.tgz",
"integrity": "sha512-x8cq/Fvu32T8cnco3CBDRF+/M2LFmfSIysKfecX09uIK3cFdHcEKBTPlPnEO6lwrdxfjkOIU6dIw3EIlEJeS1A=="
},
"human-format": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/human-format/-/human-format-0.10.1.tgz",
"integrity": "sha512-UzCHToSw3HI9MxH9tYzMr1JbHJbgzr6o0hZCun7sruv59S1leps21bmgpBkkwEvQon5n/2OWKH1iU7BEko02cg=="
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"make-error": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"napi-macros": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-1.8.2.tgz",
"integrity": "sha512-Tr0DNY4RzTaBG2W2m3l7ZtFuJChTH6VZhXVhkGGjF/4cZTt+i8GcM9ozD+30Lmr4mDoZ5Xx34t2o4GJqYWDGcg=="
},
"node-gyp-build": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.9.0.tgz",
"integrity": "sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A=="
},
"prettier-bytes": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/prettier-bytes/-/prettier-bytes-1.0.4.tgz",
"integrity": "sha1-mUsCqkb2mcULYle1+qp/4lV+YtY="
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"process-top": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/process-top/-/process-top-1.0.0.tgz",
"integrity": "sha512-er8iSmBMslOt5cgIHg9m6zilTPsuUqpEb1yfQ4bDmO80zr/e/5hNn+Tay3CJM/FOBnJo8Bt3fFiDDH6GvIgeAg==",
"requires": {
"event-loop-delay": "^1.0.0",
"prettier-bytes": "^1.0.4"
}
},
"progress-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-2.0.0.tgz",
"integrity": "sha1-+sY6Cz0R3qy7CWmrzJOyFLzhntU=",
"requires": {
"speedometer": "~1.0.0",
"through2": "~2.0.3"
}
},
"promise-toolbox": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/promise-toolbox/-/promise-toolbox-0.13.0.tgz",
"integrity": "sha512-Z6u7EL9/QyY1zZqeqpEiKS7ygKwZyl0JL0ouno/en6vMliZZc4AmM0aFCrDAVxEyKqj2f3SpkW0lXEfAZsNWiQ==",
"requires": {
"make-error": "^1.3.2"
}
},
"readable-stream": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"speedometer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.0.0.tgz",
"integrity": "sha1-zWccsGdSwivKM3Di8zREC+T8YuI="
},
"stream-parser": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
"integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=",
"requires": {
"debug": "2"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
},
"throttle": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/throttle/-/throttle-1.0.3.tgz",
"integrity": "sha1-ijLkoV8XY9mXlIMXxevjrYpB5Lc=",
"requires": {
"readable-stream": ">= 0.3.0",
"stream-parser": ">= 0.0.2"
}
},
"through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
"requires": {
"readable-stream": "~2.3.6",
"xtend": "~4.0.1"
},
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
}
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
}
}
}

View File

@@ -5,7 +5,7 @@
"human-format": "^0.10.1",
"process-top": "^1.0.0",
"progress-stream": "^2.0.0",
"promise-toolbox": "^0.11.0",
"promise-toolbox": "^0.13.0",
"readable-stream": "^3.1.1",
"throttle": "^1.0.3"
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "xen-api",
"version": "0.26.0",
"version": "0.27.2",
"license": "ISC",
"description": "Connector to the Xen API",
"keywords": [
@@ -46,7 +46,7 @@
"make-error": "^1.3.0",
"minimist": "^1.2.0",
"ms": "^2.1.1",
"promise-toolbox": "^0.12.1",
"promise-toolbox": "^0.14.0",
"pw": "0.0.4",
"xmlrpc": "^1.3.2",
"xo-collection": "^0.4.1"
@@ -60,8 +60,8 @@
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -1,4 +1,4 @@
const URL_RE = /^(?:(https?:)\/*)?(?:([^:]+):([^@]+)@)?([^/]+?)(?::([0-9]+))?\/?$/
const URL_RE = /^(?:(https?:)\/*)?(?:([^:]+):([^@]+)@)?(?:\[([^\]]+)\]|([^:/]+))(?::([0-9]+))?\/?$/
export default url => {
const matches = URL_RE.exec(url)
@@ -6,7 +6,15 @@ export default url => {
throw new Error('invalid URL: ' + url)
}
const [, protocol = 'https:', username, password, hostname, port] = matches
const [
,
protocol = 'https:',
username,
password,
ipv6,
hostname = ipv6,
port,
] = matches
const parsedUrl = { protocol, hostname, port }
if (username !== undefined) {
parsedUrl.username = decodeURIComponent(username)

View File

@@ -8,7 +8,7 @@ import execPromise from 'exec-promise'
import minimist from 'minimist'
import pw from 'pw'
import { asCallback, fromCallback } from 'promise-toolbox'
import { filter, find, isArray } from 'lodash'
import { filter, find } from 'lodash'
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
import { start as createRepl } from 'repl'
@@ -110,7 +110,7 @@ const main = async args => {
asCallback.call(
fromCallback(cb => {
evaluate.call(repl, cmd, context, filename, cb)
}).then(value => (isArray(value) ? Promise.all(value) : value)),
}).then(value => (Array.isArray(value) ? Promise.all(value) : value)),
cb
)
})(repl.eval)

View File

@@ -4,7 +4,7 @@ import kindOf from 'kindof'
import ms from 'ms'
import httpRequest from 'http-request-plus'
import { EventEmitter } from 'events'
import { isArray, map, noop, omit } from 'lodash'
import { map, noop, omit } from 'lodash'
import {
cancelable,
defer,
@@ -99,6 +99,9 @@ export class Xapi extends EventEmitter {
this._sessionId = undefined
this._status = DISCONNECTED
this._watchEventsError = undefined
this._lastEventFetchedTimestamp = undefined
this._debounce = opts.debounce ?? 200
this._objects = new Collection()
this._objectsByRef = { __proto__: null }
@@ -110,7 +113,7 @@ export class Xapi extends EventEmitter {
this._watchedTypes = undefined
const { watchEvents } = opts
if (watchEvents !== false) {
if (isArray(watchEvents)) {
if (Array.isArray(watchEvents)) {
this._watchedTypes = watchEvents
}
this.watchEvents()
@@ -479,6 +482,14 @@ export class Xapi extends EventEmitter {
return this._objectsFetched
}
get lastEventFetchedTimestamp() {
return this._lastEventFetchedTimestamp
}
get watchEventsError() {
return this._watchEventsError
}
// ensure we have received all events up to this call
//
// optionally returns the up to date object for the given ref
@@ -954,6 +965,8 @@ export class Xapi extends EventEmitter {
],
EVENT_TIMEOUT * 1e3 * 1.1
)
this._lastEventFetchedTimestamp = Date.now()
this._watchEventsError = undefined
} catch (error) {
const code = error?.code
if (code === 'EVENTS_LOST' || code === 'SESSION_INVALID') {
@@ -961,6 +974,7 @@ export class Xapi extends EventEmitter {
continue mainLoop
}
this._watchEventsError = error
console.warn('_watchEvents', error)
await pDelay(this._eventPollDelay)
continue
@@ -1061,7 +1075,7 @@ export class Xapi extends EventEmitter {
const $field = (field in RESERVED_FIELDS ? '$$' : '$') + field
const value = data[field]
if (isArray(value)) {
if (Array.isArray(value)) {
if (value.length === 0 || isOpaqueRef(value[0])) {
getters[$field] = function() {
const value = this[field]

View File

@@ -38,16 +38,16 @@
"human-format": "^0.10.0",
"l33teral": "^3.0.3",
"lodash": "^4.17.4",
"micromatch": "^3.1.3",
"micromatch": "^4.0.2",
"mkdirp": "^0.5.1",
"nice-pipe": "0.0.0",
"pretty-ms": "^4.0.0",
"pretty-ms": "^5.0.0",
"progress-stream": "^2.0.0",
"promise-toolbox": "^0.12.1",
"promise-toolbox": "^0.14.0",
"pump": "^3.0.0",
"pw": "^0.0.4",
"strip-indent": "^2.0.0",
"xdg-basedir": "^3.0.0",
"strip-indent": "^3.0.0",
"xdg-basedir": "^4.0.0",
"xo-lib": "^0.9.0"
},
"devDependencies": {
@@ -56,8 +56,8 @@
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -7,7 +7,6 @@ const promisify = require('bluebird').promisify
const readFile = promisify(require('fs').readFile)
const writeFile = promisify(require('fs').writeFile)
const assign = require('lodash/assign')
const l33t = require('l33teral')
const mkdirp = promisify(require('mkdirp'))
const xdgBasedir = require('xdg-basedir')
@@ -41,7 +40,7 @@ const save = (exports.save = function(config) {
exports.set = function(data) {
return load().then(function(config) {
return save(assign(config, data))
return save(Object.assign(config, data))
})
}

View File

@@ -17,14 +17,12 @@ const getKeys = require('lodash/keys')
const hrp = require('http-request-plus').default
const humanFormat = require('human-format')
const identity = require('lodash/identity')
const isArray = require('lodash/isArray')
const isObject = require('lodash/isObject')
const micromatch = require('micromatch')
const nicePipe = require('nice-pipe')
const pairs = require('lodash/toPairs')
const pick = require('lodash/pick')
const pump = require('pump')
const startsWith = require('lodash/startsWith')
const prettyMs = require('pretty-ms')
const progressStream = require('progress-stream')
const pw = require('pw')
@@ -81,7 +79,7 @@ function parseParameters(args) {
const name = matches[1]
let value = matches[2]
if (startsWith(value, 'json:')) {
if (value.startsWith('json:')) {
value = JSON.parse(value.slice(5))
}
@@ -200,7 +198,18 @@ function main(args) {
return exports[fnName](args.slice(1))
}
return exports.call(args)
return exports.call(args).catch(error => {
if (!(error != null && error.code === 10 && 'errors' in error.data)) {
throw error
}
const lines = [error.message]
const { errors } = error.data
errors.forEach(error => {
lines.push(` property ${error.property}: ${error.message}`)
})
throw lines.join('\n')
})
}
exports = module.exports = main
@@ -288,7 +297,11 @@ async function listCommands(args) {
str.push(
name,
'=<',
type == null ? 'unknown type' : isArray(type) ? type.join('|') : type,
type == null
? 'unknown type'
: Array.isArray(type)
? type.join('|')
: type,
'>'
)

View File

@@ -34,9 +34,9 @@
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"cross-env": "^5.1.3",
"cross-env": "^6.0.3",
"event-to-promise": "^0.8.0",
"rimraf": "^2.6.1"
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -36,8 +36,8 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -1,5 +1,5 @@
import { BaseError } from 'make-error'
import { isArray, iteratee } from 'lodash'
import { iteratee } from 'lodash'
class XoError extends BaseError {
constructor({ code, message, data }) {
@@ -77,7 +77,7 @@ export const serverUnreachable = create(9, objectId => ({
}))
export const invalidParameters = create(10, (message, errors) => {
if (isArray(message)) {
if (Array.isArray(message)) {
errors = message
message = undefined
}

View File

@@ -41,8 +41,8 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -1,6 +1,5 @@
import JsonRpcWebSocketClient, { OPEN, CLOSED } from 'jsonrpc-websocket-client'
import { BaseError } from 'make-error'
import { startsWith } from 'lodash'
// ===================================================================
@@ -35,7 +34,7 @@ export default class Xo extends JsonRpcWebSocketClient {
}
call(method, args, i) {
if (startsWith(method, 'session.')) {
if (method.startsWith('session.')) {
return Promise.reject(
new XoError('session.*() methods are disabled from this interface')
)

View File

@@ -32,9 +32,9 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"cross-env": "^6.0.3",
"deep-freeze": "^0.0.1",
"rimraf": "^2.6.1"
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -40,8 +40,8 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -36,18 +36,18 @@
"dependencies": {
"event-to-promise": "^0.8.0",
"exec-promise": "^0.7.0",
"inquirer": "^6.0.0",
"inquirer": "^7.0.0",
"ldapjs": "^1.0.1",
"lodash": "^4.17.4",
"promise-toolbox": "^0.12.1"
"promise-toolbox": "^0.14.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -230,9 +230,7 @@ class AuthLdap {
logger(`attempting to bind as ${entry.objectName}`)
await bind(entry.objectName, password)
logger(
`successfully bound as ${
entry.objectName
} => ${username} authenticated`
`successfully bound as ${entry.objectName} => ${username} authenticated`
)
logger(JSON.stringify(entry, null, 2))
return { username }

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server-auth-saml",
"version": "0.5.3",
"version": "0.6.0",
"license": "AGPL-3.0",
"description": "SAML authentication plugin for XO-Server",
"keywords": [
@@ -40,8 +40,8 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-preset-env": "^1.6.1",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server-backup-reports",
"version": "0.16.1",
"version": "0.16.2",
"license": "AGPL-3.0",
"description": "Backup reports plugin for XO-Server",
"keywords": [
@@ -36,7 +36,7 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/log": "^0.1.4",
"@xen-orchestra/log": "^0.2.0",
"human-format": "^0.10.0",
"lodash": "^4.13.1",
"moment-timezone": "^0.5.13"
@@ -48,8 +48,8 @@
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -354,12 +354,10 @@ class BackupReportsXoPlugin {
log.jobName
} ${STATUS_ICON[log.status]}`,
markdown: toMarkdown(markdown),
nagiosStatus: log.status === 'success' ? 0 : 2,
success: log.status === 'success',
nagiosMarkdown:
log.status === 'success'
? `[Xen Orchestra] [Success] Metadata backup report for ${
log.jobName
}`
? `[Xen Orchestra] [Success] Metadata backup report for ${log.jobName}`
: `[Xen Orchestra] [${log.status}] Metadata backup report for ${
log.jobName
} - ${nagiosText.join(' ')}`,
@@ -392,10 +390,8 @@ class BackupReportsXoPlugin {
log.status
} Backup report for ${jobName} ${STATUS_ICON[log.status]}`,
markdown: toMarkdown(markdown),
nagiosStatus: 2,
nagiosMarkdown: `[Xen Orchestra] [${
log.status
}] Backup report for ${jobName} - Error : ${log.result.message}`,
success: false,
nagiosMarkdown: `[Xen Orchestra] [${log.status}] Backup report for ${jobName} - Error : ${log.result.message}`,
})
}
@@ -650,7 +646,7 @@ class BackupReportsXoPlugin {
subject: `[Xen Orchestra] ${log.status} Backup report for ${jobName} ${
STATUS_ICON[log.status]
}`,
nagiosStatus: log.status === 'success' ? 0 : 2,
success: log.status === 'success',
nagiosMarkdown:
log.status === 'success'
? `[Xen Orchestra] [Success] Backup report for ${jobName}`
@@ -660,7 +656,7 @@ class BackupReportsXoPlugin {
})
}
_sendReport({ markdown, subject, nagiosStatus, nagiosMarkdown }) {
_sendReport({ markdown, subject, success, nagiosMarkdown }) {
const xo = this._xo
return Promise.all([
xo.sendEmail !== undefined &&
@@ -680,9 +676,14 @@ class BackupReportsXoPlugin {
}),
xo.sendPassiveCheck !== undefined &&
xo.sendPassiveCheck({
status: nagiosStatus,
status: success ? 0 : 2,
message: nagiosMarkdown,
}),
xo.sendIcinga2Status !== undefined &&
xo.sendIcinga2Status({
status: success ? 'OK' : 'CRITICAL',
message: markdown,
}),
])
}
@@ -712,10 +713,8 @@ class BackupReportsXoPlugin {
return this._sendReport({
subject: `[Xen Orchestra] ${globalStatus} ${icon}`,
markdown,
nagiosStatus: 2,
nagiosMarkdown: `[Xen Orchestra] [${globalStatus}] Error : ${
error.message
}`,
success: false,
nagiosMarkdown: `[Xen Orchestra] [${globalStatus}] Error : ${error.message}`,
})
}
@@ -910,7 +909,7 @@ class BackupReportsXoPlugin {
? ICON_FAILURE
: ICON_SKIPPED
}`,
nagiosStatus: globalSuccess ? 0 : 2,
success: globalSuccess,
nagiosMarkdown: globalSuccess
? `[Xen Orchestra] [Success] Backup report for ${tag}`
: `[Xen Orchestra] [${

View File

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

View File

@@ -1,158 +0,0 @@
import Client, { createBackoff } from 'jsonrpc-websocket-client'
import hrp from 'http-request-plus'
const WS_URL = 'ws://localhost:9001'
const HTTP_URL = 'http://localhost:9002'
// ===================================================================
class XoServerCloud {
constructor({ xo }) {
this._xo = xo
// Defined in configure().
this._conf = null
this._key = null
}
configure(configuration) {
this._conf = configuration
}
async load() {
const getResourceCatalog = () => this._getCatalog()
getResourceCatalog.description = 'Get the list of all available resources'
getResourceCatalog.permission = 'admin'
const registerResource = ({ namespace }) =>
this._registerResource(namespace)
registerResource.description = 'Register a resource via cloud plugin'
registerResource.params = {
namespace: {
type: 'string',
},
}
registerResource.permission = 'admin'
this._unsetApiMethods = this._xo.addApiMethods({
cloud: {
getResourceCatalog,
registerResource,
},
})
this._unsetRequestResource = this._xo.defineProperty(
'requestResource',
this._requestResource,
this
)
const updater = (this._updater = new Client(WS_URL))
const connect = () =>
updater.open(createBackoff()).catch(error => {
console.error('xo-server-cloud: fail to connect to updater', error)
return connect()
})
updater.on('closed', connect).on('scheduledAttempt', ({ delay }) => {
console.warn('xo-server-cloud: next attempt in %s ms', delay)
})
connect()
}
unload() {
this._unsetApiMethods()
this._unsetRequestResource()
}
// ----------------------------------------------------------------
async _getCatalog() {
const catalog = await this._updater.call('getResourceCatalog')
if (!catalog) {
throw new Error('cannot get catalog')
}
return catalog
}
// ----------------------------------------------------------------
async _getNamespaces() {
const catalog = await this._getCatalog()
if (!catalog._namespaces) {
throw new Error('cannot get namespaces')
}
return catalog._namespaces
}
// ----------------------------------------------------------------
async _registerResource(namespace) {
const _namespace = (await this._getNamespaces())[namespace]
if (_namespace === undefined) {
throw new Error(`${namespace} is not available`)
}
if (_namespace.registered || _namespace.pending) {
throw new Error(`already registered for ${namespace}`)
}
return this._updater.call('registerResource', { namespace })
}
// ----------------------------------------------------------------
async _getNamespaceCatalog(namespace) {
const namespaceCatalog = (await this._getCatalog())[namespace]
if (!namespaceCatalog) {
throw new Error(`cannot get catalog: ${namespace} not registered`)
}
return namespaceCatalog
}
// ----------------------------------------------------------------
async _requestResource(namespace, id, version) {
const _namespace = (await this._getNamespaces())[namespace]
if (!_namespace || !_namespace.registered) {
throw new Error(`cannot get resource: ${namespace} not registered`)
}
const { _token: token } = await this._getNamespaceCatalog(namespace)
// 2018-03-20 Extra check: getResourceDownloadToken seems to be called without a token in some cases
if (token === undefined) {
throw new Error(`${namespace} namespace token is undefined`)
}
const downloadToken = await this._updater.call('getResourceDownloadToken', {
token,
id,
version,
})
if (!downloadToken) {
throw new Error('cannot get download token')
}
const response = await hrp(HTTP_URL, {
headers: {
Authorization: `Bearer ${downloadToken}`,
},
})
// currently needed for XenApi#putResource()
response.length = response.headers['content-length']
return response
}
}
export default opts => new XoServerCloud(opts)

View File

@@ -31,7 +31,7 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/cron": "^1.0.5",
"lodash": "^4.16.2"
},
"devDependencies": {

View File

@@ -189,9 +189,7 @@ export default class DensityPlan extends Plan {
const { vm, destination } = move
const xapiDest = this.xo.getXapi(destination)
debug(
`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${
vm.$container
}).`
`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${vm.$container}).`
)
return xapiDest.migrateVm(
vm._xapiId,

View File

@@ -126,9 +126,7 @@ export default class PerformancePlan extends Plan {
destinationAverages.memoryFree -= vmAverages.memory
debug(
`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${
exceededHost.id
}).`
`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${exceededHost.id}).`
)
optimizationsCount++
@@ -143,9 +141,7 @@ export default class PerformancePlan extends Plan {
await Promise.all(promises)
debug(
`Performance mode: ${optimizationsCount} optimizations for Host (${
exceededHost.id
}).`
`Performance mode: ${optimizationsCount} optimizations for Host (${exceededHost.id}).`
)
}
}

View File

@@ -21,7 +21,7 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/cron": "^1.0.5",
"d3-time-format": "^2.1.1",
"json5": "^2.0.1",
"lodash": "^4.17.4"
@@ -32,8 +32,8 @@
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",

View File

@@ -1,6 +1,6 @@
import JSON5 from 'json5'
import { createSchedule } from '@xen-orchestra/cron'
import { assign, forOwn, map, mean } from 'lodash'
import { forOwn, map, mean } from 'lodash'
import { utcParse } from 'd3-time-format'
const COMPARATOR_FN = {
@@ -183,9 +183,7 @@ export const configurationSchema = {
description: Object.keys(HOST_FUNCTIONS)
.map(
k =>
` * ${k} (${HOST_FUNCTIONS[k].unit}): ${
HOST_FUNCTIONS[k].description
}`
` * ${k} (${HOST_FUNCTIONS[k].unit}): ${HOST_FUNCTIONS[k].description}`
)
.join('\n'),
type: 'string',
@@ -233,9 +231,7 @@ export const configurationSchema = {
description: Object.keys(VM_FUNCTIONS)
.map(
k =>
` * ${k} (${VM_FUNCTIONS[k].unit}): ${
VM_FUNCTIONS[k].description
}`
` * ${k} (${VM_FUNCTIONS[k].unit}): ${VM_FUNCTIONS[k].description}`
)
.join('\n'),
type: 'string',
@@ -284,9 +280,7 @@ export const configurationSchema = {
description: Object.keys(SR_FUNCTIONS)
.map(
k =>
` * ${k} (${SR_FUNCTIONS[k].unit}): ${
SR_FUNCTIONS[k].description
}`
` * ${k} (${SR_FUNCTIONS[k].unit}): ${SR_FUNCTIONS[k].description}`
)
.join('\n'),
type: 'string',
@@ -414,9 +408,7 @@ ${monitorBodies.join('\n')}`
}
_parseDefinition(definition) {
const alarmId = `${definition.objectType}|${definition.variableName}|${
definition.alarmTriggerLevel
}`
const alarmId = `${definition.objectType}|${definition.variableName}|${definition.alarmTriggerLevel}`
const typeFunction =
TYPE_FUNCTION_MAP[definition.objectType][definition.variableName]
const parseData = (result, uuid) => {
@@ -468,9 +460,7 @@ ${monitorBodies.join('\n')}`
...definition,
alarmId,
vmFunction: typeFunction,
title: `${typeFunction.name} ${definition.comparator} ${
definition.alarmTriggerLevel
}${typeFunction.unit}`,
title: `${typeFunction.name} ${definition.comparator} ${definition.alarmTriggerLevel}${typeFunction.unit}`,
snapshot: async () => {
return Promise.all(
map(definition.uuids, async uuid => {
@@ -493,7 +483,7 @@ ${monitorBodies.join('\n')}`
result.rrd = await this.getRrd(result.object, observationPeriod)
if (result.rrd !== null) {
const data = parseData(result.rrd, result.object.uuid)
assign(result, {
Object.assign(result, {
data,
value: data.getDisplayableValue(),
shouldAlarm: data.shouldAlarm(),
@@ -506,7 +496,7 @@ ${monitorBodies.join('\n')}`
definition.alarmTriggerLevel
)
const data = getter(result.object)
assign(result, {
Object.assign(result, {
value: data.getDisplayableValue(),
shouldAlarm: data.shouldAlarm(),
})
@@ -664,9 +654,7 @@ ${entry.listItem}
subject: `[Xen Orchestra] Performance Alert ${subjectSuffix}`,
markdown:
markdownBody +
`\n\n\nSent from Xen Orchestra [perf-alert plugin](${
this._configuration.baseUrl
}#/settings/plugins)\n`,
`\n\n\nSent from Xen Orchestra [perf-alert plugin](${this._configuration.baseUrl}#/settings/plugins)\n`,
})
} else {
throw new Error('The email alert system has a configuration issue.')
@@ -692,7 +680,7 @@ ${entry.listItem}
},
}
if (xapiObject.$type === 'VM') {
payload['vm_uuid'] = xapiObject.uuid
payload.vm_uuid = xapiObject.uuid
}
// JSON is not well formed, can't use the default node parser
return JSON5.parse(

View File

@@ -1,31 +1,14 @@
# xo-server-sdn-controller [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
XO Server plugin that allows the creation of pool-wide private networks.
XO Server plugin that allows the creation of pool-wide and cross-pool private networks.
## Install
For installing XO and the plugins from the sources, please take a look at [the documentation](https://xen-orchestra.com/docs/from_the_sources.html).
## Usage
## Documentation
### Network creation
In the network creation view, select a `pool` and `Private network`.
Create the network.
Choice is offer between `GRE` and `VxLAN`, if `VxLAN` is chosen, then the port 4789 must be open for UDP traffic.
The following line needs to be added, if not already present, in `/etc/sysconfig/iptables` of all the hosts where `VxLAN` is wanted:
`-A xapi-INPUT -p udp -m conntrack --ctstate NEW -m udp --dport 4789 -j ACCEPT`
### Configuration
Like all other xo-server plugins, it can be configured directly via
the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
The plugin's configuration contains:
- `cert-dir`: A path where to find the certificates to create SSL connections with the hosts.
If none is provided, the plugin will create its own self-signed certificates.
- `override-certs:` Whether or not to uninstall an already existing SDN controller CA certificate in order to replace it by the plugin's one.
Please see the plugin's [official documentation](https://xen-orchestra.com/docs/sdn_controller.html).
## Contributions

View File

@@ -15,21 +15,24 @@
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
},
"version": "0.0.0",
"version": "0.3.0",
"engines": {
"node": ">=6"
},
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/preset-env": "^7.4.4",
"cross-env": "^5.2.0"
"cross-env": "^6.0.3"
},
"dependencies": {
"@xen-orchestra/log": "^0.1.4",
"@xen-orchestra/log": "^0.2.0",
"lodash": "^4.17.11",
"node-openssl-cert": "^0.0.81",
"promise-toolbox": "^0.13.0"
"node-openssl-cert": "^0.0.98",
"promise-toolbox": "^0.14.0",
"uuid": "^3.3.2"
},
"private": true
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,202 @@
import createLogger from '@xen-orchestra/log'
import { filter, find, forOwn, map, sample } from 'lodash'
// =============================================================================
const log = createLogger('xo:xo-server:sdn-controller:private-network')
// =============================================================================
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!'
const createPassword = () =>
Array.from({ length: 16 }, _ => sample(CHARS)).join('')
// =============================================================================
export class PrivateNetwork {
constructor(controller, uuid) {
this.controller = controller
this.uuid = uuid
this.networks = {}
}
// ---------------------------------------------------------------------------
async addHost(host) {
if (host.$ref === this.center?.$ref) {
// Nothing to do
return
}
const hostClient = this.controller.ovsdbClients[host.$ref]
if (hostClient === undefined) {
log.error('No OVSDB client found', {
host: host.name_label,
pool: host.$pool.name_label,
})
return
}
const centerClient = this.controller.ovsdbClients[this.center.$ref]
if (centerClient === undefined) {
log.error('No OVSDB client found for star-center', {
privateNetwork: this.uuid,
host: this.center.name_label,
pool: this.center.$pool.name_label,
})
return
}
const network = this.networks[host.$pool.uuid]
const centerNetwork = this.networks[this.center.$pool.uuid]
const otherConfig = network.other_config
const encapsulation =
otherConfig['xo:sdn-controller:encapsulation'] ?? 'gre'
const vni = otherConfig['xo:sdn-controller:vni'] ?? '0'
const password =
otherConfig['xo:sdn-controller:encrypted'] === 'true'
? createPassword()
: undefined
let bridgeName
try {
;[bridgeName] = await Promise.all([
hostClient.addInterfaceAndPort(
network,
centerClient.host.address,
encapsulation,
vni,
password,
this.uuid
),
centerClient.addInterfaceAndPort(
centerNetwork,
hostClient.host.address,
encapsulation,
vni,
password,
this.uuid
),
])
} catch (error) {
log.error('Error while connecting host to private network', {
error,
privateNetwork: this.uuid,
network: network.name_label,
host: host.name_label,
pool: host.$pool.name_label,
})
return
}
log.info('Host added', {
privateNetwork: this.uuid,
network: network.name_label,
host: host.name_label,
pool: host.$pool.name_label,
})
return bridgeName
}
addNetwork(network) {
this.networks[network.$pool.uuid] = network
log.info('Adding network', {
privateNetwork: this.uuid,
network: network.name_label,
pool: network.$pool.name_label,
})
if (this.center === undefined) {
return this.electNewCenter()
}
const hosts = filter(network.$pool.$xapi.objects.all, { $type: 'host' })
return Promise.all(
map(hosts, async host => {
const hostClient = this.controller.ovsdbClients[host.$ref]
const network = this.networks[host.$pool.uuid]
await hostClient.resetForNetwork(network, this.uuid)
await this.addHost(host)
})
)
}
async electNewCenter() {
delete this.center
// TODO: make it random
const hosts = this._getHosts()
for (const host of hosts) {
const pif = find(host.$PIFs, {
network: this.networks[host.$pool.uuid].$ref,
})
if (pif?.currently_attached && host.$metrics.live) {
this.center = host
break
}
}
if (this.center === undefined) {
log.error('No available host to elect new star-center', {
privateNetwork: this.uuid,
})
return
}
await this._reset()
// Recreate star topology
await Promise.all(map(hosts, host => this.addHost(host)))
log.info('New star-center elected', {
center: this.center.name_label,
privateNetwork: this.uuid,
})
}
// ---------------------------------------------------------------------------
getPools() {
const pools = []
forOwn(this.networks, network => {
pools.push(network.$pool)
})
return pools
}
// ---------------------------------------------------------------------------
_reset() {
return Promise.all(
map(this._getHosts(), async host => {
// Clean old ports and interfaces
const hostClient = this.controller.ovsdbClients[host.$ref]
if (hostClient === undefined) {
return
}
const network = this.networks[host.$pool.uuid]
try {
await hostClient.resetForNetwork(network, this.uuid)
} catch (error) {
log.error('Error while resetting private network', {
error,
privateNetwork: this.uuid,
network: network.name_label,
host: host.name_label,
pool: network.$pool.name_label,
})
}
})
)
}
// ---------------------------------------------------------------------------
_getHosts() {
const hosts = []
forOwn(this.networks, network => {
hosts.push(...filter(network.$pool.$xapi.objects.all, { $type: 'host' }))
})
return hosts
}
}

View File

@@ -1,8 +1,8 @@
import assert from 'assert'
import createLogger from '@xen-orchestra/log'
import forOwn from 'lodash/forOwn'
import fromEvent from 'promise-toolbox/fromEvent'
import { connect } from 'tls'
import { forOwn, toPairs } from 'lodash'
const log = createLogger('xo:xo-server:sdn-controller:ovsdb-client')
@@ -10,102 +10,139 @@ const OVSDB_PORT = 6640
// =============================================================================
function toMap(object) {
return ['map', toPairs(object)]
}
// =============================================================================
export class OvsdbClient {
/*
Create an SSL connection to an XCP-ng host.
Interact with the host's OpenVSwitch (OVS) daemon to create and manage the virtual bridges
corresponding to the private networks with OVSDB (OpenVSwitch DataBase) Protocol.
See:
- OVSDB Protocol: https://tools.ietf.org/html/rfc7047
- OVS Tunneling : http://docs.openvswitch.org/en/latest/howto/tunneling/
- OVS IPSEC : http://docs.openvswitch.org/en/latest/howto/ipsec/
Attributes on created OVS ports (corresponds to a XAPI `PIF` or `VIF`):
- `other_config`:
- `xo:sdn-controller:private-network-uuid`: UUID of the private network
Attributes on created OVS interfaces:
- `options`:
- `key` : Network's VNI
- `remote_ip`: Remote IP of the tunnel
*/
constructor(host, clientKey, clientCert, caCert) {
this._host = host
this._numberOfPortAndInterface = 0
this._requestID = 0
this._requestId = 0
this._adding = []
this.host = host
this.updateCertificates(clientKey, clientCert, caCert)
log.debug(`[${this._host.name_label}] New OVSDB client`)
log.debug('New OVSDB client', {
host: this.host.name_label,
})
}
// ---------------------------------------------------------------------------
get address() {
return this._host.address
}
get host() {
return this._host.$ref
}
get id() {
return this._host.$id
}
updateCertificates(clientKey, clientCert, caCert) {
this._clientKey = clientKey
this._clientCert = clientCert
this._caCert = caCert
log.debug(`[${this._host.name_label}] Certificates have been updated`)
log.debug('Certificates have been updated', {
host: this.host.name_label,
})
}
// ---------------------------------------------------------------------------
async addInterfaceAndPort(
networkUuid,
networkName,
network,
remoteAddress,
encapsulation
encapsulation,
key,
password,
privateNetworkUuid
) {
const socket = await this._connect()
const index = this._numberOfPortAndInterface
++this._numberOfPortAndInterface
if (
this._adding.find(
elem => elem.id === network.uuid && elem.addr === remoteAddress
) !== undefined
) {
return
}
const adding = { id: network.uuid, addr: remoteAddress }
this._adding.push(adding)
const [bridgeUuid, bridgeName] = await this._getBridgeUuidForNetwork(
networkUuid,
networkName,
socket
)
if (bridgeUuid == null) {
const socket = await this._connect()
const bridge = await this._getBridgeForNetwork(network, socket)
if (bridge.uuid === undefined) {
socket.destroy()
this._adding = this._adding.filter(
elem => elem.id !== network.uuid || elem.addr !== remoteAddress
)
return
}
const alreadyExist = await this._interfaceAndPortAlreadyExist(
bridgeUuid,
bridgeName,
bridge,
remoteAddress,
socket
)
if (alreadyExist) {
socket.destroy()
return
this._adding = this._adding.filter(
elem => elem.id !== network.uuid || elem.addr !== remoteAddress
)
return bridge.name
}
const interfaceName = 'tunnel_iface' + index
const portName = 'tunnel_port' + index
const index = ++this._numberOfPortAndInterface
const interfaceName = bridge.name + '_iface' + index
const portName = bridge.name + '_port' + index
// Add interface and port to the bridge
const options = ['map', [['remote_ip', remoteAddress]]]
const options = { remote_ip: remoteAddress, key: key }
if (password !== undefined) {
options.psk = password
}
const addInterfaceOperation = {
op: 'insert',
table: 'Interface',
row: {
type: encapsulation,
options: options,
options: toMap(options),
name: interfaceName,
other_config: ['map', [['private_pool_wide', 'true']]],
},
'uuid-name': 'new_iface',
}
const addPortOperation = {
op: 'insert',
table: 'Port',
row: {
name: portName,
interfaces: ['set', [['named-uuid', 'new_iface']]],
other_config: ['map', [['private_pool_wide', 'true']]],
other_config: toMap({
'xo:sdn-controller:private-network-uuid': privateNetworkUuid,
}),
},
'uuid-name': 'new_port',
}
const mutateBridgeOperation = {
op: 'mutate',
table: 'Bridge',
where: [['_uuid', '==', ['uuid', bridgeUuid]]],
where: [['_uuid', '==', ['uuid', bridge.uuid]]],
mutations: [['ports', 'insert', ['set', [['named-uuid', 'new_port']]]]],
}
const params = [
@@ -115,7 +152,11 @@ export class OvsdbClient {
mutateBridgeOperation,
]
const jsonObjects = await this._sendOvsdbTransaction(params, socket)
if (jsonObjects == null) {
this._adding = this._adding.filter(
elem => elem.id !== network.uuid || elem.addr !== remoteAddress
)
if (jsonObjects === undefined) {
socket.destroy()
return
}
@@ -126,52 +167,55 @@ export class OvsdbClient {
let opResult
do {
opResult = jsonObjects[0].result[i]
if (opResult != null && opResult.error != null) {
if (opResult?.error !== undefined) {
error = opResult.error
details = opResult.details
}
++i
} while (opResult && !error)
} while (opResult !== undefined && error === undefined)
if (error != null) {
log.error(
`[${
this._host.name_label
}] Error while adding port: '${portName}' and interface: '${interfaceName}' to bridge: '${bridgeName}' on network: '${networkName}' because: ${error}: ${details}`
)
if (error !== undefined) {
log.error('Error while adding port and interface to bridge', {
error,
details,
port: portName,
interface: interfaceName,
bridge: bridge.name,
network: network.name_label,
host: this.host.name_label,
})
socket.destroy()
return
}
log.debug(
`[${
this._host.name_label
}] Port: '${portName}' and interface: '${interfaceName}' added to bridge: '${bridgeName}' on network: '${networkName}'`
)
log.debug('Port and interface added to bridge', {
port: portName,
interface: interfaceName,
bridge: bridge.name,
network: network.name_label,
host: this.host.name_label,
})
socket.destroy()
return bridge.name
}
async resetForNetwork(networkUuid, networkName) {
async resetForNetwork(network, privateNetworkUuid) {
const socket = await this._connect()
const [bridgeUuid, bridgeName] = await this._getBridgeUuidForNetwork(
networkUuid,
networkName,
socket
)
if (bridgeUuid == null) {
const bridge = await this._getBridgeForNetwork(network, socket)
if (bridge.uuid === undefined) {
socket.destroy()
return
}
// Delete old ports created by a SDN controller
const ports = await this._getBridgePorts(bridgeUuid, bridgeName, socket)
if (ports == null) {
const ports = await this._getBridgePorts(bridge, socket)
if (ports === undefined) {
socket.destroy()
return
}
const portsToDelete = []
for (let i = 0; i < ports.length; ++i) {
const portUuid = ports[i][1]
for (const port of ports) {
const portUuid = port[1]
const where = [['_uuid', '==', ['uuid', portUuid]]]
const selectResult = await this._select(
@@ -180,17 +224,24 @@ export class OvsdbClient {
where,
socket
)
if (selectResult == null) {
if (selectResult === undefined) {
continue
}
forOwn(selectResult.other_config[1], config => {
if (config[0] === 'private_pool_wide' && config[1] === 'true') {
log.debug(
`[${this._host.name_label}] Adding port: '${
selectResult.name
}' to delete list from bridge: '${bridgeName}'`
)
// 2019-09-03
// Compatibility code, to be removed in 1 year.
const oldShouldDelete =
config[0] === 'private_pool_wide' ||
config[0] === 'cross_pool' ||
config[0] === 'xo:sdn-controller:private-pool-wide' ||
config[0] === 'xo:sdn-controller:cross-pool'
const shouldDelete =
config[0] === 'xo:sdn-controller:private-network-uuid' &&
config[1] === privateNetworkUuid
if (shouldDelete || oldShouldDelete) {
portsToDelete.push(['uuid', portUuid])
}
})
@@ -205,33 +256,31 @@ export class OvsdbClient {
const mutateBridgeOperation = {
op: 'mutate',
table: 'Bridge',
where: [['_uuid', '==', ['uuid', bridgeUuid]]],
where: [['_uuid', '==', ['uuid', bridge.uuid]]],
mutations: [['ports', 'delete', ['set', portsToDelete]]],
}
const params = ['Open_vSwitch', mutateBridgeOperation]
const jsonObjects = await this._sendOvsdbTransaction(params, socket)
if (jsonObjects == null) {
if (jsonObjects === undefined) {
socket.destroy()
return
}
if (jsonObjects[0].error != null) {
log.error(
`[${
this._host.name_label
}] Couldn't delete ports from bridge: '${bridgeName}' because: ${
jsonObjects.error
}`
)
log.error('Error while deleting ports from bridge', {
error: jsonObjects[0].error,
bridge: bridge.name,
host: this.host.name_label,
})
socket.destroy()
return
}
log.debug(
`[${this._host.name_label}] Deleted ${
jsonObjects[0].result[0].count
} ports from bridge: '${bridgeName}'`
)
log.debug('Ports deleted from bridge', {
nPorts: jsonObjects[0].result[0].count,
bridge: bridge.name,
host: this.host.name_label,
})
socket.destroy()
}
@@ -247,9 +296,9 @@ export class OvsdbClient {
for (let i = pos; i < data.length; ++i) {
const c = data.charAt(i)
if (c === '{') {
depth++
++depth
} else if (c === '}') {
depth--
--depth
if (depth === 0) {
const object = JSON.parse(buffer + data.substr(0, i + 1))
objects.push(object)
@@ -267,13 +316,9 @@ export class OvsdbClient {
// ---------------------------------------------------------------------------
async _getBridgeUuidForNetwork(networkUuid, networkName, socket) {
async _getBridgeForNetwork(network, socket) {
const where = [
[
'external_ids',
'includes',
['map', [['xs-network-uuids', networkUuid]]],
],
['external_ids', 'includes', toMap({ 'xs-network-uuids': network.uuid })],
]
const selectResult = await this._select(
'Bridge',
@@ -281,48 +326,38 @@ export class OvsdbClient {
where,
socket
)
if (selectResult == null) {
return [null, null]
if (selectResult === undefined) {
log.error('No bridge found for network', {
network: network.name_label,
host: this.host.name_label,
})
return {}
}
const bridgeUuid = selectResult._uuid[1]
const bridgeName = selectResult.name
log.debug(
`[${
this._host.name_label
}] Found bridge: '${bridgeName}' for network: '${networkName}'`
)
return [bridgeUuid, bridgeName]
return { uuid: selectResult._uuid[1], name: selectResult.name }
}
async _interfaceAndPortAlreadyExist(
bridgeUuid,
bridgeName,
remoteAddress,
socket
) {
const ports = await this._getBridgePorts(bridgeUuid, bridgeName, socket)
if (ports == null) {
return
async _interfaceAndPortAlreadyExist(bridge, remoteAddress, socket) {
const ports = await this._getBridgePorts(bridge, socket)
if (ports === undefined) {
return false
}
for (let i = 0; i < ports.length; ++i) {
const portUuid = ports[i][1]
for (const port of ports) {
const portUuid = port[1]
const interfaces = await this._getPortInterfaces(portUuid, socket)
if (interfaces == null) {
if (interfaces === undefined) {
continue
}
let j
for (j = 0; j < interfaces.length; ++j) {
const interfaceUuid = interfaces[j][1]
for (const iface of interfaces) {
const interfaceUuid = iface[1]
const hasRemote = await this._interfaceHasRemote(
interfaceUuid,
remoteAddress,
socket
)
if (hasRemote === true) {
if (hasRemote) {
return true
}
}
@@ -331,11 +366,11 @@ export class OvsdbClient {
return false
}
async _getBridgePorts(bridgeUuid, bridgeName, socket) {
const where = [['_uuid', '==', ['uuid', bridgeUuid]]]
async _getBridgePorts(bridge, socket) {
const where = [['_uuid', '==', ['uuid', bridge.uuid]]]
const selectResult = await this._select('Bridge', ['ports'], where, socket)
if (selectResult == null) {
return null
if (selectResult === undefined) {
return
}
return selectResult.ports[0] === 'set'
@@ -351,8 +386,8 @@ export class OvsdbClient {
where,
socket
)
if (selectResult == null) {
return null
if (selectResult === undefined) {
return
}
return selectResult.interfaces[0] === 'set'
@@ -368,12 +403,11 @@ export class OvsdbClient {
where,
socket
)
if (selectResult == null) {
if (selectResult === undefined) {
return false
}
for (let i = 0; i < selectResult.options[1].length; ++i) {
const option = selectResult.options[1][i]
for (const option of selectResult.options[1]) {
if (option[0] === 'remote_ip' && option[1] === remoteAddress) {
return true
}
@@ -394,36 +428,36 @@ export class OvsdbClient {
const params = ['Open_vSwitch', selectOperation]
const jsonObjects = await this._sendOvsdbTransaction(params, socket)
if (jsonObjects == null) {
if (jsonObjects === undefined) {
return
}
const jsonResult = jsonObjects[0].result[0]
if (jsonResult.error != null) {
log.error(
`[${
this._host.name_label
}] Couldn't retrieve: '${columns}' in: '${table}' because: ${
jsonResult.error
}: ${jsonResult.details}`
)
return null
if (jsonResult.error !== undefined) {
log.error('Error while selecting columns', {
error: jsonResult.error,
details: jsonResult.details,
columns,
table,
where,
host: this.host.name_label,
})
return
}
if (jsonResult.rows.length === 0) {
log.error(
`[${
this._host.name_label
}] No '${columns}' found in: '${table}' where: '${where}'`
)
return null
log.error('No result for select', {
columns,
table,
where,
host: this.host.name_label,
})
return
}
// For now all select operations should return only 1 row
assert(
jsonResult.rows.length === 1,
`[${
this._host.name_label
}] There should exactly 1 row when searching: '${columns}' in: '${table}' where: '${where}'`
`[${this.host.name_label}] There should be exactly 1 row when searching: '${columns}' in: '${table}' where: '${where}'`
)
return jsonResult.rows[0]
@@ -431,9 +465,7 @@ export class OvsdbClient {
async _sendOvsdbTransaction(params, socket) {
const stream = socket
const requestId = this._requestID
++this._requestID
const requestId = ++this._requestId
const req = {
id: requestId,
method: 'transact',
@@ -443,10 +475,11 @@ export class OvsdbClient {
try {
stream.write(JSON.stringify(req))
} catch (error) {
log.error(
`[${this._host.name_label}] Error while writing into stream: ${error}`
)
return null
log.error('Error while writing into stream', {
error,
host: this.host.name_label,
})
return
}
let result
@@ -456,12 +489,11 @@ export class OvsdbClient {
try {
result = await fromEvent(stream, 'data', {})
} catch (error) {
log.error(
`[${
this._host.name_label
}] Error while waiting for stream data: ${error}`
)
return null
log.error('Error while waiting for stream data', {
error,
host: this.host.name_label,
})
return
}
jsonObjects = this._parseJson(result)
@@ -478,7 +510,7 @@ export class OvsdbClient {
ca: this._caCert,
key: this._clientKey,
cert: this._clientCert,
host: this._host.address,
host: this.host.address,
port: OVSDB_PORT,
rejectUnauthorized: false,
requestCert: false,
@@ -488,22 +520,20 @@ export class OvsdbClient {
try {
await fromEvent(socket, 'secureConnect', {})
} catch (error) {
log.error(
`[${this._host.name_label}] TLS connection failed because: ${error}: ${
error.code
}`
)
log.error('TLS connection failed', {
error,
code: error.code,
host: this.host.name_label,
})
throw error
}
log.debug(`[${this._host.name_label}] TLS connection successful`)
socket.on('error', error => {
log.error(
`[${
this._host.name_label
}] OVSDB client socket error: ${error} with code: ${error.code}`
)
log.error('Socket error', {
error,
code: error.code,
host: this.host.name_label,
})
})
return socket

View File

@@ -0,0 +1,8 @@
const pkg = require('./package.json')
// `xo-server-test` is a special package which has no dev dependencies but our
// babel config generator only looks in `devDependencies`.
require('assert').strictEqual(pkg.devDependencies, undefined)
pkg.devDependencies = pkg.dependencies
module.exports = require('../../@xen-orchestra/babel-config')(pkg)

View File

@@ -0,0 +1,24 @@
/benchmark/
/benchmarks/
*.bench.js
*.bench.js.map
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/fixture/
/fixtures/
*.fixture.js
*.fixture.js.map
*.fixtures.js
*.fixtures.js.map
/test/
/tests/
*.spec.js
*.spec.js.map
__snapshots__/

View File

@@ -0,0 +1,160 @@
# xo-server-test
> Test client for Xo-Server
Tests are ran sequentially to avoid concurrency issues.
## Adding a test
### Organization
```
src
├─ user
| ├─ __snapshots__
| | └─ index.spec.js.snap
| └─ index.spec.js
├─ job
| └─ index.spec.js
├─ issues
¦ └─ index.spec.js
¦
├─ _xoConnection.js
└─ util.js
```
The tests can describe:
- XO methods or scenarios:
`src/user/index.js`
```js
import xo from "../_xoConnection";
describe("user", () => {
// testing a method
describe(".set()", () => {
it("sets an email", async () => {
// some tests using xo methods and helpers from _xoConnection.js
const id = await xo.createTempUser(SIMPLE_USER);
expect(await xo.call("user.set", params)).toBe(true);
expect(await xo.getUser(id)).toMatchSnapshot({
id: expect.any(String),
});
});
});
// testing a scenario
test("create two users, modify a user email to be the same with the other and fail trying to connect them", () => {
/* some tests */
});
});
```
- issues
`src/issues/index.js`
```js
describe("issue", () => {
test("5454", () => {
/* some tests */
})
})
```
### Best practices
- The test environment must remain the same before and after each test:
* each resource created must be deleted
* existing resources should not be altered
- Make a sentence for the title of the test. It must be clear and consistent.
- If the feature you want to test is not implemented : write it and skip it, using `it.skip()`.
- Take values that cover the maximum of testing possibilities.
- If you make tests which keep track of large object, it is better to use snapshots.
- `_xoConnection.js` contains helpers to create temporary resources and to interface with XO.
You can use it if you need to create resources which will be automatically deleted after the test:
```javascript
import xo from "../_xoConnection";
describe(".create()", () => {
it("creates a user without permission", async () => {
// The user will be deleted automatically at the end of the test
const userId = await xo.createTempUser({
email: "wayne1@vates.fr",
password: "batman1",
});
expect(await xo.getUser(userId)).toMatchSnapshot({
id: expect.any(String),
});
});
});
```
The available helpers:
* `createTempUser(params)`
* `getUser(id)`
* `createTempJob(params)`
* `createTempBackupNgJob(params)`
* `createTempVm(params)`
* `getSchedule(predicate)`
## Usage
- Before running the tests, you have to create a config file for xo-server-test.
```
> cp sample.config.toml ~/.config/xo-server-test/config.toml
```
And complete it.
- To run the tests:
```
> npm ci
> yarn test
```
You get all the test suites passed (`PASS`) or failed (`FAIL`).
```
> yarn test
yarn run v1.9.4
$ jest
PASS src/user/user.spec.js
PASS src/job/job.spec.js
PASS src/backupNg/backupNg.spec.js
Test Suites: 3 passed, 3 total
Tests: 2 skipped, 36 passed, 38 total
Snapshots: 35 passed, 35 total
Time: 7.257s, estimated 8s
Ran all test suites.
Done in 7.92s.
```
- You can run only tests related to changed files, and review the failed output by using: `> yarn test --watch`
- ⚠ Warning: snapshots ⚠
After each run of the tests, check that snapshots are not inadvertently modified.
- ⚠ Jest known issue ⚠
If a test timeout is triggered the next async tests can fail, it's due to an inadvertently modified snapshots.
As a workaround, you can clean your git working tree and re-run jest using a large timeout: `> yarn test --testTimeout=100000`
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
ISC © [Vates SAS](http://vates.fr)

View File

@@ -0,0 +1,56 @@
{
"private": true,
"name": "xo-server-test",
"version": "0.0.0",
"license": "ISC",
"description": "Test client for Xo-Server",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-test",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/xo-server-test",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=6"
},
"dependencies": {
"@babel/cli": "^7.1.5",
"@babel/core": "^7.1.6",
"@babel/plugin-proposal-decorators": "^7.4.0",
"@babel/preset-env": "^7.1.6",
"@iarna/toml": "^2.2.1",
"app-conf": "^0.7.0",
"babel-plugin-lodash": "^3.2.11",
"golike-defer": "^0.4.1",
"jest": "^24.8.0",
"lodash": "^4.17.11",
"promise-toolbox": "^0.14.0",
"xo-collection": "^0.4.1",
"xo-common": "^0.2.0",
"xo-lib": "^0.9.0"
},
"scripts": {
"dev-test": "jest --bail --watch",
"test": "jest"
},
"jest": {
"modulePathIgnorePatterns": [
"<rootDir>/src/old-tests"
],
"testEnvironment": "node",
"testRegex": "\\.spec\\.js$",
"maxConcurrency": 1
}
}

View File

@@ -0,0 +1,30 @@
[xoConnection]
url = ''
email = ''
password = ''
[pools]
default = ''
[servers]
[servers.default]
username = ''
password = ''
host = ''
[vms]
default = ''
withOsAndXenTools = ''
# vmToBackup = ''
[templates]
default = ''
templateWithoutDisks = ''
[srs]
default = ''
[remotes]
default = { name = '', url = '' }
remote1 = { name = '', url = '' }
# remote2 = { name = '', url = '' }

View File

@@ -0,0 +1,13 @@
import appConf from 'app-conf'
import path from 'path'
/* eslint-env jest */
let config
export { config as default }
beforeAll(async () => {
config = await appConf.load('xo-server-test', {
appDir: path.join(__dirname, '..'),
})
})

View File

@@ -0,0 +1,6 @@
export const getDefaultName = () => `xo-server-test ${new Date().toISOString()}`
export const getDefaultSchedule = () => ({
name: getDefaultName(),
cron: '0 * * * * *',
})

View File

@@ -0,0 +1,6 @@
const randomId = () =>
Math.random()
.toString(36)
.slice(2)
export { randomId as default }

View File

@@ -0,0 +1,294 @@
/* eslint-env jest */
import defer from 'golike-defer'
import Xo from 'xo-lib'
import XoCollection from 'xo-collection'
import { defaultsDeep, find, forOwn, pick } from 'lodash'
import { fromEvent } from 'promise-toolbox'
import config from './_config'
import { getDefaultName } from './_defaultValues'
class XoConnection extends Xo {
constructor(opts) {
super(opts)
const objects = (this._objects = new XoCollection())
const watchers = (this._watchers = {})
this._tempResourceDisposers = []
this._durableResourceDisposers = []
this.on('notification', ({ method, params }) => {
if (method !== 'all') {
return
}
const fn = params.type === 'exit' ? objects.unset : objects.set
forOwn(params.items, (item, id) => {
fn.call(objects, id, item)
const watcher = watchers[id]
if (watcher !== undefined) {
watcher(item)
delete watchers[id]
}
})
})
}
get objects() {
return this._objects
}
async _fetchObjects() {
const { _objects: objects, _watchers: watchers } = this
forOwn(await this.call('xo.getAllObjects'), (object, id) => {
objects.set(id, object)
const watcher = watchers[id]
if (watcher !== undefined) {
watcher(object)
delete watchers[id]
}
})
}
// TODO: integrate in xo-lib.
waitObject(id) {
return new Promise(resolve => {
this._watchers[id] = resolve
}) // FIXME: work with multiple listeners.
}
async getOrWaitObject(id) {
const object = this._objects.all[id]
if (object !== undefined) {
return object
}
return this.waitObject(id)
}
@defer
async connect(
$defer,
credentials = pick(config.xoConnection, 'email', 'password')
) {
await this.open()
$defer.onFailure(() => this.close())
await this.signIn(credentials)
await this._fetchObjects()
return this
}
async waitObjectState(id, predicate) {
let obj = this._objects.all[id]
while (true) {
try {
await predicate(obj)
return obj
} catch (_) {}
// If failed, wait for next object state/update and retry.
obj = await this.waitObject(id)
}
}
async createTempUser(params) {
const id = await this.call('user.create', params)
this._tempResourceDisposers.push('user.delete', { id })
return id
}
async getUser(id) {
return find(await super.call('user.getAll'), { id })
}
async createTempJob(params) {
const id = await this.call('job.create', { job: params })
this._tempResourceDisposers.push('job.delete', { id })
return id
}
async createTempBackupNgJob(params) {
// mutate and inject default values
defaultsDeep(params, {
mode: 'full',
name: getDefaultName(),
settings: {
'': {
// it must be enabled because the XAPI might be not able to coalesce VDIs
// as fast as the tests run
//
// see https://xen-orchestra.com/docs/backup_troubleshooting.html#vdi-chain-protection
bypassVdiChainsCheck: true,
// it must be 'never' to avoid race conditions with the plugin `backup-reports`
reportWhen: 'never',
},
},
})
const id = await this.call('backupNg.createJob', params)
this._tempResourceDisposers.push('backupNg.deleteJob', { id })
return this.call('backupNg.getJob', { id })
}
async createTempNetwork(params) {
const id = await this.call('network.create', {
name: 'XO Test',
pool: config.pools.default,
...params,
})
this._tempResourceDisposers.push('network.delete', { id })
return this.getOrWaitObject(id)
}
async createTempVm(params) {
const id = await this.call('vm.create', {
name_label: getDefaultName(),
template: config.templates.templateWithoutDisks,
...params,
})
this._tempResourceDisposers.push('vm.delete', { id })
return this.waitObjectState(id, vm => {
if (vm.type !== 'VM') throw new Error('retry')
})
}
async startTempVm(id, params, withXenTools = false) {
await this.call('vm.start', { id, ...params })
this._tempResourceDisposers.push('vm.stop', { id, force: true })
return this.waitObjectState(id, vm => {
if (
vm.power_state !== 'Running' ||
(withXenTools && vm.xenTools === false)
) {
throw new Error('retry')
}
})
}
async createTempRemote(params) {
const remote = await this.call('remote.create', params)
this._tempResourceDisposers.push('remote.delete', { id: remote.id })
return remote
}
async createTempServer(params) {
const servers = await this.call('server.getAll')
const server = servers.find(server => server.host === params.host)
if (server !== undefined) {
if (server.status === 'disconnected') {
await this.call('server.enable', { id: server.id })
this._durableResourceDisposers.push('server.disable', { id: server.id })
await fromEvent(this._objects, 'finish')
}
return
}
const id = await this.call('server.add', {
...params,
allowUnauthorized: true,
autoConnect: false,
})
this._durableResourceDisposers.push('server.remove', { id })
await this.call('server.enable', { id })
await fromEvent(this._objects, 'finish')
}
async getSchedule(predicate) {
return find(await this.call('schedule.getAll'), predicate)
}
async runBackupJob(jobId, scheduleId, { remotes, nExecutions = 1 }) {
for (let i = 0; i < nExecutions; i++) {
await xo.call('backupNg.runJob', { id: jobId, schedule: scheduleId })
}
const backups = {}
if (remotes !== undefined) {
const backupsByRemote = await xo.call('backupNg.listVmBackups', {
remotes,
})
forOwn(backupsByRemote, (backupsByVm, remoteId) => {
backups[remoteId] = []
forOwn(backupsByVm, vmBackups => {
vmBackups.forEach(
({ jobId: backupJobId, scheduleId: backupScheduleId, id }) => {
if (jobId === backupJobId && scheduleId === backupScheduleId) {
this._tempResourceDisposers.push('backupNg.deleteVmBackup', {
id,
})
backups[remoteId].push(id)
}
}
)
})
})
}
forOwn(this.objects.all, (obj, id) => {
if (
obj.other !== undefined &&
obj.other['xo:backup:job'] === jobId &&
obj.other['xo:backup:schedule'] === scheduleId
) {
this._tempResourceDisposers.push('vm.delete', {
id,
})
}
})
return backups
}
getBackupLogs(filter) {
return this.call('backupNg.getLogs', { _forceRefresh: true, ...filter })
}
async _cleanDisposers(disposers) {
for (let n = disposers.length - 1; n > 0; ) {
const params = disposers[n--]
const method = disposers[n--]
await this.call(method, params).catch(error => {
console.warn('deleteTempResources', method, params, error)
})
}
disposers.length = 0
}
async deleteTempResources() {
await this._cleanDisposers(this._tempResourceDisposers)
}
async deleteDurableResources() {
await this._cleanDisposers(this._durableResourceDisposers)
}
}
const getConnection = credentials => {
const xo = new XoConnection({ url: config.xoConnection.url })
return xo.connect(credentials)
}
let xo
beforeAll(async () => {
// TOFIX: stop tests if the connection is not established properly and show the error
xo = await getConnection()
})
afterAll(async () => {
await xo.deleteDurableResources()
await xo.close()
xo = null
})
afterEach(() => xo.deleteTempResources())
export { xo as default }
export const testConnection = ({ credentials }) =>
getConnection(credentials).then(connection => connection.close())
export const testWithOtherConnection = defer(
async ($defer, credentials, functionToExecute) => {
const xoUser = await getConnection(credentials)
$defer(() => xoUser.close())
await functionToExecute(xoUser)
}
)

View File

@@ -0,0 +1,464 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`backupNg .runJob() : fails trying to run a backup job with a VM without disks 1`] = `
Object {
"data": Object {
"id": Any<String>,
"type": "VM",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"message": "no disks found",
"name": "Error",
"stack": Any<String>,
},
"start": Any<Number>,
"status": "skipped",
}
`;
exports[`backupNg .runJob() : fails trying to run a backup job with no matching VMs 1`] = `[JsonRpcError: unknown error from the peer]`;
exports[`backupNg .runJob() : fails trying to run a backup job with non-existent vm 1`] = `
Array [
Object {
"data": Object {
"vms": Array [
"non-existent-id",
],
},
"message": "missingVms",
},
]
`;
exports[`backupNg .runJob() : fails trying to run a backup job without schedule 1`] = `[JsonRpcError: invalid parameters]`;
exports[`backupNg .runJob() : fails trying to run backup job without retentions 1`] = `
Object {
"data": Object {
"id": Any<String>,
"type": "VM",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"message": "copy, export and snapshot retentions cannot both be 0",
"name": "Error",
"stack": Any<String>,
},
"start": Any<Number>,
"status": "failure",
}
`;
exports[`backupNg create and execute backup with enabled offline backup 1`] = `
Object {
"data": Object {
"id": Any<String>,
"type": "VM",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg create and execute backup with enabled offline backup 2`] = `
Object {
"data": Any<Object>,
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg create and execute backup with enabled offline backup 3`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg create and execute backup with enabled offline backup 4`] = `
Object {
"data": Any<Object>,
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg create and execute backup with enabled offline backup 5`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 1`] = `
Object {
"data": Object {
"id": Any<String>,
"type": "VM",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 2`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": "snapshot",
"result": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 3`] = `
Object {
"data": Object {
"id": Any<String>,
"isFull": true,
"type": "remote",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 4`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 5`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 6`] = `
Object {
"data": Object {
"id": Any<String>,
"isFull": true,
"type": "remote",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 7`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 8`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 9`] = `
Object {
"data": Object {
"id": Any<String>,
"type": "VM",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 10`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": "snapshot",
"result": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 11`] = `
Object {
"data": Object {
"id": Any<String>,
"isFull": false,
"type": "remote",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 12`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 13`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 14`] = `
Object {
"data": Object {
"id": Any<String>,
"isFull": false,
"type": "remote",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 15`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 16`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 17`] = `
Object {
"data": Object {
"id": Any<String>,
"type": "VM",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 18`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": "snapshot",
"result": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 19`] = `
Object {
"data": Object {
"id": Any<String>,
"isFull": true,
"type": "remote",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 20`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 21`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 22`] = `
Object {
"data": Object {
"id": Any<String>,
"isFull": true,
"type": "remote",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 23`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 24`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"result": Object {
"size": Any<Number>,
},
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a rolling snapshot with 2 as retention & revert to an old state 1`] = `
Object {
"end": Any<Number>,
"id": Any<String>,
"message": "snapshot",
"result": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;
exports[`backupNg execute three times a rolling snapshot with 2 as retention & revert to an old state 2`] = `
Object {
"data": Object {
"id": Any<String>,
"type": "VM",
},
"end": Any<Number>,
"id": Any<String>,
"message": Any<String>,
"start": Any<Number>,
"status": "success",
}
`;

View File

@@ -0,0 +1,693 @@
/* eslint-env jest */
import { forOwn } from 'lodash'
import { noSuchObject } from 'xo-common/api-errors'
import config from '../_config'
import randomId from '../_randomId'
import xo from '../_xoConnection'
import { getDefaultName, getDefaultSchedule } from '../_defaultValues'
const validateBackupJob = (jobInput, jobOutput, createdSchedule) => {
const expectedObj = {
id: expect.any(String),
mode: jobInput.mode,
name: jobInput.name,
type: 'backup',
settings: {
'': jobInput.settings[''],
},
userId: xo._user.id,
vms: jobInput.vms,
}
const schedules = jobInput.schedules
if (schedules !== undefined) {
const scheduleTmpId = Object.keys(schedules)[0]
expect(createdSchedule).toEqual({
...schedules[scheduleTmpId],
enabled: false,
id: expect.any(String),
jobId: jobOutput.id,
})
expectedObj.settings[createdSchedule.id] = jobInput.settings[scheduleTmpId]
}
expect(jobOutput).toEqual(expectedObj)
}
const validateRootTask = (log, expected) =>
expect(log).toEqual({
end: expect.any(Number),
id: expect.any(String),
message: 'backup',
start: expect.any(Number),
...expected,
})
const validateVmTask = (task, vmId, props) => {
expect(task).toMatchSnapshot({
data: {
id: expect.any(String),
},
end: expect.any(Number),
id: expect.any(String),
message: expect.any(String),
start: expect.any(Number),
...props,
})
expect(task.data.id).toBe(vmId)
}
const validateSnapshotTask = (task, props) =>
expect(task).toMatchSnapshot({
end: expect.any(Number),
id: expect.any(String),
result: expect.any(String),
start: expect.any(Number),
...props,
})
const validateExportTask = (task, srOrRemoteIds, props) => {
expect(task).toMatchSnapshot({
end: expect.any(Number),
id: expect.any(String),
message: expect.any(String),
start: expect.any(Number),
...props,
})
expect(srOrRemoteIds).toContain(task.data.id)
}
const validateOperationTask = (task, props) => {
expect(task).toMatchSnapshot({
end: expect.any(Number),
id: expect.any(String),
message: expect.any(String),
start: expect.any(Number),
...props,
})
}
describe('backupNg', () => {
describe('.createJob() :', () => {
it('creates a new backup job without schedules', async () => {
const jobInput = {
mode: 'full',
vms: {
id: config.vms.default,
},
}
const jobOutput = await xo.createTempBackupNgJob(jobInput)
validateBackupJob(jobInput, jobOutput)
})
it('creates a new backup job with schedules', async () => {
const scheduleTempId = randomId()
const jobInput = {
mode: 'full',
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
[scheduleTempId]: { snapshotRetention: 1 },
},
vms: {
id: config.vms.default,
},
}
const jobOutput = await xo.createTempBackupNgJob(jobInput)
validateBackupJob(
jobInput,
jobOutput,
await xo.getSchedule({ jobId: jobOutput.id })
)
})
})
describe('.delete() :', () => {
it('deletes a backup job', async () => {
const scheduleTempId = randomId()
const jobId = await xo.call('backupNg.createJob', {
mode: 'full',
name: getDefaultName(),
vms: {
id: config.vms.default,
},
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
[scheduleTempId]: { snapshotRetention: 1 },
},
})
const schedule = await xo.getSchedule({ jobId })
expect(typeof schedule).toBe('object')
await xo.call('backupNg.deleteJob', { id: jobId })
let isRejectedJobErrorValid = false
await xo.call('backupNg.getJob', { id: jobId }).catch(error => {
isRejectedJobErrorValid = noSuchObject.is(error)
})
expect(isRejectedJobErrorValid).toBe(true)
let isRejectedScheduleErrorValid = false
await xo.call('schedule.get', { id: schedule.id }).catch(error => {
isRejectedScheduleErrorValid = noSuchObject.is(error)
})
expect(isRejectedScheduleErrorValid).toBe(true)
})
})
describe('.runJob() :', () => {
it('fails trying to run a backup job without schedule', async () => {
const { id } = await xo.createTempBackupNgJob({
vms: {
id: config.vms.default,
},
})
await expect(xo.call('backupNg.runJob', { id })).rejects.toMatchSnapshot()
})
it('fails trying to run a backup job with no matching VMs', async () => {
const scheduleTempId = randomId()
const { id: jobId } = await xo.createTempBackupNgJob({
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
[scheduleTempId]: { snapshotRetention: 1 },
},
vms: {
id: config.vms.default,
name: 'test-vm-backupNg',
},
})
const schedule = await xo.getSchedule({ jobId })
expect(typeof schedule).toBe('object')
await expect(
xo.call('backupNg.runJob', { id: jobId, schedule: schedule.id })
).rejects.toMatchSnapshot()
})
it('fails trying to run a backup job with non-existent vm', async () => {
jest.setTimeout(7e3)
const scheduleTempId = randomId()
const { id: jobId } = await xo.createTempBackupNgJob({
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
[scheduleTempId]: { snapshotRetention: 1 },
},
vms: {
id: 'non-existent-id',
},
})
const schedule = await xo.getSchedule({ jobId })
expect(typeof schedule).toBe('object')
await xo.call('backupNg.runJob', { id: jobId, schedule: schedule.id })
const [log] = await xo.getBackupLogs({
scheduleId: schedule.id,
})
expect(log.warnings).toMatchSnapshot()
})
it('fails trying to run a backup job with a VM without disks', async () => {
jest.setTimeout(8e3)
await xo.createTempServer(config.servers.default)
const { id: vmIdWithoutDisks } = await xo.createTempVm({
name_description: 'Creating a vm without disks',
template: config.templates.templateWithoutDisks,
})
const scheduleTempId = randomId()
const jobInput = {
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
[scheduleTempId]: { snapshotRetention: 1 },
},
vms: {
id: vmIdWithoutDisks,
},
}
const { id: jobId } = await xo.createTempBackupNgJob(jobInput)
const schedule = await xo.getSchedule({ jobId })
expect(typeof schedule).toBe('object')
await xo.call('backupNg.runJob', { id: jobId, schedule: schedule.id })
const [
{
tasks: [vmTask],
...log
},
] = await xo.getBackupLogs({
jobId,
scheduleId: schedule.id,
})
validateRootTask(log, {
data: {
mode: jobInput.mode,
reportWhen: jobInput.settings[''].reportWhen,
},
jobId,
jobName: jobInput.name,
scheduleId: schedule.id,
status: 'skipped',
})
expect(vmTask).toMatchSnapshot({
end: expect.any(Number),
data: {
id: expect.any(String),
},
id: expect.any(String),
message: expect.any(String),
result: {
stack: expect.any(String),
},
start: expect.any(Number),
})
expect(vmTask.data.id).toBe(vmIdWithoutDisks)
})
it('fails trying to run backup job without retentions', async () => {
jest.setTimeout(7e3)
const scheduleTempId = randomId()
await xo.createTempServer(config.servers.default)
const { id: remoteId } = await xo.createTempRemote(config.remotes.default)
const jobInput = {
remotes: {
id: remoteId,
},
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
[scheduleTempId]: {},
},
srs: {
id: config.srs.default,
},
vms: {
id: config.vms.default,
},
}
const { id: jobId } = await xo.createTempBackupNgJob(jobInput)
const schedule = await xo.getSchedule({ jobId })
expect(typeof schedule).toBe('object')
await xo.call('backupNg.runJob', { id: jobId, schedule: schedule.id })
const [
{
tasks: [task],
...log
},
] = await xo.getBackupLogs({
jobId,
scheduleId: schedule.id,
})
validateRootTask(log, {
data: {
mode: jobInput.mode,
reportWhen: jobInput.settings[''].reportWhen,
},
jobId,
jobName: jobInput.name,
scheduleId: schedule.id,
status: 'failure',
})
expect(task).toMatchSnapshot({
data: {
id: expect.any(String),
},
end: expect.any(Number),
id: expect.any(String),
message: expect.any(String),
result: {
stack: expect.any(String),
},
start: expect.any(Number),
})
expect(task.data.id).toBe(config.vms.default)
})
})
test('execute three times a rolling snapshot with 2 as retention & revert to an old state', async () => {
jest.setTimeout(6e4)
await xo.createTempServer(config.servers.default)
let vm = await xo.createTempVm({
name_description: 'Creating a temporary vm',
template: config.templates.default,
VDIs: [
{
size: 1,
SR: config.srs.default,
type: 'user',
},
],
})
const scheduleTempId = randomId()
const jobInput = {
vms: {
id: vm.id,
},
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
[scheduleTempId]: { snapshotRetention: 2 },
},
}
const { id: jobId } = await xo.createTempBackupNgJob(jobInput)
const schedule = await xo.getSchedule({ jobId })
expect(typeof schedule).toBe('object')
for (let i = 0; i < 3; i++) {
await xo.call('backupNg.runJob', { id: jobId, schedule: schedule.id })
vm = await xo.waitObjectState(vm.id, ({ snapshots }) => {
// Test on updating snapshots.
expect(snapshots).not.toEqual(vm.snapshots)
})
}
// Test on the retention, how many snapshots should be saved.
expect(vm.snapshots.length).toBe(2)
const newVideoram = 16
await xo.call('vm.set', { id: vm.id, videoram: newVideoram })
await xo.waitObjectState(vm.id, ({ videoram }) => {
expect(videoram).toBe(newVideoram.toString())
})
await xo.call('vm.revert', {
snapshot: vm.snapshots[0],
})
await xo.waitObjectState(vm.id, ({ videoram }) => {
expect(videoram).toBe(vm.videoram)
})
const [
{
tasks: [{ tasks: subTasks, ...vmTask }],
...log
},
] = await xo.getBackupLogs({
jobId,
scheduleId: schedule.id,
})
validateRootTask(log, {
data: {
mode: jobInput.mode,
reportWhen: jobInput.settings[''].reportWhen,
},
jobId,
jobName: jobInput.name,
scheduleId: schedule.id,
status: 'success',
})
const subTaskSnapshot = subTasks.find(
({ message }) => message === 'snapshot'
)
expect(subTaskSnapshot).toMatchSnapshot({
end: expect.any(Number),
id: expect.any(String),
result: expect.any(String),
start: expect.any(Number),
})
expect(vmTask).toMatchSnapshot({
data: {
id: expect.any(String),
},
end: expect.any(Number),
id: expect.any(String),
message: expect.any(String),
start: expect.any(Number),
})
expect(vmTask.data.id).toBe(vm.id)
})
test('execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval', async () => {
jest.setTimeout(12e5)
const {
vms: { default: defaultVm, vmToBackup = defaultVm },
remotes: { default: defaultRemote, remote1, remote2 = defaultRemote },
servers: { default: defaultServer },
} = config
expect(vmToBackup).not.toBe(undefined)
expect(remote1).not.toBe(undefined)
expect(remote2).not.toBe(undefined)
await xo.createTempServer(defaultServer)
const { id: remoteId1 } = await xo.createTempRemote(remote1)
const { id: remoteId2 } = await xo.createTempRemote(remote2)
const remotes = [remoteId1, remoteId2]
const exportRetention = 2
const fullInterval = 2
const scheduleTempId = randomId()
const jobInput = {
mode: 'delta',
remotes: {
id: {
__or: remotes,
},
},
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
'': {
fullInterval,
},
[remoteId1]: { deleteFirst: true },
[scheduleTempId]: { exportRetention },
},
vms: {
id: vmToBackup,
},
}
const { id: jobId } = await xo.createTempBackupNgJob(jobInput)
const schedule = await xo.getSchedule({ jobId })
expect(typeof schedule).toBe('object')
const nExecutions = 3
const backupsByRemote = await xo.runBackupJob(jobId, schedule.id, {
remotes,
nExecutions,
})
forOwn(backupsByRemote, backups =>
expect(backups.length).toBe(exportRetention)
)
const backupLogs = await xo.getBackupLogs({
jobId,
scheduleId: schedule.id,
})
expect(backupLogs.length).toBe(nExecutions)
backupLogs.forEach(({ tasks = [], ...log }, key) => {
validateRootTask(log, {
data: {
mode: jobInput.mode,
reportWhen: jobInput.settings[''].reportWhen,
},
jobId,
jobName: jobInput.name,
scheduleId: schedule.id,
status: 'success',
})
const numberOfTasks = {
export: 0,
merge: 0,
snapshot: 0,
transfer: 0,
vm: 0,
}
tasks.forEach(({ tasks = [], ...vmTask }) => {
if (vmTask.data !== undefined && vmTask.data.type === 'VM') {
validateVmTask(vmTask, vmToBackup, { status: 'success' })
numberOfTasks.vm++
tasks.forEach(({ tasks = [], ...subTask }) => {
if (subTask.message === 'snapshot') {
validateSnapshotTask(subTask, { status: 'success' })
numberOfTasks.snapshot++
}
if (subTask.message === 'export') {
validateExportTask(subTask, remotes, {
data: {
id: expect.any(String),
isFull: key % fullInterval === 0,
type: 'remote',
},
status: 'success',
})
numberOfTasks.export++
let mergeTaskKey, transferTaskKey
tasks.forEach((operationTask, key) => {
if (
operationTask.message === 'transfer' ||
operationTask.message === 'merge'
) {
validateOperationTask(operationTask, {
result: { size: expect.any(Number) },
status: 'success',
})
if (operationTask.message === 'transfer') {
mergeTaskKey = key
numberOfTasks.merge++
} else {
transferTaskKey = key
numberOfTasks.transfer++
}
}
})
expect(
subTask.data.id === remoteId1
? mergeTaskKey > transferTaskKey
: mergeTaskKey < transferTaskKey
).toBe(true)
}
})
}
})
expect(numberOfTasks).toEqual({
export: 2,
merge: 2,
snapshot: 1,
transfer: 2,
vm: 1,
})
})
})
test('create and execute backup with enabled offline backup', async () => {
const vm = xo.objects.all[config.vms.withOsAndXenTools]
if (vm.power_state !== 'Running') {
await xo.startTempVm(vm.id, { force: true }, true)
}
const scheduleTempId = randomId()
const srId = config.srs.default
const { id: remoteId } = await xo.createTempRemote(config.remotes.default)
const backupInput = {
mode: 'full',
remotes: {
id: remoteId,
},
schedules: {
[scheduleTempId]: getDefaultSchedule(),
},
settings: {
'': {
offlineBackup: true,
},
[scheduleTempId]: {
copyRetention: 1,
exportRetention: 1,
},
},
srs: {
id: srId,
},
vms: {
id: vm.id,
},
}
const backup = await xo.createTempBackupNgJob(backupInput)
expect(backup.settings[''].offlineBackup).toBe(true)
const schedule = await xo.getSchedule({ jobId: backup.id })
await Promise.all([
xo.runBackupJob(backup.id, schedule.id, { remotes: [remoteId] }),
xo.waitObjectState(vm.id, vm => {
if (vm.power_state !== 'Halted') {
throw new Error('retry')
}
}),
])
await xo.waitObjectState(vm.id, vm => {
if (vm.power_state !== 'Running') {
throw new Error('retry')
}
})
const backupLogs = await xo.getBackupLogs({
jobId: backup.id,
scheduleId: schedule.id,
})
expect(backupLogs.length).toBe(1)
const { tasks, ...log } = backupLogs[0]
validateRootTask(log, {
data: {
mode: backupInput.mode,
reportWhen: backupInput.settings[''].reportWhen,
},
jobId: backup.id,
jobName: backupInput.name,
scheduleId: schedule.id,
status: 'success',
})
expect(Array.isArray(tasks)).toBe(true)
tasks.forEach(({ tasks, ...vmTask }) => {
validateVmTask(vmTask, vm.id, { status: 'success' })
expect(Array.isArray(tasks)).toBe(true)
tasks.forEach(({ tasks, ...subTask }) => {
expect(subTask.message).not.toBe('snapshot')
if (subTask.message === 'export') {
validateExportTask(
subTask,
subTask.data.type === 'remote' ? remoteId : srId,
{
data: expect.any(Object),
status: 'success',
}
)
expect(Array.isArray(tasks)).toBe(true)
tasks.forEach(operationTask => {
if (
operationTask.message === 'transfer' ||
operationTask.message === 'merge'
) {
validateOperationTask(operationTask, {
result: { size: expect.any(Number) },
status: 'success',
})
}
})
}
})
})
}, 200e3)
})

View File

@@ -0,0 +1,51 @@
/* eslint-env jest */
import config from '../_config'
import xo from '../_xoConnection'
describe('issue', () => {
test('4507', async () => {
await xo.createTempServer(config.servers.default)
const props = {
coresPerSocket: 1,
cpuCap: 1,
}
const vm = await xo.createTempVm(props)
expect(vm).toMatchObject(props)
await xo.call('vm.set', {
coresPerSocket: null,
cpuCap: null,
id: vm.id,
})
await xo.waitObjectState(vm.id, vm => {
expect(vm.coresPerSocket).toBe(undefined)
expect(vm.cpuCap).toBe(undefined)
})
})
test('4514', async () => {
await xo.createTempServer(config.servers.default)
const oldName = 'Old XO Test name'
const { id, name_label } = await xo.createTempNetwork({ name: oldName })
expect(name_label).toBe(oldName)
const newName = 'New XO Test name'
await xo.call('network.set', { id, name_label: newName })
await xo.waitObjectState(id, ({ name_label }) => {
expect(name_label).toBe(newName)
})
})
test('4523', async () => {
const id = await xo.call('network.create', {
name: 'XO Test',
pool: config.pools.default,
})
expect(typeof id).toBe('string')
await xo.call('network.delete', { id })
})
})

View File

@@ -0,0 +1,76 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`job .create() : creates a new job 1`] = `
Object {
"id": Any<String>,
"key": "snapshot",
"method": "vm.snapshot",
"name": "jobTest",
"paramsVector": Any<Object>,
"timeout": 2000,
"type": "call",
"userId": Any<String>,
}
`;
exports[`job .create() : fails trying to create a job without job params 1`] = `[JsonRpcError: invalid parameters]`;
exports[`job .delete() : deletes an existing job 1`] = `[JsonRpcError: no such job [object Object]]`;
exports[`job .delete() : deletes an existing job 2`] = `[JsonRpcError: no such schedule [object Object]]`;
exports[`job .get() : fails trying to get a job with a non existent id 1`] = `[JsonRpcError: no such job [object Object]]`;
exports[`job .get() : gets an existing job 1`] = `
Object {
"id": Any<String>,
"key": "snapshot",
"method": "vm.snapshot",
"name": "jobTest",
"paramsVector": Any<Object>,
"timeout": 2000,
"type": "call",
"userId": Any<String>,
}
`;
exports[`job .getAll() : gets all available jobs 1`] = `
Object {
"id": Any<String>,
"key": "snapshot",
"method": "vm.snapshot",
"name": "jobTest",
"paramsVector": Any<Object>,
"timeout": 2000,
"type": "call",
"userId": Any<String>,
}
`;
exports[`job .getAll() : gets all available jobs 2`] = `
Object {
"id": Any<String>,
"key": "snapshot",
"method": "vm.snapshot",
"name": "jobTest2",
"paramsVector": Any<Object>,
"timeout": 2000,
"type": "call",
"userId": Any<String>,
}
`;
exports[`job .set() : fails trying to set a job without job.id 1`] = `[JsonRpcError: invalid parameters]`;
exports[`job .set() : sets a job 1`] = `
Object {
"id": Any<String>,
"key": "snapshot",
"method": "vm.clone",
"name": "jobTest",
"paramsVector": Any<Object>,
"timeout": 2000,
"type": "call",
"userId": Any<String>,
}
`;

View File

@@ -0,0 +1,226 @@
/* eslint-env jest */
import { difference, keyBy } from 'lodash'
import config from '../_config'
import xo, { testWithOtherConnection } from '../_xoConnection'
const ADMIN_USER = {
email: 'admin2@admin.net',
password: 'admin',
permission: 'admin',
}
describe('job', () => {
let defaultJob
beforeAll(() => {
defaultJob = {
name: 'jobTest',
timeout: 2000,
type: 'call',
key: 'snapshot',
method: 'vm.snapshot',
paramsVector: {
type: 'crossProduct',
items: [
{
type: 'set',
values: [
{
id: config.vms.default,
name: 'test-snapshot',
},
],
},
],
},
}
})
describe('.create() :', () => {
it('creates a new job', async () => {
jest.setTimeout(6e3)
const userId = await xo.createTempUser(ADMIN_USER)
const { email, password } = ADMIN_USER
await testWithOtherConnection({ email, password }, async xo => {
const id = await xo.call('job.create', { job: defaultJob })
expect(typeof id).toBe('string')
const job = await xo.call('job.get', { id })
expect(job).toMatchSnapshot({
id: expect.any(String),
paramsVector: expect.any(Object),
userId: expect.any(String),
})
expect(job.paramsVector).toEqual(defaultJob.paramsVector)
expect(job.userId).toBe(userId)
await xo.call('job.delete', { id })
})
})
it('creates a job with a userId', async () => {
const userId = await xo.createTempUser(ADMIN_USER)
const id = await xo.createTempJob({ ...defaultJob, userId })
const { userId: expectedUserId } = await xo.call('job.get', { id })
expect(userId).toBe(expectedUserId)
})
it('fails trying to create a job without job params', async () => {
await expect(xo.createTempJob({})).rejects.toMatchSnapshot()
})
})
describe('.getAll() :', () => {
it('gets all available jobs', async () => {
const jobId1 = await xo.createTempJob(defaultJob)
const job2 = {
...defaultJob,
name: 'jobTest2',
paramsVector: {
type: 'crossProduct',
items: [
{
type: 'set',
values: [
{
id: config.vms.default,
name: 'test2-snapshot',
},
],
},
],
},
}
const jobId2 = await xo.createTempJob(job2)
let jobs = await xo.call('job.getAll')
expect(Array.isArray(jobs)).toBe(true)
jobs = keyBy(jobs, 'id')
const newJob1 = jobs[jobId1]
expect(newJob1).toMatchSnapshot({
id: expect.any(String),
paramsVector: expect.any(Object),
userId: expect.any(String),
})
expect(newJob1.paramsVector).toEqual(defaultJob.paramsVector)
const newJob2 = jobs[jobId2]
expect(newJob2).toMatchSnapshot({
id: expect.any(String),
paramsVector: expect.any(Object),
userId: expect.any(String),
})
expect(newJob2.paramsVector).toEqual(job2.paramsVector)
})
})
describe('.get() :', () => {
it('gets an existing job', async () => {
const id = await xo.createTempJob(defaultJob)
const job = await xo.call('job.get', { id })
expect(job).toMatchSnapshot({
id: expect.any(String),
paramsVector: expect.any(Object),
userId: expect.any(String),
})
expect(job.paramsVector).toEqual(defaultJob.paramsVector)
})
it('fails trying to get a job with a non existent id', async () => {
await expect(
xo.call('job.get', { id: 'non-existent-id' })
).rejects.toMatchSnapshot()
})
})
describe('.set() :', () => {
it('sets a job', async () => {
const id = await xo.createTempJob(defaultJob)
const job = {
id,
type: 'call',
key: 'snapshot',
method: 'vm.clone',
paramsVector: {
type: 'crossProduct',
items: [
{
type: 'set',
values: [
{
id: config.vms.default,
name: 'clone',
full_copy: true,
},
],
},
],
},
}
await xo.call('job.set', {
job,
})
const newJob = await xo.call('job.get', { id })
expect(newJob).toMatchSnapshot({
id: expect.any(String),
paramsVector: expect.any(Object),
userId: expect.any(String),
})
expect(newJob.paramsVector).toEqual(job.paramsVector)
})
it('fails trying to set a job without job.id', async () => {
await expect(xo.call('job.set', defaultJob)).rejects.toMatchSnapshot()
})
})
describe('.delete() :', () => {
it('deletes an existing job', async () => {
const id = await xo.call('job.create', { job: defaultJob })
const { id: scheduleId } = await xo.call('schedule.create', {
jobId: id,
cron: '* * * * * *',
enabled: false,
})
await xo.call('job.delete', { id })
await expect(xo.call('job.get', { id })).rejects.toMatchSnapshot()
await expect(
xo.call('schedule.get', { id: scheduleId })
).rejects.toMatchSnapshot()
})
it.skip('fails trying to delete a job with a non existent id', async () => {
await expect(
xo.call('job.delete', { id: 'non-existent-id' })
).rejects.toMatchSnapshot()
})
})
describe('.runSequence() :', () => {
let id
afterEach(async () => {
await xo
.call('vm.delete', { id, deleteDisks: true })
.catch(error => console.error(error))
})
it('runs a job', async () => {
jest.setTimeout(7e4)
await xo.createTempServer(config.servers.default)
const jobId = await xo.createTempJob(defaultJob)
const snapshots = xo.objects.all[config.vms.default].snapshots
await xo.call('job.runSequence', { idSequence: [jobId] })
await xo.waitObjectState(
config.vms.default,
({ snapshots: actualSnapshots }) => {
expect(actualSnapshots.length).toBe(snapshots.length + 1)
id = difference(actualSnapshots, snapshots)[0]
}
)
})
})
})

View File

@@ -0,0 +1,156 @@
/* eslint-env jest */
// Doc: https://github.com/moll/js-must/blob/master/doc/API.md#must
import expect from 'must'
// ===================================================================
import { getConfig, getMainConnection, getSrId, waitObjectState } from './util'
import { map } from 'lodash'
import eventToPromise from 'event-to-promise'
// ===================================================================
describe('disk', () => {
let diskId
let diskIds = []
let serverId
let srId
let xo
// -----------------------------------------------------------------
beforeAll(async () => {
jest.setTimeout(10e3)
xo = await getMainConnection()
const config = await getConfig()
serverId = await xo.call(
'server.add',
Object.assign({ autoConnect: false }, config.xenServer1)
)
await xo.call('server.connect', { id: serverId })
await eventToPromise(xo.objects, 'finish')
srId = await getSrId(xo)
})
// -----------------------------------------------------------------
afterEach(async () => {
await Promise.all(
map(diskIds, diskId => xo.call('vdi.delete', { id: diskId }))
)
diskIds = []
})
// -----------------------------------------------------------------
afterAll(async () => {
await xo.call('server.remove', { id: serverId })
})
// -----------------------------------------------------------------
async function createDisk(params) {
const id = await xo.call('disk.create', params)
diskIds.push(id)
return id
}
async function createDiskTest() {
const id = await createDisk({
name: 'diskTest',
size: '1GB',
sr: srId,
})
return id
}
// ===================================================================
describe('.create()', () => {
it('create a new disk on a SR', async () => {
diskId = await createDisk({
name: 'diskTest',
size: '1GB',
sr: srId,
})
await Promise.all([
waitObjectState(xo, diskId, disk => {
expect(disk.type).to.be.equal('VDI')
expect(disk.name_label).to.be.equal('diskTest')
// TODO: should not test an exact value but around 10%
expect(disk.size).to.be.equal(1000341504)
expect(disk.$SR).to.be.equal(srId)
}),
waitObjectState(xo, srId, sr => {
expect(sr.VDIs).include(diskId)
}),
])
})
})
// -------------------------------------------------------------------
describe('.delete()', () => {
beforeEach(async () => {
diskId = await createDiskTest()
})
it('deletes a disk', async () => {
await Promise.all([
xo.call('vdi.delete', { id: diskId }),
waitObjectState(xo, diskId, disk => {
expect(disk).to.be.undefined()
}),
waitObjectState(xo, srId, sr => {
expect(sr.VDIs).not.include(diskId)
}),
])
diskIds = []
})
})
// ---------------------------------------------------------------------
describe('.set()', () => {
beforeEach(async () => {
diskId = await createDiskTest()
})
it('set the name of the disk', async () => {
await xo.call('vdi.set', {
id: diskId,
name_label: 'disk2',
})
await waitObjectState(xo, diskId, disk => {
expect(disk.name_label).to.be.equal('disk2')
})
})
it('set the description of the disk', async () => {
await xo.call('vdi.set', {
id: diskId,
name_description: 'description',
})
await waitObjectState(xo, diskId, disk => {
expect(disk.name_description).to.be.equal('description')
})
})
it.skip('set the size of the disk', async () => {
await xo.getOrWaitObject(diskId)
await xo.call('vdi.set', {
id: diskId,
size: '5MB',
})
await waitObjectState(xo, diskId, disk => {
expect(disk.size).to.be.equal(6291456)
})
})
})
})

View File

@@ -0,0 +1,59 @@
/* eslint-env jest */
// Doc: https://github.com/moll/js-must/blob/master/doc/API.md#must
// import expect from 'must'
// ===================================================================
// import {getConnection} from './util'
// ===================================================================
describe('docker', () => {
// let xo
// beforeAll(async () => {
// xo = await getConnection()
// })
// ===================================================================
describe('.register()', async () => {
it('registers the VM for Docker management')
})
// -------------------------------------------------------------------
describe('.deregister()', async () => {
it('deregister the VM for Docker management')
})
// -------------------------------------------------------------------
describe('.start()', async () => {
it('starts the Docker')
})
// -------------------------------------------------------------------
describe('.stop()', async () => {
it('stops the Docker')
})
// -------------------------------------------------------------------
describe('.restart()', async () => {
it('restarts the Docker')
})
// -------------------------------------------------------------------
describe('.pause()', async () => {
it('pauses the Docker')
})
// -------------------------------------------------------------------
describe('.unpause()', async () => {
it('unpauses the Docker')
})
})

View File

@@ -0,0 +1,377 @@
/* eslint-env jest */
import { find, map } from 'lodash'
import { createUser, deleteUsers, getUser, xo } from './util.js'
// ===================================================================
describe('group', () => {
const userIds = []
const groupIds = []
// -----------------------------------------------------------------
afterEach(async () => {
await Promise.all(map(groupIds, id => xo.call('group.delete', { id })))
// Deleting users must be done AFTER deleting the group
// because there is a race condition in xo-server
// which cause some users to not be properly deleted.
// The test “delete the group with its users” highlight this issue.
await deleteUsers(xo, userIds)
userIds.length = groupIds.length = 0
})
// -----------------------------------------------------------------
async function createGroup(params) {
const groupId = await xo.call('group.create', params)
groupIds.push(groupId)
return groupId
}
// ----------------------------------------------------------------
function compareGroup(actual, expected) {
expect(actual.name).toEqual(expected.name)
expect(actual.id).toEqual(expected.id)
expect(actual.users).toEqual(expected.users)
}
// ----------------------------------------------------------------
function getAllGroups() {
return xo.call('group.getAll')
}
// ---------------------------------------------------------------
async function getGroup(id) {
const groups = await getAllGroups()
return find(groups, { id: id })
}
// =================================================================
describe('.create()', () => {
it('creates a group and return its id', async () => {
const groupId = await createGroup({
name: 'Avengers',
})
const group = await getGroup(groupId)
compareGroup(group, {
id: groupId,
name: 'Avengers',
users: [],
})
})
it.skip('does not create two groups with the same name', async () => {
await createGroup({
name: 'Avengers',
})
await createGroup({
name: 'Avengers',
}).then(
() => {
throw new Error('createGroup() should have thrown')
},
function(error) {
expect(error.message).to.match(/duplicate group/i)
}
)
})
})
// ------------------------------------------------------------------
describe('.delete()', () => {
let groupId
let userId1
let userId2
let userId3
beforeEach(async () => {
groupId = await xo.call('group.create', {
name: 'Avengers',
})
})
it('delete a group', async () => {
await xo.call('group.delete', {
id: groupId,
})
const group = await getGroup(groupId)
expect(group).toBeUndefined()
})
it.skip("erase the group from user's groups list", async () => {
// create user and add it to the group
const userId = await createUser(xo, userIds, {
email: 'tony.stark@stark_industry.com',
password: 'IronMan',
})
await xo.call('group.addUser', {
id: groupId,
userId: userId,
})
// delete the group
await xo.call('group.delete', { id: groupId })
const user = await getUser(userId)
expect(user.groups).toEqual([])
})
it.skip("erase the user from group's users list", async () => {
// create user and add it to the group
const userId = await createUser(xo, userIds, {
email: 'tony.stark@stark_industry.com',
password: 'IronMan',
})
await xo.call('group.addUser', {
id: groupId,
userId: userId,
})
// delete the group
await xo.call('user.delete', { id: userId })
const group = await getGroup(groupId)
expect(group.users).toEqual([])
})
// FIXME: some users are not properly deleted because of a race condition with group deletion.
it.skip('delete the group with its users', async () => {
// create users
;[userId1, userId2, userId3] = await Promise.all([
xo.call('user.create', {
email: 'tony.stark@stark_industry.com',
password: 'IronMan',
}),
xo.call('user.create', {
email: 'natasha.romanov@shield.com',
password: 'BlackWidow',
}),
xo.call('user.create', {
email: 'pietro.maximoff@shield.com',
password: 'QickSilver',
}),
])
await xo.call('group.setUsers', {
id: groupId,
userIds: [userId1, userId2, userId3],
})
// delete the group with his users
await Promise.all([
xo.call('group.delete', {
id: groupId,
}),
deleteUsers(xo, [userId1, userId2, userId3]),
])
const [group, user1, user2, user3] = await Promise.all([
getGroup(groupId),
getUser(xo, userId1),
getUser(xo, userId2),
getUser(xo, userId3),
])
expect(group).toBeUndefined()
expect(user1).toBeUndefined()
expect(user2).toBeUndefined()
expect(user3).toBeUndefined()
})
})
// -------------------------------------------------------------------
describe('.getAll()', () => {
it('returns an array', async () => {
const groups = await xo.call('group.getAll')
expect(groups).toBeInstanceOf(Array)
})
})
// -------------------------------------------------------------------
describe('.setUsers ()', () => {
let groupId
let userId1
let userId2
let userId3
beforeEach(async () => {
;[groupId, userId1, userId2, userId3] = await Promise.all([
createGroup({
name: 'Avengers',
}),
createUser(xo, userIds, {
email: 'tony.stark@stark_industry.com',
password: 'IronMan',
}),
createUser(xo, userIds, {
email: 'natasha.romanov@shield.com',
password: 'BlackWidow',
}),
createUser(xo, userIds, {
email: 'pietro.maximoff@shield.com',
password: 'QickSilver',
}),
])
})
it('can set users of a group', async () => {
// add two users on the group
await xo.call('group.setUsers', {
id: groupId,
userIds: [userId1, userId2],
})
{
const [group, user1, user2, user3] = await Promise.all([
getGroup(groupId),
getUser(xo, userId1),
getUser(xo, userId2),
getUser(xo, userId3),
])
compareGroup(group, {
id: groupId,
name: 'Avengers',
users: [userId1, userId2],
})
expect(user1.groups).toEqual([groupId])
expect(user2.groups).toEqual([groupId])
expect(user3.groups).toEqual([])
}
// change users of the group
await xo.call('group.setUsers', {
id: groupId,
userIds: [userId1, userId3],
})
{
const [group, user1, user2, user3] = await Promise.all([
getGroup(groupId),
getUser(xo, userId1),
getUser(xo, userId2),
getUser(xo, userId3),
])
compareGroup(group, {
id: groupId,
name: 'Avengers',
users: [userId1, userId3],
})
expect(user1.groups).toEqual([groupId])
expect(user2.groups).toEqual([])
expect(user3.groups).toEqual([groupId])
}
})
})
// -------------------------------------------------------------------
describe('.addUser()', () => {
let groupId
let userId
beforeEach(async () => {
;[groupId, userId] = await Promise.all([
createGroup({
name: 'Avengers',
}),
createUser(xo, userIds, {
email: 'tony.stark@stark_industry.com',
password: 'IronMan',
}),
])
})
it('adds a user id to a group', async () => {
await xo.call('group.addUser', {
id: groupId,
userId: userId,
})
const [group, user] = await Promise.all([
getGroup(groupId),
getUser(xo, userId),
])
compareGroup(group, {
id: groupId,
name: 'Avengers',
users: [userId],
})
expect(user.groups).toEqual([groupId])
})
})
// -------------------------------------------------------------------
describe('removeUser()', () => {
let groupId
let userId
beforeEach(async () => {
;[groupId, userId] = await Promise.all([
createGroup({
name: 'Avengers',
}),
createUser(xo, userIds, {
email: 'tony.stark@stark_industry.com',
password: 'IronMan',
}),
])
await xo.call('group.addUser', {
id: groupId,
userId: userId,
})
})
it('removes a user to a group', async () => {
await xo.call('group.removeUser', {
id: groupId,
userId: userId,
})
const [group, user] = await Promise.all([
getGroup(groupId),
getUser(xo, userId),
])
compareGroup(group, {
id: groupId,
name: 'Avengers',
users: [],
})
expect(user.groups).toEqual([])
})
})
// -------------------------------------------------------------------
describe('set()', () => {
let groupId
beforeEach(async () => {
groupId = await createGroup({
name: 'Avengers',
})
})
it('changes name of a group', async () => {
await xo.call('group.set', {
id: groupId,
name: 'Guardians of the Galaxy',
})
const group = await getGroup(groupId)
compareGroup(group, {
id: groupId,
name: 'Guardians of the Galaxy',
users: [],
})
})
})
})

View File

@@ -0,0 +1,239 @@
/* eslint-env jest */
// Doc: https://github.com/moll/js-must/blob/master/doc/API.md#must
import expect from 'must'
import eventToPromise from 'event-to-promise'
import {
getAllHosts,
getConfig,
getMainConnection,
getVmToMigrateId,
waitObjectState,
} from './util'
import { find, forEach } from 'lodash'
// ===================================================================
describe('host', () => {
let xo
let serverId
let hostId
// -----------------------------------------------------------------
beforeAll(async () => {
jest.setTimeout(10e3)
let config
;[xo, config] = await Promise.all([getMainConnection(), getConfig()])
serverId = await xo.call('server.add', config.xenServer2).catch(() => {})
await eventToPromise(xo.objects, 'finish')
hostId = getHost(config.host1)
})
// -------------------------------------------------------------------
afterAll(async () => {
await xo.call('server.remove', {
id: serverId,
})
})
// -------------------------------------------------------------------
function getHost(nameLabel) {
const hosts = getAllHosts(xo)
const host = find(hosts, { name_label: nameLabel })
return host.id
}
// ===================================================================
describe('.set()', () => {
let nameLabel
let nameDescription
beforeEach(async () => {
// get values to set them at the end of the test
const host = xo.objects.all[hostId]
nameLabel = host.name_label
nameDescription = host.name_description
})
afterEach(async () => {
await xo.call('host.set', {
id: hostId,
name_label: nameLabel,
name_description: nameDescription,
})
})
it('changes properties of the host', async () => {
await xo.call('host.set', {
id: hostId,
name_label: 'labTest',
name_description: 'description',
})
await waitObjectState(xo, hostId, host => {
expect(host.name_label).to.be.equal('labTest')
expect(host.name_description).to.be.equal('description')
})
})
})
// ------------------------------------------------------------------
describe('.restart()', () => {
jest.setTimeout(330e3)
it('restart the host', async () => {
await xo.call('host.restart', { id: hostId })
await waitObjectState(xo, hostId, host => {
expect(host.current_operations)
})
await waitObjectState(xo, hostId, host => {
expect(host.power_state).to.be.equal('Halted')
})
await waitObjectState(xo, hostId, host => {
expect(host.power_state).to.be.equal('Running')
})
})
})
// ------------------------------------------------------------------
describe('.restartAgent()', () => {
it('restart a Xen agent on the host')
})
// ------------------------------------------------------------------
describe('.start()', () => {
jest.setTimeout(300e3)
beforeEach(async () => {
try {
await xo.call('host.stop', { id: hostId })
} catch (_) {}
// test if the host is shutdown
await waitObjectState(xo, hostId, host => {
expect(host.power_state).to.be.equal('Halted')
})
})
it('start the host', async () => {
await xo.call('host.start', { id: hostId })
await waitObjectState(xo, hostId, host => {
expect(host.power_state).to.be.equal('Running')
})
})
})
// ------------------------------------------------------------------
describe('.stop()', () => {
jest.setTimeout(300e3)
let vmId
beforeAll(async () => {
vmId = await getVmToMigrateId(xo)
try {
await xo.call('vm.start', { id: vmId })
} catch (_) {}
try {
await xo.call('vm.migrate', {
vm: vmId,
host: hostId,
})
} catch (_) {}
})
afterEach(async () => {
await xo.call('host.start', { id: hostId })
})
it('stop the host and shutdown its VMs', async () => {
await xo.call('host.stop', { id: hostId })
await Promise.all([
waitObjectState(xo, vmId, vm => {
expect(vm.$container).not.to.be.equal(hostId)
expect(vm.power_state).to.be.equal('Halted')
}),
waitObjectState(xo, hostId, host => {
expect(host.power_state).to.be.equal('Halted')
}),
])
})
})
// ------------------------------------------------------------------
describe('.detach()', () => {
it('ejects the host of a pool')
})
// ------------------------------------------------------------------
describe('.disable(), ', () => {
afterEach(async () => {
await xo.call('host.enable', {
id: hostId,
})
})
it('disables to create VM on the host', async () => {
await xo.call('host.disable', { id: hostId })
await waitObjectState(xo, hostId, host => {
expect(host.enabled).to.be.false()
})
})
})
// ------------------------------------------------------------------
describe('.enable()', async () => {
beforeEach(async () => {
await xo.call('host.disable', { id: hostId })
})
it('enables to create VM on the host', async () => {
await xo.call('host.enable', { id: hostId })
await waitObjectState(xo, hostId, host => {
expect(host.enabled).to.be.true()
})
})
})
// -----------------------------------------------------------------
describe('.createNetwork()', () => {
it('create a network')
})
// -----------------------------------------------------------------
describe('.listMissingPatches()', () => {
it('returns an array of missing patches in the host')
it('returns a empty array if up-to-date')
})
// ------------------------------------------------------------------
describe('.installPatch()', () => {
it('installs a patch patch on the host')
})
// ------------------------------------------------------------------
describe('.stats()', () => {
it('returns an array with statistics of the host', async () => {
const stats = await xo.call('host.stats', {
host: hostId,
})
expect(stats).to.be.an.object()
forEach(stats, function(array, key) {
expect(array).to.be.an.array()
})
})
})
})

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