Compare commits

..

68 Commits

Author SHA1 Message Date
Pierre Donias
929ca767ca feat(xo-vmdk-to-vhd): 0.1.4 2018-09-28 17:01:43 +02:00
Pierre Donias
342c1bc6fa feat(vhd-lib): 0.3.1 2018-09-28 16:58:46 +02:00
Pierre Donias
317e301067 feat(fs): 0.3.1 2018-09-28 16:56:01 +02:00
Julien Fontanet
ac5741a341 fix(xo-server/deleteVm): build disks list before deleting VM (#3465)
Otherwise there is a race condition and VBD records might have been removed from cache before listing the VM disks due to destroying the VM.
2018-09-28 16:48:21 +02:00
badrAZ
3f1fb7092b fix(xo-web/modal#form): prevent default behavior of submit (#3462) 2018-09-28 11:42:47 +02:00
Julien Fontanet
7298a8b8f0 fix(fs/NfsHandler): race conds on sync() (#3460)
Fixes #3380
2018-09-28 11:40:12 +02:00
Julien Fontanet
856924c970 chore(log): remove @babel-polyfill dep 2018-09-26 17:41:43 +02:00
Julien Fontanet
9a285d280f chore(fs/NfsHandler): merge _loadRealMounts and _matchesRealMount in _sync 2018-09-26 17:00:47 +02:00
Julien Fontanet
9c3fc56d4a chore(fs/NfsHandler#_umount): remove unused remote param 2018-09-26 17:00:47 +02:00
Julien Fontanet
aaaee45eeb chore(xo-server): remove @babel/polyfill (#3453)
Fixes #2580
Fixes #2820
2018-09-26 14:22:02 +02:00
Nicolas Raynaud
ac2ab21826 fix(vhd-lib): fix geometry computation for more than 2^28 sectors (#3451)
Fixes https://gitlab.com/vates/xoa-support/issues/942
It was a bug in the disk geometry computation.
2018-09-25 14:51:17 +02:00
badrAZ
b22514646e feat(xo-web/backup-ng/overview): display the schedule's name (#3445)
Fixes #3444
2018-09-24 13:43:49 +02:00
Julien Fontanet
c7ab1ddb0c chore: update dependencies 2018-09-24 13:39:31 +02:00
Pierre Donias
7ddc595a1c fix(xo-web/file-restore): incorrect line-through on similar filenames (#3447) 2018-09-24 11:09:45 +02:00
Pierre Donias
4147800266 fix(xo-web/file-restore): ensure folder path trailing slash (#3446) 2018-09-24 10:52:48 +02:00
Pierre Donias
99e6d54647 chore(CHANGELOG): 5.27.0 2018-09-24 10:01:39 +02:00
Julien Fontanet
dac5901c6b feat(xo-server): 5.27.1 2018-09-22 14:01:24 +02:00
Julien Fontanet
308928a7d4 feat(xen-api): 0.19.0 2018-09-22 14:00:55 +02:00
Julien Fontanet
e6e3d2cd52 chore: update http-request-plus to 0.6.0 2018-09-22 13:06:30 +02:00
Pierre Donias
2e01de7ff8 feat(xo-web): 5.27.0 2018-09-21 18:02:57 +02:00
Pierre Donias
90f94da4e4 feat(xo-server): 5.27.0 2018-09-21 18:01:27 +02:00
Pierre Donias
29b5acef3f chore(xo): packages should not be private 2018-09-21 17:50:49 +02:00
Pierre Donias
599b094b50 feat(xo-server-backup-reports): 0.14.0 2018-09-21 17:43:41 +02:00
Julien Fontanet
7c6e423d24 fix(Backup NG): remove all unnecessary snapshots (#3439)
Even those from other schedules.

Fixes #3132
2018-09-21 17:37:49 +02:00
badrAZ
82956af785 fix(xo-web/logs): fix @xen-orchestra/log import (#3441) 2018-09-21 15:36:54 +02:00
badrAZ
6ca09dc9fe feat(xo-web/vm/tab-advanced): ability to set the NIC type (#3440)
Fixes #3423
2018-09-21 15:30:23 +02:00
badrAZ
e0ecbab841 feat(xo-server/vm): ability to change NIC type (#3437)
See #3423
2018-09-21 14:42:43 +02:00
Julien Fontanet
83d1a5ff13 feat(defined): helpers to deal with undefined (#3436)
Extracted from xo-web.
2018-09-21 11:56:33 +02:00
badrAZ
a71740d49a feat(backup-ng): ability to restart all failed VMs (#3420)
Fixes #3339
2018-09-20 17:05:57 +02:00
Julien Fontanet
0215c19d1d feat(xo-server/proxy-console): work around missing host (#3435)
Fixes #3432
2018-09-20 14:47:39 +02:00
Julien Fontanet
ea1c3ab54a fix(xo-server/delta NG): dont start export before all targets ready (#3427)
Fixes #3424
2018-09-20 14:38:45 +02:00
badrAZ
b98b618be8 fix(xo-server-backup-reports): handle log not found (#3430) 2018-09-20 14:34:29 +02:00
Julien Fontanet
5e363761a2 fix(xo-server/proxy-console): pass hostname to net.connect 2018-09-20 10:25:47 +02:00
Julien Fontanet
62d48bd59d fix(async-map): apply → call 😖
Fixes #3431
2018-09-20 10:04:09 +02:00
badrAZ
a0049bae8d fix(xo-web/backup-ng): make log value dynamic in copy/report buttons (#3360)
Fixes #3273
2018-09-18 15:15:59 +02:00
Julien Fontanet
18660cb0e1 fix(async-map): accept objects as collection 2018-09-18 12:10:44 +02:00
Julien Fontanet
e3c6c1c1ca chore(async-map): use lodash/map import 2018-09-18 12:10:43 +02:00
badrAZ
56114a7d18 feat(xo-server-backup-report): ability to test the plugin (#3421) 2018-09-17 17:23:45 +02:00
Julien Fontanet
0fe70b1a91 feat(log): new lib to help logging (#3414)
Fixes #2414
Related to #1669
2018-09-17 17:02:27 +02:00
Julien Fontanet
527eb0b1e6 feat(async-map): better Promise.all + map (#3422) 2018-09-17 16:37:49 +02:00
Julien Fontanet
cfd6fd722a feat(mixin): split a class in independant parts (#3415) 2018-09-17 16:33:21 +02:00
badrAZ
42591bd4bd feat(xo-web/VM): display the PVHVM status (#3418)
Fixes #3014
2018-09-17 11:06:19 +02:00
Julien Fontanet
8689cff26b feat(emit-async): handle async listeners (#3416)
Extracted from xo-server.
2018-09-17 10:42:22 +02:00
badrAZ
b4e4d32255 feat(xo-web/backup-ng/overview): display transferred and merged data size (#3408) 2018-09-14 17:21:06 +02:00
Julien Fontanet
18c88ba770 feat(cr-seed-cli): CLI to help CR seeding (#3346) 2018-09-14 17:04:55 +02:00
badrAZ
d212168f59 feat(xo-web/backup-ng/new): use a modal for creating/editing a schedule (#3359)
Fixes #3138
2018-09-14 15:53:28 +02:00
Julien Fontanet
3625477187 fix(xo-web/xoa-updater): wait trial request before checking state (#3412)
Fixes #3407
2018-09-14 12:26:41 +02:00
Pierre Donias
9be8525439 await xoaState 2018-09-14 12:04:19 +02:00
Pierre Donias
6e013a0dc5 PR 2018-09-14 11:54:39 +02:00
Pierre Donias
06d555d4f9 fix(xo-web/xoa-updater): wait for trial request before checking trial state
Fixes #3407
2018-09-14 11:51:43 +02:00
Julien Fontanet
63fe0f2c88 fix(xo-server/importDeltaVm): dont fail on empty VBDs (#3410) 2018-09-14 10:37:09 +02:00
Julien Fontanet
2a276dfb99 changelog 2018-09-14 10:36:49 +02:00
Julien Fontanet
1f009ca954 fix(xo-server/importDeltaVm): dont fail on empty VBDs 2018-09-13 17:27:11 +02:00
badrAZ
5cf1ba41f3 feat(xo-web/modal#form): support an additional icon to the title (#3409) 2018-09-13 16:58:03 +02:00
badrAZ
1ef205cb74 feat(xo-web/backup-ng/new): tip for creating vms on a thin-provisioned storage (#3402)
Fixes #3334
2018-09-13 16:23:51 +02:00
Julien Fontanet
1e59af3ab2 fix(xo-server/deleteVm): dont delete VDIs/VIFs if destroy is blocked (#3406) 2018-09-13 11:44:17 +02:00
badrAZ
bb88b420c1 chore(xo-web/backup-ng/new): make "setSchedule" support multiple params (#3404) 2018-09-12 13:53:37 +02:00
badrAZ
e8475d144c chore(xo-web/backup-ng/new): improve new schedule implementation (#3348) 2018-09-12 13:43:02 +02:00
badrAZ
dedac62269 feat(xo-web): implementation of the modal dedicated to forms (#3358) 2018-09-11 17:31:20 +02:00
badrAZ
f8fdd888c4 fix(xo-web/remotes): error appears twice on testing (#3399) 2018-09-11 17:27:10 +02:00
Julien Fontanet
d8bd30e355 feat(xo-web/remotes): use WORKGROUP as default SMB domain (#3398) 2018-09-11 10:46:52 +02:00
badrAZ
4fec816274 feat(xo-web/settings/remotes): test on creation/edition (#3397)
See #3323
2018-09-11 10:09:16 +02:00
badrAZ
1211447e81 fix(xo-web/settings/remotes): rename connect(ed)/disconnect(ed) to enable(d)/disable(d) (#3396)
See #3323
2018-09-10 17:15:33 +02:00
Julien Fontanet
ed78f4c5ee fix(xo-server/backup NG): third time is the charm (sigh) 2018-09-10 10:59:56 +02:00
Julien Fontanet
4a2e9d4c88 fix(xo-server/backup NG): CancelToken.race → .source 2018-09-10 10:51:05 +02:00
Julien Fontanet
1807917204 fix(xo-server/backup NG): CancelToken#fork() no longer exists
Issue introduced in b3004a38a
2018-09-10 10:47:53 +02:00
Julien Fontanet
b3004a38aa chore: update promise-toolbox to v0.10 (#3392) 2018-09-07 14:34:47 +02:00
Pierre Donias
0ad6c073ee chore(CHANGELOG): 5.26.0 2018-09-07 11:39:26 +02:00
137 changed files with 3085 additions and 1454 deletions

View File

@@ -0,0 +1,3 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)

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,49 @@
# @xen-orchestra/async-map [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> ${pkg.description}
## Install
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/async-map):
```
> npm install --save @xen-orchestra/async-map
```
## Usage
**TODO**
## 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

@@ -0,0 +1,49 @@
{
"name": "@xen-orchestra/async-map",
"version": "0.0.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/async-map",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"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/"
],
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"dependencies": {
"lodash": "^4.17.4"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@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"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"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"
}
}

View File

@@ -0,0 +1,43 @@
// type MaybePromise<T> = Promise<T> | T
//
// declare export function asyncMap<T1, T2>(
// collection: MaybePromise<T1[]>,
// (T1, number) => MaybePromise<T2>
// ): Promise<T2[]>
// declare export function asyncMap<K, V1, V2>(
// collection: MaybePromise<{ [K]: V1 }>,
// (V1, K) => MaybePromise<V2>
// ): Promise<V2[]>
import map from 'lodash/map'
// Similar to map() + Promise.all() but wait for all promises to
// settle before rejecting (with the first error)
const asyncMap = (collection, iteratee) => {
let then
if (collection != null && typeof (then = collection.then) === 'function') {
return then.call(collection, collection => asyncMap(collection, iteratee))
}
let errorContainer
const onError = error => {
if (errorContainer === undefined) {
errorContainer = { error }
}
}
return Promise.all(
map(collection, (item, key, collection) =>
new Promise(resolve => {
resolve(iteratee(item, key, collection))
}).catch(onError)
)
).then(values => {
if (errorContainer !== undefined) {
throw errorContainer.error
}
return values
})
}
export { asyncMap as default }

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env node
const defer = require('golike-defer').default
const { NULL_REF, Xapi } = require('xen-api')
const pkg = require('./package.json')
Xapi.prototype.getVmDisks = async function (vm) {
const disks = { __proto__: null }
await Promise.all([
...vm.VBDs.map(async vbdRef => {
const vbd = await this.getRecord('VBD', vbdRef)
let vdiRef
if (vbd.type === 'Disk' && (vdiRef = vbd.VDI) !== NULL_REF) {
disks[vbd.userdevice] = await this.getRecord('VDI', vdiRef)
}
}),
])
return disks
}
defer(async function main ($defer, args) {
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
const cliName = Object.keys(pkg.bin)[0]
return console.error(
'%s',
`
Usage: ${cliName} <source XAPI URL> <source snapshot UUID> <target XAPI URL> <target VM UUID> <backup job id> <backup schedule id>
${cliName} v${pkg.version}
`
)
}
const [
srcXapiUrl,
srcSnapshotUuid,
tgtXapiUrl,
tgtVmUuid,
jobId,
scheduleId,
] = args
const srcXapi = new Xapi({
allowUnauthorized: true,
url: srcXapiUrl,
watchEvents: false,
})
await srcXapi.connect()
defer.call(srcXapi, 'disconnect')
const tgtXapi = new Xapi({
allowUnauthorized: true,
url: tgtXapiUrl,
watchEvents: false,
})
await tgtXapi.connect()
defer.call(tgtXapi, 'disconnect')
const [srcSnapshot, tgtVm] = await Promise.all([
srcXapi.getRecordByUuid('VM', srcSnapshotUuid),
tgtXapi.getRecordByUuid('VM', tgtVmUuid),
])
const srcVm = await srcXapi.getRecord('VM', srcSnapshot.snapshot_of)
const metadata = {
'xo:backup:datetime': srcSnapshot.snapshot_time,
'xo:backup:job': jobId,
'xo:backup:schedule': scheduleId,
'xo:backup:vm': srcVm.uuid,
}
const [srcDisks, tgtDisks] = await Promise.all([
srcXapi.getVmDisks(srcSnapshot),
tgtXapi.getVmDisks(tgtVm),
])
const userDevices = Object.keys(tgtDisks)
const tgtSr = await tgtXapi.getRecord(
'SR',
tgtDisks[Object.keys(tgtDisks)[0]].SR
)
await Promise.all([
srcXapi.setFieldEntries(srcSnapshot, 'other_config', metadata),
tgtXapi.setField(
tgtVm,
'name_label',
`${srcVm.name_label} (${srcSnapshot.snapshot_time})`
),
tgtXapi.setFieldEntries(tgtVm, 'other_config', metadata),
tgtXapi.setFieldEntries(tgtVm, 'other_config', {
'xo:backup:sr': tgtSr.uuid,
'xo:copy_of': srcSnapshotUuid,
}),
tgtXapi.setFieldEntries(tgtVm, 'blocked_operations', {
start:
'Start operation for this vm is blocked, clone it if you want to use it.',
}),
Promise.all(
userDevices.map(userDevice => {
const srcDisk = srcDisks[userDevice]
const tgtDisk = tgtDisks[userDevice]
return tgtXapi.setFieldEntry(
tgtDisk,
'other_config',
'xo:copy_of',
srcDisk.uuid
)
})
),
])
})(process.argv.slice(2)).catch(console.error.bind(console, 'Fatal error:'))

View File

@@ -0,0 +1,20 @@
{
"name": "@xen-orchestra/cr-seed-cli",
"version": "0.1.0",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/cr-seed-cli",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"engines": {
"node": ">=8"
},
"bin": {
"xo-cr-seed": "./index.js"
},
"dependencies": {
"golike-defer": "^0.4.1",
"xen-api": "^0.19.0"
}
}

View File

@@ -41,10 +41,10 @@
"moment-timezone": "^0.5.14"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@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"
},

View File

@@ -0,0 +1,3 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)

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,49 @@
# ${pkg.name} [![Build Status](https://travis-ci.org/${pkg.shortGitHubPath}.png?branch=master)](https://travis-ci.org/${pkg.shortGitHubPath})
> ${pkg.description}
## Install
Installation of the [npm package](https://npmjs.org/package/${pkg.name}):
```
> npm install --save ${pkg.name}
```
## Usage
**TODO**
## 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](${pkg.bugs})
you've encountered;
- fork and create a pull request.
## License
${pkg.license} © [${pkg.author.name}](${pkg.author.url})

View File

@@ -0,0 +1,47 @@
{
"name": "@xen-orchestra/defined",
"version": "0.0.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/defined",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"dependencies": {},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@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"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"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"
}
}

View File

@@ -1,3 +1,5 @@
// @flow
// Usage:
//
// ```js
@@ -39,7 +41,7 @@ export default function defined () {
// const getFriendName = _ => _.friends[0].name
// const friendName = get(getFriendName, props.user)
// ```
export const get = (accessor, arg) => {
export const get = (accessor: (input: ?any) => any, arg: ?any) => {
try {
return accessor(arg)
} catch (error) {
@@ -58,5 +60,5 @@ export const get = (accessor, arg) => {
// _ => new ProxyAgent(_)
// )
// ```
export const ifDef = (value, thenFn) =>
export const ifDef = (value: ?any, thenFn: (value: any) => any) =>
value !== undefined ? thenFn(value) : value

View File

@@ -0,0 +1,3 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)

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,71 @@
# @xen-orchestra/emit-async [![Build Status](https://travis-ci.org/${pkg.shortGitHubPath}.png?branch=master)](https://travis-ci.org/${pkg.shortGitHubPath})
> ${pkg.description}
## Install
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/emit-async):
```
> npm install --save @xen-orchestra/emit-async
```
## Usage
```js
import EE from 'events'
import emitAsync from '@xen-orchestra/emit-async'
const ee = new EE()
ee.emitAsync = emitAsync
ee.on('start', async function () {
// whatever
})
// similar to EventEmmiter#emit() but returns a promise which resolves when all
// listeners have resolved
await ee.emitAsync('start')
// by default, it will rejects as soon as one listener reject, you can customise
// error handling though:
await ee.emitAsync({
onError (error) {
console.warn(error)
}
}, 'start')
```
## 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](${pkg.bugs})
you've encountered;
- fork and create a pull request.
## License
${pkg.license} © [${pkg.author.name}](${pkg.author.url})

View File

@@ -0,0 +1,46 @@
{
"name": "@xen-orchestra/emit-async",
"version": "0.0.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/emit-async",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"dependencies": {},
"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.2"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"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"
}
}

View File

@@ -0,0 +1,26 @@
export default function emitAsync (event) {
let opts
let i = 1
// an option object has been passed as first param
if (typeof event !== 'string') {
opts = event
event = arguments[i++]
}
const n = arguments.length - i
const args = new Array(n)
for (let j = 0; j < n; ++j) {
args[j] = arguments[j + i]
}
const onError = opts != null && opts.onError
return Promise.all(
this.listeners(event).map(listener =>
new Promise(resolve => {
resolve(listener.apply(this, args))
}).catch(onError)
)
)
}

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/fs",
"version": "0.3.0",
"version": "0.3.1",
"license": "AGPL-3.0",
"description": "The File System for Xen Orchestra backups.",
"keywords": [],
@@ -25,17 +25,17 @@
"fs-extra": "^7.0.0",
"get-stream": "^4.0.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"through2": "^2.0.3",
"tmp": "^0.0.33",
"xo-remote-parser": "^0.5.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/plugin-proposal-function-bind": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"index-modules": "^0.3.0",

View File

@@ -2,8 +2,8 @@
import rimraf from 'rimraf'
import tmp from 'tmp'
import { pFromCallback } from 'promise-toolbox'
import { fromCallback as pFromCallback } from 'promise-toolbox'
import { getHandler } from '.'
const initialDir = process.cwd()

View File

@@ -1,6 +1,5 @@
import execa from 'execa'
import fs from 'fs-extra'
import { forEach } from 'lodash'
import LocalHandler from './local'
@@ -15,63 +14,38 @@ export default class NfsHandler extends LocalHandler {
return `/run/xo-server/mounts/${this._remote.id}`
}
async _loadRealMounts () {
let stdout
const mounted = {}
try {
stdout = await execa.stdout('findmnt', [
'-P',
'-t',
'nfs,nfs4',
'--output',
'SOURCE,TARGET',
'--noheadings',
])
const regex = /^SOURCE="([^:]*):(.*)" TARGET="(.*)"$/
forEach(stdout.split('\n'), m => {
if (m) {
const match = regex.exec(m)
mounted[match[3]] = {
host: match[1],
share: match[2],
}
}
})
} catch (exc) {
// When no mounts are found, the call pretends to fail...
if (exc.stderr !== '') {
throw exc
}
}
this._realMounts = mounted
return mounted
}
_matchesRealMount () {
return this._getRealPath() in this._realMounts
}
async _mount () {
await fs.ensureDir(this._getRealPath())
const { host, path, port, options } = this._remote
return execa('mount', [
'-t',
'nfs',
'-o',
DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
`${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(),
])
return execa(
'mount',
[
'-t',
'nfs',
'-o',
DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
`${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(),
],
{
env: {
LANG: 'C',
},
}
).catch(error => {
if (!error.stderr.includes('already mounted')) {
throw error
}
})
}
async _sync () {
await this._loadRealMounts()
if (this._matchesRealMount() && !this._remote.enabled) {
await this._umount(this._remote)
} else if (!this._matchesRealMount() && this._remote.enabled) {
if (this._remote.enabled) {
await this._mount()
} else {
await this._umount()
}
return this._remote
}
@@ -83,7 +57,15 @@ export default class NfsHandler extends LocalHandler {
}
}
async _umount (remote) {
await execa('umount', ['--force', this._getRealPath()])
async _umount () {
await execa('umount', ['--force', this._getRealPath()], {
env: {
LANG: 'C',
},
}).catch(error => {
if (!error.stderr.includes('not mounted')) {
throw error
}
})
}
}

View File

@@ -1,5 +1,5 @@
import Smb2 from '@marsaud/smb2'
import { lastly as pFinally } from 'promise-toolbox'
import { pFinally } from 'promise-toolbox'
import RemoteHandlerAbstract from './abstract'

View File

@@ -0,0 +1,3 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)

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 @@
# @xen-orchestra/log [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
> ${pkg.description}
## Install
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/log):
```
> npm install --save @xen-orchestra/log
```
## Usage
Everywhere something should be logged:
```js
import createLogger from '@xen-orchestra/log'
const log = createLogger('my-module')
log.debug('only useful for debugging')
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')
```
Then, at application level, configure the logs are handled:
```js
import { configure, catchGlobalErrors } from '@xen-orchestra/log/configure'
import transportConsole from '@xen-orchestra/log/transports/console'
import transportEmail from '@xen-orchestra/log/transports/email'
const transport = transportEmail({
service: 'gmail',
auth: {
user: 'jane.smith@gmail.com',
pass: 'H&NbECcpXF|pyXe#%ZEb'
},
from: 'jane.smith@gmail.com',
to: [
'jane.smith@gmail.com',
'sam.doe@yahoo.com'
]
})
configure([
{
// if filter is a string, then it is pattern
// (https://github.com/visionmedia/debug#wildcards) which is
// matched against the namespace of the logs
filter: process.env.DEBUG,
transport: transportConsole()
},
{
// only levels >= warn
level: 'warn',
transport
}
])
// send all global errors (uncaught exceptions, warnings, unhandled rejections)
// to this transport
catchGlobalErrors(transport)
```
### Transports
#### Console
```js
import transportConsole from '@xen-orchestra/log/transports/console'
configure(transports.console())
```
#### Email
Optional dependency:
```
> yarn add nodemailer pretty-format
```
Configuration:
```js
import transportEmail from '@xen-orchestra/log/transports/email'
configure(transportEmail({
service: 'gmail',
auth: {
user: 'jane.smith@gmail.com',
pass: 'H&NbECcpXF|pyXe#%ZEb'
},
from: 'jane.smith@gmail.com',
to: [
'jane.smith@gmail.com',
'sam.doe@yahoo.com'
]
}))
```
#### Syslog
Optional dependency:
```
> yarn add split-host syslog-client
```
Configuration:
```js
import transportSyslog from '@xen-orchestra/log/transports/syslog'
// By default, log to udp://localhost:514
configure(transportSyslog())
// But TCP, a different host, or a different port can be used
configure(transportSyslog('tcp://syslog.company.lan'))
```
## 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/xo-web/issues/)
you've encountered;
- fork and create a pull request.
## License
ISC © [Vates SAS](https://vates.fr)

View File

@@ -0,0 +1 @@
module.exports = require('./dist/configure')

View File

@@ -0,0 +1,51 @@
{
"private": true,
"name": "@xen-orchestra/log",
"version": "0.0.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/log",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"browserslist": [
">2%"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"lodash": "^4.17.4",
"promise-toolbox": "^0.10.1"
},
"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",
"index-modules": "^0.3.0",
"rimraf": "^2.6.2"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"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"
}
}

View File

@@ -0,0 +1,106 @@
import createConsoleTransport from './transports/console'
import LEVELS, { resolve } from './levels'
import { compileGlobPattern } from './utils'
// ===================================================================
const createTransport = config => {
if (typeof config === 'function') {
return config
}
if (Array.isArray(config)) {
const transports = config.map(createTransport)
const { length } = transports
return function () {
for (let i = 0; i < length; ++i) {
transports[i].apply(this, arguments)
}
}
}
let { filter, transport } = config
const level = resolve(config.level)
if (filter !== undefined) {
if (typeof filter === 'string') {
const re = compileGlobPattern(filter)
filter = log => re.test(log.namespace)
}
const orig = transport
transport = function (log) {
if ((level !== undefined && log.level >= level) || filter(log)) {
return orig.apply(this, arguments)
}
}
} else if (level !== undefined) {
const orig = transport
transport = function (log) {
if (log.level >= level) {
return orig.apply(this, arguments)
}
}
}
return transport
}
const symbol =
typeof Symbol !== 'undefined'
? Symbol.for('@xen-orchestra/log')
: '@@@xen-orchestra/log'
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,
transport: createConsoleTransport(),
})
export const configure = config => {
global[symbol] = createTransport(config)
}
// -------------------------------------------------------------------
export const catchGlobalErrors = logger => {
// patch process
const onUncaughtException = error => {
logger.error('uncaught exception', { error })
}
const onUnhandledRejection = error => {
logger.warn('possibly unhandled rejection', { error })
}
const onWarning = error => {
logger.warn('Node warning', { error })
}
process.on('uncaughtException', onUncaughtException)
process.on('unhandledRejection', onUnhandledRejection)
process.on('warning', onWarning)
// patch EventEmitter
const EventEmitter = require('events')
const { prototype } = EventEmitter
const { emit } = prototype
function patchedEmit (event, error) {
if (event === 'error' && !this.listenerCount(event)) {
logger.error('unhandled error event', { error })
return false
}
return emit.apply(this, arguments)
}
prototype.emit = patchedEmit
return () => {
process.removeListener('uncaughtException', onUncaughtException)
process.removeListener('unhandledRejection', onUnhandledRejection)
process.removeListener('warning', onWarning)
if (prototype.emit === patchedEmit) {
prototype.emit = emit
}
}
}

View File

@@ -0,0 +1,65 @@
import createTransport from './transports/console'
import LEVELS from './levels'
const symbol =
typeof Symbol !== 'undefined'
? Symbol.for('@xen-orchestra/log')
: '@@@xen-orchestra/log'
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)
}
// -------------------------------------------------------------------
function Log (data, level, namespace, message, time) {
this.data = data
this.level = level
this.namespace = namespace
this.message = message
this.time = time
}
function Logger (namespace) {
this._namespace = namespace
// bind all logging methods
for (const name in LEVELS) {
const lowerCase = name.toLowerCase()
this[lowerCase] = this[lowerCase].bind(this)
}
}
const { prototype } = Logger
for (const name in LEVELS) {
const level = LEVELS[name]
prototype[name.toLowerCase()] = function (message, data) {
global[symbol](new Log(data, level, this._namespace, message, new Date()))
}
}
prototype.wrap = function (message, fn) {
const logger = this
const warnAndRethrow = error => {
logger.warn(message, { error })
throw error
}
return function () {
try {
const result = fn.apply(this, arguments)
const then = result != null && result.then
return typeof then === 'function'
? then.call(result, warnAndRethrow)
: result
} catch (error) {
warnAndRethrow(error)
}
}
}
const createLogger = namespace => new Logger(namespace)
export { createLogger as default }

View File

@@ -0,0 +1,24 @@
const LEVELS = Object.create(null)
export { LEVELS as default }
// https://github.com/trentm/node-bunyan#levels
LEVELS.FATAL = 60 // service/app is going down
LEVELS.ERROR = 50 // fatal for current action
LEVELS.WARN = 40 // something went wrong but it's not fatal
LEVELS.INFO = 30 // detail on unusual but normal operation
LEVELS.DEBUG = 20
export const NAMES = Object.create(null)
for (const name in LEVELS) {
NAMES[LEVELS[name]] = name
}
export const resolve = level => {
if (typeof level === 'string') {
level = LEVELS[level.toUpperCase()]
}
return level
}
Object.freeze(LEVELS)
Object.freeze(NAMES)

View File

@@ -0,0 +1,32 @@
/* eslint-env jest */
import { forEach, isInteger } from 'lodash'
import LEVELS, { NAMES, resolve } from './levels'
describe('LEVELS', () => {
it('maps level names to their integer values', () => {
forEach(LEVELS, (value, name) => {
expect(isInteger(value)).toBe(true)
})
})
})
describe('NAMES', () => {
it('maps level values to their names', () => {
forEach(LEVELS, (value, name) => {
expect(NAMES[value]).toBe(name)
})
})
})
describe('resolve()', () => {
it('returns level values either from values or names', () => {
forEach(LEVELS, value => {
expect(resolve(value)).toBe(value)
})
forEach(NAMES, (name, value) => {
expect(resolve(name)).toBe(+value)
})
})
})

View File

@@ -0,0 +1,24 @@
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 =
level < INFO
? debugConsole
: level < WARN
? infoConsole
: level < ERROR
? warnConsole
: errorConsole
fn('%s - %s - [%s] %s', time.toISOString(), namespace, NAMES[level], message)
data != null && fn(data)
}
export default () => consoleTransport

View File

@@ -0,0 +1,70 @@
import fromCallback from 'promise-toolbox/fromCallback'
import prettyFormat from 'pretty-format' // eslint-disable-line node/no-extraneous-import
import { createTransport } from 'nodemailer' // eslint-disable-line node/no-extraneous-import
import { evalTemplate, required } from '../utils'
import { NAMES } from '../levels'
export default ({
// transport options (https://nodemailer.com/smtp/)
auth,
authMethod,
host,
ignoreTLS,
port,
proxy,
requireTLS,
secure,
service,
tls,
// message options (https://nodemailer.com/message/)
bcc,
cc,
from = required('from'),
to = required('to'),
subject = '[{{level}} - {{namespace}}] {{time}} {{message}}',
}) => {
const transporter = createTransport(
{
auth,
authMethod,
host,
ignoreTLS,
port,
proxy,
requireTLS,
secure,
service,
tls,
disableFileAccess: true,
disableUrlAccess: true,
},
{
bcc,
cc,
from,
to,
}
)
return log =>
fromCallback(cb =>
transporter.sendMail(
{
subject: evalTemplate(
subject,
key =>
key === 'level'
? NAMES[log.level]
: key === 'time'
? log.time.toISOString()
: log[key]
),
text: prettyFormat(log.data),
},
cb
)
)
}

View File

@@ -0,0 +1,7 @@
export default () => {
const memoryLogger = log => {
logs.push(log)
}
const logs = (memoryLogger.logs = [])
return memoryLogger
}

View File

@@ -0,0 +1,42 @@
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 LEVELS from '../levels'
// https://github.com/paulgrove/node-syslog-client#syslogseverity
const LEVEL_TO_SEVERITY = {
[LEVELS.FATAL]: Severity.Critical,
[LEVELS.ERROR]: Severity.Error,
[LEVELS.WARN]: Severity.Warning,
[LEVELS.INFO]: Severity.Informational,
[LEVELS.DEBUG]: Severity.Debug,
}
const facility = Facility.User
export default target => {
const opts = {}
if (target !== undefined) {
if (startsWith(target, 'tcp://')) {
target = target.slice(6)
opts.transport = Transport.Tcp
} else if (startsWith(target, 'udp://')) {
target = target.slice(6)
opts.transport = Transport.Udp
}
;({ host: target, port: opts.port } = splitHost(target))
}
const client = createClient(target, opts)
return log =>
fromCallback(cb =>
client.log(log.message, {
facility,
severity: LEVEL_TO_SEVERITY[log.level],
})
)
}

View File

@@ -0,0 +1,62 @@
import escapeRegExp from 'lodash/escapeRegExp'
// ===================================================================
const TPL_RE = /\{\{(.+?)\}\}/g
export const evalTemplate = (tpl, data) => {
const getData =
typeof data === 'function' ? (_, key) => data(key) : (_, key) => data[key]
return tpl.replace(TPL_RE, getData)
}
// -------------------------------------------------------------------
const compileGlobPatternFragment = pattern =>
pattern
.split('*')
.map(escapeRegExp)
.join('.*')
export const compileGlobPattern = pattern => {
const no = []
const yes = []
pattern.split(/[\s,]+/).forEach(pattern => {
if (pattern[0] === '-') {
no.push(pattern.slice(1))
} else {
yes.push(pattern)
}
})
const raw = ['^']
if (no.length !== 0) {
raw.push('(?!', no.map(compileGlobPatternFragment).join('|'), ')')
}
if (yes.length !== 0) {
raw.push('(?:', yes.map(compileGlobPatternFragment).join('|'), ')')
} else {
raw.push('.*')
}
raw.push('$')
return new RegExp(raw.join(''))
}
// -------------------------------------------------------------------
export const required = name => {
throw new Error(`missing required arg ${name}`)
}
// -------------------------------------------------------------------
export const serializeError = error => ({
...error,
message: error.message,
name: error.name,
stack: error.stack,
})

View File

@@ -0,0 +1,13 @@
/* eslint-env jest */
import { compileGlobPattern } from './utils'
describe('compileGlobPattern()', () => {
it('works', () => {
const re = compileGlobPattern('foo, ba*, -bar')
expect(re.test('foo')).toBe(true)
expect(re.test('bar')).toBe(false)
expect(re.test('baz')).toBe(true)
expect(re.test('qux')).toBe(false)
})
})

View File

@@ -0,0 +1 @@
dist/transports

View File

@@ -0,0 +1,3 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)

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,49 @@
# ${pkg.name} [![Build Status](https://travis-ci.org/${pkg.shortGitHubPath}.png?branch=master)](https://travis-ci.org/${pkg.shortGitHubPath})
> ${pkg.description}
## Install
Installation of the [npm package](https://npmjs.org/package/${pkg.name}):
```
> npm install --save ${pkg.name}
```
## Usage
**TODO**
## 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](${pkg.bugs})
you've encountered;
- fork and create a pull request.
## License
${pkg.license} © [${pkg.author.name}](${pkg.author.url})

View File

@@ -0,0 +1,49 @@
{
"name": "@xen-orchestra/mixin",
"version": "0.0.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/mixin",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"dependencies": {
"bind-property-descriptor": "^1.0.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@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"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"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"
}
}

View File

@@ -0,0 +1,130 @@
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
// ===================================================================
const { defineProperties, getOwnPropertyDescriptor } = Object
const isIgnoredProperty = name => name[0] === '_' || name === 'constructor'
const IGNORED_STATIC_PROPERTIES = {
__proto__: null,
arguments: true,
caller: true,
length: true,
name: true,
prototype: true,
}
const isIgnoredStaticProperty = name => name in IGNORED_STATIC_PROPERTIES
const ownKeys =
(typeof Reflect !== 'undefined' && Reflect.ownKeys) ||
(({ getOwnPropertyNames: names, getOwnPropertySymbols: symbols }) =>
symbols !== undefined ? obj => names(obj).concat(symbols(obj)) : names)(
Object
)
// -------------------------------------------------------------------
const mixin = Mixins => Class => {
if (__DEV__ && !Array.isArray(Mixins)) {
throw new TypeError('Mixins should be an array')
}
const { name } = Class
// Copy properties of plain object mix-ins to the prototype.
{
const allMixins = Mixins
Mixins = []
const { prototype } = Class
const descriptors = { __proto__: null }
allMixins.forEach(Mixin => {
if (typeof Mixin === 'function') {
Mixins.push(Mixin)
return
}
for (const prop of ownKeys(Mixin)) {
if (__DEV__ && prop in prototype) {
throw new Error(`${name}#${prop} is already defined`)
}
;(descriptors[prop] = getOwnPropertyDescriptor(
Mixin,
prop
)).enumerable = false // Object methods are enumerable but class methods are not.
}
})
defineProperties(prototype, descriptors)
}
const n = Mixins.length
function DecoratedClass (...args) {
const instance = new Class(...args)
for (let i = 0; i < n; ++i) {
const Mixin = Mixins[i]
const { prototype } = Mixin
const mixinInstance = new Mixin(instance, ...args)
const descriptors = { __proto__: null }
const props = ownKeys(prototype)
for (let j = 0, m = props.length; j < m; ++j) {
const prop = props[j]
if (isIgnoredProperty(prop)) {
continue
}
if (prop in instance) {
throw new Error(`${name}#${prop} is already defined`)
}
descriptors[prop] = getBoundPropertyDescriptor(
prototype,
prop,
mixinInstance
)
}
defineProperties(instance, descriptors)
}
return instance
}
// Copy original and mixed-in static properties on Decorator class.
const descriptors = { __proto__: null }
ownKeys(Class).forEach(prop => {
let descriptor
if (
!(
isIgnoredStaticProperty(prop) &&
// if they already exist...
(descriptor = getOwnPropertyDescriptor(DecoratedClass, prop)) !==
undefined &&
// and are not configurable.
!descriptor.configurable
)
) {
descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
}
})
Mixins.forEach(Mixin => {
ownKeys(Mixin).forEach(prop => {
if (isIgnoredStaticProperty(prop)) {
return
}
if (__DEV__ && prop in descriptors) {
throw new Error(`${name}.${prop} is already defined`)
}
descriptors[prop] = getOwnPropertyDescriptor(Mixin, prop)
})
})
defineProperties(DecoratedClass, descriptors)
return DecoratedClass
}
export { mixin as default }

View File

@@ -4,6 +4,64 @@
### Enhancements
### Bug fixes
- [OVA Import] Allow import of files bigger than 127GB (PR [#3451](https://github.com/vatesfr/xen-orchestra/pull/3451))
- [File restore] Fix a path issue when going back to the parent folder (PR [#3446](https://github.com/vatesfr/xen-orchestra/pull/3446))
- [File restore] Fix a minor issue when showing which selected files are redundant (PR [#3447](https://github.com/vatesfr/xen-orchestra/pull/3447))
- [Memory] Fix a major leak [#2580](https://github.com/vatesfr/xen-orchestra/issues/2580) [#2820](https://github.com/vatesfr/xen-orchestra/issues/2820) (PR [#3453](https://github.com/vatesfr/xen-orchestra/pull/3453))
- [NFS Remotes] Fix `already mounted` race condition [#3380](https://github.com/vatesfr/xen-orchestra/issues/3380) (PR [#3460](https://github.com/vatesfr/xen-orchestra/pull/3460))
- Fix `Cannot read property 'type' of undefined` when deleting a VM (PR [#3465](https://github.com/vatesfr/xen-orchestra/pull/3465))
### Released packages
- @xen-orchestra/fs v0.3.1
- vhd-lib v0.3.1
- xo-vmdk-to-vhd v0.1.4
- xo-server v5.28.0
- xo-web v5.28.0
## **5.27.0** (2018-09-24)
### Enhancements
- [Remotes] Test the remote automatically on changes [#3323](https://github.com/vatesfr/xen-orchestra/issues/3323) (PR [#3397](https://github.com/vatesfr/xen-orchestra/pull/3397))
- [Remotes] Use *WORKGROUP* as default domain for new SMB remote (PR [#3398](https://github.com/vatesfr/xen-orchestra/pull/3398))
- [Backup NG form] Display a tip to encourage users to create vms on a thin-provisioned storage [#3334](https://github.com/vatesfr/xen-orchestra/issues/3334) (PR [#3402](https://github.com/vatesfr/xen-orchestra/pull/3402))
- [Backup NG form] improve schedule's form [#3138](https://github.com/vatesfr/xen-orchestra/issues/3138) (PR [#3359](https://github.com/vatesfr/xen-orchestra/pull/3359))
- [Backup NG Overview] Display transferred and merged data size for backup jobs [#3340](https://github.com/vatesfr/xen-orchestra/issues/3340) (PR [#3408](https://github.com/vatesfr/xen-orchestra/pull/3408))
- [VM] Display the PVHVM status [#3014](https://github.com/vatesfr/xen-orchestra/issues/3014) (PR [#3418](https://github.com/vatesfr/xen-orchestra/pull/3418))
- [Backup reports] Ability to test the plugin (PR [#3421](https://github.com/vatesfr/xen-orchestra/pull/3421))
- [Backup NG] Ability to restart failed VMs' backup [#3339](https://github.com/vatesfr/xen-orchestra/issues/3339) (PR [#3420](https://github.com/vatesfr/xen-orchestra/pull/3420))
- [VM] Ability to change the NIC type [#3423](https://github.com/vatesfr/xen-orchestra/issues/3423) (PR [#3440](https://github.com/vatesfr/xen-orchestra/pull/3440))
- [Backup NG Overview] Display the schedule's name [#3444](https://github.com/vatesfr/xen-orchestra/issues/3444) (PR [#3445](https://github.com/vatesfr/xen-orchestra/pull/3445))
### Bug fixes
- [Remotes] Rename connect(ed)/disconnect(ed) to enable(d)/disable(d) [#3323](https://github.com/vatesfr/xen-orchestra/issues/3323) (PR [#3396](https://github.com/vatesfr/xen-orchestra/pull/3396))
- [Remotes] Fix error appears twice on testing (PR [#3399](https://github.com/vatesfr/xen-orchestra/pull/3399))
- [Backup NG] Don't fail on VMs with empty VBDs (like CDs or floppy disks) (PR [#3410](https://github.com/vatesfr/xen-orchestra/pull/3410))
- [XOA updater] Fix issue where trial request would fail [#3407](https://github.com/vatesfr/xen-orchestra/issues/3407) (PR [#3412](https://github.com/vatesfr/xen-orchestra/pull/3412))
- [Backup NG logs] Fix log's value not being updated in the copy and report button [#3273](https://github.com/vatesfr/xen-orchestra/issues/3273) (PR [#3360](https://github.com/vatesfr/xen-orchestra/pull/3360))
- [Backup NG] Fix issue when *Delete first* was enabled for some of the remotes [#3424](https://github.com/vatesfr/xen-orchestra/issues/3424) (PR [#3427](https://github.com/vatesfr/xen-orchestra/pull/3427))
- [VM/host consoles] Work around a XenServer/XCP-ng issue which lead to some consoles not working [#3432](https://github.com/vatesfr/xen-orchestra/issues/3432) (PR [#3435](https://github.com/vatesfr/xen-orchestra/pull/3435))
- [Backup NG] Remove extraneous snapshots in case of multiple schedules [#3132](https://github.com/vatesfr/xen-orchestra/issues/3132) (PR [#3439](https://github.com/vatesfr/xen-orchestra/pull/3439))
- [Backup NG] Fix page reloaded on creating a schedule [#3461](https://github.com/vatesfr/xen-orchestra/issues/3461) (PR [#3462](https://github.com/vatesfr/xen-orchestra/pull/3462))
### Released packages
- xo-server-backup-reports v0.14.0
- @xen-orchestra/async-map v0.0.0
- @xen-orchestra/defined v0.0.0
- @xen-orchestra/emit-async v0.0.0
- @xen-orchestra/mixin v0.0.0
- xo-server v5.27.0
- xo-web v5.27.0
## **5.26.0** (2018-09-07)
### Enhancements
- [Backup (file) restore] Order backups by date in selector [#3294](https://github.com/vatesfr/xen-orchestra/issues/3294) (PR [#3374](https://github.com/vatesfr/xen-orchestra/pull/3374))
- [Self] Hide Tasks entry in menu for self users [#3311](https://github.com/vatesfr/xen-orchestra/issues/3311) (PR [#3373](https://github.com/vatesfr/xen-orchestra/pull/3373))
- [Tasks] Show previous tasks [#3266](https://github.com/vatesfr/xen-orchestra/issues/3266) (PR [#3377](https://github.com/vatesfr/xen-orchestra/pull/3377))

BIN
docs/assets/log-runId.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -29,6 +29,12 @@ You also have a filter to search anything related to these logs.
> Logs are not "live" tasks. If you restart XOA during a backup, the log associated with the job will stay in orange (in progress), because it wasn't finished. It will stay forever unfinished because the job was cut in the middle.
## Backups execution
Each backups' job execution is identified by a `runId`. You can find this `runId` in its detailed log.
![](./assets/log-runId.png)
## Consistent backup (with quiesce snapshots)
All backup types rely on snapshots. But what about data consistency? By default, Xen Orchestra will try to take a **quiesced snapshot** every time a snapshot is done (and fall back to normal snapshots if it's not possible).

View File

@@ -1,7 +1,7 @@
{
"devDependencies": {
"@babel/core": "7.0.0",
"@babel/register": "7.0.0",
"@babel/core": "^7.0.0",
"@babel/register": "^7.0.0",
"babel-core": "^7.0.0-0",
"babel-eslint": "^9.0.0",
"babel-jest": "^23.0.1",
@@ -15,18 +15,23 @@
"eslint-plugin-react": "^7.6.1",
"eslint-plugin-standard": "^4.0.0",
"exec-promise": "^0.7.0",
"flow-bin": "^0.80.0",
"flow-bin": "^0.81.0",
"globby": "^8.0.0",
"husky": "^0.14.3",
"husky": "^1.0.0-rc.15",
"jest": "^23.0.1",
"lodash": "^4.17.4",
"prettier": "^1.10.2",
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"sorted-object": "^2.0.1"
},
"engines": {
"yarn": "^1.7.0"
},
"husky": {
"hooks": {
"pre-commit": "scripts/lint-staged"
}
},
"jest": {
"collectCoverage": true,
"projects": [
@@ -49,7 +54,6 @@
"dev": "scripts/run-script --parallel dev",
"dev-test": "jest --bail --watch \"^(?!.*\\.integ\\.spec\\.js$)\"",
"posttest": "scripts/run-script test",
"precommit": "scripts/lint-staged",
"prepare": "scripts/run-script prepare",
"pretest": "eslint --ignore-path .gitignore .",
"test": "jest \"^(?!.*\\.integ\\.spec\\.js$)\"",

View File

@@ -30,9 +30,9 @@
"lodash": "^4.17.4"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@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.1",
"rimraf": "^2.6.2"

View File

@@ -28,10 +28,10 @@
},
"dependencies": {},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@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"
},

View File

@@ -33,14 +33,14 @@
"vhd-lib": "^0.3.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@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",
"execa": "^1.0.0",
"index-modules": "^0.3.0",
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"rimraf": "^2.6.1",
"tmp": "^0.0.33"
},

View File

@@ -3,7 +3,7 @@
import execa from 'execa'
import rimraf from 'rimraf'
import tmp from 'tmp'
import { fromCallback as pFromCallback } from 'promise-toolbox'
import { pFromCallback } from 'promise-toolbox'
import command from './commands/info'

View File

@@ -1,6 +1,6 @@
{
"name": "vhd-lib",
"version": "0.3.0",
"version": "0.3.1",
"license": "AGPL-3.0",
"description": "Primitives for VHD file handling",
"keywords": [],
@@ -24,16 +24,16 @@
"from2": "^2.3.0",
"fs-extra": "^7.0.0",
"limit-concurrency-decorator": "^0.4.0",
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"struct-fu": "^1.2.0",
"uuid": "^3.0.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@xen-orchestra/fs": "^0.3.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"@xen-orchestra/fs": "^0.3.1",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"execa": "^1.0.0",

View File

@@ -1,13 +1,10 @@
import { SECTOR_SIZE } from './_constants'
export default function computeGeometryForSize (size) {
const totalSectors = Math.ceil(size / 512)
const totalSectors = Math.min(Math.ceil(size / 512), 65535 * 16 * 255)
let sectorsPerTrackCylinder
let heads
let cylinderTimesHeads
if (totalSectors > 65535 * 16 * 255) {
throw Error('disk is too big')
}
// straight copypasta from the file spec appendix on CHS Calculation
if (totalSectors >= 65535 * 16 * 63) {
sectorsPerTrackCylinder = 255

View File

@@ -5,9 +5,9 @@ import fs from 'fs-extra'
import getStream from 'get-stream'
import rimraf from 'rimraf'
import tmp from 'tmp'
import { fromEvent, pFromCallback } from 'promise-toolbox'
import { getHandler } from '@xen-orchestra/fs'
import { randomBytes } from 'crypto'
import { fromEvent, fromCallback as pFromCallback } from 'promise-toolbox'
import chainVhd from './chain'
import createReadStream from './createSyntheticStream'

View File

@@ -3,7 +3,7 @@ import execa from 'execa'
import rimraf from 'rimraf'
import tmp from 'tmp'
import { createWriteStream, readFile } from 'fs-promise'
import { fromCallback as pFromCallback, fromEvent } from 'promise-toolbox'
import { fromEvent, pFromCallback } from 'promise-toolbox'
import { createFooter } from './_createFooterHeader'
import createReadableRawVHDStream from './createReadableRawStream'

View File

@@ -1,6 +1,6 @@
{
"name": "xen-api",
"version": "0.18.0",
"version": "0.19.0",
"license": "ISC",
"description": "Connector to the Xen API",
"keywords": [
@@ -33,10 +33,10 @@
},
"dependencies": {
"blocked": "^1.2.1",
"debug": "^3.1.0",
"debug": "^4.0.1",
"event-to-promise": "^0.8.0",
"exec-promise": "^0.7.0",
"http-request-plus": "^0.5.0",
"http-request-plus": "^0.6.0",
"iterable-backoff": "^0.0.0",
"jest-diff": "^23.5.0",
"json-rpc-protocol": "^0.12.0",
@@ -45,16 +45,16 @@
"make-error": "^1.3.0",
"minimist": "^1.2.0",
"ms": "^2.1.1",
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"pw": "0.0.4",
"xmlrpc": "^1.3.2",
"xo-collection": "^0.4.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/plugin-proposal-decorators": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.1"

View File

@@ -21,12 +21,12 @@ import {
import {
Cancel,
cancelable,
catchPlus as pCatch,
defer,
delay as pDelay,
fromEvents,
lastly,
timeout as pTimeout,
pCatch,
pDelay,
pFinally,
pTimeout,
TimeoutError,
} from 'promise-toolbox'
@@ -428,7 +428,7 @@ export class Xapi extends EventEmitter {
this._sessionCall('task.cancel', [taskRef]).catch(noop)
})
return lastly.call(this.watchTask(taskRef), () => {
return pFinally.call(this.watchTask(taskRef), () => {
this._sessionCall('task.destroy', [taskRef]).catch(noop)
})
})

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { delay as pDelay } from 'promise-toolbox'
import { pDelay } from 'promise-toolbox'
import { createClient } from './'

View File

@@ -28,7 +28,7 @@
"node": ">=6"
},
"dependencies": {
"@babel/polyfill": "7.0.0",
"@babel/polyfill": "^7.0.0",
"bluebird": "^3.5.1",
"chalk": "^2.2.0",
"event-to-promise": "^0.8.0",
@@ -41,7 +41,7 @@
"micromatch": "^3.1.3",
"mkdirp": "^0.5.1",
"nice-pipe": "0.0.0",
"pretty-ms": "^3.0.1",
"pretty-ms": "^4.0.0",
"progress-stream": "^2.0.0",
"pw": "^0.0.4",
"strip-indent": "^2.0.0",
@@ -49,10 +49,10 @@
"xo-lib": "^0.9.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@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"

View File

@@ -30,9 +30,9 @@
"make-error": "^1.0.2"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"cross-env": "^5.1.3",
"event-to-promise": "^0.8.0",
"rimraf": "^2.6.1"

View File

@@ -27,9 +27,9 @@
"lodash": "^4.13.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@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",
"deep-freeze": "^0.0.1",

View File

@@ -38,12 +38,12 @@
"inquirer": "^6.0.0",
"ldapjs": "^1.0.1",
"lodash": "^4.17.4",
"promise-toolbox": "^0.9.5"
"promise-toolbox": "^0.10.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@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"

View File

@@ -1,5 +1,5 @@
import { forEach, isFinite, isInteger } from 'lodash'
import { forOwn as forOwnAsync } from 'promise-toolbox'
import { pforOwn } from 'promise-toolbox'
import { prompt } from 'inquirer'
// ===================================================================
@@ -160,7 +160,7 @@ const promptByType = {
}
}
await forOwnAsync.call(schema.properties || {}, promptProperty)
await pforOwn.call(schema.properties || {}, promptProperty)
return value
},

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server-backup-reports",
"version": "0.13.0",
"version": "0.14.0",
"license": "AGPL-3.0",
"description": "Backup reports plugin for XO-Server",
"keywords": [
@@ -40,9 +40,9 @@
"moment-timezone": "^0.5.13"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@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"

View File

@@ -30,6 +30,20 @@ export const configurationSchema = {
},
}
export const testSchema = {
type: 'object',
properties: {
runId: {
type: 'string',
description: `<a href="https://xen-orchestra.com/docs/backups.html#backups-execution" rel="noopener noreferrer" target="_blank">job's runId</a>`,
},
},
additionalProperties: false,
required: ['runId'],
}
// ===================================================================
const ICON_FAILURE = '🚨'
@@ -118,6 +132,10 @@ class BackupReportsXoPlugin {
this._xo.on('job:terminated', this._report)
}
test ({ runId }) {
return this._backupNgListener(undefined, undefined, undefined, runId)
}
unload () {
this._xo.removeListener('job:terminated', this._report)
}
@@ -135,6 +153,9 @@ class BackupReportsXoPlugin {
async _backupNgListener (_1, _2, schedule, runJobId) {
const xo = this._xo
const log = await xo.getBackupNgLogs(runJobId)
if (log === undefined) {
throw new Error(`no log found with runId=${JSON.stringify(runJobId)}`)
}
const { reportWhen, mode } = log.data || {}
if (

View File

@@ -36,9 +36,9 @@
"superagent": "^3.8.2"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@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"
},

View File

@@ -34,9 +34,9 @@
"lodash": "^4.16.2"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2"
},
"scripts": {

View File

@@ -26,10 +26,10 @@
"lodash": "^4.17.4"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@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"

View File

@@ -33,7 +33,7 @@
"dependencies": {
"nodemailer": "^4.4.1",
"nodemailer-markdown": "^1.0.1",
"promise-toolbox": "^0.9.5"
"promise-toolbox": "^0.10.1"
},
"devDependencies": {
"@babel/cli": "^7.0.0",

View File

@@ -32,7 +32,7 @@
"node": ">=6"
},
"dependencies": {
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"slack-node": "^0.1.8"
},
"devDependencies": {

View File

@@ -38,12 +38,12 @@
"handlebars": "^4.0.6",
"html-minifier": "^3.5.8",
"lodash": "^4.17.4",
"promise-toolbox": "^0.9.5"
"promise-toolbox": "^0.10.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@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"

View File

@@ -1,6 +1,6 @@
{
"name": "xo-server",
"version": "5.26.0",
"version": "5.27.1",
"license": "AGPL-3.0",
"description": "Server part of Xen-Orchestra",
"keywords": [
@@ -31,15 +31,16 @@
"node": ">=6"
},
"dependencies": {
"@babel/polyfill": "7.0.0",
"@xen-orchestra/async-map": "^0.0.0",
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/fs": "^0.3.0",
"@xen-orchestra/emit-async": "^0.0.0",
"@xen-orchestra/fs": "^0.3.1",
"@xen-orchestra/mixin": "^0.0.0",
"ajv": "^6.1.1",
"app-conf": "^0.5.0",
"archiver": "^3.0.0",
"async-iterator-to-stream": "^1.0.1",
"base64url": "^3.0.0",
"bind-property-descriptor": "^1.0.0",
"blocked": "^1.2.1",
"bluebird": "^3.5.1",
"body-parser": "^1.18.2",
@@ -47,7 +48,7 @@
"cookie": "^0.3.1",
"cookie-parser": "^1.4.3",
"d3-time-format": "^2.1.1",
"debug": "^3.1.0",
"debug": "^4.0.1",
"decorator-synchronized": "^0.3.0",
"deptree": "^1.0.0",
"escape-string-regexp": "^1.0.5",
@@ -65,7 +66,7 @@
"helmet": "^3.9.0",
"highland": "^2.11.1",
"http-proxy": "^1.16.2",
"http-request-plus": "^0.5.0",
"http-request-plus": "^0.6.0",
"http-server-plus": "^0.10.0",
"human-format": "^0.10.0",
"is-redirect": "^1.0.0",
@@ -93,7 +94,7 @@
"passport": "^0.4.0",
"passport-local": "^1.0.0",
"pretty-format": "^23.0.0",
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"proxy-agent": "^3.0.0",
"pug": "^2.0.0-rc.4",
"pump": "^3.0.0",
@@ -111,29 +112,29 @@
"tmp": "^0.0.33",
"uuid": "^3.0.1",
"value-matcher": "^0.2.0",
"vhd-lib": "^0.3.0",
"vhd-lib": "^0.3.1",
"ws": "^6.0.0",
"xen-api": "^0.18.0",
"xen-api": "^0.19.0",
"xml2js": "^0.4.19",
"xo-acl-resolver": "^0.2.4",
"xo-collection": "^0.4.1",
"xo-common": "^0.1.1",
"xo-remote-parser": "^0.5.0",
"xo-vmdk-to-vhd": "^0.1.3",
"xo-vmdk-to-vhd": "^0.1.4",
"yazl": "^2.4.3"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/plugin-proposal-decorators": "7.0.0",
"@babel/plugin-proposal-export-default-from": "7.0.0",
"@babel/plugin-proposal-export-namespace-from": "7.0.0",
"@babel/plugin-proposal-function-bind": "7.0.0",
"@babel/plugin-proposal-optional-chaining": "7.0.0",
"@babel/plugin-proposal-pipeline-operator": "7.0.0",
"@babel/plugin-proposal-throw-expressions": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-proposal-export-default-from": "^7.0.0",
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"index-modules": "^0.3.0",

View File

@@ -118,8 +118,13 @@ getJob.params = {
},
}
export async function runJob ({ id, schedule, vm }) {
return this.runJobSequence([id], await this.getSchedule(schedule), vm)
export async function runJob ({
id,
schedule,
vm,
vms = vm !== undefined ? [vm] : undefined,
}) {
return this.runJobSequence([id], await this.getSchedule(schedule), vms)
}
runJob.permission = 'admin'
@@ -135,6 +140,13 @@ runJob.params = {
type: 'string',
optional: true,
},
vms: {
type: 'array',
items: {
type: 'string',
},
optional: true,
},
}
// -----------------------------------------------------------------------------

View File

@@ -1,7 +1,8 @@
import asyncMap from '@xen-orchestra/async-map'
import { some } from 'lodash'
import { asInteger } from '../xapi/utils'
import { asyncMap, ensureArray, forEach, parseXml } from '../utils'
import { ensureArray, forEach, parseXml } from '../utils'
// ===================================================================

View File

@@ -615,6 +615,9 @@ set.params = {
resourceSet: { type: ['string', 'null'], optional: true },
share: { type: 'boolean', optional: true },
// set the VM network interface controller
nicType: { type: ['string', 'null'], optional: true },
}
set.resolve = {

View File

@@ -1,3 +1,4 @@
import asyncMap from '@xen-orchestra/async-map'
import createLogger from 'debug'
import defer from 'golike-defer'
import execa from 'execa'
@@ -9,7 +10,7 @@ import { v4 as generateUuid } from 'uuid'
import { includes, remove, filter, find, range } from 'lodash'
import { asInteger } from '../xapi/utils'
import { asyncMap, parseXml, ensureArray } from '../utils'
import { parseXml, ensureArray } from '../utils'
const debug = createLogger('xo:xosan')

View File

@@ -1,3 +1,4 @@
import asyncMap from '@xen-orchestra/async-map'
import { createClient as createRedisClient } from 'redis'
import {
difference,
@@ -11,7 +12,6 @@ import { ignoreErrors, promisifyAll } from 'promise-toolbox'
import { v4 as generateUuid } from 'uuid'
import Collection, { ModelAlreadyExists } from '../collection'
import { asyncMap } from '../utils'
// ===================================================================

View File

@@ -1,13 +1,3 @@
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
import { isArray, isFunction } from './utils'
// ===================================================================
const { defineProperties, getOwnPropertyDescriptor } = Object
// ===================================================================
// Debounce decorator for methods.
//
// See: https://github.com/wycats/javascript-decorators
@@ -49,118 +39,3 @@ export const debounce = duration => (target, name, descriptor) => {
descriptor.value = debounced
return descriptor
}
// -------------------------------------------------------------------
const _ownKeys =
(typeof Reflect !== 'undefined' && Reflect.ownKeys) ||
(({ getOwnPropertyNames: names, getOwnPropertySymbols: symbols }) =>
symbols ? obj => names(obj).concat(symbols(obj)) : names)(Object)
const _isIgnoredProperty = name => name[0] === '_' || name === 'constructor'
const _IGNORED_STATIC_PROPERTIES = {
__proto__: null,
arguments: true,
caller: true,
length: true,
name: true,
prototype: true,
}
const _isIgnoredStaticProperty = name => _IGNORED_STATIC_PROPERTIES[name]
export const mixin = MixIns => Class => {
if (!isArray(MixIns)) {
MixIns = [MixIns]
}
const { name } = Class
// Copy properties of plain object mix-ins to the prototype.
{
const allMixIns = MixIns
MixIns = []
const { prototype } = Class
const descriptors = { __proto__: null }
for (const MixIn of allMixIns) {
if (isFunction(MixIn)) {
MixIns.push(MixIn)
continue
}
for (const prop of _ownKeys(MixIn)) {
if (prop in prototype) {
throw new Error(`${name}#${prop} is already defined`)
}
;(descriptors[prop] = getOwnPropertyDescriptor(
MixIn,
prop
)).enumerable = false // Object methods are enumerable but class methods are not.
}
}
defineProperties(prototype, descriptors)
}
function Decorator (...args) {
const instance = new Class(...args)
for (const MixIn of MixIns) {
const { prototype } = MixIn
const mixinInstance = new MixIn(instance, ...args)
const descriptors = { __proto__: null }
for (const prop of _ownKeys(prototype)) {
if (_isIgnoredProperty(prop)) {
continue
}
if (prop in instance) {
throw new Error(`${name}#${prop} is already defined`)
}
descriptors[prop] = getBoundPropertyDescriptor(
prototype,
prop,
mixinInstance
)
}
defineProperties(instance, descriptors)
}
return instance
}
// Copy original and mixed-in static properties on Decorator class.
const descriptors = { __proto__: null }
for (const prop of _ownKeys(Class)) {
let descriptor
if (
!(
_isIgnoredStaticProperty(prop) &&
// if they already exist...
(descriptor = getOwnPropertyDescriptor(Decorator, prop)) &&
// and are not configurable.
!descriptor.configurable
)
) {
descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
}
}
for (const MixIn of MixIns) {
for (const prop of _ownKeys(MixIn)) {
if (_isIgnoredStaticProperty(prop)) {
continue
}
if (prop in descriptors) {
throw new Error(`${name}.${prop} is already defined`)
}
descriptors[prop] = getOwnPropertyDescriptor(MixIn, prop)
}
}
defineProperties(Decorator, descriptors)
return Decorator
}

View File

@@ -1,4 +1,5 @@
import appConf from 'app-conf'
import assert from 'assert'
import bind from 'lodash/bind'
import blocked from 'blocked'
import createExpress from 'express'
@@ -540,6 +541,9 @@ ${name} v${version}`)(require('../package.json'))
// ===================================================================
export default async function main (args) {
// makes sure the global Promise has not been changed by a lib
assert(global.Promise === require('bluebird'))
if (includes(args, '--help') || includes(args, '-h')) {
return USAGE
}

View File

@@ -7,12 +7,23 @@ const debug = createDebug('xo:proxy-console')
export default function proxyConsole (ws, vmConsole, sessionId) {
const url = parse(vmConsole.location)
let { hostname } = url
if (hostname === null || hostname === '') {
console.warn(
'host is missing in console (%s) URI (%s)',
vmConsole.uuid,
vmConsole.location
)
const { address } = vmConsole.$VM.$resident_on
console.warn(' using host address (%s) as fallback', address)
hostname = address
}
let closed = false
const socket = connect(
{
host: url.host,
host: hostname,
port: url.port || 443,
rejectUnauthorized: false,
},
@@ -21,7 +32,7 @@ export default function proxyConsole (ws, vmConsole, sessionId) {
socket.write(
[
`CONNECT ${url.path} HTTP/1.0`,
`Host: ${url.hostname}`,
`Host: ${hostname}`,
`Cookie: session_id=${sessionId}`,
'',
'',

View File

@@ -7,7 +7,6 @@ import isArray from 'lodash/isArray'
import isString from 'lodash/isString'
import keys from 'lodash/keys'
import kindOf from 'kindof'
import mapToArray from 'lodash/map'
import multiKeyHashInt from 'multikey-hash'
import pick from 'lodash/pick'
import tmp from 'tmp'
@@ -15,46 +14,10 @@ import xml2js from 'xml2js'
import { randomBytes } from 'crypto'
import { dirname, resolve } from 'path'
import { utcFormat, utcParse } from 'd3-time-format'
import {
all as pAll,
fromCallback,
isPromise,
promisify,
reflect as pReflect,
} from 'promise-toolbox'
import { fromCallback, pAll, pReflect, promisify } from 'promise-toolbox'
// ===================================================================
// Similar to map() + Promise.all() but wait for all promises to
// settle before rejecting (with the first error)
export const asyncMap = (collection, iteratee) => {
if (isPromise(collection)) {
return collection.then(collection => asyncMap(collection, iteratee))
}
let errorContainer
const onError = error => {
if (errorContainer === undefined) {
errorContainer = { error }
}
}
return Promise.all(
mapToArray(collection, (item, key, collection) =>
new Promise(resolve => {
resolve(iteratee(item, key, collection))
}).catch(onError)
)
).then(values => {
if (errorContainer !== undefined) {
throw errorContainer.error
}
return values
})
}
// -------------------------------------------------------------------
export function camelToSnakeCase (string) {
return string.replace(
/([a-z0-9])([A-Z])/g,
@@ -267,19 +230,19 @@ export function pDebug (promise, name) {
//
// Usage: pSettle(promises) or promises::pSettle()
export function pSettle (promises) {
return (this || promises)::pAll(p => p::pReflect())
return (this || promises)::pAll(p => Promise.resolve(p)::pReflect())
}
// -------------------------------------------------------------------
export {
all as pAll,
delay as pDelay,
fromCallback as pFromCallback,
lastly as pFinally,
pAll,
pDelay,
pFinally,
pFromCallback,
pReflect,
promisify,
promisifyAll,
reflect as pReflect,
} from 'promise-toolbox'
// -------------------------------------------------------------------

View File

@@ -2,17 +2,6 @@
import { type Readable } from 'stream'
type MaybePromise<T> = Promise<T> | T
declare export function asyncMap<T1, T2>(
collection: MaybePromise<T1[]>,
(T1, number) => MaybePromise<T2>
): Promise<T2[]>
declare export function asyncMap<K, V1, V2>(
collection: MaybePromise<{ [K]: V1 }>,
(V1, K) => MaybePromise<V2>
): Promise<V2[]>
declare export function getPseudoRandomBytes(n: number): Buffer
declare export function resolveRelativeFromFile(file: string, path: string): string

View File

@@ -343,7 +343,11 @@ const TRANSFORMS = {
startTime: metrics && toTimestamp(metrics.start_time),
tags: obj.tags,
VIFs: link(obj, 'VIFs'),
virtualizationMode: isHvm ? 'hvm' : 'pv',
virtualizationMode: isHvm
? guestMetrics !== undefined && guestMetrics.PV_drivers_detected
? 'pvhvm'
: 'hvm'
: 'pv',
// <=> Are the Xen Server tools installed?
//
@@ -360,6 +364,7 @@ const TRANSFORMS = {
// TODO: dedupe
VGPUs: link(obj, 'VGPUs'),
$VGPUs: link(obj, 'VGPUs'),
nicType: obj.platform.nic_type,
}
if (isHvm) {

View File

@@ -1,16 +1,18 @@
/* eslint-disable camelcase */
import asyncMap from '@xen-orchestra/async-map'
import concurrency from 'limit-concurrency-decorator'
import deferrable from 'golike-defer'
import fatfs from 'fatfs'
import mixin from '@xen-orchestra/mixin'
import synchronized from 'decorator-synchronized'
import tarStream from 'tar-stream'
import vmdkToVhd from 'xo-vmdk-to-vhd'
import {
cancelable,
catchPlus as pCatch,
defer,
fromEvent,
ignoreErrors,
pCatch,
} from 'promise-toolbox'
import { PassThrough } from 'stream'
import { forbiddenOperation } from 'xo-common/api-errors'
@@ -32,9 +34,7 @@ import { satisfies as versionSatisfies } from 'semver'
import createSizeStream from '../size-stream'
import fatfsBuffer, { init as fatfsBufferInit } from '../fatfs-buffer'
import { mixin } from '../decorators'
import {
asyncMap,
camelToSnakeCase,
ensureArray,
forEach,
@@ -653,6 +653,9 @@ export default class Xapi extends XapiBase {
const { $ref } = vm
// ensure the vm record is up-to-date
vm = await this.barrier($ref)
// It is necessary for suspended VMs to be shut down
// to be able to delete their VDIs.
if (vm.power_state !== 'Halted') {
@@ -665,22 +668,26 @@ export default class Xapi extends XapiBase {
})
}
// ensure the vm record is up-to-date
vm = await this.barrier($ref)
if (forceDeleteDefaultTemplate) {
await this._updateObjectMapProperty(vm, 'other_config', {
default_template: null,
})
}
// must be done before destroying the VM
const disks = getVmDisks(vm)
// this cannot be done in parallel, otherwise disks and snapshots will be
// destroyed even if this fails
await this.call('VM.destroy', $ref)
return Promise.all([
forceDeleteDefaultTemplate &&
this._updateObjectMapProperty(vm, 'other_config', {
default_template: null,
}),
this.call('VM.destroy', $ref),
asyncMap(vm.$snapshots, snapshot =>
this._deleteVm(snapshot)
)::ignoreErrors(),
deleteDisks &&
asyncMap(getVmDisks(vm), ({ $ref: vdiRef }) => {
asyncMap(disks, ({ $ref: vdiRef }) => {
let onFailure = () => {
onFailure = vdi => {
console.error(
@@ -977,7 +984,10 @@ export default class Xapi extends XapiBase {
const baseVdis = {}
baseVm &&
forEach(baseVm.$VBDs, vbd => {
baseVdis[vbd.VDI] = vbd.$VDI
const vdi = vbd.$VDI
if (vdi !== undefined) {
baseVdis[vbd.VDI] = vbd.$VDI
}
})
// 1. Create the VMs.
@@ -1548,23 +1558,21 @@ export default class Xapi extends XapiBase {
return host === undefined
? this.call(
'VM.start',
vm.$ref,
false, // Start paused?
false // Skip pre-boot checks?
)
: this.call(
'VM.start_on',
vm.$ref,
host.$ref,
false,
false
)
'VM.start',
vm.$ref,
false, // Start paused?
false // Skip pre-boot checks?
)
: this.call('VM.start_on', vm.$ref, host.$ref, false, false)
}
async startVm (vmId, hostId, force) {
try {
await this._startVm(this.getObject(vmId), hostId && this.getObject(hostId), force)
await this._startVm(
this.getObject(vmId),
hostId && this.getObject(hostId),
force
)
} catch (e) {
if (e.code === 'OPERATION_BLOCKED') {
throw forbiddenOperation('Start', e.params[1])

View File

@@ -1,3 +1,4 @@
import asyncMap from '@xen-orchestra/async-map'
import deferrable from 'golike-defer'
import every from 'lodash/every'
import filter from 'lodash/filter'
@@ -12,7 +13,6 @@ import unzip from 'julien-f-unzip'
import { debounce } from '../../decorators'
import {
asyncMap,
ensureArray,
forEach,
mapFilter,

View File

@@ -1,5 +1,5 @@
import deferrable from 'golike-defer'
import { catchPlus as pCatch, ignoreErrors } from 'promise-toolbox'
import { ignoreErrors, pCatch } from 'promise-toolbox'
import { find, gte, includes, isEmpty, lte, noop } from 'lodash'
import { forEach, mapToArray, parseSize } from '../../utils'
@@ -382,6 +382,14 @@ export default {
hasVendorDevice: true,
nicType: {
set (nicType, vm) {
return this._updateObjectMapProperty(vm, 'platform', {
nic_type: nicType,
})
},
},
vga: {
set (vga, vm) {
if (!includes(XEN_VGA_VALUES, vga)) {

View File

@@ -2,6 +2,7 @@
// $FlowFixMe
import type RemoteHandler from '@xen-orchestra/fs'
import asyncMap from '@xen-orchestra/async-map'
import defer from 'golike-defer'
import limitConcurrency from 'limit-concurrency-decorator'
import { type Pattern, createPredicate } from 'value-matcher'
@@ -10,7 +11,9 @@ import { AssertionError } from 'assert'
import { basename, dirname } from 'path'
import {
countBy,
flatMap,
forEach,
groupBy,
isEmpty,
last,
mapValues,
@@ -19,7 +22,7 @@ import {
sum,
values,
} from 'lodash'
import { fromEvent as pFromEvent, ignoreErrors } from 'promise-toolbox'
import { CancelToken, pFromEvent, ignoreErrors } from 'promise-toolbox'
import Vhd, {
chainVhd,
createSyntheticStream as createVhdReadStream,
@@ -39,7 +42,6 @@ import {
} from '../../xapi'
import { getVmDisks } from '../../xapi/utils'
import {
asyncMap,
resolveRelativeFromFile,
safeDateFormat,
serializeError,
@@ -117,14 +119,11 @@ const compareReplicatedVmDatetime = (a: Vm, b: Vm): number =>
const compareTimestamp = (a: Metadata, b: Metadata): number =>
a.timestamp - b.timestamp
// returns all entries but the last (retention - 1)-th
//
// the “-1” is because this code is usually run with entries computed before the
// new entry is created
// returns all entries but the last retention-th
const getOldEntries = <T>(retention: number, entries?: T[]): T[] =>
entries === undefined
? []
: --retention > 0
: retention > 0
? entries.slice(0, -retention)
: entries
@@ -433,7 +432,7 @@ export default class BackupNg {
app.on('start', () => {
const executor: Executor = async ({
cancelToken,
data: vmId,
data: vmsId,
job: job_,
logger,
runJobId,
@@ -445,17 +444,20 @@ export default class BackupNg {
const job: BackupJob = (job_: any)
let vms: $Dict<Vm> | void
if (vmId === undefined) {
vms = app.getObjects({
filter: createPredicate({
type: 'VM',
...job.vms,
}),
})
if (isEmpty(vms)) {
throw new Error('no VMs match this pattern')
}
const vms: $Dict<Vm> = app.getObjects({
filter: createPredicate({
type: 'VM',
...(vmsId !== undefined
? {
id: {
__or: vmsId,
},
}
: job.vms),
}),
})
if (isEmpty(vms)) {
throw new Error('no VMs match this pattern')
}
const jobId = job.id
const srs = unboxIds(job.srs).map(id => {
@@ -474,9 +476,9 @@ export default class BackupNg {
const timeout = getSetting(job.settings, 'timeout', [''])
if (timeout !== 0) {
cancelToken = cancelToken.fork(cancel => {
setTimeout(cancel, timeout)
})
const source = CancelToken.source([cancelToken])
cancelToken = source.token
setTimeout(source.cancel, timeout)
}
let handleVm = async vm => {
@@ -495,7 +497,7 @@ export default class BackupNg {
let vmCancel
try {
cancelToken.throwIfRequested()
vmCancel = cancelToken.fork()
vmCancel = CancelToken.source([cancelToken])
// $FlowFixMe injected $defer param
const p = this._backupVm(
@@ -541,10 +543,6 @@ export default class BackupNg {
}
}
if (vms === undefined) {
return handleVm(await app.getObject(vmId))
}
const concurrency: number = getSetting(job.settings, 'concurrency', [
'',
])
@@ -778,7 +776,7 @@ export default class BackupNg {
)
}
const { id: jobId, settings } = job
const { id: jobId, mode, settings } = job
const { id: scheduleId } = schedule
let exportRetention: number = getSetting(settings, 'exportRetention', [
@@ -889,18 +887,29 @@ export default class BackupNg {
})
)
$defer(() =>
asyncMap(
snapshot = await xapi.barrier(snapshot.$ref)
let baseSnapshot = mode === 'delta' ? last(snapshots) : undefined
snapshots.push(snapshot)
// snapshots to delete due to the snapshot retention settings
const snapshotsToDelete = flatMap(
groupBy(snapshots, _ => _.other_config['xo:backup:schedule']),
(snapshots, scheduleId) =>
getOldEntries(
snapshotRetention,
snapshots.filter(
_ => _.other_config['xo:backup:schedule'] === scheduleId
)
),
_ => xapi.deleteVm(_)
)
getSetting(settings, 'snapshotRetention', [scheduleId]),
snapshots
)
)
// delete unused snapshots
await asyncMap(snapshotsToDelete, vm => {
// snapshot and baseSnapshot should not be deleted right now
if (vm !== snapshot && vm !== baseSnapshot) {
return xapi.deleteVm(vm)
}
})
snapshot = ((await wrapTask(
{
logger,
@@ -923,10 +932,10 @@ export default class BackupNg {
const metadataFilename = `${vmDir}/${basename}.json`
if (job.mode === 'full') {
if (mode === 'full') {
// TODO: do not create the snapshot if there are no snapshotRetention and
// the VM is not running
if (snapshotRetention === 0) {
if (snapshotsToDelete.includes(snapshot)) {
$defer.call(xapi, 'deleteVm', snapshot)
}
@@ -992,7 +1001,7 @@ export default class BackupNg {
)::ignoreErrors()
const oldBackups: MetadataFull[] = (getOldEntries(
exportRetention,
exportRetention - 1,
await this._listVmBackups(
handler,
vm,
@@ -1039,7 +1048,7 @@ export default class BackupNg {
const { $id: srId, xapi } = sr
const oldVms = getOldEntries(
copyRetention,
copyRetention - 1,
listReplicatedVms(xapi, scheduleId, srId, vmUuid)
)
@@ -1086,11 +1095,13 @@ export default class BackupNg {
],
noop // errors are handled in logs
)
} else if (job.mode === 'delta') {
if (snapshotRetention === 0) {
// only keep the snapshot in case of success
} else if (mode === 'delta') {
if (snapshotsToDelete.includes(snapshot)) {
$defer.onFailure.call(xapi, 'deleteVm', snapshot)
}
if (snapshotsToDelete.includes(baseSnapshot)) {
$defer.onSuccess.call(xapi, 'deleteVm', baseSnapshot)
}
// JFT: TODO: remove when enough time has passed (~2018-09)
//
@@ -1115,9 +1126,8 @@ export default class BackupNg {
)
)
let baseSnapshot, fullVdisRequired
let fullVdisRequired
await (async () => {
baseSnapshot = (last(snapshots): Vm | void)
if (baseSnapshot === undefined) {
return
}
@@ -1227,17 +1237,28 @@ export default class BackupNg {
const streams: any = mapValues(
deltaExport.streams,
lazyStream => {
const pStream = lazyStream()
const forks = Array.from({ length: nTargets }, _ => {
const promise = pStream.then(stream => {
const fork: any = stream.pipe(new PassThrough())
fork.task = stream.task
return fork
})
promise.catch(noop) // prevent unhandled rejection
return promise
})
return () => forks.pop()
// wait for all targets to require the stream and then starts
// the real export and create the forks.
const resolves = []
function resolver (resolve) {
resolves.push(resolve)
if (resolves.length === nTargets) {
const pStream = lazyStream()
resolves.forEach(resolve => {
resolve(
pStream.then(stream => {
const fork: any = stream.pipe(new PassThrough())
fork.task = stream.task
return fork
})
)
})
resolves.length = 0
}
}
return () => new Promise(resolver)
}
)
return () => {
@@ -1266,7 +1287,7 @@ export default class BackupNg {
const fork = forkExport()
const oldBackups: MetadataDelta[] = (getOldEntries(
exportRetention,
exportRetention - 1,
await this._listVmBackups(
handler,
vm,
@@ -1370,7 +1391,7 @@ export default class BackupNg {
const { $id: srId, xapi } = sr
const oldVms = getOldEntries(
copyRetention,
copyRetention - 1,
listReplicatedVms(xapi, scheduleId, srId, vmUuid)
)
@@ -1416,7 +1437,7 @@ export default class BackupNg {
noop // errors are handled in logs
)
} else {
throw new Error(`no exporter for backup mode ${job.mode}`)
throw new Error(`no exporter for backup mode ${mode}`)
}
}

View File

@@ -1,3 +1,4 @@
import asyncMap from '@xen-orchestra/async-map'
import deferrable from 'golike-defer'
import escapeStringRegexp from 'escape-string-regexp'
import execa from 'execa'
@@ -29,7 +30,6 @@ import createSizeStream from '../size-stream'
import xapiObjectToXo from '../xapi-object-to-xo'
import { lvs, pvs } from '../lvm'
import {
asyncMap,
forEach,
getFirstPropertyName,
mapFilter,

View File

@@ -1,7 +1,7 @@
import createDebug from 'debug'
import DepTree from 'deptree'
import { all as pAll } from 'promise-toolbox'
import { mapValues } from 'lodash'
import { pAll } from 'promise-toolbox'
const debug = createDebug('xo:config-management')

View File

@@ -1,34 +1,8 @@
import createLogger from 'debug'
import emitAsync from '@xen-orchestra/emit-async'
const debug = createLogger('xo:hooks')
function emitAsync (event) {
let opts
let i = 1
// an option object has been passed as first param
if (typeof event !== 'string') {
opts = event
event = arguments[i++]
}
const n = arguments.length - i
const args = new Array(n)
for (let j = 0; j < n; ++j) {
args[j] = arguments[j + i]
}
const onError = opts != null && opts.onError
return Promise.all(
this.listeners(event).map(listener =>
new Promise(resolve => {
resolve(listener.apply(this, args))
}).catch(onError)
)
)
}
const makeSingletonHook = (hook, postEvent) => {
let promise
return function () {

View File

@@ -1,9 +1,10 @@
import asyncMap from '@xen-orchestra/async-map'
import { createPredicate } from 'value-matcher'
import { timeout } from 'promise-toolbox'
import { assign, filter, isEmpty, map, mapValues } from 'lodash'
import { crossProduct } from '../../math'
import { asyncMap, serializeError, thunkToArray } from '../../utils'
import { serializeError, thunkToArray } from '../../utils'
// ===================================================================

View File

@@ -2,13 +2,14 @@
import type { Pattern } from 'value-matcher'
import asyncMap from '@xen-orchestra/async-map'
import { CancelToken, ignoreErrors } from 'promise-toolbox'
import { map as mapToArray } from 'lodash'
import { noSuchObject } from 'xo-common/api-errors'
import Collection from '../../collection/redis'
import patch from '../../patch'
import { asyncMap, serializeError } from '../../utils'
import { serializeError } from '../../utils'
import type Logger from '../logs/loggers/abstract'
import { type Schedule } from '../scheduling'

View File

@@ -1,3 +1,4 @@
import asyncMap from '@xen-orchestra/async-map'
import synchronized from 'decorator-synchronized'
import {
assign,
@@ -11,13 +12,7 @@ import {
} from 'lodash'
import { noSuchObject, unauthorized } from 'xo-common/api-errors'
import {
asyncMap,
generateUnsecureToken,
lightSet,
map,
streamToArray,
} from '../utils'
import { generateUnsecureToken, lightSet, map, streamToArray } from '../utils'
// ===================================================================

View File

@@ -1,12 +1,12 @@
// @flow
import asyncMap from '@xen-orchestra/async-map'
import { createSchedule } from '@xen-orchestra/cron'
import { keyBy } from 'lodash'
import { noSuchObject } from 'xo-common/api-errors'
import Collection from '../collection/redis'
import patch from '../patch'
import { asyncMap } from '../utils'
export type Schedule = {|
cron: string,

View File

@@ -1,5 +1,6 @@
import XoCollection from 'xo-collection'
import XoUniqueIndex from 'xo-collection/unique-index'
import mixin from '@xen-orchestra/mixin'
import { createClient as createRedisClient } from 'redis'
import { EventEmitter } from 'events'
import { noSuchObject } from 'xo-common/api-errors'
@@ -16,7 +17,6 @@ import {
import mixins from './xo-mixins'
import Connection from './connection'
import { mixin } from './decorators'
import { generateToken, noop } from './utils'
// ===================================================================

View File

@@ -1,6 +1,6 @@
{
"name": "xo-vmdk-to-vhd",
"version": "0.1.3",
"version": "0.1.4",
"license": "AGPL-3.0",
"description": "JS lib streaming a vmdk file to a vhd",
"keywords": [
@@ -25,14 +25,14 @@
"dependencies": {
"child-process-promise": "^2.0.3",
"pipette": "^0.9.3",
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"tmp": "^0.0.33",
"vhd-lib": "^0.3.0"
"vhd-lib": "^0.3.1"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@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",
"event-to-promise": "^0.8.0",

View File

@@ -2,7 +2,7 @@
import { createReadStream, readFile } from 'fs-extra'
import { exec } from 'child-process-promise'
import { fromCallback as pFromCallback } from 'promise-toolbox'
import { pFromCallback } from 'promise-toolbox'
import rimraf from 'rimraf'
import tmp from 'tmp'

View File

@@ -2,7 +2,7 @@
import { createReadStream } from 'fs-extra'
import { exec } from 'child-process-promise'
import { fromCallback as pFromCallback } from 'promise-toolbox'
import { pFromCallback } from 'promise-toolbox'
import rimraf from 'rimraf'
import tmp from 'tmp'

View File

@@ -7,7 +7,7 @@ import rimraf from 'rimraf'
import tmp from 'tmp'
import { createReadStream, createWriteStream, stat } from 'fs-extra'
import { fromCallback as pFromCallback } from 'promise-toolbox'
import { pFromCallback } from 'promise-toolbox'
import convertFromVMDK, { readVmdkGrainTable } from '.'
const initialDir = process.cwd()

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "xo-web",
"version": "5.26.0",
"version": "5.27.0",
"license": "AGPL-3.0",
"description": "Web interface client for Xen-Orchestra",
"keywords": [
@@ -33,6 +33,7 @@
"@julien-f/freactal": "0.4.0",
"@nraynaud/novnc": "0.6.1",
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/defined": "^0.0.0",
"ansi_up": "^3.0.0",
"asap": "^2.0.6",
"babel-core": "^6.26.0",
@@ -94,7 +95,7 @@
"moment": "^2.20.1",
"moment-timezone": "^0.5.14",
"notifyjs": "^3.0.0",
"promise-toolbox": "^0.9.5",
"promise-toolbox": "^0.10.1",
"prop-types": "^15.6.0",
"random-password": "^0.1.2",
"react": "^15.4.1",
@@ -138,7 +139,7 @@
"xo-common": "^0.1.1",
"xo-lib": "^0.8.0",
"xo-remote-parser": "^0.5.0",
"xo-vmdk-to-vhd": "^0.1.3"
"xo-vmdk-to-vhd": "^0.1.4"
},
"scripts": {
"build": "NODE_ENV=production gulp build",

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