Compare commits

...

160 Commits

Author SHA1 Message Date
Julien Fontanet
5e1d627834 0.10.0 2017-04-28 16:26:08 +02:00
Julien Fontanet
37b23bdc47 feat: JSON-RPC with fallback to XML-RPC (#50)
Better performance for XS >=7.0
2017-04-28 16:22:35 +02:00
greenkeeper[bot]
47f3788f55 fix(package): update ms to version 1.0.0 (#61)
https://greenkeeper.io/
2017-03-19 22:48:05 +01:00
Julien Fontanet
63be8afd35 chore(README): reverse XenServer versions order 2017-03-08 15:00:01 +01:00
Olivier Lambert
1166db7834 chore(README): add 7.1 and fix XenServer (#60) 2017-03-08 14:59:03 +01:00
greenkeeper[bot]
3d96765b1f fix(package): update event-to-promise to version 0.8.0 (#57)
https://greenkeeper.io/
2017-03-01 11:15:07 +01:00
greenkeeper[bot]
4c52fe11d3 chore(package): update standard to version 9.0.0 (#56)
https://greenkeeper.io/
2017-03-01 00:18:56 +01:00
greenkeeper[bot]
6c4178e107 chore(package): update jest to version 19.0.0 (#54)
https://greenkeeper.io/
2017-02-21 10:54:36 +01:00
Julien Fontanet
02d1fb436d 0.10.0-2 2017-02-13 15:52:26 +01:00
Julien Fontanet
1c97bf9019 0.10.0-1 2017-02-13 15:50:45 +01:00
Julien Fontanet
bfc4bb1f4c fix(package): commitmsg hook 2017-02-13 15:50:22 +01:00
Julien Fontanet
ccdce32562 chore(package): update all dependencies 2017-02-13 15:50:09 +01:00
Julien Fontanet
87fdcf14e5 feat(Xapi#_rawCall): add method to error object 2017-02-13 15:47:09 +01:00
Julien Fontanet
c0c63f49b1 0.10.0-0 2017-01-09 18:30:20 +01:00
Julien Fontanet
4e1bef9537 feat: redirect event 2017-01-09 18:29:54 +01:00
Julien Fontanet
8b78727d20 chore: add yarn.lock 2017-01-09 18:29:38 +01:00
Julien Fontanet
4cd7025be4 chore(package): update all dependencies 2017-01-09 17:30:52 +01:00
Julien Fontanet
015ce2690d chore(package): simplify scripts 2017-01-09 15:12:04 +01:00
Julien Fontanet
3e7f552a63 chore(package): use husky instead of ghooks 2017-01-09 15:05:36 +01:00
greenkeeper[bot]
22882b1ff2 fix(package): update promise-toolbox to version 0.8.0 (#53)
https://greenkeeper.io/
2017-01-06 11:31:23 +01:00
Julien Fontanet
c7e6e72ce8 fix(events watching): stop if not connected
Fixes vatesfr/xo-web#1799
2016-12-07 23:18:05 +01:00
Julien Fontanet
27e0621ad8 0.9.6 2016-11-23 09:46:33 +01:00
Julien Fontanet
1987a8c68a chore(events): rely on event.from timeout instead of inject
Should be enough.
2016-11-23 09:42:11 +01:00
Julien Fontanet
1642798aa6 0.9.5 2016-11-02 14:04:15 +01:00
Julien Fontanet
36b589c2db chore(package): various updates 2016-10-28 11:44:11 +02:00
Greenkeeper
1f710b9b78 chore(package): update promise-toolbox to version 0.7.0 (#49)
https://greenkeeper.io/
2016-10-24 15:00:35 +02:00
Julien Fontanet
9b9f0e5607 fix(package): fix various URLs 2016-10-14 11:57:37 +02:00
Julien Fontanet
590f6cb7b3 chore(package): update all dependencies 2016-10-14 11:53:39 +02:00
Julien Fontanet
2c35ee11b3 fix(Xapi#_init): fix promisify() usage 2016-10-11 17:22:43 +02:00
Greenkeeper
bd0c747d98 chore(package): update promise-toolbox to version 0.6.0 (#48)
https://greenkeeper.io/
2016-10-11 17:20:32 +02:00
Greenkeeper
b12fd45df1 chore(package): update babel-eslint to version 7.0.0 (#47)
https://greenkeeper.io/
2016-09-27 23:26:52 +02:00
Greenkeeper
c21fef7c72 Update standard to version 8.0.0 🚀 (#46)
https://greenkeeper.io/
2016-08-24 11:50:04 -04:00
Julien Fontanet
b7f34b9da6 0.9.4 2016-08-18 09:39:28 +02:00
Julien Fontanet
f82a6efda1 feat(Xapi#_watchEvents): keep-alive, inject 1 event per minute (#45)
See vatesfr/xo-web#1384
2016-08-18 09:37:31 +02:00
Julien Fontanet
556ca71c3a 0.9.3 2016-08-16 19:02:24 +02:00
Julien Fontanet
42be2d5031 fix(Xapi#_watchEvent): timeouts are in ms 2016-08-16 19:01:49 +02:00
Julien Fontanet
876e22b092 0.9.2 2016-08-16 18:09:47 +02:00
Julien Fontanet
f9f9c16cb0 feat(Xapi#_watchEvents): timeout request after 10mins (#44) 2016-08-16 18:07:44 +02:00
Greenkeeper
a881090e65 chore(package): update clarify to version 2.0.0 (#37)
https://greenkeeper.io/
2016-08-16 15:09:59 +02:00
Greenkeeper
3d9fce02a4 chore(package): update promise-toolbox to version 0.5.0 (#43)
https://greenkeeper.io/
2016-08-16 12:22:21 +02:00
Julien Fontanet
ab6b1e2c32 chore(Xapi#_transportCall): simplify and improve code
It may even fixes some issues.
2016-08-15 16:36:18 +02:00
Greenkeeper
59d9b3c6b4 chore(package): update nyc to version 8.1.0 (#42)
https://greenkeeper.io/
2016-08-14 19:07:12 +02:00
Julien Fontanet
8851a661c0 0.9.1 2016-08-05 10:49:08 +02:00
Julien Fontanet
2d327961da fix(objects): use $$ to avoid conflict with reserverd keys 2016-08-05 10:42:33 +02:00
Greenkeeper
3a41efbea8 chore(package): update mocha to version 3.0.0 (#40)
https://greenkeeper.io/
2016-08-02 16:20:00 +02:00
Greenkeeper
45b8fd0100 chore(package): update nyc to version 7.0.0 (#38)
https://greenkeeper.io/
2016-07-15 11:29:14 +02:00
Julien Fontanet
155f2fc36c fix(Xapi#_sessionCall): must pCatch when there is a predicate 2016-05-17 10:40:59 +02:00
Julien Fontanet
c3dc136de4 0.9.0 2016-05-11 17:44:45 +02:00
Julien Fontanet
4cbc5c4e2f fix(memory leak): due to Bluebird#cancellable()
To be fair, it may be due to an incorrect usage of
Bluebird#cancellable().

Bluebird has been completely removed at the same occasion.
2016-05-11 17:42:01 +02:00
Julien Fontanet
68820aaf59 fix(cli): add babel-polyfill dep 2016-05-11 17:40:56 +02:00
Julien Fontanet
cd65bc7683 chore: simplify _watchEvents() and _watchEventsLegacy() implementation
I finally understood ES6 Temporal Dead Zone!
2016-05-11 15:23:57 +02:00
Julien Fontanet
6e0956f09f feat(memory-test): split into inject-events 2016-05-11 11:30:49 +02:00
Julien Fontanet
1191f0ba93 feat(events): it's possible not to watch them 2016-05-10 15:35:43 +02:00
Julien Fontanet
6534ffea26 perf(objects): memoize () => this._pool 2016-05-10 14:55:19 +02:00
Julien Fontanet
f9173c41d1 chore(package): remove unnecessary babel-runtime 2016-05-10 10:12:24 +02:00
Julien Fontanet
a2faedcacb fix(debounce): it can be changed at runtime 2016-05-09 18:40:27 +02:00
Julien Fontanet
cb56b3b9d0 chore: rewrite some code using invoke pattern 2016-05-09 18:11:14 +02:00
Julien Fontanet
a61b50548c feat: memory test 2016-05-09 18:00:09 +02:00
Julien Fontanet
75ad588e0b fix(package): clarify, source-map-support & trace are prod deps 2016-05-09 14:07:55 +02:00
Julien Fontanet
6564edcc32 chore(package): update all dependencies (except Bluebird) 2016-05-04 15:54:04 +02:00
Greenkeeper
c46c0018ea chore(package): update nyc to version 6.4.0 (#9)
https://greenkeeper.io/
2016-04-27 22:20:32 +02:00
Julien Fontanet
35e8dcc3be 0.8.0 2016-04-26 08:46:22 +02:00
Julien Fontanet
d1600fd058 fix: handle UUID changes (fix #5). (#7) 2016-04-26 08:46:04 +02:00
Julien Fontanet
1416fb0c71 Xapi#call(): only *.get_*() methods in readonly mode. 2016-04-19 16:32:18 +01:00
Julien Fontanet
2975db247d Do not attempt to fix JSON is parsing is successful. 2016-04-19 13:29:51 +01:00
Julien Fontanet
03eaa652ce Optional debounce for events. 2016-04-14 17:55:41 +02:00
Julien Fontanet
eac29993d3 0.7.4 2016-03-22 09:37:55 +01:00
Julien Fontanet
af2a9225b8 Fix invalid \t in JSON. 2016-03-09 17:41:55 +01:00
Julien Fontanet
a24de7fe3f Fix type case in README. 2016-02-29 18:02:30 +01:00
Julien Fontanet
16a9f44d4d Xapi#getObject(): behave nicely when a XAPI object is passed. 2016-01-28 10:08:26 +01:00
Julien Fontanet
6fcc148105 0.7.3 2016-01-05 16:09:19 +01:00
Julien Fontanet
3485cb4ec4 Guard against null/undefined error.res. 2016-01-05 16:09:06 +01:00
Julien Fontanet
b2a51bd658 0.7.2 2015-12-18 11:29:08 +01:00
Julien Fontanet
e5ab1dc154 Fix opaque ref detection in arrays. 2015-12-18 11:28:58 +01:00
Julien Fontanet
6274969635 0.7.1 2015-12-18 11:23:29 +01:00
Julien Fontanet
069c430346 Fix opaque ref detection. 2015-12-18 11:23:24 +01:00
Julien Fontanet
cbcc4dd21d 0.7.0 2015-12-17 16:33:55 +01:00
Julien Fontanet
b4cdf4d277 Minor optimizations. 2015-12-16 14:19:30 +01:00
Julien Fontanet
716d7bfcf6 Optimize opaque refs detection. 2015-12-16 14:15:37 +01:00
Julien Fontanet
b45a169a2f Read only mode can be altered after initial construction. 2015-12-16 13:58:33 +01:00
Julien Fontanet
720b9ef999 0.6.9 2015-12-03 12:40:31 +01:00
Julien Fontanet
9b9e4dddfc Gracefully fails if error.res does not exists. 2015-12-03 12:39:59 +01:00
Julien Fontanet
7434e0352f 0.6.8 2015-12-02 17:37:28 +01:00
Julien Fontanet
26d61af902 Fall back to legacy events on server failure (work around #2). 2015-12-02 17:36:29 +01:00
Julien Fontanet
5bd12c5f9e Fix XML-RPC error display. 2015-12-02 17:31:13 +01:00
Julien Fontanet
e07fae4290 Revert to Babel 5 for now. 2015-12-02 17:30:42 +01:00
Julien Fontanet
e304395179 Work around ESLint bug. 2015-11-11 15:58:15 +01:00
Julien Fontanet
6b83130853 Various updates. 2015-11-11 15:52:12 +01:00
Julien Fontanet
9565718699 Update deps. 2015-11-11 15:52:06 +01:00
Julien Fontanet
ac11885379 0.6.7 2015-10-23 16:53:53 +02:00
Julien Fontanet
277669a13c Fix events watching XenServer < 6.0. 2015-10-23 16:53:42 +02:00
Julien Fontanet
fcbc476462 Fix coding style. 2015-10-23 16:53:05 +02:00
Julien Fontanet
4944b415c7 0.6.6 2015-10-23 16:03:02 +02:00
Julien Fontanet
5da7312d2d Correctly publish .mocha.js 2015-10-23 16:02:42 +02:00
Julien Fontanet
954d19fe50 Fix objects collection in read-only mode. 2015-10-23 15:33:22 +02:00
Julien Fontanet
addd86f5d2 Better stack traces in CLI. 2015-10-23 15:33:02 +02:00
Julien Fontanet
1b90223210 0.6.5 2015-10-23 14:54:02 +02:00
Julien Fontanet
95989ff63b Add find() and findAll() in CLI. 2015-10-23 14:51:25 +02:00
Julien Fontanet
799f758dce Document constructor options. 2015-10-20 15:46:50 +02:00
Julien Fontanet
e075f1c08b 0.6.4 2015-10-06 14:35:33 +02:00
Julien Fontanet
7e0aa719b4 Use Collection.unset() instead of remove() to avoid exceptions. 2015-10-02 14:05:26 +02:00
Julien Fontanet
4ee352fdb2 Always use instances of Error for errors. 2015-09-14 16:45:17 +02:00
Julien Fontanet
96ea3ded4a Better stacktraces in CLI. 2015-09-14 16:02:09 +02:00
Julien Fontanet
8bbc6e9ff5 Initial read only mode (fix #3). 2015-09-14 16:01:46 +02:00
Julien Fontanet
af7029812c 0.6.3 2015-09-11 15:32:16 +02:00
Julien Fontanet
c517b59138 Enable testing on Node 4. 2015-09-10 15:22:09 +02:00
Julien Fontanet
5485e8a322 Test on iojs 3 and use containers on Travis. 2015-09-07 16:23:33 +02:00
Julien Fontanet
2540ac34b3 Be verbose on XML-RPC errors. 2015-08-28 08:51:38 +02:00
Julien Fontanet
76e5d41a34 Upgrade deps. 2015-08-28 08:51:15 +02:00
Julien Fontanet
2c32a4e912 0.6.2 2015-08-10 15:52:55 +02:00
Julien Fontanet
c66f7235b6 Fix master change. 2015-08-10 15:51:45 +02:00
Julien Fontanet
5444381f7d 0.6.1 2015-06-30 17:20:31 +02:00
Julien Fontanet
dc44679031 Optimize and clean _watchEvents() & _watchEventsLegacy(). 2015-06-30 14:24:43 +02:00
Julien Fontanet
2cbd17b745 Better traces. 2015-06-30 11:07:33 +02:00
Julien Fontanet
9ef13696d8 0.6.0 2015-06-23 10:39:55 +02:00
Julien Fontanet
c3f635fd12 Ask JSON encoded values to XenApi.
The XML is therefore much faster to parse (3-4 ×).
2015-06-23 10:39:04 +02:00
Julien Fontanet
e3d1380435 0.5.7 2015-06-23 10:38:41 +02:00
Julien Fontanet
f83737b538 Make objects immutable. 2015-06-23 09:17:42 +02:00
Julien Fontanet
bb1ea4e4d0 Optimization for empty arrays. 2015-06-23 09:13:43 +02:00
Julien Fontanet
9cb4de2ea8 Inline an only-used-once function. 2015-06-22 23:33:52 +02:00
Julien Fontanet
048cbf60ec Added tested Xen Server versions in README. 2015-06-22 16:23:38 +02:00
Julien Fontanet
36f40b4188 Support Xen Server < 6. 2015-06-22 16:19:32 +02:00
Julien Fontanet
a3bba92063 Remove duplicate source-map-support. 2015-06-19 15:53:23 +02:00
Julien Fontanet
ebcc6c9341 Remove unused lodash.{find,findkey,size}. 2015-06-19 15:51:22 +02:00
Julien Fontanet
95f765055e Fix invalid session handling. 2015-06-19 15:15:23 +02:00
Julien Fontanet
49aa5ffccc Password can be supplied on the command line. 2015-06-19 15:13:21 +02:00
Julien Fontanet
d09d3fa80b 0.5.6 2015-06-18 13:40:18 +02:00
Julien Fontanet
4c8cd50643 Better build & tests. 2015-06-18 13:15:27 +02:00
Julien Fontanet
eee72f4f27 Test on both io.js v1 & v2. 2015-06-18 13:13:40 +02:00
Julien Fontanet
45f6a7cb4d Minor changes. 2015-06-17 17:09:46 +02:00
Julien Fontanet
8866bd8663 Minor code simplification. 2015-06-17 15:53:48 +02:00
Julien Fontanet
3f9c515f1d Better error messages. 2015-06-17 15:39:33 +02:00
Julien Fontanet
c92567d4fa 0.5.5 2015-05-27 17:04:04 +02:00
Julien Fontanet
df3c76fa72 Remove useless statement. 2015-05-26 16:47:14 +02:00
Julien Fontanet
cea4157402 Perf traces in the CLI. 2015-05-26 16:35:51 +02:00
Julien Fontanet
29ce3bd05e Source maps support in the CLI. 2015-05-22 10:38:38 +02:00
Julien Fontanet
b3d58f4f0c Update to xo-collection 0.3. 2015-05-22 10:38:08 +02:00
Julien Fontanet
d93d234c71 Fix Travis badge. 2015-05-14 15:06:27 +02:00
Julien Fontanet
7fe9ae8a04 Document custom props. 2015-05-14 15:04:55 +02:00
Julien Fontanet
87cf1ed7cb All custom properties are read-only and non enumerable. 2015-05-14 14:58:54 +02:00
Julien Fontanet
a0ba5c8a57 Fix auto links for arrays. 2015-05-14 14:58:27 +02:00
Julien Fontanet
d7208a15d9 Remove unused dep. 2015-05-14 14:46:06 +02:00
Julien Fontanet
debde0c67a Links do not shadow refs. 2015-05-14 14:39:36 +02:00
Julien Fontanet
97db55156a $pool should not be enumerable. 2015-05-13 17:29:24 +02:00
Julien Fontanet
9d3477d465 Fix propagation of Xapi errors. 2015-05-13 17:27:22 +02:00
Julien Fontanet
031af000e6 Alias Object.create() and Object.defineProperty() for perf. 2015-05-06 18:14:43 +02:00
Julien Fontanet
0512fac3aa Better name for the OpaqueRef regex constant. 2015-05-06 18:14:20 +02:00
Julien Fontanet
4272e8196a Bypass Xapi#objects getter internally for perf. 2015-05-06 14:08:59 +02:00
Julien Fontanet
140f9d05df Code style and comments. 2015-05-06 14:06:25 +02:00
Julien Fontanet
9222733243 Xapi#getObject() 2015-05-06 14:06:17 +02:00
Julien Fontanet
5838c56c4e Xapi#getObjectByRef() and Xapi#getObjectByUuid() 2015-05-06 13:27:59 +02:00
Julien Fontanet
1814e0a260 0.5.4 2015-05-05 13:46:03 +02:00
Julien Fontanet
711c5781e6 Export wrapError(). 2015-05-05 13:45:51 +02:00
Julien Fontanet
7e8c2211d8 The REPL waits for promise completion. 2015-04-21 17:20:38 +02:00
Julien Fontanet
f0858b7d93 0.5.3 2015-04-20 19:14:48 +02:00
Julien Fontanet
3af6c28ab0 Do not swallow all errors. 2015-04-20 19:01:05 +02:00
Julien Fontanet
5c31c7f14c Typo! 2015-04-20 19:00:52 +02:00
Julien Fontanet
2610a9c777 0.5.2 2015-04-20 15:19:06 +02:00
Julien Fontanet
58cf611497 Fix this._sessionId. 2015-04-20 15:18:48 +02:00
Julien Fontanet
61631e405b Coding style. 2015-04-17 16:22:23 +02:00
15 changed files with 4678 additions and 276 deletions

View File

@@ -1,7 +1,11 @@
/bower_components/
/coverage/
/dist/
/node_modules/
npm-debug.log
npm-debug.log.*
pnpm-debug.log
pnpm-debug.log.*
yarn-error.log*
!node_modules/*
node_modules/*/
/plot.dat

View File

@@ -1,2 +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

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

View File

@@ -1,7 +1,15 @@
# xen-api [![Build Status](https://travis-ci.org/js-xen-api.png?branch=master)](https://travis-ci.org/js-xen-api)
# xen-api [![Build Status](https://travis-ci.org/vatesfr/xen-api.png?branch=master)](https://travis-ci.org/vatesfr/xen-api)
> Connector to the Xen API
Tested with:
- XenServer 7.1
- XenServer 7
- XenServer 6.5
- XenServer 6.2
- XenServer 5.6
## Install
Installation of the [npm package](https://npmjs.org/package/xen-api):
@@ -22,9 +30,18 @@ var xapi = createClient({
auth: {
user: 'root',
password: 'important secret password'
}
},
readOnly: false
})
```
Options:
- `url`: address of a host in the pool we are trying to connect to
- `auth`: credentials used to sign in
- `readOnly = false`: if true, no methods with side-effects can be called
```js
// Force connection.
xapi.connect().catch(error => {
console.error(error)
@@ -36,6 +53,22 @@ xapi.objects.on('add', function (objects) {
})
```
> Note: all objects are frozen and cannot be altered!
Custom fields on objects (hidden ie. non enumerable):
- `$type`: the type of the object (`VM`, `task`, …);
- `$ref`: the (opaque) reference of the object;
- `$id`: the identifier of this object (its UUID if any, otherwise its reference);
- `$pool`: the pool object this object belongs to.
Furthermore, any field containing a reference (or references if an
array) can be resolved by prepending the field name with a `$`:
```javascript
console.log(xapi.pool.$master.$resident_VMs[0].name_label)
// vm1
```
### CLI
A CLI is provided to help exploration and discovery of the XAPI.
@@ -45,32 +78,36 @@ A CLI is provided to help exploration and discovery of the XAPI.
Password: ******
root@xen1.company.net> xapi.status
'connected'
root@xen1.company.net> xapi.pool.master.name_label
root@xen1.company.net> xapi.pool.master
'OpaqueRef:ec7c5147-8aee-990f-c70b-0de916a8e993'
root@xen1.company.net> xapi.pool.$master.name_label
'xen1'
```
To ease searches, `find()` and `findAll()` functions are available:
```
root@xen1.company.net> findAll({ $type: 'vm' }).length
183
```
## Development
### Installing dependencies
```
# Install dependencies
> npm install
```
### Compilation
# Run the tests
> npm test
The sources files are watched and automatically recompiled on changes.
```
# Continuously compile
> npm run dev
```
### Tests
# Continuously run the tests
> npm run dev-test
```
> npm run test-dev
# Build for production (automatically called by npm install)
> npm run build
```
## Contributions
@@ -80,7 +117,7 @@ the code.
You may:
- report any [issue](https://github.com/julien-f/js-xen-api/issues)
- report any [issue](https://github.com/pbdxen-api/issues)
you've encountered;
- fork and create a pull request.

View File

@@ -0,0 +1,4 @@
set yrange [ 0 : ]
set grid
plot for [i=2:4] "plot.dat" using 1:i with lines

View File

@@ -1,6 +1,6 @@
{
"name": "xen-api",
"version": "0.5.1",
"version": "0.10.0",
"license": "ISC",
"description": "Connector to the Xen API",
"keywords": [
@@ -10,11 +10,11 @@
"xenapi",
"xapi"
],
"homepage": "https://github.com/julien-f/js-xen-api",
"bugs": "https://github.com/julien-f/js-xen-api/issues",
"homepage": "https://github.com/vatesfr/xen-api",
"bugs": "https://github.com/vatesfr/xen-api/issues",
"repository": {
"type": "git",
"url": "https://github.com/julien-f/js-xen-api.git"
"url": "https://github.com/vatesfr/xen-api.git"
},
"author": {
"name": "Julien Fontanet",
@@ -28,39 +28,78 @@
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"babel-runtime": "^5",
"bluebird": "^2.9.21",
"debug": "^2.1.3",
"event-to-promise": "^0.3.2",
"exec-promise": "^0.5.1",
"lodash.find": "^3.2.0",
"lodash.findkey": "^3.0.1",
"lodash.foreach": "^3.0.2",
"lodash.size": "^3.0.0",
"lodash.startswith": "^3.0.1",
"make-error": "^1.0.2",
"minimist": "^1.1.1",
"babel-polyfill": "^6.22.0",
"blocked": "^1.2.1",
"debug": "^2.6.1",
"event-to-promise": "^0.8.0",
"exec-promise": "^0.6.1",
"json-rpc-protocol": "^0.11.2",
"kindof": "^2.0.0",
"lodash": "^4.17.4",
"make-error": "^1.2.1",
"minimist": "^1.2.0",
"ms": "^1.0.0",
"promise-toolbox": "^0.8.0",
"pw": "0.0.4",
"xmlrpc": "^1.3.0",
"xo-collection": "0.0.1"
"superagent": "^3.5.2",
"xmlrpc": "^1.3.2",
"xo-collection": "^0.4.1"
},
"devDependencies": {
"babel": "^5",
"mocha": "*",
"must": "*",
"standard": "*"
"babel-cli": "^6.22.2",
"babel-eslint": "^7.1.1",
"babel-plugin-lodash": "^3.2.11",
"babel-preset-env": "^1.1.8",
"babel-preset-stage-0": "^6.22.0",
"cross-env": "^3.1.4",
"dependency-check": "^2.8.0",
"husky": "^0.13.1",
"jest": "^19.0.0",
"rimraf": "^2.5.4",
"standard": "^9.0.0"
},
"scripts": {
"build": "mkdir --parents dist && babel --optional=runtime --compact=true --source-maps --out-dir=dist/ src/",
"dev": "mkdir --parents dist && babel --watch --optional=runtime --source-maps --out-dir=dist/ src/",
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"commitmsg": "npm test",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"dev-test": "jest --bail --watch",
"plot": "gnuplot -p memory-test.gnu",
"posttest": "standard && dependency-check ./package.json",
"prebuild": "rimraf dist/",
"predev": "npm run prebuild",
"prepublish": "npm run build",
"test": "standard && mocha 'dist/**/*.spec.js'",
"test-dev": "standard && mocha --watch --reporter=min 'dist/**/*.spec.js'"
"test": "jest"
},
"babel": {
"plugins": [
"lodash"
],
"presets": [
[
"env",
{
"targets": {
"node": 4
}
}
],
"stage-0"
]
},
"jest": {
"testPathDirs": [
"<rootDir>/src"
],
"testRegex": "\\.spec\\.js$"
},
"standard": {
"ignore": [
"dist/**"
]
"dist"
],
"parser": "babel-eslint"
}
}

View File

@@ -1,22 +1,48 @@
#!/usr/bin/env node
import Bluebird, {coroutine} from 'bluebird'
import 'babel-polyfill'
import blocked from 'blocked'
import createDebug from 'debug'
import eventToPromise from 'event-to-promise'
import execPromise from 'exec-promise'
import minimist from 'minimist'
import pw from 'pw'
import {start as createRepl} from 'repl'
import { asCallback, fromCallback } from 'promise-toolbox'
import { filter, find } from 'lodash'
import { start as createRepl } from 'repl'
import {createClient} from './'
import { createClient } from './'
const usage = `Usage: xen-api <url> <user>`
// ===================================================================
const main = coroutine(function * (args) {
function askPassword (prompt = 'Password: ') {
if (prompt) {
process.stdout.write(prompt)
}
return new Promise(resolve => {
pw(resolve)
})
}
function required (name) {
throw new Error(`missing required argument ${name}`)
}
// ===================================================================
const usage = 'Usage: xen-api <url> <user> [<password>]'
const main = async args => {
const opts = minimist(args, {
boolean: ['help'],
boolean: ['help', 'read-only', 'verbose'],
alias: {
help: 'h'
debounce: 'd',
help: 'h',
'read-only': 'ro',
verbose: 'v'
}
})
@@ -24,29 +50,55 @@ const main = coroutine(function * (args) {
return usage
}
const [url, user] = opts._
if (!url || !user) {
throw new Error('missing arguments')
if (opts.verbose) {
// Does not work perfectly.
//
// https://github.com/visionmedia/debug/pull/156
createDebug.enable('xen-api,xen-api:*')
}
process.stdout.write('Password: ')
const password = yield new Bluebird(resolve => {
pw(resolve)
})
const [
url = required('url'),
user = required('user'),
password = await askPassword()
] = opts._
const xapi = createClient({url, auth: {user, password}})
yield xapi.connect()
{
const debug = createDebug('xen-api:perf')
blocked(ms => {
debug('blocked for %sms', ms | 0)
})
}
const xapi = createClient({
url,
auth: { user, password },
debounce: opts.debounce != null ? +opts.debounce : null,
readOnly: opts.ro
})
await xapi.connect()
const repl = createRepl({
prompt: `${xapi._humanId}> `
})
repl.context.xapi = xapi
yield eventToPromise(repl, 'exit')
repl.context.find = predicate => find(xapi.objects.all, predicate)
repl.context.findAll = predicate => filter(xapi.objects.all, predicate)
// Make the REPL waits for promise completion.
repl.eval = (evaluate => (cmd, context, filename, cb) => {
fromCallback(cb => {
evaluate.call(repl, cmd, context, filename, cb)
})::asCallback(cb)
})(repl.eval)
await eventToPromise(repl, 'exit')
try {
yield xapi.disconnect()
await xapi.disconnect()
} catch (error) {}
})
}
export default main
if (!module.parent) {

View File

@@ -1,19 +1,38 @@
import Bluebird, {promisify} from 'bluebird'
import Collection from 'xo-collection'
import createDebug from 'debug'
import forEach from 'lodash.foreach'
import startsWith from 'lodash.startswith'
import {BaseError} from 'make-error'
import kindOf from 'kindof'
import ms from 'ms'
import { BaseError } from 'make-error'
import { EventEmitter } from 'events'
import { filter, forEach, isArray, isObject, map, startsWith } from 'lodash'
import {
createClient as createXmlRpcClient,
createSecureClient as createSecureXmlRpcClient
} from 'xmlrpc'
import {EventEmitter} from 'events'
catchPlus as pCatch,
delay as pDelay
} from 'promise-toolbox'
import autoTransport from './transports/auto'
const debug = createDebug('xen-api')
// ===================================================================
function invoke (fn) {
const n = arguments.length - 1
if (!n) {
return fn()
}
fn = arguments[n]
const args = new Array(n)
for (let i = 0; i < n; ++i) {
args[i] = arguments[i]
}
return fn.apply(undefined, args)
}
// ===================================================================
// http://www.gnu.org/software/libc/manual/html_node/Error-Codes.html
const NETWORK_ERRORS = {
// Connection has been closed outside of our control.
@@ -35,7 +54,7 @@ const NETWORK_ERRORS = {
ETIMEDOUT: true
}
const isNetworkError = (error) => NETWORK_ERRORS[error.code]
const isNetworkError = ({code}) => NETWORK_ERRORS[code]
// -------------------------------------------------------------------
@@ -44,170 +63,270 @@ const XAPI_NETWORK_ERRORS = {
HOST_HAS_NO_MANAGEMENT_IP: true
}
const isXapiNetworkError = (error) => XAPI_NETWORK_ERRORS[error.code]
const isXapiNetworkError = ({code}) => XAPI_NETWORK_ERRORS[code]
// -------------------------------------------------------------------
const areEventsLost = (error) => error.code === 'EVENTS_LOST'
const areEventsLost = ({code}) => code === 'EVENTS_LOST'
const isHostSlave = (error) => error.code === 'HOST_IS_SLAVE'
const isHostSlave = ({code}) => code === 'HOST_IS_SLAVE'
const isSessionInvalid = (error) => error.code === 'SESSION_INVALID'
const isMethodUnknown = ({code}) => code === 'MESSAGE_METHOD_UNKNOWN'
const isSessionInvalid = ({code}) => code === 'SESSION_INVALID'
// -------------------------------------------------------------------
class XapiError extends BaseError {
constructor (error) {
super(error[0])
constructor ([code, ...params]) {
super(`${code}(${params.join(', ')})`)
this.code = error[0]
this.params = error.slice(1)
this.code = code
this.params = params
// slot than can be assigned later
this.method = null
}
}
export const wrapError = error => new XapiError(error)
// ===================================================================
const URL_RE = /^(http(s)?:\/\/)?([^/]+?)(?::([0-9]+))?(?:\/.*)?$/
function parseUrl (url) {
const URL_RE = /^(?:http(s)?:\/*)?([^/]+?)(?::([0-9]+))?\/?$/
const parseUrl = url => {
const matches = URL_RE.exec(url)
if (!matches) {
throw new Error('invalid URL: ' + url)
}
const [, protocol, , host, port] = matches
let [, , isSecure] = matches
if (!protocol) {
isSecure = true
url = {
hostname: matches[2],
port: matches[3],
protocol: matches[1] === 's' ? 'https:' : 'http:'
}
return {
isSecure: Boolean(isSecure),
host,
port: port !== undefined ?
+port :
isSecure ? 443 : 80
}
return url
}
// -------------------------------------------------------------------
const noop = () => {}
const {
create: createObject,
defineProperties,
defineProperty,
freeze: freezeObject,
prototype: { toString }
} = Object
const isString = invoke(toString.call(''), tag =>
value => toString.call(value) === tag
)
// -------------------------------------------------------------------
const notConnectedPromise = Bluebird.reject(new Error('not connected'))
const OPAQUE_REF_PREFIX = 'OpaqueRef:'
const isOpaqueRef = value => isString(value) && startsWith(value, OPAQUE_REF_PREFIX)
// Does nothing but avoid a Bluebird message error.
notConnectedPromise.catch(noop)
// -------------------------------------------------------------------
const isReadOnlyCall = invoke(/^[^.]+\.get_/, RE => (method, args) => (
args.length === 1 &&
isOpaqueRef(args[0]) &&
RE.test(method)
))
// -------------------------------------------------------------------
const getKey = o => o.$id
// -------------------------------------------------------------------
const EMPTY_ARRAY = freezeObject([])
// ===================================================================
const MAX_TRIES = 5
const CONNECTED = 'connected'
const CONNECTING = 'connecting'
const DISCONNECTED = 'disconnected'
// -------------------------------------------------------------------
export class Xapi extends EventEmitter {
constructor (opts) {
super()
this._url = parseUrl(opts.url)
this._auth = opts.auth
this._sessionId = notConnectedPromise
this._init()
this._pool = null
this._objectsByRefs = Object.create(null)
this._objectsByRefs['OpaqueRef:NULL'] = null
this._objects = new Collection()
this._objects.getId = (object) => object.$id
this._readOnly = Boolean(opts.readOnly)
this._sessionId = null
this._url = parseUrl(opts.url)
this._fromToken = ''
this.on('connected', this._watchEvents)
this.on('disconnected', () => {
if (opts.watchEvents !== false) {
this._debounce = opts.debounce == null
? 200
: opts.debounce
this._fromToken = ''
this._objects.clear()
})
// Memoize this function _addObject().
this._getPool = () => this._pool
const objects = this._objects = new Collection()
objects.getKey = getKey
this._objectsByRefs = createObject(null)
this._objectsByRefs['OpaqueRef:NULL'] = null
this.on('connected', this._watchEvents)
this.on('disconnected', () => {
this._fromToken = ''
objects.clear()
})
}
}
get _url () {
return this.__url
}
set _url (url) {
this.__url = url
this._call = autoTransport({ url })
}
get readOnly () {
return this._readOnly
}
set readOnly (ro) {
this._readOnly = Boolean(ro)
}
get sessionId () {
if (this.status !== 'connected') {
const id = this._sessionId
if (!id || id === CONNECTING) {
throw new Error('sessionId is only available when connected')
}
return this._sessionId.value()
return id
}
get status () {
const {_sessionId: sessionId} = this
const id = this._sessionId
if (sessionId.isFulfilled()) {
return 'connected'
}
if (sessionId.isPending()) {
return 'connecting'
}
return 'disconnected'
return id
? (
id === CONNECTING
? CONNECTING
: CONNECTED
)
: DISCONNECTED
}
get _humanId () {
return `${this._auth.user}@${this._url.host}`
return `${this._auth.user}@${this._url.hostname}`
}
connect () {
const {status} = this
if (status === 'connected') {
return Bluebird.reject(new Error('already connected'))
if (status === CONNECTED) {
return Promise.reject(new Error('already connected'))
}
if (status === 'connecting') {
return Bluebird.reject(new Error('already connecting'))
if (status === CONNECTING) {
return Promise.reject(new Error('already connecting'))
}
this._sessionId = this._transportCall('session.login_with_password', [
this._sessionId = CONNECTING
return this._transportCall('session.login_with_password', [
this._auth.user,
this._auth.password
])
]).then(
sessionId => {
this._sessionId = sessionId
return this._sessionId.then(() => {
debug('%s: connected', this._humanId)
debug('%s: connected', this._humanId)
this.emit('connected')
})
this.emit(CONNECTED)
},
error => {
this._sessionId = null
throw error
}
)
}
disconnect () {
const {status} = this
return Promise.resolve().then(() => {
const { status } = this
if (status === 'disconnected') {
return Bluebird.reject('already disconnected')
}
if (status === DISCONNECTED) {
return Promise.reject(new Error('already disconnected'))
}
if (status === 'connecting') {
return this._sessionId.cancel().catch(Bluebird.CancellationError, () => {
debug('%s: disconnected', this._humanId)
this._sessionId = null
this.emit('disconnected')
})
}
this._sessionId = notConnectedPromise
return Bluebird.resolve().then(() => {
debug('%s: disconnected', this._humanId)
this.emit('disconnected')
this.emit(DISCONNECTED)
})
}
// High level calls.
call (method, ...args) {
return this._sessionCall(method, args)
return this._readOnly && !isReadOnlyCall(method, args)
? Promise.reject(new Error(`cannot call ${method}() in read only mode`))
: this._sessionCall(method, args)
}
// Nice getter which returns the object for a given $id (internal to
// this lib), UUID (unique identifier that some objects have) or
// opaque reference (internal to XAPI).
getObject (idOrUuidOrRef, defaultValue) {
const object = isString(idOrUuidOrRef)
? (
// if there is an UUID, it is also the $id.
this._objects.all[idOrUuidOrRef] ||
this._objectsByRefs[idOrUuidOrRef]
)
: this._objects.all[idOrUuidOrRef.$id]
if (object) return object
if (arguments.length > 1) return defaultValue
throw new Error('there is not object can be matched to ' + idOrUuidOrRef)
}
// Returns the object for a given opaque reference (internal to
// XAPI).
getObjectByRef (ref, defaultValue) {
const object = this._objectsByRefs[ref]
if (object) return object
if (arguments.length > 1) return defaultValue
throw new Error('there is no object with the ref ' + ref)
}
// Returns the object for a given UUID (unique identifier that some
// objects have).
getObjectByUuid (uuid, defaultValue) {
// Objects ids are already UUIDs if they have one.
const object = this._objects.all[uuid]
if (object) return object
if (arguments.length > 1) return defaultValue
throw new Error('there is no object with the UUID ' + uuid)
}
get pool () {
@@ -220,31 +339,30 @@ export class Xapi extends EventEmitter {
// Medium level call: handle session errors.
_sessionCall (method, args) {
if (startsWith(method, 'session.')) {
return Bluebird.reject(
new Error('session.*() methods are disabled from this interface')
)
try {
if (startsWith(method, 'session.')) {
throw new Error('session.*() methods are disabled from this interface')
}
return this._transportCall(method, [this.sessionId].concat(args))
::pCatch(isSessionInvalid, () => {
// XAPI is sometimes reinitialized and sessions are lost.
// Try to login again.
debug('%s: the session has been reinitialized', this._humanId)
this._sessionId = null
return this.connect().then(() => this._sessionCall(method, args))
})
} catch (error) {
return Promise.reject(error)
}
return this._sessionId.then((sessionId) => {
return this._transportCall(method, [sessionId].concat(args))
}).catch(isSessionInvalid, () => {
// XAPI is sometimes reinitialized and sessions are lost.
// Try to login again.
debug('%s: the session has been reinitialized', this._humanId)
this._sessionId = null
return this._sessionCall(method, args)
})
}
// Low level call: handle transport errors.
_transportCall (method, args, tries = 1) {
debug('%s: %s(...)', this._humanId, method)
return this._rawCall(method, args)
.catch(isNetworkError, isXapiNetworkError, error => {
_transportCall (method, args) {
let tries = 1
const loop = () => this._rawCall(method, args)
::pCatch(isNetworkError, isXapiNetworkError, error => {
debug('%s: network error %s', this._humanId, error.code)
if (!(tries < MAX_TRIES)) {
@@ -257,123 +375,254 @@ export class Xapi extends EventEmitter {
// TODO: ability to force immediate reconnection
// TODO: implement back-off
return Bluebird.delay(5e3).then(() => {
return pDelay(5e3).then(() => {
// TODO: handling not responding host.
return this._transportCall(method, args, tries + 1)
return this._transportCall(method, args, startTime, tries + 1)
})
})
.catch(isHostSlave, ({params: [master]}) => {
::pCatch(isHostSlave, ({params: [master]}) => {
debug('%s: host is slave, attempting to connect at %s', this._humanId, master)
this._url.host = master
this._init()
const newUrl = {
...this._url,
hostname: master
}
this.emit('redirect', newUrl)
this._url = newUrl
return this._transportCall(method, args)
return this._transportCall(method, args, startTime)
})
const startTime = Date.now()
return loop().then(
result => {
debug(
'%s: %s(...) [%s] ==> %s',
this._humanId,
method,
ms(Date.now() - startTime),
kindOf(result)
)
return result
},
error => {
debug(
'%s: %s(...) [%s] =!> %s',
this._humanId,
method,
ms(Date.now() - startTime),
error
)
throw error
}
)
}
// Lowest level call: do not handle any errors.
_rawCall (method, args) {
return this._xmlRpcCall(method, args)
.then(result => {
const {Status: status} = result
return this._call(method, args).catch(error => {
if (isArray(error)) {
error = wrapError(error)
}
// Return the plain result if it does not have a valid XAPI
// format.
if (!status) {
return result
}
if (status === 'Success') {
return result.Value
}
throw new XapiError(result.ErrorDescription)
})
.cancellable()
}
_init () {
const {isSecure, host, port} = this._url
const client = (isSecure ?
createSecureXmlRpcClient :
createXmlRpcClient
)({
host,
port,
rejectUnauthorized: false,
timeout: 10
error.method = method
throw error
})
this._xmlRpcCall = promisify(client.methodCall, client)
}
_normalizeObject (type, ref, object) {
_addObject (type, ref, object) {
const {_objectsByRefs: objectsByRefs} = this
const REF_RE = /^OpaqueRef:/
forEach(object, function resolveIfLink(value, key, object) {
if (typeof value === 'string' && REF_RE.test(value)) {
Object.defineProperty(object, key, {
enumerable: true,
const reservedKeys = {
id: true,
pool: true,
ref: true,
type: true
}
const getKey = (key, obj) => reservedKeys[key] && obj === object
? `$$${key}`
: `$${key}`
// Creates resolved properties.
forEach(object, function resolveObject (value, key, object) {
if (isArray(value)) {
if (!value.length) {
// If the array is empty, it isn't possible to be sure that
// it is not supposed to contain links, therefore, in
// benefice of the doubt, a resolved property is defined.
defineProperty(object, getKey(key, object), {
value: EMPTY_ARRAY
})
// Minor memory optimization, use the same empty array for
// everyone.
object[key] = EMPTY_ARRAY
} else if (isOpaqueRef(value[0])) {
// This is an array of refs.
defineProperty(object, getKey(key, object), {
get: () => freezeObject(map(value, (ref) => objectsByRefs[ref]))
})
freezeObject(value)
}
} else if (isObject(value)) {
forEach(value, resolveObject)
freezeObject(value)
} else if (isOpaqueRef(value)) {
defineProperty(object, getKey(key, object), {
get: () => objectsByRefs[value]
})
} else if (typeof value === 'object') {
forEach(value, resolveIfLink)
}
})
object.$id = object.uuid || ref
object.$ref = ref
object.$type = type
// All custom properties are read-only and non enumerable.
defineProperties(object, {
$id: { value: object.uuid || ref },
$pool: { get: this._getPool },
$ref: { value: ref },
$type: { value: type }
})
Object.defineProperty(object, '$pool', {
enumerable: true,
get: () => this._pool
// Finally freezes the object.
freezeObject(object)
const objects = this._objects
// An object's UUID can change during its life.
const prev = objectsByRefs[ref]
let prevUuid
if (prev && (prevUuid = prev.uuid) && prevUuid !== object.uuid) {
objects.remove(prevUuid)
}
this._objects.set(object)
objectsByRefs[ref] = object
if (type === 'pool') {
this._pool = object
}
}
_removeObject (ref) {
const {_objectsByRefs: objectsByRefs} = this
const object = objectsByRefs[ref]
if (object) {
this._objects.unset(object.$id)
delete objectsByRefs[ref]
}
}
_processEvents (events) {
forEach(events, event => {
const {operation: op} = event
const {ref} = event
if (op === 'del') {
this._removeObject(ref)
} else {
this._addObject(event.class, ref, event.snapshot)
}
})
}
_watchEvents () {
this.call(
'event.from', ['*'], this._fromToken, 1e3 + 0.1
).then(({token, events}) => {
const loop = () => this.status === CONNECTED && this._sessionCall('event.from', [
['*'],
this._fromToken,
60 + 0.1 // Force float.
]).then(onSuccess, onFailure)
const onSuccess = ({token, events}) => {
this._fromToken = token
this._processEvents(events)
const {
_objects: objects,
_objectsByRefs: objectsByRefs
} = this
const debounce = this._debounce
return debounce != null
? pDelay(debounce).then(loop)
: loop()
}
const onFailure = error => {
if (areEventsLost(error)) {
this._fromToken = ''
this._objects.clear()
forEach(events, event => {
const {operation: op} = event
return loop()
}
const {ref} = event
if (op === 'del') {
const objects = objectsByRefs[ref]
throw error
}
if (objects) {
objects.remove(objects.$id)
delete objectsByRefs[ref]
}
} else {
const {class: type, snapshot: object} = event
return loop()::pCatch(
isMethodUnknown,
this._normalizeObject(type, ref, object)
objects.set(object)
objectsByRefs[ref] = object
// If the server failed, it is probably due to an excessively
// large response.
// Falling back to legacy events watch should be enough.
error => error && error.res && error.res.statusCode === 500,
if (object.$type === 'pool') {
this._pool = object
}
}
() => this._watchEventsLegacy()
)
}
// This method watches events using the legacy `event.next` XAPI
// methods.
//
// It also has to manually get all objects first.
_watchEventsLegacy () {
const getAllObjects = () => {
return this._sessionCall('system.listMethods', []).then(methods => {
// Uses introspection to determine the methods to use to get
// all objects.
const getAllRecordsMethods = filter(
methods,
::/\.get_all_records$/.test
)
return Promise.all(map(
getAllRecordsMethods,
method => this._sessionCall(method, []).then(
objects => {
const type = method.slice(0, method.indexOf('.')).toLowerCase()
forEach(objects, (object, ref) => {
this._addObject(type, ref, object)
})
},
error => {
if (error.code !== 'MESSAGE_REMOVED') {
throw error
}
}
)
))
})
}).catch(areEventsLost, () => {
this._objects.clear()
}).then(() => {
this._watchEvents()
}).catch(noop)
}
const watchEvents = () => this._sessionCall('event.register', [ ['*'] ]).then(loop)
const loop = () => this.status === CONNECTED && this._sessionCall('event.next', []).then(onSuccess, onFailure)
const onSuccess = events => {
this._processEvents(events)
const debounce = this._debounce
return debounce == null
? loop()
: pDelay(debounce).then(loop)
}
const onFailure = error => {
if (areEventsLost(error)) {
return this._sessionCall('event.unregister', [ ['*'] ]).then(watchEvents)
}
throw error
}
return getAllObjects().then(watchEvents)
}
}

View File

@@ -1,17 +0,0 @@
/* eslint-env mocha */
import {createClient, Xapi} from './'
import expect from 'must'
// ===================================================================
describe('createClient()', function () {
it('is a function', function () {
expect(createClient).to.be.a.function()
})
it.skip('returns an instance of Xapi', function () {
expect(createClient('example.org')).to.be.a(Xapi)
})
})

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
import { delay as pDelay } from 'promise-toolbox'
import { createClient } from './'
const xapi = (() => {
const [ , , url, user, password ] = process.argv
return createClient({
auth: { user, password },
url,
watchEvents: false
})
})()
xapi.connect()
// Get the pool record's ref.
.then(() => xapi.call('pool.get_all'))
// Injects lots of events.
.then(([ poolRef ]) => {
const loop = () => xapi.call('event.inject', 'pool', poolRef)
::pDelay(10) // A small delay is required to avoid overloading the Xen API.
.then(loop)
return loop()
})

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env node
import { createClient } from './'
let i = 0
setInterval(() => {
const usage = process.memoryUsage()
console.log(
'%s %s %s %s',
i++,
Math.round(usage.rss / 1e6),
Math.round(usage.heapTotal / 1e6),
Math.round(usage.heapUsed / 1e6)
)
}, 1e2)
const [ , , url, user, password ] = process.argv
createClient({
auth: { user, password },
readOnly: true,
url
}).connect()

View File

@@ -0,0 +1,29 @@
import { InvalidJson } from 'json-rpc-protocol'
import jsonRpc from './json-rpc'
import xmlRpc from './xml-rpc'
export default opts => {
let call = (jsonRpcCall =>
(method, args) => jsonRpcCall(method, args).then(
result => {
// JSON-RPC seems to be working, remove the test
call = jsonRpcCall
return result
},
error => {
if (error instanceof InvalidJson) {
// fallback to XML-RPC
call = xmlRpc(opts)
return call(method, args)
}
throw error
}
)
)(jsonRpc(opts))
return (method, args) => call(method, args)
}

View File

@@ -0,0 +1,23 @@
import superagent from 'superagent'
import { format, parse } from 'json-rpc-protocol'
export default ({ url: { hostname, port, protocol } }) => {
const href = `${protocol}//${hostname}${
port != null ? `:${port}` : ''
}/jsonrpc`
return (method, args) =>
superagent.post(href)
.accept('application/json')
.parse(superagent.parse.text) // no smart parsing from superagent
.send(format.request(0, method, args))
.type('application/json')
.then(({ text }) => {
const response = parse(text)
if (response.type === 'response') {
return response.result
}
throw response.error
})
}

View File

@@ -0,0 +1,84 @@
import { createClient, createSecureClient } from 'xmlrpc'
import { promisify } from 'promise-toolbox'
const logError = error => {
if (error.res) {
console.error(
'XML-RPC Error: %s (response status %s)',
error.message,
error.res.statusCode
)
console.error('%s', error.body)
}
throw error
}
const SPECIAL_CHARS = {
'\r': '\\r',
'\t': '\\t'
}
const SPECIAL_CHARS_RE = new RegExp(
Object.keys(SPECIAL_CHARS).join('|'),
'g'
)
const parseResult = result => {
const status = result.Status
// Return the plain result if it does not have a valid XAPI
// format.
if (status === undefined) {
return result
}
if (status !== 'Success') {
throw result.ErrorDescription
}
const value = result.Value
// XAPI returns an empty string (invalid JSON) for an empty
// result.
if (value === '') {
return ''
}
try {
return JSON.parse(value)
} catch (error) {
// XAPI JSON sometimes contains invalid characters.
if (error instanceof SyntaxError) {
let replaced
const fixedValue = value.replace(SPECIAL_CHARS_RE, match => {
replaced = true
return SPECIAL_CHARS[match]
})
if (replaced) {
return JSON.parse(fixedValue)
}
}
throw error
}
}
export default ({ url: { hostname, path, port, protocol } }) => {
const client = (
protocol === 'https:'
? createSecureClient
: createClient
)({
host: hostname,
path: '/json',
port,
rejectUnauthorized: false
})
const call = promisify(client.methodCall, client)
return (method, args) => call(method, args).then(
parseResult,
logError
)
}

3821
packages/xen-api/yarn.lock Normal file

File diff suppressed because it is too large Load Diff