async-xenapi 1.0.0 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -11
- package/dist/XenAPI.d.ts +2 -4
- package/dist/XenAPI.js +40 -53
- package/package.json +23 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# async-xenapi (JavaScript / TypeScript)
|
|
2
2
|
|
|
3
|
-
An async
|
|
3
|
+
An async library for [XenAPI](https://xapi-project.github.io/xen-api).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ An async TypeScript library for [XenAPI](https://xapi-project.github.io/xen-api)
|
|
|
8
8
|
npm install async-xenapi
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Requires **Node.js
|
|
11
|
+
Requires **Node.js 24+** (uses the global `fetch` API).
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -22,27 +22,99 @@ async function main() {
|
|
|
22
22
|
const hosts = await session.xenapi.host.get_all();
|
|
23
23
|
console.log(hosts);
|
|
24
24
|
} finally {
|
|
25
|
-
await session.
|
|
25
|
+
await session.logout();
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
main();
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
The API mirrors the
|
|
32
|
+
The API mirrors the Python XenAPI SDK — any dotted method path under `session.xenapi` is translated directly to the corresponding JSON-RPC call. See the [XenAPI Reference](https://xapi-project.github.io/xen-api) for all available classes and fields.
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Best Practices
|
|
35
|
+
|
|
36
|
+
### 1. Use `get_all_records()` instead of N+1 queries
|
|
37
|
+
|
|
38
|
+
The single biggest performance win. Instead of fetching a list of refs then querying each one individually, fetch everything in one call:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// SLOW — N+1 round-trips (1 for get_all + N for each getter)
|
|
42
|
+
const vms = await session.xenapi.VM.get_all();
|
|
43
|
+
for (const vm of vms as string[]) {
|
|
44
|
+
const name = await session.xenapi.VM.get_name_label(vm);
|
|
45
|
+
console.log(name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// FAST — 1 round-trip, returns { ref: { field: value, ... }, ... }
|
|
49
|
+
const records = await session.xenapi.VM.get_all_records() as Record<string, Record<string, unknown>>;
|
|
50
|
+
for (const rec of Object.values(records)) {
|
|
51
|
+
if (!rec.is_a_template && !rec.is_a_snapshot) {
|
|
52
|
+
console.log(`${rec.name_label} (${rec.power_state})`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This applies to every XenAPI class: `host`, `SR`, `network`, `VM`, `pool`, etc.
|
|
58
|
+
|
|
59
|
+
### 2. Use `Promise.all()` for independent calls
|
|
60
|
+
|
|
61
|
+
When you need results from multiple independent API calls, run them concurrently:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// SLOW — sequential, each awaits the previous
|
|
65
|
+
const major = await session.xenapi.host.get_API_version_major(host);
|
|
66
|
+
const minor = await session.xenapi.host.get_API_version_minor(host);
|
|
67
|
+
|
|
68
|
+
// FAST — concurrent, both requests in flight at the same time
|
|
69
|
+
const [major, minor] = await Promise.all([
|
|
70
|
+
session.xenapi.host.get_API_version_major(host),
|
|
71
|
+
session.xenapi.host.get_API_version_minor(host),
|
|
72
|
+
]);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
You can also gather across different classes:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const [hosts, vms, networks] = await Promise.all([
|
|
79
|
+
session.xenapi.host.get_all_records(),
|
|
80
|
+
session.xenapi.VM.get_all_records(),
|
|
81
|
+
session.xenapi.network.get_all_records(),
|
|
82
|
+
]);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. Always clean up the session
|
|
86
|
+
|
|
87
|
+
Use `try/finally` to ensure `logout()` is called, which closes the server-side session:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const session = new AsyncXenAPISession("https://xen-host");
|
|
91
|
+
await session.login_with_password("root", "password");
|
|
92
|
+
try {
|
|
93
|
+
// ... your code ...
|
|
94
|
+
} finally {
|
|
95
|
+
await session.logout();
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Key Takeaways
|
|
100
|
+
|
|
101
|
+
| Pattern | Calls | Approach |
|
|
102
|
+
|------------------------------|--------------|----------------------|
|
|
103
|
+
| List objects with fields | 1 | `get_all_records()` |
|
|
104
|
+
| Multiple independent values | N concurrent | `Promise.all()` |
|
|
105
|
+
| Lookup one field for one ref | 1 | `get_<field>(ref)` |
|
|
106
|
+
|
|
107
|
+
## Run Tests
|
|
35
108
|
|
|
36
109
|
```
|
|
37
110
|
git clone git@github.com:acefei/async-xenapi.git
|
|
38
|
-
cd async-xenapi
|
|
111
|
+
cd async-xenapi
|
|
112
|
+
cp .env.example .env # then edit .env with your credentials
|
|
113
|
+
cd javascript
|
|
39
114
|
npm install
|
|
40
|
-
|
|
41
|
-
echo "USERNAME=root" >> .env
|
|
42
|
-
echo "PASSWORD=<password>" >> .env
|
|
43
|
-
npm test tests/getXapiVersion.test.ts
|
|
115
|
+
npm test
|
|
44
116
|
```
|
|
45
117
|
|
|
46
118
|
## License
|
|
47
119
|
|
|
48
|
-
|
|
120
|
+
LGPL-2.1-only
|
package/dist/XenAPI.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* An async library for XenAPI
|
|
3
3
|
*
|
|
4
|
-
* Usage mirrors the Python
|
|
4
|
+
* Usage mirrors the Python XenAPI:
|
|
5
5
|
*
|
|
6
6
|
* const session = new AsyncXenAPISession("https://host-ip");
|
|
7
7
|
* await session.login_with_password("root", "password");
|
|
@@ -31,5 +31,3 @@ export declare class AsyncXenAPISession {
|
|
|
31
31
|
logout(): Promise<void>;
|
|
32
32
|
_call(method: string, params?: unknown[]): Promise<unknown>;
|
|
33
33
|
}
|
|
34
|
-
/** Convenience factory — equivalent to `new AsyncXenAPISession(url)` */
|
|
35
|
-
export declare function xapi_client(url: string | undefined): AsyncXenAPISession;
|
package/dist/XenAPI.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* An async library for XenAPI
|
|
4
4
|
*
|
|
5
|
-
* Usage mirrors the Python
|
|
5
|
+
* Usage mirrors the Python XenAPI:
|
|
6
6
|
*
|
|
7
7
|
* const session = new AsyncXenAPISession("https://host-ip");
|
|
8
8
|
* await session.login_with_password("root", "password");
|
|
@@ -15,33 +15,27 @@
|
|
|
15
15
|
*
|
|
16
16
|
* await session.logout();
|
|
17
17
|
*/
|
|
18
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
19
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
20
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
21
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
22
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
23
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
24
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
25
|
-
});
|
|
26
|
-
};
|
|
27
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
19
|
exports.AsyncXenAPISession = void 0;
|
|
29
|
-
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// JSON-RPC helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// XenServer typically uses self-signed certificates — skip TLS verification
|
|
24
|
+
// (mirrors Python's ssl.CERT_NONE behaviour).
|
|
25
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
30
26
|
function _jsonrpcReq(method, params) {
|
|
31
27
|
return { jsonrpc: "2.0", method, params, id: crypto.randomUUID() };
|
|
32
28
|
}
|
|
33
|
-
function _post(url, payload) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
body: JSON.stringify(payload),
|
|
39
|
-
});
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
42
|
-
}
|
|
43
|
-
return response.json();
|
|
29
|
+
async function _post(url, payload) {
|
|
30
|
+
const response = await fetch(url, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
body: JSON.stringify(payload),
|
|
44
34
|
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
return response.json();
|
|
45
39
|
}
|
|
46
40
|
// Accumulates dotted property access (e.g. xenapi.VM.get_all) and dispatches
|
|
47
41
|
// the final call as an authenticated JSON-RPC request.
|
|
@@ -56,42 +50,35 @@ function _xenApiNamespace(session, path = []) {
|
|
|
56
50
|
});
|
|
57
51
|
}
|
|
58
52
|
class AsyncXenAPISession {
|
|
53
|
+
_url;
|
|
54
|
+
_sessionRef;
|
|
55
|
+
xenapi;
|
|
59
56
|
constructor(url) {
|
|
60
57
|
this._url = `${url}/jsonrpc`;
|
|
61
58
|
this.xenapi = _xenApiNamespace(this);
|
|
62
59
|
}
|
|
63
|
-
login_with_password(user, password) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return this._sessionRef;
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
logout() {
|
|
73
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
74
|
-
if (this._sessionRef) {
|
|
75
|
-
const ret = yield _post(this._url, _jsonrpcReq("session.logout", [this._sessionRef]));
|
|
76
|
-
if (ret.error)
|
|
77
|
-
throw new Error(`Logout failed: ${JSON.stringify(ret.error)}`);
|
|
78
|
-
this._sessionRef = undefined;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
60
|
+
async login_with_password(user, password) {
|
|
61
|
+
const ret = await _post(this._url, _jsonrpcReq("session.login_with_password", [user, password, "version", "originator"]));
|
|
62
|
+
if (ret.error)
|
|
63
|
+
throw new Error(`Login failed: ${JSON.stringify(ret.error)}`);
|
|
64
|
+
this._sessionRef = ret.result;
|
|
65
|
+
return this._sessionRef;
|
|
81
66
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
throw new Error("Not logged in");
|
|
86
|
-
const ret = yield _post(this._url, _jsonrpcReq(method, [this._sessionRef, ...params]));
|
|
67
|
+
async logout() {
|
|
68
|
+
if (this._sessionRef) {
|
|
69
|
+
const ret = await _post(this._url, _jsonrpcReq("session.logout", [this._sessionRef]));
|
|
87
70
|
if (ret.error)
|
|
88
|
-
throw new Error(`
|
|
89
|
-
|
|
90
|
-
}
|
|
71
|
+
throw new Error(`Logout failed: ${JSON.stringify(ret.error)}`);
|
|
72
|
+
this._sessionRef = undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async _call(method, params = []) {
|
|
76
|
+
if (!this._sessionRef)
|
|
77
|
+
throw new Error("Not logged in");
|
|
78
|
+
const ret = await _post(this._url, _jsonrpcReq(method, [this._sessionRef, ...params]));
|
|
79
|
+
if (ret.error)
|
|
80
|
+
throw new Error(`XAPI ${method} failed: ${JSON.stringify(ret.error)}`);
|
|
81
|
+
return ret.result;
|
|
91
82
|
}
|
|
92
83
|
}
|
|
93
84
|
exports.AsyncXenAPISession = AsyncXenAPISession;
|
|
94
|
-
/** Convenience factory — equivalent to `new AsyncXenAPISession(url)` */
|
|
95
|
-
function xapi_client(url) {
|
|
96
|
-
return new AsyncXenAPISession(url);
|
|
97
|
-
}
|
package/package.json
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "async-xenapi",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "An async
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "An async library for XenAPI",
|
|
5
|
+
"homepage": "https://github.com/acefei/async-xenapi",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/acefei/async-xenapi.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/acefei/async-xenapi/issues"
|
|
12
|
+
},
|
|
5
13
|
"main": "dist/XenAPI.js",
|
|
6
14
|
"types": "dist/XenAPI.d.ts",
|
|
7
|
-
"files": [
|
|
15
|
+
"files": [
|
|
16
|
+
"/dist"
|
|
17
|
+
],
|
|
8
18
|
"scripts": {
|
|
9
|
-
"test": "
|
|
19
|
+
"test": "vitest run",
|
|
10
20
|
"build": "tsc",
|
|
11
21
|
"lint": "biome check src/ tests/",
|
|
12
22
|
"fmt": "biome check --write src/ tests/",
|
|
13
23
|
"lint:ci": "biome check --diagnostic-level=error src/ tests/"
|
|
14
24
|
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
"keywords": [
|
|
26
|
+
"xenserver",
|
|
27
|
+
"xen-api",
|
|
28
|
+
"typescript",
|
|
29
|
+
"async"
|
|
30
|
+
],
|
|
31
|
+
"author": "Su Fei <acefei@163.com>",
|
|
32
|
+
"license": "LGPL-2.1-only",
|
|
18
33
|
"devDependencies": {
|
|
19
34
|
"@biomejs/biome": "^1.9.0",
|
|
20
35
|
"@types/node": "^20.8.9",
|
|
21
36
|
"axios": "^1.6.0",
|
|
22
37
|
"dotenv": "^16.3.1",
|
|
23
|
-
"
|
|
38
|
+
"vitest": "^3.0.0",
|
|
24
39
|
"typescript": "^5.2.2"
|
|
25
40
|
},
|
|
26
41
|
"dependencies": {}
|