Compare commits
1 Commits
gen-signed
...
florent-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4984d2c5dd |
@@ -65,23 +65,6 @@ const f = compose(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions can receive extra parameters:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const isIn = (value, min, max) => min <= value && value <= max
|
|
||||||
|
|
||||||
// Only compatible when `fns` is passed as an array!
|
|
||||||
const f = compose([
|
|
||||||
[add, 2],
|
|
||||||
[isIn, 3, 10],
|
|
||||||
])
|
|
||||||
|
|
||||||
console.log(f(1))
|
|
||||||
// → true
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note: if the first function is defined with extra parameters, it will only receive the first value passed to the composed function, instead of all the parameters.
|
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Contributions are _very_ welcomed, either on the documentation or on
|
Contributions are _very_ welcomed, either on the documentation or on
|
||||||
|
|||||||
@@ -46,20 +46,3 @@ const f = compose(
|
|||||||
[add2, mul3]
|
[add2, mul3]
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions can receive extra parameters:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const isIn = (value, min, max) => min <= value && value <= max
|
|
||||||
|
|
||||||
// Only compatible when `fns` is passed as an array!
|
|
||||||
const f = compose([
|
|
||||||
[add, 2],
|
|
||||||
[isIn, 3, 10],
|
|
||||||
])
|
|
||||||
|
|
||||||
console.log(f(1))
|
|
||||||
// → true
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note: if the first function is defined with extra parameters, it will only receive the first value passed to the composed function, instead of all the parameters.
|
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ const defaultOpts = { async: false, right: false }
|
|||||||
|
|
||||||
exports.compose = function compose(opts, fns) {
|
exports.compose = function compose(opts, fns) {
|
||||||
if (Array.isArray(opts)) {
|
if (Array.isArray(opts)) {
|
||||||
fns = opts.slice() // don't mutate passed array
|
fns = opts
|
||||||
opts = defaultOpts
|
opts = defaultOpts
|
||||||
} else if (typeof opts === 'object') {
|
} else if (typeof opts === 'object') {
|
||||||
opts = Object.assign({}, defaultOpts, opts)
|
opts = Object.assign({}, defaultOpts, opts)
|
||||||
if (Array.isArray(fns)) {
|
if (!Array.isArray(fns)) {
|
||||||
fns = fns.slice() // don't mutate passed array
|
|
||||||
} else {
|
|
||||||
fns = Array.prototype.slice.call(arguments, 1)
|
fns = Array.prototype.slice.call(arguments, 1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -22,24 +20,6 @@ exports.compose = function compose(opts, fns) {
|
|||||||
if (n === 0) {
|
if (n === 0) {
|
||||||
throw new TypeError('at least one function must be passed')
|
throw new TypeError('at least one function must be passed')
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < n; ++i) {
|
|
||||||
const entry = fns[i]
|
|
||||||
if (Array.isArray(entry)) {
|
|
||||||
const fn = entry[0]
|
|
||||||
const args = entry.slice()
|
|
||||||
args[0] = undefined
|
|
||||||
fns[i] = function composeWithArgs(value) {
|
|
||||||
args[0] = value
|
|
||||||
try {
|
|
||||||
return fn.apply(this, args)
|
|
||||||
} finally {
|
|
||||||
args[0] = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n === 1) {
|
if (n === 1) {
|
||||||
return fns[0]
|
return fns[0]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"url": "https://vates.fr"
|
"url": "https://vates.fr"
|
||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"version": "2.1.0",
|
"version": "2.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=7.6"
|
"node": ">=7.6"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -59,17 +59,6 @@ decorateMethodsWith(Foo, {
|
|||||||
|
|
||||||
The decorated class is returned, so you can export it directly.
|
The decorated class is returned, so you can export it directly.
|
||||||
|
|
||||||
To apply multiple transforms to a method, you can either call `decorateMethodsWith` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
|
|
||||||
|
|
||||||
```js
|
|
||||||
decorateMethodsWith(Foo, {
|
|
||||||
bar: compose([
|
|
||||||
[lodash.debounce, 150]
|
|
||||||
lodash.curry,
|
|
||||||
])
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Contributions are _very_ welcomed, either on the documentation or on
|
Contributions are _very_ welcomed, either on the documentation or on
|
||||||
|
|||||||
@@ -40,14 +40,3 @@ decorateMethodsWith(Foo, {
|
|||||||
```
|
```
|
||||||
|
|
||||||
The decorated class is returned, so you can export it directly.
|
The decorated class is returned, so you can export it directly.
|
||||||
|
|
||||||
To apply multiple transforms to a method, you can either call `decorateMethodsWith` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
|
|
||||||
|
|
||||||
```js
|
|
||||||
decorateMethodsWith(Foo, {
|
|
||||||
bar: compose([
|
|
||||||
[lodash.debounce, 150]
|
|
||||||
lodash.curry,
|
|
||||||
])
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xen-orchestra/async-map": "^0.1.2",
|
"@xen-orchestra/async-map": "^0.1.2",
|
||||||
"@xen-orchestra/backups": "^0.16.2",
|
"@xen-orchestra/backups": "^0.16.0",
|
||||||
"@xen-orchestra/fs": "^0.19.2",
|
"@xen-orchestra/fs": "^0.19.1",
|
||||||
"filenamify": "^4.1.0",
|
"filenamify": "^4.1.0",
|
||||||
"getopts": "^2.2.5",
|
"getopts": "^2.2.5",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
|||||||
@@ -469,10 +469,10 @@ class RemoteAdapter {
|
|||||||
|
|
||||||
async writeVhd(path, input, { checksum = true, validator = noop } = {}) {
|
async writeVhd(path, input, { checksum = true, validator = noop } = {}) {
|
||||||
const handler = this._handler
|
const handler = this._handler
|
||||||
|
let dataPath = path
|
||||||
|
|
||||||
if (path.endsWith('.alias.vhd')) {
|
if (path.endsWith('.alias.vhd')) {
|
||||||
const dataPath = `${dirname(path)}/data/${uuidv4()}.vhd`
|
await createVhdDirectoryFromStream(handler, `${dirname(path)}/data/${uuidv4()}.vhd`, input, {
|
||||||
await createVhdDirectoryFromStream(handler, dataPath, input, {
|
|
||||||
concurrency: 16,
|
concurrency: 16,
|
||||||
async validator() {
|
async validator() {
|
||||||
await input.task
|
await input.task
|
||||||
@@ -481,7 +481,7 @@ class RemoteAdapter {
|
|||||||
})
|
})
|
||||||
await VhdAbstract.createAlias(handler, path, dataPath)
|
await VhdAbstract.createAlias(handler, path, dataPath)
|
||||||
} else {
|
} else {
|
||||||
await this.outputStream(path, input, { checksum, validator })
|
await this.outputStream(dataPath, input, { checksum, validator })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,6 @@ const forkDeltaExport = deltaExport =>
|
|||||||
|
|
||||||
exports.VmBackup = class VmBackup {
|
exports.VmBackup = class VmBackup {
|
||||||
constructor({ config, getSnapshotNameLabel, job, remoteAdapters, remotes, schedule, settings, srs, vm }) {
|
constructor({ config, getSnapshotNameLabel, job, remoteAdapters, remotes, schedule, settings, srs, vm }) {
|
||||||
if (vm.other_config['xo:backup:job'] === job.id) {
|
|
||||||
// otherwise replicated VMs would be matched and replicated again and again
|
|
||||||
throw new Error('cannot backup a VM created by this very job')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config = config
|
this.config = config
|
||||||
this.job = job
|
this.job = job
|
||||||
this.remoteAdapters = remoteAdapters
|
this.remoteAdapters = remoteAdapters
|
||||||
@@ -338,16 +333,13 @@ exports.VmBackup = class VmBackup {
|
|||||||
|
|
||||||
const baseUuidToSrcVdi = new Map()
|
const baseUuidToSrcVdi = new Map()
|
||||||
await asyncMap(await baseVm.$getDisks(), async baseRef => {
|
await asyncMap(await baseVm.$getDisks(), async baseRef => {
|
||||||
const [baseUuid, snapshotOf] = await Promise.all([
|
const snapshotOf = await xapi.getField('VDI', baseRef, 'snapshot_of')
|
||||||
xapi.getField('VDI', baseRef, 'uuid'),
|
|
||||||
xapi.getField('VDI', baseRef, 'snapshot_of'),
|
|
||||||
])
|
|
||||||
const srcVdi = srcVdis[snapshotOf]
|
const srcVdi = srcVdis[snapshotOf]
|
||||||
if (srcVdi !== undefined) {
|
if (srcVdi !== undefined) {
|
||||||
baseUuidToSrcVdi.set(baseUuid, srcVdi)
|
baseUuidToSrcVdi.set(await xapi.getField('VDI', baseRef, 'uuid'), srcVdi)
|
||||||
} else {
|
} else {
|
||||||
debug('ignore snapshot VDI because no longer present on VM', {
|
debug('no base VDI found', {
|
||||||
vdi: baseUuid,
|
vdi: srcVdi.uuid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -359,11 +351,6 @@ exports.VmBackup = class VmBackup {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (presentBaseVdis.size === 0) {
|
|
||||||
debug('no base VM found')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullVdisRequired = new Set()
|
const fullVdisRequired = new Set()
|
||||||
baseUuidToSrcVdi.forEach((srcVdi, baseUuid) => {
|
baseUuidToSrcVdi.forEach((srcVdi, baseUuid) => {
|
||||||
if (presentBaseVdis.has(baseUuid)) {
|
if (presentBaseVdis.has(baseUuid)) {
|
||||||
|
|||||||
@@ -256,12 +256,9 @@ exports.cleanVm = async function cleanVm(
|
|||||||
const { vhds } = metadata
|
const { vhds } = metadata
|
||||||
return Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
|
return Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const missingVhds = linkedVhds.filter(_ => !vhds.has(_))
|
|
||||||
|
|
||||||
// FIXME: find better approach by keeping as much of the backup as
|
// FIXME: find better approach by keeping as much of the backup as
|
||||||
// possible (existing disks) even if one disk is missing
|
// possible (existing disks) even if one disk is missing
|
||||||
if (missingVhds.length === 0) {
|
if (linkedVhds.every(_ => vhds.has(_))) {
|
||||||
linkedVhds.forEach(_ => unusedVhds.delete(_))
|
linkedVhds.forEach(_ => unusedVhds.delete(_))
|
||||||
|
|
||||||
// checking the size of a vhd directory is costly
|
// checking the size of a vhd directory is costly
|
||||||
@@ -280,7 +277,7 @@ exports.cleanVm = async function cleanVm(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onLog(`Some VHDs linked to the metadata ${json} are missing`, { missingVhds })
|
onLog(`Some VHDs linked to the metadata ${json} are missing`)
|
||||||
if (remove) {
|
if (remove) {
|
||||||
onLog(`deleting incomplete backup ${json}`)
|
onLog(`deleting incomplete backup ${json}`)
|
||||||
await handler.unlink(json)
|
await handler.unlink(json)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||||
},
|
},
|
||||||
"version": "0.16.2",
|
"version": "0.16.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.6"
|
"node": ">=14.6"
|
||||||
},
|
},
|
||||||
@@ -16,11 +16,11 @@
|
|||||||
"postversion": "npm publish --access public"
|
"postversion": "npm publish --access public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vates/compose": "^2.1.0",
|
"@vates/compose": "^2.0.0",
|
||||||
"@vates/disposable": "^0.1.1",
|
"@vates/disposable": "^0.1.1",
|
||||||
"@vates/parse-duration": "^0.1.1",
|
"@vates/parse-duration": "^0.1.1",
|
||||||
"@xen-orchestra/async-map": "^0.1.2",
|
"@xen-orchestra/async-map": "^0.1.2",
|
||||||
"@xen-orchestra/fs": "^0.19.2",
|
"@xen-orchestra/fs": "^0.19.1",
|
||||||
"@xen-orchestra/log": "^0.3.0",
|
"@xen-orchestra/log": "^0.3.0",
|
||||||
"@xen-orchestra/template": "^0.1.0",
|
"@xen-orchestra/template": "^0.1.0",
|
||||||
"compare-versions": "^4.0.1",
|
"compare-versions": "^4.0.1",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
"pump": "^3.0.0",
|
"pump": "^3.0.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"vhd-lib": "^2.0.3",
|
"vhd-lib": "^2.0.1",
|
||||||
"yazl": "^2.5.1"
|
"yazl": "^2.5.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -21,18 +21,10 @@ exports.MixinBackupWriter = (BaseClass = Object) =>
|
|||||||
this.#vmBackupDir = getVmBackupDir(this._backup.vm.uuid)
|
this.#vmBackupDir = getVmBackupDir(this._backup.vm.uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
async _cleanVm(options) {
|
_cleanVm(options) {
|
||||||
try {
|
return this._adapter
|
||||||
return await this._adapter.cleanVm(this.#vmBackupDir, {
|
.cleanVm(this.#vmBackupDir, { ...options, fixMetadata: true, onLog: warn, lock: false })
|
||||||
...options,
|
.catch(warn)
|
||||||
fixMetadata: true,
|
|
||||||
onLog: warn,
|
|
||||||
lock: false,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
warn(error)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async beforeBackup() {
|
async beforeBackup() {
|
||||||
@@ -51,13 +43,7 @@ exports.MixinBackupWriter = (BaseClass = Object) =>
|
|||||||
// merge worker only compatible with local remotes
|
// merge worker only compatible with local remotes
|
||||||
const { handler } = this._adapter
|
const { handler } = this._adapter
|
||||||
if (merge && !disableMergeWorker && typeof handler._getRealPath === 'function') {
|
if (merge && !disableMergeWorker && typeof handler._getRealPath === 'function') {
|
||||||
const taskFile =
|
await handler.outputFile(join(MergeWorker.CLEAN_VM_QUEUE, formatFilenameDate(new Date())), this._backup.vm.uuid)
|
||||||
join(MergeWorker.CLEAN_VM_QUEUE, formatFilenameDate(new Date())) +
|
|
||||||
'-' +
|
|
||||||
// add a random suffix to avoid collision in case multiple tasks are created at the same second
|
|
||||||
Math.random().toString(36).slice(2)
|
|
||||||
|
|
||||||
await handler.outputFile(taskFile, this._backup.vm.uuid)
|
|
||||||
const remotePath = handler._getRealPath()
|
const remotePath = handler._getRealPath()
|
||||||
await MergeWorker.run(remotePath)
|
await MergeWorker.run(remotePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "@xen-orchestra/fs",
|
"name": "@xen-orchestra/fs",
|
||||||
"version": "0.19.2",
|
"version": "0.19.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"description": "The File System for Xen Orchestra backups.",
|
"description": "The File System for Xen Orchestra backups.",
|
||||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/fs",
|
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/fs",
|
||||||
|
|||||||
@@ -152,30 +152,16 @@ export default class S3Handler extends RemoteHandlerAbstract {
|
|||||||
const splitPrefix = splitPath(prefix)
|
const splitPrefix = splitPath(prefix)
|
||||||
const result = await this._s3.listObjectsV2({
|
const result = await this._s3.listObjectsV2({
|
||||||
Bucket: this._bucket,
|
Bucket: this._bucket,
|
||||||
Prefix: splitPrefix.join('/') + '/', // need slash at the end with the use of delimiters
|
Prefix: splitPrefix.join('/'),
|
||||||
Delimiter: '/', // will only return path until delimiters
|
|
||||||
})
|
})
|
||||||
|
const uniq = new Set()
|
||||||
if (result.isTruncated) {
|
|
||||||
const error = new Error('more than 1000 objects, unsupported in this implementation')
|
|
||||||
error.dir = dir
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
const uniq = []
|
|
||||||
|
|
||||||
// sub directories
|
|
||||||
for (const entry of result.CommonPrefixes) {
|
|
||||||
const line = splitPath(entry.Prefix)
|
|
||||||
uniq.push(line[line.length - 1])
|
|
||||||
}
|
|
||||||
// files
|
|
||||||
for (const entry of result.Contents) {
|
for (const entry of result.Contents) {
|
||||||
const line = splitPath(entry.Key)
|
const line = splitPath(entry.Key)
|
||||||
uniq.push(line[line.length - 1])
|
if (line.length > splitPrefix.length) {
|
||||||
|
uniq.add(line[splitPrefix.length])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return [...uniq]
|
||||||
return uniq
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _mkdir(path) {
|
async _mkdir(path) {
|
||||||
@@ -236,9 +222,9 @@ export default class S3Handler extends RemoteHandlerAbstract {
|
|||||||
// nothing to do, directories do not exist, they are part of the files' path
|
// nothing to do, directories do not exist, they are part of the files' path
|
||||||
}
|
}
|
||||||
|
|
||||||
// reimplement _rmtree to handle efficiantly path with more than 1000 entries in trees
|
// reimplement _rmTree to handle efficiantly path with more than 1000 entries in trees
|
||||||
// @todo : use parallel processing for unlink
|
// @todo : use parallel processing for unlink
|
||||||
async _rmtree(path) {
|
async _rmTree(path) {
|
||||||
let NextContinuationToken
|
let NextContinuationToken
|
||||||
do {
|
do {
|
||||||
const result = await this._s3.listObjectsV2({
|
const result = await this._s3.listObjectsV2({
|
||||||
@@ -247,13 +233,8 @@ export default class S3Handler extends RemoteHandlerAbstract {
|
|||||||
ContinuationToken: NextContinuationToken,
|
ContinuationToken: NextContinuationToken,
|
||||||
})
|
})
|
||||||
NextContinuationToken = result.isTruncated ? null : result.NextContinuationToken
|
NextContinuationToken = result.isTruncated ? null : result.NextContinuationToken
|
||||||
for (const { Key } of result.Contents) {
|
for (const path of result.Contents) {
|
||||||
// _unlink will add the prefix, but Key contains everything
|
await this._unlink(path)
|
||||||
// also we don't need to check if we delete a directory, since the list only return files
|
|
||||||
await this._s3.deleteObject({
|
|
||||||
Bucket: this._bucket,
|
|
||||||
Key,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} while (NextContinuationToken !== null)
|
} while (NextContinuationToken !== null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "@xen-orchestra/proxy",
|
"name": "@xen-orchestra/proxy",
|
||||||
"version": "0.15.5",
|
"version": "0.15.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"description": "XO Proxy used to remotely execute backup jobs",
|
"description": "XO Proxy used to remotely execute backup jobs",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -27,12 +27,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.0",
|
"@iarna/toml": "^2.2.0",
|
||||||
"@koa/router": "^10.0.0",
|
"@koa/router": "^10.0.0",
|
||||||
"@vates/compose": "^2.1.0",
|
"@vates/compose": "^2.0.0",
|
||||||
"@vates/decorate-with": "^0.1.0",
|
"@vates/decorate-with": "^0.1.0",
|
||||||
"@vates/disposable": "^0.1.1",
|
"@vates/disposable": "^0.1.1",
|
||||||
"@xen-orchestra/async-map": "^0.1.2",
|
"@xen-orchestra/async-map": "^0.1.2",
|
||||||
"@xen-orchestra/backups": "^0.16.2",
|
"@xen-orchestra/backups": "^0.16.0",
|
||||||
"@xen-orchestra/fs": "^0.19.2",
|
"@xen-orchestra/fs": "^0.19.1",
|
||||||
"@xen-orchestra/log": "^0.3.0",
|
"@xen-orchestra/log": "^0.3.0",
|
||||||
"@xen-orchestra/mixin": "^0.1.0",
|
"@xen-orchestra/mixin": "^0.1.0",
|
||||||
"@xen-orchestra/mixins": "^0.1.1",
|
"@xen-orchestra/mixins": "^0.1.1",
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
const { execFile } = require('child_process')
|
const { execFile } = require('child_process')
|
||||||
const { promisify } = require('util')
|
|
||||||
|
|
||||||
const randomBytes = promisify(require('crypto').randomBytes)
|
|
||||||
|
|
||||||
const openssl = (cmd, args, { input, ...opts } = {}) =>
|
const openssl = (cmd, args, { input, ...opts } = {}) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
@@ -13,35 +10,12 @@ const openssl = (cmd, args, { input, ...opts } = {}) =>
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const req = (key, selfSigned, { days = 360 } = {}) => {
|
exports.genSelfSignedCert = async ({ days = 360 } = {}) => {
|
||||||
const args = ['-batch', '-new', '-key', '-', '-nodes']
|
|
||||||
if (selfSigned) {
|
|
||||||
args.push('-x509', '-days', String(days))
|
|
||||||
}
|
|
||||||
return openssl('req', args, { input: key })
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.genSelfSignedCert = async opts => {
|
|
||||||
const key = await openssl('genrsa', ['2048'])
|
const key = await openssl('genrsa', ['2048'])
|
||||||
return {
|
return {
|
||||||
cert: await req(key, true, opts),
|
cert: await openssl('req', ['-batch', '-new', '-key', '-', '-x509', '-days', String(days), '-nodes'], {
|
||||||
key,
|
input: key,
|
||||||
}
|
}),
|
||||||
}
|
|
||||||
|
|
||||||
exports.genSignedCert = async (ca, { days = 360 } = {}) => {
|
|
||||||
const key = await openssl('genrsa', ['2048'])
|
|
||||||
const csr = await req(key, false)
|
|
||||||
const serial = '0x' + (await randomBytes(40)).toString('hex')
|
|
||||||
const input = [csr, ca.cert, ca.key].join('\n')
|
|
||||||
return {
|
|
||||||
cert: await openssl(
|
|
||||||
'x509',
|
|
||||||
['-req', '-in', '-', '-CA', '-', '-CAkey', '-', '-days', String(days), '-set_serial', serial],
|
|
||||||
{
|
|
||||||
input,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
key,
|
key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"strip-indent": "^3.0.0",
|
"strip-indent": "^3.0.0",
|
||||||
"xdg-basedir": "^4.0.0",
|
"xdg-basedir": "^4.0.0",
|
||||||
"xo-lib": "^0.11.1",
|
"xo-lib": "^0.11.1",
|
||||||
"xo-vmdk-to-vhd": "^2.0.1"
|
"xo-vmdk-to-vhd": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.0.0",
|
"@babel/cli": "^7.0.0",
|
||||||
|
|||||||
39
CHANGELOG.md
39
CHANGELOG.md
@@ -1,44 +1,9 @@
|
|||||||
# ChangeLog
|
# ChangeLog
|
||||||
|
|
||||||
## **5.65.2** (2021-12-10)
|
## **5.65.0** (2021-11-30)
|
||||||
|
|
||||||
<img id="latest" src="https://badgen.net/badge/channel/latest/yellow" alt="Channel: latest" />
|
<img id="latest" src="https://badgen.net/badge/channel/latest/yellow" alt="Channel: latest" />
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- [Backup] Fix `handler.rmTree` is not a function (Forum [5256](https://xcp-ng.org/forum/topic/5256/s3-backup-try-it/29) PR [#6041](https://github.com/vatesfr/xen-orchestra/pull/6041) )
|
|
||||||
- [Backup] Fix `EEXIST` in logs when multiple merge tasks are created at the same time ([Forum #5301](https://xcp-ng.org/forum/topic/5301/warnings-errors-in-journalctl))
|
|
||||||
- [Backup] Fix missing backup on restore (Forum [5256](https://xcp-ng.org/forum/topic/5256/s3-backup-try-it/29) (PR [#6048](https://github.com/vatesfr/xen-orchestra/pull/6048))
|
|
||||||
|
|
||||||
### Released packages
|
|
||||||
|
|
||||||
- @xen-orchestra/fs 0.19.2
|
|
||||||
- vhd-lib 2.0.3
|
|
||||||
- @xen-orchestra/backups 0.16.2
|
|
||||||
- xo-server 5.84.3
|
|
||||||
- @xen-orchestra/proxy 0.15.5
|
|
||||||
|
|
||||||
## **5.65.1** (2021-12-03)
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- [Delta Backup Restoration] Fix assertion error [Forum #5257](https://xcp-ng.org/forum/topic/5257/problems-building-from-source/16)
|
|
||||||
- [Delta Backup Restoration] `TypeError: this disposable has already been disposed` [Forum #5257](https://xcp-ng.org/forum/topic/5257/problems-building-from-source/20)
|
|
||||||
- [Backups] Fix: `Error: Chaining alias is forbidden xo-vm-backups/..alias.vhd to xo-vm-backups/....alias.vhd` when backuping a file to s3 [Forum #5226](https://xcp-ng.org/forum/topic/5256/s3-backup-try-it)
|
|
||||||
- [Delta Backup Restoration] `VDI_IO_ERROR(Device I/O errors)` [Forum #5727](https://xcp-ng.org/forum/topic/5257/problems-building-from-source/4) (PR [#6031](https://github.com/vatesfr/xen-orchestra/pull/6031))
|
|
||||||
- [Delta Backup] Fix `Cannot read property 'uuid' of undefined` when a VDI has been removed from a backed up VM (PR [#6034](https://github.com/vatesfr/xen-orchestra/pull/6034))
|
|
||||||
|
|
||||||
### Released packages
|
|
||||||
|
|
||||||
- @vates/compose 2.1.0
|
|
||||||
- vhd-lib 2.0.2
|
|
||||||
- xo-vmdk-to-vhd 2.0.1
|
|
||||||
- @xen-orchestra/backups 0.16.1
|
|
||||||
- @xen-orchestra/proxy 0.15.4
|
|
||||||
- xo-server 5.84.2
|
|
||||||
|
|
||||||
## **5.65.0** (2021-11-30)
|
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- [VM] Ability to export a snapshot's memory (PR [#6015](https://github.com/vatesfr/xen-orchestra/pull/6015))
|
- [VM] Ability to export a snapshot's memory (PR [#6015](https://github.com/vatesfr/xen-orchestra/pull/6015))
|
||||||
@@ -54,7 +19,7 @@
|
|||||||
|
|
||||||
- [Import/VM] Fix the import of OVA files (PR [#5976](https://github.com/vatesfr/xen-orchestra/pull/5976))
|
- [Import/VM] Fix the import of OVA files (PR [#5976](https://github.com/vatesfr/xen-orchestra/pull/5976))
|
||||||
|
|
||||||
### Released packages
|
### Packages to release
|
||||||
|
|
||||||
- @vates/async-each 0.1.0
|
- @vates/async-each 0.1.0
|
||||||
- xo-remote-parser 0.8.4
|
- xo-remote-parser 0.8.4
|
||||||
|
|||||||
@@ -7,17 +7,13 @@
|
|||||||
|
|
||||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||||
|
|
||||||
- [About] Show commit instead of version numbers for source users (PR [#6045](https://github.com/vatesfr/xen-orchestra/pull/6045))
|
|
||||||
- [Health] Display default SRs that aren't shared [#5871](https://github.com/vatesfr/xen-orchestra/issues/5871) (PR [#6033](https://github.com/vatesfr/xen-orchestra/pull/6033))
|
|
||||||
- [Pool,VM/advanced] Ability to change the suspend SR [#4163](https://github.com/vatesfr/xen-orchestra/issues/4163) (PR [#6044](https://github.com/vatesfr/xen-orchestra/pull/6044))
|
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||||
|
|
||||||
- [Tables/actions] Fix collapsed actions being clickable despite being disabled (PR [#6023](https://github.com/vatesfr/xen-orchestra/pull/6023))
|
- [Delta Backup Restoration] Fix assertion error [Forum #5257](https://xcp-ng.org/forum/topic/5257/problems-building-from-source/16)
|
||||||
- [Continuous Replication] Fix `could not find the base VM`
|
- [Delta Backup Restoration] `TypeError: this disposable has already been disposed` [Forum #5257](https://xcp-ng.org/forum/topic/5257/problems-building-from-source/20)
|
||||||
- [Backup/Smart mode] Always ignore replicated VMs created by the current job
|
- [Delta Backup Restoration] Fix error `ENOENT: no such file '/xo-vm-backups/../parentLocatorEntryN' with vhd having empty parent locator
|
||||||
|
|
||||||
### Packages to release
|
### Packages to release
|
||||||
|
|
||||||
@@ -36,7 +32,8 @@
|
|||||||
>
|
>
|
||||||
> In case of conflict, the highest (lowest in previous list) `$version` wins.
|
> In case of conflict, the highest (lowest in previous list) `$version` wins.
|
||||||
|
|
||||||
- @xen-orchestra/backups minor
|
- xo-vmdk-to-vhd patch
|
||||||
- @xen-orchestra/proxy minor
|
- vhd-lib patch
|
||||||
- xo-server minor
|
- @xen-orchestra/backups patch
|
||||||
- xo-web minor
|
- @xen-orchestra/proxy patch
|
||||||
|
- xo-server patch
|
||||||
|
|||||||
@@ -341,16 +341,6 @@ XO will try to find the right prefix for each IP address. If it can't find a pre
|
|||||||
- Generate a token:
|
- Generate a token:
|
||||||
- Go to Admin > Tokens > Add token
|
- Go to Admin > Tokens > Add token
|
||||||
- Create a token with "Write enabled"
|
- Create a token with "Write enabled"
|
||||||
- The owner of the token must have at least the following permissions:
|
|
||||||
- View permissions on:
|
|
||||||
- extras > custom-fields
|
|
||||||
- ipam > prefixes
|
|
||||||
- All permissions on:
|
|
||||||
- ipam > ip-addresses
|
|
||||||
- virtualization > cluster-types
|
|
||||||
- virtualization > clusters
|
|
||||||
- virtualization > interfaces
|
|
||||||
- virtualization > virtual-machines
|
|
||||||
- Add a UUID custom field (for **Netbox 2.x**):
|
- Add a UUID custom field (for **Netbox 2.x**):
|
||||||
- Got to Admin > Custom fields > Add custom field
|
- Got to Admin > Custom fields > Add custom field
|
||||||
- Create a custom field called "uuid" (lower case!)
|
- Create a custom field called "uuid" (lower case!)
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ In that case, you already set the password for `xoa` user. If you forgot it, see
|
|||||||
|
|
||||||
### Manually deployed
|
### Manually deployed
|
||||||
|
|
||||||
If you connect via SSH or console for the first time without using our [web deploy form](https://xen-orchestra.com/#!/xoa), be aware **there is NO default password set for security reasons**. To set it, you need to connect to your host to find the XOA VM UUID (eg via `xe vm-list`).
|
If you connect via SSH or console for the first time without using our [web deploy form](https://xen-orchestra.com/#!/xoa), be aware **there's NO default password set for security reasons**. To set it, you need to connect to your host to find the XOA VM UUID (eg via `xe vm-list`).
|
||||||
|
|
||||||
Next, you can replace `<UUID>` with the UUID you found previously, and `<password>` with your password:
|
Then replace `<UUID>` with the previously find UUID, and `<password>` with your password:
|
||||||
|
|
||||||
```
|
```
|
||||||
xe vm-param-set uuid=<UUID> xenstore-data:vm-data/system-account-xoa-password=<password>
|
xe vm-param-set uuid=<UUID> xenstore-data:vm-data/system-account-xoa-password=<password>
|
||||||
@@ -115,9 +115,7 @@ xe vm-param-set uuid=<UUID> xenstore-data:vm-data/system-account-xoa-password=<p
|
|||||||
Don't forget to use quotes for your password, eg: `xenstore-data:vm-data/system-account-xoa-password='MyPassW0rd!'`
|
Don't forget to use quotes for your password, eg: `xenstore-data:vm-data/system-account-xoa-password='MyPassW0rd!'`
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Finally, you must reboot the VM to implement the changes.
|
Then, you could connect with `xoa` username and the password you defined in the previous command, eg with `ssh xoa@<XOA IP ADDRESS>`.
|
||||||
|
|
||||||
You can now connect with the `xoa` username and password you defined in the previous command, eg with `ssh xoa@<XOA IP ADDRESS>`.
|
|
||||||
|
|
||||||
### Using sudo
|
### Using sudo
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,12 @@
|
|||||||
"node": ">=8.10"
|
"node": ">=8.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xen-orchestra/fs": "^0.19.2",
|
"@xen-orchestra/fs": "^0.19.1",
|
||||||
"cli-progress": "^3.1.0",
|
"cli-progress": "^3.1.0",
|
||||||
"exec-promise": "^0.7.0",
|
"exec-promise": "^0.7.0",
|
||||||
"getopts": "^2.2.3",
|
"getopts": "^2.2.3",
|
||||||
"human-format": "^0.11.0",
|
"human-format": "^0.11.0",
|
||||||
"vhd-lib": "^2.0.3"
|
"vhd-lib": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.0.0",
|
"@babel/cli": "^7.0.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "vhd-lib",
|
"name": "vhd-lib",
|
||||||
"version": "2.0.3",
|
"version": "2.0.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"description": "Primitives for VHD file handling",
|
"description": "Primitives for VHD file handling",
|
||||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/vhd-lib",
|
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/vhd-lib",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"@babel/cli": "^7.0.0",
|
"@babel/cli": "^7.0.0",
|
||||||
"@babel/core": "^7.0.0",
|
"@babel/core": "^7.0.0",
|
||||||
"@babel/preset-env": "^7.0.0",
|
"@babel/preset-env": "^7.0.0",
|
||||||
"@xen-orchestra/fs": "^0.19.2",
|
"@xen-orchestra/fs": "^0.19.1",
|
||||||
"babel-plugin-lodash": "^3.3.2",
|
"babel-plugin-lodash": "^3.3.2",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"execa": "^5.0.0",
|
"execa": "^5.0.0",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Disposable, pFromCallback } from 'promise-toolbox'
|
|||||||
import { openVhd } from '../index'
|
import { openVhd } from '../index'
|
||||||
import { checkFile, createRandomFile, convertFromRawToVhd, createRandomVhdDirectory } from '../tests/utils'
|
import { checkFile, createRandomFile, convertFromRawToVhd, createRandomVhdDirectory } from '../tests/utils'
|
||||||
import { VhdAbstract } from './VhdAbstract'
|
import { VhdAbstract } from './VhdAbstract'
|
||||||
import { BLOCK_UNUSED, FOOTER_SIZE, HEADER_SIZE, PLATFORMS, SECTOR_SIZE } from '../_constants'
|
import { BLOCK_UNUSED, FOOTER_SIZE, HEADER_SIZE, SECTOR_SIZE } from '../_constants'
|
||||||
import { unpackHeader, unpackFooter } from './_utils'
|
import { unpackHeader, unpackFooter } from './_utils'
|
||||||
|
|
||||||
let tempDir
|
let tempDir
|
||||||
@@ -163,22 +163,20 @@ test('it can create a vhd stream', async () => {
|
|||||||
await createRandomFile(rawFileName, initalSize)
|
await createRandomFile(rawFileName, initalSize)
|
||||||
const vhdFileName = `${tempDir}/vhd.vhd`
|
const vhdFileName = `${tempDir}/vhd.vhd`
|
||||||
await convertFromRawToVhd(rawFileName, vhdFileName)
|
await convertFromRawToVhd(rawFileName, vhdFileName)
|
||||||
|
const bat = Buffer.alloc(512)
|
||||||
|
|
||||||
await Disposable.use(async function* () {
|
await Disposable.use(async function* () {
|
||||||
const handler = yield getSyncedHandler({ url: 'file://' + tempDir })
|
const handler = yield getSyncedHandler({ url: 'file://' + tempDir })
|
||||||
|
|
||||||
const vhd = yield openVhd(handler, 'vhd.vhd')
|
const vhd = yield openVhd(handler, 'vhd.vhd')
|
||||||
await vhd.readBlockAllocationTable()
|
|
||||||
|
|
||||||
const parentLocatorBase = Buffer.from('a file path, not aligned', 'utf16le')
|
// mark first block as unused
|
||||||
const aligned = Buffer.alloc(SECTOR_SIZE, 0)
|
await handler.read('vhd.vhd', bat, vhd.header.tableOffset)
|
||||||
parentLocatorBase.copy(aligned)
|
bat.writeUInt32BE(BLOCK_UNUSED, 0)
|
||||||
await vhd.writeParentLocator({
|
await handler.write('vhd.vhd', bat, vhd.header.tableOffset)
|
||||||
id: 0,
|
|
||||||
platformCode: PLATFORMS.W2KU,
|
// read our modified bat
|
||||||
data: parentLocatorBase,
|
await vhd.readBlockAllocationTable()
|
||||||
})
|
|
||||||
await vhd.writeFooter()
|
|
||||||
const stream = vhd.stream()
|
const stream = vhd.stream()
|
||||||
|
|
||||||
// read all the stream into a buffer
|
// read all the stream into a buffer
|
||||||
@@ -196,21 +194,17 @@ test('it can create a vhd stream', async () => {
|
|||||||
expect(() => unpackHeader(bufHeader, footer)).not.toThrow()
|
expect(() => unpackHeader(bufHeader, footer)).not.toThrow()
|
||||||
|
|
||||||
// 1 deleted block should be in ouput
|
// 1 deleted block should be in ouput
|
||||||
let start = FOOTER_SIZE + HEADER_SIZE + vhd.batSize
|
const start = FOOTER_SIZE + HEADER_SIZE + vhd.batSize
|
||||||
|
expect(length).toEqual(start + (initialNbBlocks - 1) * vhd.fullBlockSize + FOOTER_SIZE)
|
||||||
const parentLocatorData = buffer.slice(start, start + SECTOR_SIZE)
|
|
||||||
expect(parentLocatorData.equals(aligned)).toEqual(true)
|
|
||||||
start += SECTOR_SIZE // parent locator
|
|
||||||
expect(length).toEqual(start + initialNbBlocks * vhd.fullBlockSize + FOOTER_SIZE)
|
|
||||||
expect(stream.length).toEqual(buffer.length)
|
expect(stream.length).toEqual(buffer.length)
|
||||||
// blocks
|
// blocks
|
||||||
const blockBuf = Buffer.alloc(vhd.sectorsPerBlock * SECTOR_SIZE, 0)
|
const blockBuf = Buffer.alloc(vhd.sectorsPerBlock * SECTOR_SIZE, 0)
|
||||||
for (let i = 0; i < initialNbBlocks; i++) {
|
for (let i = 1; i < initialNbBlocks; i++) {
|
||||||
const blockDataStart = start + i * vhd.fullBlockSize + 512 /* block bitmap */
|
const blockDataStart = start + (i - 1) * vhd.fullBlockSize + 512 /* block bitmap */
|
||||||
const blockDataEnd = blockDataStart + vhd.sectorsPerBlock * SECTOR_SIZE
|
const blockDataEnd = blockDataStart + vhd.sectorsPerBlock * SECTOR_SIZE
|
||||||
const content = buffer.slice(blockDataStart, blockDataEnd)
|
const content = buffer.slice(blockDataStart, blockDataEnd)
|
||||||
await handler.read('randomfile', blockBuf, i * vhd.sectorsPerBlock * SECTOR_SIZE)
|
await handler.read('randomfile', blockBuf, i * vhd.sectorsPerBlock * SECTOR_SIZE)
|
||||||
expect(content.equals(blockBuf)).toEqual(true)
|
expect(content).toEqual(blockBuf)
|
||||||
}
|
}
|
||||||
// footer
|
// footer
|
||||||
const endFooter = buffer.slice(length - FOOTER_SIZE)
|
const endFooter = buffer.slice(length - FOOTER_SIZE)
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
import { computeBatSize, computeSectorOfBitmap, computeSectorsPerBlock, sectorsToBytes } from './_utils'
|
import { computeBatSize, computeSectorOfBitmap, computeSectorsPerBlock, sectorsToBytes } from './_utils'
|
||||||
import {
|
import { PLATFORMS, SECTOR_SIZE, PARENT_LOCATOR_ENTRIES, FOOTER_SIZE, HEADER_SIZE, BLOCK_UNUSED } from '../_constants'
|
||||||
ALIAS_MAX_PATH_LENGTH,
|
|
||||||
PLATFORMS,
|
|
||||||
SECTOR_SIZE,
|
|
||||||
PARENT_LOCATOR_ENTRIES,
|
|
||||||
FOOTER_SIZE,
|
|
||||||
HEADER_SIZE,
|
|
||||||
BLOCK_UNUSED,
|
|
||||||
} from '../_constants'
|
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import asyncIteratorToStream from 'async-iterator-to-stream'
|
import asyncIteratorToStream from 'async-iterator-to-stream'
|
||||||
@@ -15,6 +7,9 @@ import { checksumStruct, fuFooter, fuHeader } from '../_structs'
|
|||||||
import { isVhdAlias, resolveAlias } from '../_resolveAlias'
|
import { isVhdAlias, resolveAlias } from '../_resolveAlias'
|
||||||
|
|
||||||
export class VhdAbstract {
|
export class VhdAbstract {
|
||||||
|
#header
|
||||||
|
footer
|
||||||
|
|
||||||
get bitmapSize() {
|
get bitmapSize() {
|
||||||
return sectorsToBytes(this.sectorsOfBitmap)
|
return sectorsToBytes(this.sectorsOfBitmap)
|
||||||
}
|
}
|
||||||
@@ -23,6 +18,11 @@ export class VhdAbstract {
|
|||||||
return sectorsToBytes(this.sectorsOfBitmap + this.sectorsPerBlock)
|
return sectorsToBytes(this.sectorsOfBitmap + this.sectorsPerBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get header() {
|
||||||
|
assert.notStrictEqual(this.#header, undefined, `header must be read before it's used`)
|
||||||
|
return this.#header
|
||||||
|
}
|
||||||
|
|
||||||
get sectorsOfBitmap() {
|
get sectorsOfBitmap() {
|
||||||
return computeSectorOfBitmap(this.header.blockSize)
|
return computeSectorOfBitmap(this.header.blockSize)
|
||||||
}
|
}
|
||||||
@@ -31,12 +31,8 @@ export class VhdAbstract {
|
|||||||
return computeSectorsPerBlock(this.header.blockSize)
|
return computeSectorsPerBlock(this.header.blockSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
get header() {
|
set header(header) {
|
||||||
throw new Error('get header is not implemented')
|
this.#header = header
|
||||||
}
|
|
||||||
|
|
||||||
get footer() {
|
|
||||||
throw new Error('get footer not implemented')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,7 +137,7 @@ export class VhdAbstract {
|
|||||||
const entry = this.header.parentLocatorEntry[id]
|
const entry = this.header.parentLocatorEntry[id]
|
||||||
const dataSpaceSectors = Math.ceil(data.length / SECTOR_SIZE)
|
const dataSpaceSectors = Math.ceil(data.length / SECTOR_SIZE)
|
||||||
entry.platformCode = platformCode
|
entry.platformCode = platformCode
|
||||||
entry.platformDataSpace = dataSpaceSectors
|
entry.platformDataSpace = dataSpaceSectors * SECTOR_SIZE
|
||||||
entry.platformDataLength = data.length
|
entry.platformDataLength = data.length
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,12 +217,6 @@ export class VhdAbstract {
|
|||||||
const aliasDir = path.dirname(path.resolve('/', aliasPath))
|
const aliasDir = path.dirname(path.resolve('/', aliasPath))
|
||||||
// only store the relative path from alias to target
|
// only store the relative path from alias to target
|
||||||
const relativePathToTarget = path.relative(aliasDir, path.resolve('/', targetPath))
|
const relativePathToTarget = path.relative(aliasDir, path.resolve('/', targetPath))
|
||||||
|
|
||||||
if (relativePathToTarget.length > ALIAS_MAX_PATH_LENGTH) {
|
|
||||||
throw new Error(
|
|
||||||
`Alias relative path ${relativePathToTarget} is too long : ${relativePathToTarget.length} chars, max is ${ALIAS_MAX_PATH_LENGTH}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
await handler.writeFile(aliasPath, relativePathToTarget)
|
await handler.writeFile(aliasPath, relativePathToTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,23 +226,23 @@ export class VhdAbstract {
|
|||||||
const rawFooter = fuFooter.pack(footer)
|
const rawFooter = fuFooter.pack(footer)
|
||||||
checksumStruct(rawFooter, fuFooter)
|
checksumStruct(rawFooter, fuFooter)
|
||||||
|
|
||||||
|
// compute parent locator place and size
|
||||||
// update them in header
|
// update them in header
|
||||||
// update checksum in header
|
// update checksum in header
|
||||||
|
|
||||||
let offset = FOOTER_SIZE + HEADER_SIZE + batSize
|
let offset = FOOTER_SIZE + HEADER_SIZE + batSize
|
||||||
|
for (let i = 0; i < PARENT_LOCATOR_ENTRIES; i++) {
|
||||||
|
const { ...entry } = header.parentLocatorEntry[i]
|
||||||
|
if (entry.platformDataSpace > 0) {
|
||||||
|
entry.platformDataOffset = offset
|
||||||
|
offset += entry.platformDataSpace
|
||||||
|
}
|
||||||
|
header.parentLocatorEntry[i] = entry
|
||||||
|
}
|
||||||
|
|
||||||
const rawHeader = fuHeader.pack(header)
|
const rawHeader = fuHeader.pack(header)
|
||||||
checksumStruct(rawHeader, fuHeader)
|
checksumStruct(rawHeader, fuHeader)
|
||||||
|
|
||||||
// add parentlocator size
|
|
||||||
for (let i = 0; i < PARENT_LOCATOR_ENTRIES; i++) {
|
|
||||||
header.parentLocatorEntry[i] = {
|
|
||||||
...header.parentLocatorEntry[i],
|
|
||||||
platformDataOffset: offset,
|
|
||||||
}
|
|
||||||
offset += header.parentLocatorEntry[i].platformDataSpace * SECTOR_SIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.strictEqual(offset % SECTOR_SIZE, 0)
|
assert.strictEqual(offset % SECTOR_SIZE, 0)
|
||||||
|
|
||||||
const bat = Buffer.allocUnsafe(batSize)
|
const bat = Buffer.allocUnsafe(batSize)
|
||||||
@@ -276,16 +266,12 @@ export class VhdAbstract {
|
|||||||
yield rawHeader
|
yield rawHeader
|
||||||
yield bat
|
yield bat
|
||||||
|
|
||||||
// yield parent locator
|
// yield parent locator entries
|
||||||
|
|
||||||
for (let i = 0; i < PARENT_LOCATOR_ENTRIES; i++) {
|
for (let i = 0; i < PARENT_LOCATOR_ENTRIES; i++) {
|
||||||
const space = header.parentLocatorEntry[i].platformDataSpace * SECTOR_SIZE
|
if (header.parentLocatorEntry[i].platformDataSpace > 0) {
|
||||||
if (space > 0) {
|
const parentLocator = await self.readParentLocator(i)
|
||||||
const data = (await self.readParentLocator(i)).data
|
// @ todo pad to platformDataSpace
|
||||||
// align data to a sector
|
yield parentLocator.data
|
||||||
const buffer = Buffer.alloc(space, 0)
|
|
||||||
data.copy(buffer)
|
|
||||||
yield buffer
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,17 +20,14 @@ const { debug } = createLogger('vhd-lib:VhdDirectory')
|
|||||||
|
|
||||||
export class VhdDirectory extends VhdAbstract {
|
export class VhdDirectory extends VhdAbstract {
|
||||||
#uncheckedBlockTable
|
#uncheckedBlockTable
|
||||||
#header
|
|
||||||
footer
|
|
||||||
|
|
||||||
set header(header) {
|
set header(header) {
|
||||||
this.#header = header
|
super.header = header
|
||||||
this.#blockTable = Buffer.alloc(header.maxTableEntries)
|
this.#blockTable = Buffer.alloc(header.maxTableEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
get header() {
|
get header() {
|
||||||
assert.notStrictEqual(this.#header, undefined, `header must be read before it's used`)
|
return super.header
|
||||||
return this.#header
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get #blockTable() {
|
get #blockTable() {
|
||||||
@@ -185,7 +182,14 @@ export class VhdDirectory extends VhdAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _readParentLocatorData(id) {
|
async _readParentLocatorData(id) {
|
||||||
return (await this._readChunk('parentLocatorEntry' + id)).buffer
|
try {
|
||||||
|
return (await this._readChunk('parentLocatorEntry' + id)).buffer
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'ENOENT') {
|
||||||
|
return Buffer.alloc(0)
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _writeParentLocatorData(id, data) {
|
async _writeParentLocatorData(id, data) {
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ const { debug } = createLogger('vhd-lib:VhdFile')
|
|||||||
|
|
||||||
export class VhdFile extends VhdAbstract {
|
export class VhdFile extends VhdAbstract {
|
||||||
#uncheckedBlockTable
|
#uncheckedBlockTable
|
||||||
#header
|
|
||||||
footer
|
|
||||||
|
|
||||||
get #blockTable() {
|
get #blockTable() {
|
||||||
assert.notStrictEqual(this.#uncheckedBlockTable, undefined, 'Block table must be initialized before access')
|
assert.notStrictEqual(this.#uncheckedBlockTable, undefined, 'Block table must be initialized before access')
|
||||||
@@ -62,7 +60,7 @@ export class VhdFile extends VhdAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set header(header) {
|
set header(header) {
|
||||||
this.#header = header
|
super.header = header
|
||||||
const size = this.batSize
|
const size = this.batSize
|
||||||
this.#blockTable = Buffer.alloc(size)
|
this.#blockTable = Buffer.alloc(size)
|
||||||
for (let i = 0; i < this.header.maxTableEntries; i++) {
|
for (let i = 0; i < this.header.maxTableEntries; i++) {
|
||||||
@@ -70,7 +68,7 @@ export class VhdFile extends VhdAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
get header() {
|
get header() {
|
||||||
return this.#header
|
return super.header
|
||||||
}
|
}
|
||||||
|
|
||||||
static async open(handler, path, { flags, checkSecondFooter = true } = {}) {
|
static async open(handler, path, { flags, checkSecondFooter = true } = {}) {
|
||||||
@@ -416,7 +414,7 @@ export class VhdFile extends VhdAbstract {
|
|||||||
async _readParentLocatorData(parentLocatorId) {
|
async _readParentLocatorData(parentLocatorId) {
|
||||||
const { platformDataOffset, platformDataLength } = this.header.parentLocatorEntry[parentLocatorId]
|
const { platformDataOffset, platformDataLength } = this.header.parentLocatorEntry[parentLocatorId]
|
||||||
if (platformDataLength > 0) {
|
if (platformDataLength > 0) {
|
||||||
return await this._read(platformDataOffset, platformDataLength)
|
return (await this._read(platformDataOffset, platformDataLength)).buffer
|
||||||
}
|
}
|
||||||
return Buffer.alloc(0)
|
return Buffer.alloc(0)
|
||||||
}
|
}
|
||||||
@@ -428,8 +426,7 @@ export class VhdFile extends VhdAbstract {
|
|||||||
// reset offset if data is empty
|
// reset offset if data is empty
|
||||||
header.parentLocatorEntry[parentLocatorId].platformDataOffset = 0
|
header.parentLocatorEntry[parentLocatorId].platformDataOffset = 0
|
||||||
} else {
|
} else {
|
||||||
const space = header.parentLocatorEntry[parentLocatorId].platformDataSpace * SECTOR_SIZE
|
if (data.length <= header.parentLocatorEntry[parentLocatorId].platformDataSpace) {
|
||||||
if (data.length <= space) {
|
|
||||||
// new parent locator length is smaller than available space : keep it in place
|
// new parent locator length is smaller than available space : keep it in place
|
||||||
position = header.parentLocatorEntry[parentLocatorId].platformDataOffset
|
position = header.parentLocatorEntry[parentLocatorId].platformDataOffset
|
||||||
} else {
|
} else {
|
||||||
@@ -444,7 +441,7 @@ export class VhdFile extends VhdAbstract {
|
|||||||
// move the first(s) block(s) at the end of the data
|
// move the first(s) block(s) at the end of the data
|
||||||
// move the parent locator to the precedent position of the first block
|
// move the parent locator to the precedent position of the first block
|
||||||
const { firstSector } = firstAndLastBlocks
|
const { firstSector } = firstAndLastBlocks
|
||||||
await this._freeFirstBlockSpace(space)
|
await this._freeFirstBlockSpace(header.parentLocatorEntry[parentLocatorId].platformDataSpace)
|
||||||
position = sectorsToBytes(firstSector)
|
position = sectorsToBytes(firstSector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as UUID from 'uuid'
|
import * as UUID from 'uuid'
|
||||||
import cloneDeep from 'lodash/cloneDeep.js'
|
|
||||||
import { asyncMap } from '@xen-orchestra/async-map'
|
import { asyncMap } from '@xen-orchestra/async-map'
|
||||||
import { VhdAbstract } from './VhdAbstract'
|
import { VhdAbstract } from './VhdAbstract'
|
||||||
import { DISK_TYPES, FOOTER_SIZE, HEADER_SIZE } from '../_constants'
|
import { DISK_TYPES, FOOTER_SIZE, HEADER_SIZE } from '../_constants'
|
||||||
@@ -8,6 +7,9 @@ import assert from 'assert'
|
|||||||
|
|
||||||
export class VhdSynthetic extends VhdAbstract {
|
export class VhdSynthetic extends VhdAbstract {
|
||||||
#vhds = []
|
#vhds = []
|
||||||
|
set header(_) {
|
||||||
|
throw new Error('Header is read only for VhdSynthetic')
|
||||||
|
}
|
||||||
|
|
||||||
get header() {
|
get header() {
|
||||||
// this the VHD we want to synthetize
|
// this the VHD we want to synthetize
|
||||||
@@ -20,7 +22,6 @@ export class VhdSynthetic extends VhdAbstract {
|
|||||||
// TODO: set parentLocatorEntry-s in header
|
// TODO: set parentLocatorEntry-s in header
|
||||||
return {
|
return {
|
||||||
...vhd.header,
|
...vhd.header,
|
||||||
parentLocatorEntry: cloneDeep(rootVhd.header.parentLocatorEntry),
|
|
||||||
tableOffset: FOOTER_SIZE + HEADER_SIZE,
|
tableOffset: FOOTER_SIZE + HEADER_SIZE,
|
||||||
parentTimestamp: rootVhd.header.parentTimestamp,
|
parentTimestamp: rootVhd.header.parentTimestamp,
|
||||||
parentUnicodeName: rootVhd.header.parentUnicodeName,
|
parentUnicodeName: rootVhd.header.parentUnicodeName,
|
||||||
@@ -28,6 +29,10 @@ export class VhdSynthetic extends VhdAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set footer(_) {
|
||||||
|
throw new Error('Footer is read only for VhdSynthetic')
|
||||||
|
}
|
||||||
|
|
||||||
get footer() {
|
get footer() {
|
||||||
// this is the root VHD
|
// this is the root VHD
|
||||||
const rootVhd = this.#vhds[this.#vhds.length - 1]
|
const rootVhd = this.#vhds[this.#vhds.length - 1]
|
||||||
|
|||||||
@@ -36,5 +36,3 @@ export const PLATFORMS = {
|
|||||||
|
|
||||||
export const FILE_FORMAT_VERSION = 1 << 16
|
export const FILE_FORMAT_VERSION = 1 << 16
|
||||||
export const HEADER_VERSION = 1 << 16
|
export const HEADER_VERSION = 1 << 16
|
||||||
|
|
||||||
export const ALIAS_MAX_PATH_LENGTH = 1024
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { getSyncedHandler } from '@xen-orchestra/fs'
|
|||||||
import { Disposable, pFromCallback } from 'promise-toolbox'
|
import { Disposable, pFromCallback } from 'promise-toolbox'
|
||||||
|
|
||||||
import { isVhdAlias, resolveAlias } from './_resolveAlias'
|
import { isVhdAlias, resolveAlias } from './_resolveAlias'
|
||||||
import { ALIAS_MAX_PATH_LENGTH } from './_constants'
|
|
||||||
|
|
||||||
let tempDir
|
let tempDir
|
||||||
|
|
||||||
@@ -33,32 +32,25 @@ test('resolve return the path in argument for a non alias file ', async () => {
|
|||||||
test('resolve get the path of the target file for an alias', async () => {
|
test('resolve get the path of the target file for an alias', async () => {
|
||||||
await Disposable.use(async function* () {
|
await Disposable.use(async function* () {
|
||||||
// same directory
|
// same directory
|
||||||
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
|
const handler = yield getSyncedHandler({ url: 'file:///' })
|
||||||
const alias = `alias.alias.vhd`
|
const tempDirFomRemoteUrl = tempDir.slice(1) // remove the / which is included in the remote url
|
||||||
|
const alias = `${tempDirFomRemoteUrl}/alias.alias.vhd`
|
||||||
await handler.writeFile(alias, 'target.vhd')
|
await handler.writeFile(alias, 'target.vhd')
|
||||||
await expect(await resolveAlias(handler, alias)).toEqual(`target.vhd`)
|
expect(await resolveAlias(handler, alias)).toEqual(`${tempDirFomRemoteUrl}/target.vhd`)
|
||||||
|
|
||||||
// different directory
|
// different directory
|
||||||
await handler.mkdir(`sub`)
|
await handler.mkdir(`${tempDirFomRemoteUrl}/sub/`)
|
||||||
await handler.writeFile(alias, 'sub/target.vhd', { flags: 'w' })
|
await handler.writeFile(alias, 'sub/target.vhd', { flags: 'w' })
|
||||||
await expect(await resolveAlias(handler, alias)).toEqual(`sub/target.vhd`)
|
expect(await resolveAlias(handler, alias)).toEqual(`${tempDirFomRemoteUrl}/sub/target.vhd`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('resolve throws an error an alias to an alias', async () => {
|
test('resolve throws an error an alias to an alias', async () => {
|
||||||
await Disposable.use(async function* () {
|
await Disposable.use(async function* () {
|
||||||
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
|
const handler = yield getSyncedHandler({ url: 'file:///' })
|
||||||
const alias = `alias.alias.vhd`
|
const alias = `${tempDir}/alias.alias.vhd`
|
||||||
const target = `target.alias.vhd`
|
const target = `${tempDir}/target.alias.vhd`
|
||||||
await handler.writeFile(alias, target)
|
await handler.writeFile(alias, target)
|
||||||
await expect(async () => await resolveAlias(handler, alias)).rejects.toThrow(Error)
|
expect(async () => await resolveAlias(handler, alias)).rejects.toThrow(Error)
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('resolve throws an error on a file too big ', async () => {
|
|
||||||
await Disposable.use(async function* () {
|
|
||||||
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
|
|
||||||
await handler.writeFile('toobig.alias.vhd', Buffer.alloc(ALIAS_MAX_PATH_LENGTH + 1, 0))
|
|
||||||
await expect(async () => await resolveAlias(handler, 'toobig.alias.vhd')).rejects.toThrow(Error)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ALIAS_MAX_PATH_LENGTH } from './_constants'
|
|
||||||
import resolveRelativeFromFile from './_resolveRelativeFromFile'
|
import resolveRelativeFromFile from './_resolveRelativeFromFile'
|
||||||
|
|
||||||
export function isVhdAlias(filename) {
|
export function isVhdAlias(filename) {
|
||||||
@@ -9,11 +8,6 @@ export async function resolveAlias(handler, filename) {
|
|||||||
if (!isVhdAlias(filename)) {
|
if (!isVhdAlias(filename)) {
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
const size = await handler.getSize(filename)
|
|
||||||
if (size > ALIAS_MAX_PATH_LENGTH) {
|
|
||||||
// seems reasonnable for a relative path
|
|
||||||
throw new Error(`The alias file ${filename} is too big (${size} bytes)`)
|
|
||||||
}
|
|
||||||
const aliasContent = (await handler.readFile(filename)).toString().trim()
|
const aliasContent = (await handler.readFile(filename)).toString().trim()
|
||||||
// also handle circular references and unreasonnably long chains
|
// also handle circular references and unreasonnably long chains
|
||||||
if (isVhdAlias(aliasContent)) {
|
if (isVhdAlias(aliasContent)) {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export async function createVhdDirectoryFromStream(handler, path, inputStream, {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// cleanup on error
|
// cleanup on error
|
||||||
await handler.rmtree(path)
|
await handler.rmTree(path)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "xapi-explore-sr",
|
"name": "xapi-explore-sr",
|
||||||
"version": "0.4.1",
|
"version": "0.4.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "Display the list of VDIs (unmanaged and snapshots included) of a SR",
|
"description": "Display the list of VDIs (unmanaged and snapshots included) of a SR",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xen-orchestra/defined": "^0.0.1",
|
|
||||||
"archy": "^1.0.0",
|
"archy": "^1.0.0",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"exec-promise": "^0.7.0",
|
"exec-promise": "^0.7.0",
|
||||||
|
|||||||
@@ -8,6 +8,6 @@
|
|||||||
"promise-toolbox": "^0.19.2",
|
"promise-toolbox": "^0.19.2",
|
||||||
"readable-stream": "^3.1.1",
|
"readable-stream": "^3.1.1",
|
||||||
"throttle": "^1.0.3",
|
"throttle": "^1.0.3",
|
||||||
"vhd-lib": "^2.0.3"
|
"vhd-lib": "^2.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import asyncMapSettled from '@xen-orchestra/async-map/legacy'
|
import asyncMapSettled from '@xen-orchestra/async-map/legacy'
|
||||||
import Handlebars from 'handlebars'
|
import Handlebars from 'handlebars'
|
||||||
import humanFormat from 'human-format'
|
import humanFormat from 'human-format'
|
||||||
import { stringify } from 'csv-stringify'
|
import stringify from 'csv-stringify'
|
||||||
import { createLogger } from '@xen-orchestra/log'
|
import { createLogger } from '@xen-orchestra/log'
|
||||||
import { createSchedule } from '@xen-orchestra/cron'
|
import { createSchedule } from '@xen-orchestra/cron'
|
||||||
import { minify } from 'html-minifier'
|
import { minify } from 'html-minifier'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "xo-server",
|
"name": "xo-server",
|
||||||
"version": "5.84.3",
|
"version": "5.84.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"description": "Server part of Xen-Orchestra",
|
"description": "Server part of Xen-Orchestra",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -28,18 +28,18 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.1",
|
"@iarna/toml": "^2.2.1",
|
||||||
"@vates/compose": "^2.1.0",
|
"@vates/compose": "^2.0.0",
|
||||||
"@vates/decorate-with": "^0.1.0",
|
"@vates/decorate-with": "^0.1.0",
|
||||||
"@vates/disposable": "^0.1.1",
|
"@vates/disposable": "^0.1.1",
|
||||||
"@vates/multi-key-map": "^0.1.0",
|
"@vates/multi-key-map": "^0.1.0",
|
||||||
"@vates/parse-duration": "^0.1.1",
|
"@vates/parse-duration": "^0.1.1",
|
||||||
"@vates/read-chunk": "^0.1.2",
|
"@vates/read-chunk": "^0.1.2",
|
||||||
"@xen-orchestra/async-map": "^0.1.2",
|
"@xen-orchestra/async-map": "^0.1.2",
|
||||||
"@xen-orchestra/backups": "^0.16.2",
|
"@xen-orchestra/backups": "^0.16.0",
|
||||||
"@xen-orchestra/cron": "^1.0.6",
|
"@xen-orchestra/cron": "^1.0.6",
|
||||||
"@xen-orchestra/defined": "^0.0.1",
|
"@xen-orchestra/defined": "^0.0.1",
|
||||||
"@xen-orchestra/emit-async": "^0.1.0",
|
"@xen-orchestra/emit-async": "^0.1.0",
|
||||||
"@xen-orchestra/fs": "^0.19.2",
|
"@xen-orchestra/fs": "^0.19.1",
|
||||||
"@xen-orchestra/log": "^0.3.0",
|
"@xen-orchestra/log": "^0.3.0",
|
||||||
"@xen-orchestra/mixin": "^0.1.0",
|
"@xen-orchestra/mixin": "^0.1.0",
|
||||||
"@xen-orchestra/mixins": "^0.1.1",
|
"@xen-orchestra/mixins": "^0.1.1",
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
"unzipper": "^0.10.5",
|
"unzipper": "^0.10.5",
|
||||||
"uuid": "^8.3.1",
|
"uuid": "^8.3.1",
|
||||||
"value-matcher": "^0.2.0",
|
"value-matcher": "^0.2.0",
|
||||||
"vhd-lib": "^2.0.3",
|
"vhd-lib": "^2.0.1",
|
||||||
"ws": "^8.2.3",
|
"ws": "^8.2.3",
|
||||||
"xdg-basedir": "^4.0.0",
|
"xdg-basedir": "^4.0.0",
|
||||||
"xen-api": "^0.35.1",
|
"xen-api": "^0.35.1",
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
"xo-collection": "^0.5.0",
|
"xo-collection": "^0.5.0",
|
||||||
"xo-common": "^0.7.0",
|
"xo-common": "^0.7.0",
|
||||||
"xo-remote-parser": "^0.8.0",
|
"xo-remote-parser": "^0.8.0",
|
||||||
"xo-vmdk-to-vhd": "^2.0.1"
|
"xo-vmdk-to-vhd": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.0.0",
|
"@babel/cli": "^7.0.0",
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ const TRANSFORMS = {
|
|||||||
cores: cpuInfo && +cpuInfo.cpu_count,
|
cores: cpuInfo && +cpuInfo.cpu_count,
|
||||||
sockets: cpuInfo && +cpuInfo.socket_count,
|
sockets: cpuInfo && +cpuInfo.socket_count,
|
||||||
},
|
},
|
||||||
suspendSr: link(obj, 'suspend_image_SR'),
|
|
||||||
zstdSupported: obj.restrictions.restrict_zstd_export === 'false',
|
zstdSupported: obj.restrictions.restrict_zstd_export === 'false',
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@@ -438,7 +437,6 @@ const TRANSFORMS = {
|
|||||||
startDelay: +obj.start_delay,
|
startDelay: +obj.start_delay,
|
||||||
startTime: metrics && toTimestamp(metrics.start_time),
|
startTime: metrics && toTimestamp(metrics.start_time),
|
||||||
secureBoot: obj.platform.secureboot === 'true',
|
secureBoot: obj.platform.secureboot === 'true',
|
||||||
suspendSr: link(obj, 'suspend_SR'),
|
|
||||||
tags: obj.tags,
|
tags: obj.tags,
|
||||||
VIFs: link(obj, 'VIFs'),
|
VIFs: link(obj, 'VIFs'),
|
||||||
virtualizationMode: domainType,
|
virtualizationMode: domainType,
|
||||||
|
|||||||
@@ -187,15 +187,6 @@ export default class BackupNg {
|
|||||||
filter: createPredicate({
|
filter: createPredicate({
|
||||||
type: 'VM',
|
type: 'VM',
|
||||||
...vmsPattern,
|
...vmsPattern,
|
||||||
|
|
||||||
// don't match VMs created by this very job
|
|
||||||
//
|
|
||||||
// otherwise replicated VMs would be matched and replicated again and again
|
|
||||||
other_config: {
|
|
||||||
__not: {
|
|
||||||
'xo:backup:job': job.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "xo-vmdk-to-vhd",
|
"name": "xo-vmdk-to-vhd",
|
||||||
"version": "2.0.1",
|
"version": "2.0.0",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"description": "JS lib streaming a vmdk file to a vhd",
|
"description": "JS lib streaming a vmdk file to a vhd",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"pako": "^2.0.4",
|
"pako": "^2.0.4",
|
||||||
"promise-toolbox": "^0.20.0",
|
"promise-toolbox": "^0.20.0",
|
||||||
"vhd-lib": "^2.0.3",
|
"vhd-lib": "^2.0.1",
|
||||||
"xml2js": "^0.4.23"
|
"xml2js": "^0.4.23"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -138,12 +138,12 @@
|
|||||||
"xo-common": "^0.7.0",
|
"xo-common": "^0.7.0",
|
||||||
"xo-lib": "^0.11.1",
|
"xo-lib": "^0.11.1",
|
||||||
"xo-remote-parser": "^0.8.0",
|
"xo-remote-parser": "^0.8.0",
|
||||||
"xo-vmdk-to-vhd": "^2.0.1"
|
"xo-vmdk-to-vhd": "^2.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "GIT_HEAD=$(git rev-parse HEAD) NODE_ENV=production gulp build",
|
"build": "NODE_ENV=production gulp build",
|
||||||
"clean": "gulp clean",
|
"clean": "gulp clean",
|
||||||
"dev": "GIT_HEAD=$(git rev-parse HEAD) NODE_ENV=development gulp build",
|
"dev": "NODE_ENV=development gulp build",
|
||||||
"prebuild": "yarn run clean && index-modules --auto src",
|
"prebuild": "yarn run clean && index-modules --auto src",
|
||||||
"predev": "yarn run prebuild",
|
"predev": "yarn run prebuild",
|
||||||
"prepublishOnly": "yarn run build"
|
"prepublishOnly": "yarn run build"
|
||||||
|
|||||||
@@ -849,7 +849,6 @@ const messages = {
|
|||||||
disconnectServer: 'Disconnect',
|
disconnectServer: 'Disconnect',
|
||||||
|
|
||||||
// ----- Host item ------
|
// ----- Host item ------
|
||||||
host: 'Host',
|
|
||||||
noMoreMaintained: 'This host version is no longer maintained',
|
noMoreMaintained: 'This host version is no longer maintained',
|
||||||
|
|
||||||
// ----- Host actions ------
|
// ----- Host actions ------
|
||||||
@@ -1267,7 +1266,6 @@ const messages = {
|
|||||||
vmLimitsLabel: 'VM limits',
|
vmLimitsLabel: 'VM limits',
|
||||||
resourceSet: 'Resource set',
|
resourceSet: 'Resource set',
|
||||||
resourceSetNone: 'None',
|
resourceSetNone: 'None',
|
||||||
suspendSr: 'Suspend SR',
|
|
||||||
vmCpuLimitsLabel: 'CPU limits',
|
vmCpuLimitsLabel: 'CPU limits',
|
||||||
vmCpuTopology: 'Topology',
|
vmCpuTopology: 'Topology',
|
||||||
vmChooseCoresPerSocket: 'Default behavior',
|
vmChooseCoresPerSocket: 'Default behavior',
|
||||||
@@ -1381,9 +1379,6 @@ const messages = {
|
|||||||
detachedBackups: 'Detached backups',
|
detachedBackups: 'Detached backups',
|
||||||
detachedVmSnapshots: 'Detached VM snapshots',
|
detachedVmSnapshots: 'Detached VM snapshots',
|
||||||
duplicatedMacAddresses: 'Duplicated MAC addresses',
|
duplicatedMacAddresses: 'Duplicated MAC addresses',
|
||||||
localDefaultSrs: 'Local default SRs',
|
|
||||||
localDefaultSrsStatusTip:
|
|
||||||
"It is usually recommended for a pool's default SR to be shared to avoid unexpected behaviors",
|
|
||||||
missingJob: 'Missing job',
|
missingJob: 'Missing job',
|
||||||
missingVm: 'Missing VM',
|
missingVm: 'Missing VM',
|
||||||
missingVmInJob: 'This VM does not belong to this job',
|
missingVmInJob: 'This VM does not belong to this job',
|
||||||
@@ -1397,7 +1392,6 @@ const messages = {
|
|||||||
noOrphanedObject: 'No orphans',
|
noOrphanedObject: 'No orphans',
|
||||||
tooManySnapshots: 'Too many snapshots',
|
tooManySnapshots: 'Too many snapshots',
|
||||||
tooManySnapshotsTip: 'VMs with more than the recommended amount of snapshots',
|
tooManySnapshotsTip: 'VMs with more than the recommended amount of snapshots',
|
||||||
noLocalDefaultSrs: 'No local default SRs',
|
|
||||||
noTooManySnapshotsObject: 'No VMs with too many snapshots',
|
noTooManySnapshotsObject: 'No VMs with too many snapshots',
|
||||||
numberOfSnapshots: 'Number of snapshots',
|
numberOfSnapshots: 'Number of snapshots',
|
||||||
guestToolStatus: 'Guest Tools status',
|
guestToolStatus: 'Guest Tools status',
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
import _ from 'intl'
|
|
||||||
import React from 'react'
|
|
||||||
import defined, { get } from '@xen-orchestra/defined'
|
|
||||||
import { injectState, provideState } from 'reaclette'
|
|
||||||
|
|
||||||
import decorate from './apply-decorators'
|
|
||||||
import Icon from './icon'
|
|
||||||
import renderXoItem from './render-xo-item'
|
|
||||||
import { connectStore } from './utils'
|
|
||||||
import { createGetObject } from './selectors'
|
|
||||||
import { editVm, editPool, isSrWritable } from './xo'
|
|
||||||
import { XoSelect } from './editable'
|
|
||||||
|
|
||||||
export const SelectSuspendSr = decorate([
|
|
||||||
connectStore({
|
|
||||||
suspendSr: createGetObject((_, { pool, vm }) => (vm || pool).suspendSr),
|
|
||||||
}),
|
|
||||||
provideState({
|
|
||||||
effects: {
|
|
||||||
onChange(_, value) {
|
|
||||||
const { isVm } = this.state
|
|
||||||
const method = isVm ? editVm : editPool
|
|
||||||
method(isVm ? this.props.vm : this.props.pool, {
|
|
||||||
suspendSr: defined(
|
|
||||||
get(() => value.id),
|
|
||||||
null
|
|
||||||
),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isVm: (state, props) => props.vm !== undefined,
|
|
||||||
predicate: (state, props) => sr =>
|
|
||||||
isSrWritable(sr) && (state.isVm ? props.vm.$pool === sr.$pool : props.pool.id === sr.$pool),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
injectState,
|
|
||||||
({ effects: { onChange }, state: { predicate }, suspendSr }) => (
|
|
||||||
<span>
|
|
||||||
<XoSelect onChange={onChange} predicate={predicate} value={suspendSr} xoType='SR'>
|
|
||||||
{suspendSr !== undefined ? renderXoItem(suspendSr) : _('noValue')}
|
|
||||||
</XoSelect>{' '}
|
|
||||||
{suspendSr !== undefined && (
|
|
||||||
<a role='button' onClick={onChange}>
|
|
||||||
<Icon icon='remove' />
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
])
|
|
||||||
@@ -212,7 +212,7 @@ const CollapsedActions = decorate([
|
|||||||
className={action.level !== undefined ? `text-${action.level}` : ''}
|
className={action.level !== undefined ? `text-${action.level}` : ''}
|
||||||
disabled={action.disabled}
|
disabled={action.disabled}
|
||||||
key={key}
|
key={key}
|
||||||
onClick={action.disabled ? undefined : () => effects.execute(action)}
|
onClick={() => effects.execute(action)}
|
||||||
>
|
>
|
||||||
<Icon icon={action.icon} /> {action.label}
|
<Icon icon={action.icon} /> {action.label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@@ -149,10 +149,6 @@
|
|||||||
@extend .fa;
|
@extend .fa;
|
||||||
@extend .fa-keyboard-o;
|
@extend .fa-keyboard-o;
|
||||||
}
|
}
|
||||||
&-git {
|
|
||||||
@extend .fa;
|
|
||||||
@extend .fa-git-square;
|
|
||||||
}
|
|
||||||
&-info {
|
&-info {
|
||||||
@extend .fa;
|
@extend .fa;
|
||||||
@extend .fa-info-circle;
|
@extend .fa-info-circle;
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import { connectStore, getXoaPlan } from 'utils'
|
|||||||
|
|
||||||
import pkg from '../../../package'
|
import pkg from '../../../package'
|
||||||
|
|
||||||
const COMMIT_ID = process.env.GIT_HEAD
|
|
||||||
|
|
||||||
const HEADER = (
|
const HEADER = (
|
||||||
<Container>
|
<Container>
|
||||||
<Row>
|
<Row>
|
||||||
@@ -42,19 +40,8 @@ export default class About extends Component {
|
|||||||
return (
|
return (
|
||||||
<Page header={HEADER} title='aboutPage' formatTitle>
|
<Page header={HEADER} title='aboutPage' formatTitle>
|
||||||
<Container className='text-xs-center'>
|
<Container className='text-xs-center'>
|
||||||
{isAdmin && [
|
{isAdmin && (
|
||||||
process.env.XOA_PLAN > 4 && COMMIT_ID !== '' && (
|
<Row>
|
||||||
<Row key='0'>
|
|
||||||
<Col>
|
|
||||||
<Icon icon='git' size={4} />
|
|
||||||
<h4>
|
|
||||||
Xen Orchestra, commit{' '}
|
|
||||||
<a href={'https://github.com/vatesfr/xen-orchestra/commit/' + COMMIT_ID}>{COMMIT_ID.slice(0, 5)}</a>
|
|
||||||
</h4>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
),
|
|
||||||
<Row key='1'>
|
|
||||||
<Col mediumSize={6}>
|
<Col mediumSize={6}>
|
||||||
<Icon icon='host' size={4} />
|
<Icon icon='host' size={4} />
|
||||||
<Copiable tagName='h4' data={`xo-server ${this.state.serverVersion}`}>
|
<Copiable tagName='h4' data={`xo-server ${this.state.serverVersion}`}>
|
||||||
@@ -69,8 +56,8 @@ export default class About extends Component {
|
|||||||
</Copiable>
|
</Copiable>
|
||||||
<p className='text-muted'>{_('xenOrchestraWeb')}</p>
|
<p className='text-muted'>{_('xenOrchestraWeb')}</p>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>,
|
</Row>
|
||||||
]}
|
)}
|
||||||
{process.env.XOA_PLAN > 4 ? (
|
{process.env.XOA_PLAN > 4 ? (
|
||||||
<div>
|
<div>
|
||||||
<Row>
|
<Row>
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import NoObjects from 'no-objects'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import SortedTable from 'sorted-table'
|
import SortedTable from 'sorted-table'
|
||||||
import Tooltip from 'tooltip'
|
import Tooltip from 'tooltip'
|
||||||
import { Host, Network, Pool, Sr, Vm } from 'render-xo-item'
|
import { Network, Sr, Vm } from 'render-xo-item'
|
||||||
import { SelectPool } from 'select-objects'
|
import { SelectPool } from 'select-objects'
|
||||||
import { Container, Row, Col } from 'grid'
|
import { Container, Row, Col } from 'grid'
|
||||||
import { Card, CardHeader, CardBlock } from 'card'
|
import { Card, CardHeader, CardBlock } from 'card'
|
||||||
import { FormattedRelative, FormattedTime } from 'react-intl'
|
import { FormattedRelative, FormattedTime } from 'react-intl'
|
||||||
import { countBy, filter, flatten, forEach, includes, isEmpty, map, pick } from 'lodash'
|
import { flatten, forEach, includes, isEmpty, map } from 'lodash'
|
||||||
import { connectStore, formatLogs, formatSize, noop, resolveIds } from 'utils'
|
import { connectStore, formatLogs, formatSize, noop, resolveIds } from 'utils'
|
||||||
import {
|
import {
|
||||||
deleteMessage,
|
deleteMessage,
|
||||||
@@ -105,23 +105,6 @@ const DUPLICATED_MAC_ADDRESSES_FILTERS = {
|
|||||||
filterOnlyRunningVms: 'nRunningVms:>1',
|
filterOnlyRunningVms: 'nRunningVms:>1',
|
||||||
}
|
}
|
||||||
|
|
||||||
const LOCAL_DEFAULT_SRS_COLUMNS = [
|
|
||||||
{
|
|
||||||
name: _('pool'),
|
|
||||||
itemRenderer: pool => <Pool id={pool.id} link />,
|
|
||||||
sortCriteria: 'name_label',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('sr'),
|
|
||||||
itemRenderer: pool => <Sr container={false} id={pool.default_SR} link spaceLeft={false} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('host'),
|
|
||||||
itemRenderer: (pool, { srs }) => <Host id={srs[pool.default_SR].$container} link pool={false} />,
|
|
||||||
sortCriteria: (pool, { hosts, srs }) => hosts[srs[pool.default_SR].$container].name_label,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const SR_COLUMNS = [
|
const SR_COLUMNS = [
|
||||||
{
|
{
|
||||||
name: _('srName'),
|
name: _('srName'),
|
||||||
@@ -545,10 +528,8 @@ const HANDLED_VDI_TYPES = new Set(['system', 'user', 'ephemeral'])
|
|||||||
return {
|
return {
|
||||||
alertMessages: getAlertMessages,
|
alertMessages: getAlertMessages,
|
||||||
areObjectsFetched,
|
areObjectsFetched,
|
||||||
hosts: createGetObjectsOfType('host'),
|
|
||||||
orphanVdis: getOrphanVdis,
|
orphanVdis: getOrphanVdis,
|
||||||
orphanVmSnapshots: getOrphanVmSnapshots,
|
orphanVmSnapshots: getOrphanVmSnapshots,
|
||||||
pools: createGetObjectsOfType('pool'),
|
|
||||||
tooManySnapshotsVms: getTooManySnapshotsVms,
|
tooManySnapshotsVms: getTooManySnapshotsVms,
|
||||||
guestToolsVms: getGuestToolsVms,
|
guestToolsVms: getGuestToolsVms,
|
||||||
userSrs: getUserSrs,
|
userSrs: getUserSrs,
|
||||||
@@ -604,22 +585,6 @@ export default class Health extends Component {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
_getLocalDefaultSrs = createCollectionWrapper(
|
|
||||||
createSelector(
|
|
||||||
() => this.props.hosts,
|
|
||||||
() => this.props.pools,
|
|
||||||
() => this.props.userSrs,
|
|
||||||
() => this._getPoolIds(),
|
|
||||||
(hosts, pools, userSrs, poolIds) => {
|
|
||||||
const nbHostsPerPool = countBy(hosts, host => host.$pool)
|
|
||||||
return filter(
|
|
||||||
isEmpty(poolIds) ? pools : pick(pools, poolIds),
|
|
||||||
pool => !userSrs[pool.default_SR].shared && nbHostsPerPool[pool.id] > 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
_getPoolIds = createCollectionWrapper(createSelector(() => this.state.pools, resolveIds))
|
_getPoolIds = createCollectionWrapper(createSelector(() => this.state.pools, resolveIds))
|
||||||
|
|
||||||
_getPoolPredicate = createSelector(this._getPoolIds, poolIds =>
|
_getPoolPredicate = createSelector(this._getPoolIds, poolIds =>
|
||||||
@@ -651,7 +616,6 @@ export default class Health extends Component {
|
|||||||
const { props, state } = this
|
const { props, state } = this
|
||||||
|
|
||||||
const duplicatedMacAddresses = this._getDuplicatedMacAddresses()
|
const duplicatedMacAddresses = this._getDuplicatedMacAddresses()
|
||||||
const localDefaultSrs = this._getLocalDefaultSrs()
|
|
||||||
const userSrs = this._getUserSrs()
|
const userSrs = this._getUserSrs()
|
||||||
const orphanVdis = this._getOrphanVdis()
|
const orphanVdis = this._getOrphanVdis()
|
||||||
|
|
||||||
@@ -686,41 +650,6 @@ export default class Health extends Component {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{localDefaultSrs.length > 0 && (
|
|
||||||
<Row>
|
|
||||||
<Col>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<Icon icon='disk' /> {_('localDefaultSrs')}
|
|
||||||
</CardHeader>
|
|
||||||
<CardBlock>
|
|
||||||
<p>
|
|
||||||
<Icon icon='info' /> <em>{_('localDefaultSrsStatusTip')}</em>
|
|
||||||
</p>
|
|
||||||
<NoObjects
|
|
||||||
collection={props.areObjectsFetched ? localDefaultSrs : null}
|
|
||||||
emptyMessage={_('noLocalDefaultSrs')}
|
|
||||||
>
|
|
||||||
{() => (
|
|
||||||
<Row>
|
|
||||||
<Col>
|
|
||||||
<SortedTable
|
|
||||||
collection={localDefaultSrs}
|
|
||||||
columns={LOCAL_DEFAULT_SRS_COLUMNS}
|
|
||||||
data-hosts={props.hosts}
|
|
||||||
data-srs={userSrs}
|
|
||||||
shortcutsTarget='body'
|
|
||||||
stateUrlParam='s_local_default_srs'
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
</NoObjects>
|
|
||||||
</CardBlock>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import {
|
|||||||
subscribePlugins,
|
subscribePlugins,
|
||||||
synchronizeNetbox,
|
synchronizeNetbox,
|
||||||
} from 'xo'
|
} from 'xo'
|
||||||
import { SelectSuspendSr } from 'select-suspend-sr'
|
|
||||||
|
|
||||||
@connectStore(() => ({
|
@connectStore(() => ({
|
||||||
master: createGetObjectsOfType('host').find((_, { pool }) => ({
|
master: createGetObjectsOfType('host').find((_, { pool }) => ({
|
||||||
@@ -203,12 +202,6 @@ export default class TabAdvanced extends Component {
|
|||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th>{_('suspendSr')}</th>
|
|
||||||
<td>
|
|
||||||
<SelectSuspendSr pool={pool} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import {
|
|||||||
XEN_VIDEORAM_VALUES,
|
XEN_VIDEORAM_VALUES,
|
||||||
} from 'xo'
|
} from 'xo'
|
||||||
import { createGetObject, createGetObjectsOfType, createSelector, isAdmin } from 'selectors'
|
import { createGetObject, createGetObjectsOfType, createSelector, isAdmin } from 'selectors'
|
||||||
import { SelectSuspendSr } from 'select-suspend-sr'
|
|
||||||
|
|
||||||
import BootOrder from './boot-order'
|
import BootOrder from './boot-order'
|
||||||
|
|
||||||
@@ -799,12 +798,6 @@ export default class TabAdvanced extends Component {
|
|||||||
<CustomFields object={vm.id} />
|
<CustomFields object={vm.id} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th>{_('suspendSr')}</th>
|
|
||||||
<td>
|
|
||||||
<SelectSuspendSr vm={vm} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
Reference in New Issue
Block a user