Compare commits
173 Commits
ldap-origi
...
licenses-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78df47c726 | ||
|
|
efffbafa42 | ||
|
|
899cec8814 | ||
|
|
55beb993e5 | ||
|
|
fba46a1e00 | ||
|
|
68c4d6f295 | ||
|
|
2ad07c018e | ||
|
|
c2418559f1 | ||
|
|
de0277eb3b | ||
|
|
b58802b495 | ||
|
|
898e20e8b2 | ||
|
|
75bf10d83d | ||
|
|
e8e58cc4a2 | ||
|
|
342d02c8a8 | ||
|
|
ae885eaddc | ||
|
|
b75bb4ed9f | ||
|
|
a171863591 | ||
|
|
bcc2286018 | ||
|
|
beb8b9723f | ||
|
|
ab3129b9c3 | ||
|
|
c061505bf8 | ||
|
|
171bbedcde | ||
|
|
ae655727c0 | ||
|
|
288b3783c3 | ||
|
|
320612c826 | ||
|
|
9b0a293d2b | ||
|
|
d5b6f27e97 | ||
|
|
cf8bd759e3 | ||
|
|
c2142c5cd9 | ||
|
|
eeef536803 | ||
|
|
ec51cf7606 | ||
|
|
ec83b76e46 | ||
|
|
6bc73f8f43 | ||
|
|
65127e04aa | ||
|
|
2726045fb0 | ||
|
|
d4046c3295 | ||
|
|
97d4f5583c | ||
|
|
96dc36cd16 | ||
|
|
ab2344ebfc | ||
|
|
4dd7d86ea8 | ||
|
|
6adfa618a3 | ||
|
|
855e896a5a | ||
|
|
2bacb0f073 | ||
|
|
399131a5a9 | ||
|
|
4c77be9e83 | ||
|
|
8ed5463f19 | ||
|
|
63739df903 | ||
|
|
3d72232bb9 | ||
|
|
d5ff811de7 | ||
|
|
f74f47965c | ||
|
|
79adb7225e | ||
|
|
11e79914ef | ||
|
|
9b34fc369b | ||
|
|
ae812806a1 | ||
|
|
956c7728bf | ||
|
|
6d5a5a46e4 | ||
|
|
b244126d60 | ||
|
|
2a7f8c5229 | ||
|
|
59fddf7c59 | ||
|
|
d21fd2a1ed | ||
|
|
6828a5d9a0 | ||
|
|
32960332b9 | ||
|
|
9ba77c9498 | ||
|
|
65f9a9dcc1 | ||
|
|
13315eec42 | ||
|
|
e43ccd155b | ||
|
|
e92960a413 | ||
|
|
e82eec0cd6 | ||
|
|
23b687c528 | ||
|
|
5044a814a1 | ||
|
|
3250b95f5e | ||
|
|
2a0934ec28 | ||
|
|
f1752abc5d | ||
|
|
e931ec74dd | ||
|
|
528d823c55 | ||
|
|
6475b58541 | ||
|
|
59e8b26015 | ||
|
|
0894c21296 | ||
|
|
4223bdd4ad | ||
|
|
c55ed42b2b | ||
|
|
7abc833ebe | ||
|
|
6abe399e36 | ||
|
|
555b1a16da | ||
|
|
52b956e677 | ||
|
|
0296cbe9e9 | ||
|
|
8315d7790a | ||
|
|
dd60d289ff | ||
|
|
8c61fd0bf7 | ||
|
|
814e08edd4 | ||
|
|
44956dff85 | ||
|
|
18685d061a | ||
|
|
692d5be166 | ||
|
|
60bdaef716 | ||
|
|
cb51f44a45 | ||
|
|
88f43a8124 | ||
|
|
7de7cdba60 | ||
|
|
ba140c60e3 | ||
|
|
95a5c7a001 | ||
|
|
b399da72d8 | ||
|
|
f9a10d8932 | ||
|
|
549ce6fbf9 | ||
|
|
e63d27a035 | ||
|
|
ab3621fe3c | ||
|
|
8b99f2ecbc | ||
|
|
433b309907 | ||
|
|
ac40cec138 | ||
|
|
93702ece48 | ||
|
|
1678474830 | ||
|
|
4260099c23 | ||
|
|
6139cb50bc | ||
|
|
49e334d726 | ||
|
|
6beded153b | ||
|
|
750308a16a | ||
|
|
57fa00b765 | ||
|
|
b57d4fdbec | ||
|
|
93aca81265 | ||
|
|
5c704e142e | ||
|
|
1a4a77066e | ||
|
|
a690b9d825 | ||
|
|
b70969fd03 | ||
|
|
daf5fff83f | ||
|
|
aef1f9b857 | ||
|
|
c1c8ea7df0 | ||
|
|
b75034b40c | ||
|
|
cb41a79b36 | ||
|
|
cedc20ce6a | ||
|
|
43bc1e9116 | ||
|
|
6fa1379c1e | ||
|
|
cb16438b1c | ||
|
|
e6c8d6cc7d | ||
|
|
f0e87e71ab | ||
|
|
cdd28b8e31 | ||
|
|
9bff564ace | ||
|
|
62c616c6af | ||
|
|
8a35f5d6ca | ||
|
|
e1b051324d | ||
|
|
a947c3152a | ||
|
|
cca43040d3 | ||
|
|
9b42657ca7 | ||
|
|
bb19c55c3a | ||
|
|
1ec6611410 | ||
|
|
218bd0ffc1 | ||
|
|
d649a22b80 | ||
|
|
0bffbbfe65 | ||
|
|
04a562372b | ||
|
|
11b08ce53a | ||
|
|
70f8f9679d | ||
|
|
4773f9ebf6 | ||
|
|
c1b6d1706a | ||
|
|
9f94b8f915 | ||
|
|
3abd97d0fb | ||
|
|
416a0687ee | ||
|
|
7056e20075 | ||
|
|
de4b158a44 | ||
|
|
c7f4648d5a | ||
|
|
a456be9d76 | ||
|
|
3befaac114 | ||
|
|
11616ee03b | ||
|
|
2a59feddb6 | ||
|
|
e2fa0aface | ||
|
|
6d4d954713 | ||
|
|
a4592ca425 | ||
|
|
700eae4cc6 | ||
|
|
2c9fe6f37d | ||
|
|
558ede11cf | ||
|
|
4c0a68ab0b | ||
|
|
d6b4931001 | ||
|
|
79ef01cb25 | ||
|
|
ea8735f390 | ||
|
|
7b70855e94 | ||
|
|
65f818b631 | ||
|
|
0a0baeaeab | ||
|
|
e011ed1f64 |
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/async-map",
|
||||
"version": "0.0.0",
|
||||
"license": "ISC",
|
||||
|
||||
3
@xen-orchestra/audit-core/.babelrc.js
Normal file
3
@xen-orchestra/audit-core/.babelrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = require('../../@xen-orchestra/babel-config')(
|
||||
require('./package.json')
|
||||
)
|
||||
24
@xen-orchestra/audit-core/.npmignore
Normal file
24
@xen-orchestra/audit-core/.npmignore
Normal 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__/
|
||||
40
@xen-orchestra/audit-core/package.json
Normal file
40
@xen-orchestra/audit-core/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@xen-orchestra/audit-core",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/audit-core",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@xen-orchestra/audit-core",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"version": "0.1.1",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"main": "dist/",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"postversion": "npm publish --access public",
|
||||
"prebuild": "rimraf dist/",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.7.4",
|
||||
"@babel/core": "^7.7.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.8.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.0",
|
||||
"@babel/preset-env": "^7.7.4",
|
||||
"@babel/preset-typescript": "^7.7.4",
|
||||
"cross": "^1.0.0",
|
||||
"rimraf": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.4",
|
||||
"golike-defer": "^0.4.1",
|
||||
"lodash": "^4.17.15",
|
||||
"object-hash": "^2.0.1"
|
||||
},
|
||||
"private": false
|
||||
}
|
||||
142
@xen-orchestra/audit-core/src/index.js
Normal file
142
@xen-orchestra/audit-core/src/index.js
Normal file
@@ -0,0 +1,142 @@
|
||||
// see https://github.com/babel/babel/issues/8450
|
||||
import 'core-js/features/symbol/async-iterator'
|
||||
|
||||
import assert from 'assert'
|
||||
import defer from 'golike-defer'
|
||||
import hash from 'object-hash'
|
||||
|
||||
export class Storage {
|
||||
constructor() {
|
||||
this._lock = Promise.resolve()
|
||||
}
|
||||
|
||||
async acquireLock() {
|
||||
const lock = this._lock
|
||||
let releaseLock
|
||||
this._lock = new Promise(resolve => {
|
||||
releaseLock = resolve
|
||||
})
|
||||
await lock
|
||||
return releaseLock
|
||||
}
|
||||
}
|
||||
|
||||
// Format: $<algorithm>$<salt>$<encrypted>
|
||||
//
|
||||
// http://man7.org/linux/man-pages/man3/crypt.3.html#NOTES
|
||||
const ID_TO_ALGORITHM = {
|
||||
'5': 'sha256',
|
||||
}
|
||||
|
||||
export class AlteredRecordError extends Error {
|
||||
constructor(id, nValid, record) {
|
||||
super('altered record')
|
||||
|
||||
this.id = id
|
||||
this.nValid = nValid
|
||||
this.record = record
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingRecordError extends Error {
|
||||
constructor(id, nValid) {
|
||||
super('missing record')
|
||||
|
||||
this.id = id
|
||||
this.nValid = nValid
|
||||
}
|
||||
}
|
||||
|
||||
export const NULL_ID = 'nullId'
|
||||
|
||||
const HASH_ALGORITHM_ID = '5'
|
||||
const createHash = (data, algorithmId = HASH_ALGORITHM_ID) =>
|
||||
`$${algorithmId}$$${hash(data, {
|
||||
algorithm: ID_TO_ALGORITHM[algorithmId],
|
||||
excludeKeys: key => key === 'id',
|
||||
})}`
|
||||
|
||||
export class AuditCore {
|
||||
constructor(storage) {
|
||||
assert.notStrictEqual(storage, undefined)
|
||||
this._storage = storage
|
||||
}
|
||||
|
||||
@defer
|
||||
async add($defer, subject, event, data) {
|
||||
const time = Date.now()
|
||||
const storage = this._storage
|
||||
$defer(await storage.acquireLock())
|
||||
|
||||
// delete "undefined" properties and normalize data with JSON.stringify
|
||||
const record = JSON.parse(
|
||||
JSON.stringify({
|
||||
data,
|
||||
event,
|
||||
previousId: (await storage.getLastId()) ?? NULL_ID,
|
||||
subject,
|
||||
time,
|
||||
})
|
||||
)
|
||||
record.id = createHash(record)
|
||||
await storage.put(record)
|
||||
await storage.setLastId(record.id)
|
||||
return record
|
||||
}
|
||||
|
||||
async checkIntegrity(oldest, newest) {
|
||||
const storage = this._storage
|
||||
|
||||
// handle separated chains case
|
||||
if (newest !== (await storage.getLastId())) {
|
||||
let isNewestAccessible = false
|
||||
for await (const { id } of this.getFrom()) {
|
||||
if (id === newest) {
|
||||
isNewestAccessible = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!isNewestAccessible) {
|
||||
throw new MissingRecordError(newest, 0)
|
||||
}
|
||||
}
|
||||
|
||||
let nValid = 0
|
||||
while (newest !== oldest) {
|
||||
const record = await storage.get(newest)
|
||||
if (record === undefined) {
|
||||
throw new MissingRecordError(newest, nValid)
|
||||
}
|
||||
if (
|
||||
newest !== createHash(record, newest.slice(1, newest.indexOf('$', 1)))
|
||||
) {
|
||||
throw new AlteredRecordError(newest, nValid, record)
|
||||
}
|
||||
newest = record.previousId
|
||||
nValid++
|
||||
}
|
||||
return nValid
|
||||
}
|
||||
|
||||
async *getFrom(newest) {
|
||||
const storage = this._storage
|
||||
|
||||
let id = newest ?? (await storage.getLastId())
|
||||
if (id === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
let record
|
||||
while ((record = await storage.get(id)) !== undefined) {
|
||||
yield record
|
||||
id = record.previousId
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFrom(newest) {
|
||||
assert.notStrictEqual(newest, undefined)
|
||||
for await (const { id } of this.getFrom(newest)) {
|
||||
await this._storage.del(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
126
@xen-orchestra/audit-core/src/index.spec.js
Normal file
126
@xen-orchestra/audit-core/src/index.spec.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import {
|
||||
AlteredRecordError,
|
||||
AuditCore,
|
||||
MissingRecordError,
|
||||
NULL_ID,
|
||||
Storage,
|
||||
} from '.'
|
||||
|
||||
const asyncIteratorToArray = async asyncIterator => {
|
||||
const array = []
|
||||
for await (const entry of asyncIterator) {
|
||||
array.push(entry)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
class DB extends Storage {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this._db = new Map()
|
||||
this._lastId = undefined
|
||||
}
|
||||
|
||||
async put(record) {
|
||||
this._db.set(record.id, record)
|
||||
}
|
||||
|
||||
async setLastId(id) {
|
||||
this._lastId = id
|
||||
}
|
||||
|
||||
async getLastId() {
|
||||
return this._lastId
|
||||
}
|
||||
|
||||
async del(id) {
|
||||
this._db.delete(id)
|
||||
}
|
||||
|
||||
async get(id) {
|
||||
return this._db.get(id)
|
||||
}
|
||||
|
||||
_clear() {
|
||||
return this._db.clear()
|
||||
}
|
||||
}
|
||||
|
||||
const DATA = [
|
||||
[
|
||||
{
|
||||
name: 'subject0',
|
||||
},
|
||||
'event0',
|
||||
{},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'subject1',
|
||||
},
|
||||
'event1',
|
||||
{},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'subject2',
|
||||
},
|
||||
'event2',
|
||||
{},
|
||||
],
|
||||
]
|
||||
|
||||
const db = new DB()
|
||||
const auditCore = new AuditCore(db)
|
||||
const storeAuditRecords = async () => {
|
||||
await Promise.all(DATA.map(data => auditCore.add(...data)))
|
||||
const records = await asyncIteratorToArray(auditCore.getFrom())
|
||||
expect(records.length).toBe(DATA.length)
|
||||
return records
|
||||
}
|
||||
|
||||
describe('auditCore', () => {
|
||||
afterEach(() => db._clear())
|
||||
|
||||
it('detects that a record is missing', async () => {
|
||||
const [newestRecord, deletedRecord] = await storeAuditRecords()
|
||||
|
||||
const nValidRecords = await auditCore.checkIntegrity(
|
||||
NULL_ID,
|
||||
newestRecord.id
|
||||
)
|
||||
expect(nValidRecords).toBe(DATA.length)
|
||||
|
||||
await db.del(deletedRecord.id)
|
||||
await expect(
|
||||
auditCore.checkIntegrity(NULL_ID, newestRecord.id)
|
||||
).rejects.toEqual(new MissingRecordError(deletedRecord.id, 1))
|
||||
})
|
||||
|
||||
it('detects that a record has been altered', async () => {
|
||||
const [newestRecord, alteredRecord] = await storeAuditRecords()
|
||||
|
||||
alteredRecord.event = ''
|
||||
await db.put(alteredRecord)
|
||||
|
||||
await expect(
|
||||
auditCore.checkIntegrity(NULL_ID, newestRecord.id)
|
||||
).rejects.toEqual(
|
||||
new AlteredRecordError(alteredRecord.id, 1, alteredRecord)
|
||||
)
|
||||
})
|
||||
|
||||
it('confirms interval integrity after deletion of records outside of the interval', async () => {
|
||||
const [thirdRecord, secondRecord, firstRecord] = await storeAuditRecords()
|
||||
|
||||
await auditCore.deleteFrom(secondRecord.id)
|
||||
|
||||
expect(await db.get(firstRecord.id)).toBe(undefined)
|
||||
expect(await db.get(secondRecord.id)).toBe(undefined)
|
||||
|
||||
await auditCore.checkIntegrity(secondRecord.id, thirdRecord.id)
|
||||
})
|
||||
})
|
||||
25
@xen-orchestra/audit-core/src/specification.ts
Normal file
25
@xen-orchestra/audit-core/src/specification.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
class Storage {
|
||||
acquire: () => Promise<() => undefined>
|
||||
del: (id: string) => Promise<void>
|
||||
get: (id: string) => Promise<Record | void>
|
||||
getLastId: () => Promise<string | void>
|
||||
put: (record: Record) => Promise<void>
|
||||
setLastId: (id: string) => Promise<void>
|
||||
}
|
||||
|
||||
interface Record {
|
||||
data: object
|
||||
event: string
|
||||
id: string
|
||||
previousId: string
|
||||
subject: object
|
||||
time: number
|
||||
}
|
||||
|
||||
export class AuditCore {
|
||||
constructor(storage: Storage) {}
|
||||
public add(subject: any, event: string, data: any): Promise<Record> {}
|
||||
public checkIntegrity(oldest: string, newest: string): Promise<number> {}
|
||||
public getFrom(newest?: string): AsyncIterator {}
|
||||
public deleteFrom(newest: string): Promise<void> {}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ let force
|
||||
const assert = require('assert')
|
||||
const flatten = require('lodash/flatten')
|
||||
const getopts = require('getopts')
|
||||
const isValidXva = require('@xen-orchestra/backups/isValidXva')
|
||||
const lockfile = require('proper-lockfile')
|
||||
const pipe = require('promise-toolbox/pipe')
|
||||
const { default: Vhd } = require('vhd-lib')
|
||||
@@ -21,70 +22,6 @@ const handler = require('@xen-orchestra/fs').getHandler({ url: 'file://' })
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const isGzipFile = async fd => {
|
||||
// https://tools.ietf.org/html/rfc1952.html#page-5
|
||||
const magicNumber = Buffer.allocUnsafe(2)
|
||||
assert.strictEqual(
|
||||
await fs.read(fd, magicNumber, 0, magicNumber.length, 0),
|
||||
magicNumber.length
|
||||
)
|
||||
return magicNumber[0] === 31 && magicNumber[1] === 139
|
||||
}
|
||||
|
||||
// TODO: better check?
|
||||
//
|
||||
// our heuristic is not good enough, there has been some false positives
|
||||
// (detected as invalid by us but valid by `tar` and imported with success),
|
||||
// either THOUGH THEY MAY HAVE BEEN COMPRESSED FILES:
|
||||
// - these files were normal but the check is incorrect
|
||||
// - these files were invalid but without data loss
|
||||
// - these files were invalid but with silent data loss
|
||||
//
|
||||
// maybe reading the end of the file looking for a file named
|
||||
// /^Ref:\d+/\d+\.checksum$/ and then validating the tar structure from it
|
||||
//
|
||||
// https://github.com/npm/node-tar/issues/234#issuecomment-538190295
|
||||
const isValidTar = async (size, fd) => {
|
||||
if (size <= 1024 || size % 512 !== 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
const buf = Buffer.allocUnsafe(1024)
|
||||
assert.strictEqual(
|
||||
await fs.read(fd, buf, 0, buf.length, size - buf.length),
|
||||
buf.length
|
||||
)
|
||||
return buf.every(_ => _ === 0)
|
||||
}
|
||||
|
||||
// TODO: find an heuristic for compressed files
|
||||
const isValidXva = async path => {
|
||||
try {
|
||||
const fd = await fs.open(path, 'r')
|
||||
try {
|
||||
const { size } = await fs.fstat(fd)
|
||||
if (size < 20) {
|
||||
// neither a valid gzip not tar
|
||||
return false
|
||||
}
|
||||
|
||||
return (await isGzipFile(fd))
|
||||
? true // gzip files cannot be validated at this time
|
||||
: await isValidTar(size, fd)
|
||||
} finally {
|
||||
fs.close(fd).catch(noop)
|
||||
}
|
||||
} catch (error) {
|
||||
// never throw, log and report as valid to avoid side effects
|
||||
console.error('isValidXva', path, error)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const noop = Function.prototype
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// chain is an array of VHDs from child to parent
|
||||
//
|
||||
// the whole chain will be merged into parent, parent will be renamed to child
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"private": false,
|
||||
"bin": {
|
||||
"xo-backups": "index.js"
|
||||
},
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"dependencies": {
|
||||
"@xen-orchestra/fs": "^0.10.2",
|
||||
"@xen-orchestra/backups": "^0.1.1",
|
||||
"@xen-orchestra/fs": "^0.10.3",
|
||||
"filenamify": "^4.1.0",
|
||||
"getopts": "^2.2.5",
|
||||
"lodash": "^4.17.15",
|
||||
@@ -15,6 +17,10 @@
|
||||
"engines": {
|
||||
"node": ">=7.10.1"
|
||||
},
|
||||
"files": [
|
||||
"commands",
|
||||
"*.js"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/backups-cli",
|
||||
"name": "@xen-orchestra/backups-cli",
|
||||
"repository": {
|
||||
|
||||
30
@xen-orchestra/backups/extractIdsFromSimplePattern.js
Normal file
30
@xen-orchestra/backups/extractIdsFromSimplePattern.js
Normal file
@@ -0,0 +1,30 @@
|
||||
function extractIdsFromSimplePattern(pattern) {
|
||||
if (pattern === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (pattern !== null && typeof pattern === 'object') {
|
||||
let keys = Object.keys(pattern)
|
||||
|
||||
if (keys.length === 1 && keys[0] === 'id') {
|
||||
pattern = pattern.id
|
||||
if (typeof pattern === 'string') {
|
||||
return [pattern]
|
||||
}
|
||||
if (pattern !== null && typeof pattern === 'object') {
|
||||
keys = Object.keys(pattern)
|
||||
if (
|
||||
keys.length === 1 &&
|
||||
keys[0] === '__or' &&
|
||||
Array.isArray((pattern = pattern.__or)) &&
|
||||
pattern.every(_ => typeof _ === 'string')
|
||||
) {
|
||||
return pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('invalid pattern')
|
||||
}
|
||||
exports.extractIdsFromSimplePattern = extractIdsFromSimplePattern
|
||||
6
@xen-orchestra/backups/filenameDate.js
Normal file
6
@xen-orchestra/backups/filenameDate.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { utcFormat, utcParse } = require('d3-time-format')
|
||||
|
||||
// Format a date in ISO 8601 in a safe way to be used in filenames
|
||||
// (even on Windows).
|
||||
exports.formatFilenameDate = utcFormat('%Y%m%dT%H%M%SZ')
|
||||
exports.parseFilenameDate = utcParse('%Y%m%dT%H%M%SZ')
|
||||
7
@xen-orchestra/backups/getOldEntries.js
Normal file
7
@xen-orchestra/backups/getOldEntries.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// returns all entries but the last retention-th
|
||||
exports.getOldEntries = (retention, entries) =>
|
||||
entries === undefined
|
||||
? []
|
||||
: retention > 0
|
||||
? entries.slice(0, -retention)
|
||||
: entries
|
||||
65
@xen-orchestra/backups/isValidXva.js
Normal file
65
@xen-orchestra/backups/isValidXva.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const assert = require('assert')
|
||||
const fs = require('fs-extra')
|
||||
|
||||
const isGzipFile = async fd => {
|
||||
// https://tools.ietf.org/html/rfc1952.html#page-5
|
||||
const magicNumber = Buffer.allocUnsafe(2)
|
||||
assert.strictEqual(
|
||||
(await fs.read(fd, magicNumber, 0, magicNumber.length, 0)).bytesRead,
|
||||
magicNumber.length
|
||||
)
|
||||
return magicNumber[0] === 31 && magicNumber[1] === 139
|
||||
}
|
||||
|
||||
// TODO: better check?
|
||||
//
|
||||
// our heuristic is not good enough, there has been some false positives
|
||||
// (detected as invalid by us but valid by `tar` and imported with success),
|
||||
// either THOUGH THEY MAY HAVE BEEN COMPRESSED FILES:
|
||||
// - these files were normal but the check is incorrect
|
||||
// - these files were invalid but without data loss
|
||||
// - these files were invalid but with silent data loss
|
||||
//
|
||||
// maybe reading the end of the file looking for a file named
|
||||
// /^Ref:\d+/\d+\.checksum$/ and then validating the tar structure from it
|
||||
//
|
||||
// https://github.com/npm/node-tar/issues/234#issuecomment-538190295
|
||||
const isValidTar = async (size, fd) => {
|
||||
if (size <= 1024 || size % 512 !== 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
const buf = Buffer.allocUnsafe(1024)
|
||||
assert.strictEqual(
|
||||
(await fs.read(fd, buf, 0, buf.length, size - buf.length)).bytesRead,
|
||||
buf.length
|
||||
)
|
||||
return buf.every(_ => _ === 0)
|
||||
}
|
||||
|
||||
// TODO: find an heuristic for compressed files
|
||||
const isValidXva = async path => {
|
||||
try {
|
||||
const fd = await fs.open(path, 'r')
|
||||
try {
|
||||
const { size } = await fs.fstat(fd)
|
||||
if (size < 20) {
|
||||
// neither a valid gzip not tar
|
||||
return false
|
||||
}
|
||||
|
||||
return (await isGzipFile(fd))
|
||||
? true // gzip files cannot be validated at this time
|
||||
: await isValidTar(size, fd)
|
||||
} finally {
|
||||
fs.close(fd).catch(noop)
|
||||
}
|
||||
} catch (error) {
|
||||
// never throw, log and report as valid to avoid side effects
|
||||
console.error('isValidXva', path, error)
|
||||
return true
|
||||
}
|
||||
}
|
||||
exports.isValidXva = isValidXva
|
||||
|
||||
const noop = Function.prototype
|
||||
22
@xen-orchestra/backups/package.json
Normal file
22
@xen-orchestra/backups/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/backups",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/backups",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "@xen-orchestra/backups",
|
||||
"type": "git",
|
||||
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||
},
|
||||
"version": "0.1.1",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish --access public"
|
||||
},
|
||||
"dependencies": {
|
||||
"d3-time-format": "^2.2.3",
|
||||
"fs-extra": "^8.1.0"
|
||||
}
|
||||
}
|
||||
11
@xen-orchestra/backups/watchStreamSize.js
Normal file
11
@xen-orchestra/backups/watchStreamSize.js
Normal file
@@ -0,0 +1,11 @@
|
||||
exports.watchStreamSize = stream => {
|
||||
const container = { size: 0 }
|
||||
const isPaused = stream.isPaused()
|
||||
stream.on('data', data => {
|
||||
container.size += data.length
|
||||
})
|
||||
if (isPaused) {
|
||||
stream.pause()
|
||||
}
|
||||
return container
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/cr-seed-cli",
|
||||
"version": "0.2.0",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/cr-seed-cli",
|
||||
@@ -16,7 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"golike-defer": "^0.4.1",
|
||||
"xen-api": "^0.27.3"
|
||||
"xen-api": "^0.28.3"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/cron",
|
||||
"version": "1.0.6",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/defined",
|
||||
"version": "0.0.0",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/emit-async",
|
||||
"version": "0.0.0",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/fs",
|
||||
"version": "0.10.2",
|
||||
"version": "0.10.3",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "The File System for Xen Orchestra backups.",
|
||||
"keywords": [],
|
||||
|
||||
@@ -118,7 +118,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async closeFile(fd: FileDescriptor): Promise<void> {
|
||||
await timeout.call(this._closeFile(fd.fd), this._timeout)
|
||||
await this.__closeFile(fd)
|
||||
}
|
||||
|
||||
async createOutputStream(
|
||||
@@ -283,30 +283,15 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
async mkdir(dir: string): Promise<void> {
|
||||
dir = normalizePath(dir)
|
||||
try {
|
||||
await this._mkdir(dir)
|
||||
} catch (error) {
|
||||
if (error == null || error.code !== 'EEXIST') {
|
||||
throw error
|
||||
}
|
||||
|
||||
// this operation will throw if it's not already a directory
|
||||
await this._list(dir)
|
||||
}
|
||||
await this.__mkdir(normalizePath(dir))
|
||||
}
|
||||
|
||||
async mktree(dir: string): Promise<void> {
|
||||
await this._mktree(normalizePath(dir))
|
||||
}
|
||||
|
||||
async openFile(path: string, flags: string): Promise<FileDescriptor> {
|
||||
path = normalizePath(path)
|
||||
|
||||
return {
|
||||
fd: await timeout.call(this._openFile(path, flags), this._timeout),
|
||||
path,
|
||||
}
|
||||
openFile(path: string, flags: string): Promise<FileDescriptor> {
|
||||
return this.__openFile(path, flags)
|
||||
}
|
||||
|
||||
async outputFile(
|
||||
@@ -455,6 +440,34 @@ export default class RemoteHandlerAbstract {
|
||||
await this._writeFile(normalizePath(file), data, { flags })
|
||||
}
|
||||
|
||||
// Methods that can be called by private methods to avoid parallel limit on public methods
|
||||
|
||||
async __closeFile(fd: FileDescriptor): Promise<void> {
|
||||
await timeout.call(this._closeFile(fd.fd), this._timeout)
|
||||
}
|
||||
|
||||
async __mkdir(dir: string): Promise<void> {
|
||||
try {
|
||||
await this._mkdir(dir)
|
||||
} catch (error) {
|
||||
if (error == null || error.code !== 'EEXIST') {
|
||||
throw error
|
||||
}
|
||||
|
||||
// this operation will throw if it's not already a directory
|
||||
await this._list(dir)
|
||||
}
|
||||
}
|
||||
|
||||
async __openFile(path: string, flags: string): Promise<FileDescriptor> {
|
||||
path = normalizePath(path)
|
||||
|
||||
return {
|
||||
fd: await timeout.call(this._openFile(path, flags), this._timeout),
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
// Methods that can be implemented by inheriting classes
|
||||
|
||||
async _closeFile(fd: mixed): Promise<void> {
|
||||
@@ -503,7 +516,7 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
async _mktree(dir: string): Promise<void> {
|
||||
try {
|
||||
return await this.mkdir(dir)
|
||||
return await this.__mkdir(dir)
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error
|
||||
@@ -586,13 +599,13 @@ export default class RemoteHandlerAbstract {
|
||||
async _write(file: File, buffer: Buffer, position: number): Promise<void> {
|
||||
const isPath = typeof file === 'string'
|
||||
if (isPath) {
|
||||
file = await this.openFile(file, 'r+')
|
||||
file = await this.__openFile(file, 'r+')
|
||||
}
|
||||
try {
|
||||
return await this._writeFd(file, buffer, position)
|
||||
} finally {
|
||||
if (isPath) {
|
||||
await this.closeFile(file)
|
||||
await this.__closeFile(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +219,12 @@ handlers.forEach(url => {
|
||||
const error = await rejectionOf(handler.outputFile('file', ''))
|
||||
expect(error.code).toBe('EEXIST')
|
||||
})
|
||||
|
||||
it("shouldn't timeout in case of the respect of the parallel execution restriction", async () => {
|
||||
const handler = getHandler({ url }, { maxParallelOperations: 1 })
|
||||
await handler.sync()
|
||||
await handler.outputFile(`xo-fs-tests-${Date.now()}/test`, '')
|
||||
}, 40)
|
||||
})
|
||||
|
||||
describe('#read()', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/log",
|
||||
"version": "0.2.0",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,6 +1,85 @@
|
||||
import LEVELS, { NAMES } from '../levels'
|
||||
|
||||
const { ERROR, INFO, WARN } = LEVELS
|
||||
const { DEBUG, ERROR, FATAL, INFO, WARN } = LEVELS
|
||||
|
||||
let formatLevel, formatNamespace
|
||||
if (
|
||||
process.stdout !== undefined &&
|
||||
process.stdout.isTTY &&
|
||||
process.stderr !== undefined &&
|
||||
process.stderr.isTTY
|
||||
) {
|
||||
const ansi = (style, str) => `\x1b[${style}m${str}\x1b[0m`
|
||||
|
||||
const LEVEL_STYLES = {
|
||||
[DEBUG]: '2',
|
||||
[ERROR]: '1;31',
|
||||
[FATAL]: '1;31',
|
||||
[INFO]: '1',
|
||||
[WARN]: '1;33',
|
||||
}
|
||||
formatLevel = level => {
|
||||
const style = LEVEL_STYLES[level]
|
||||
const name = NAMES[level]
|
||||
return style === undefined ? name : ansi(style, name)
|
||||
}
|
||||
|
||||
const NAMESPACE_COLORS = [
|
||||
196,
|
||||
202,
|
||||
208,
|
||||
214,
|
||||
220,
|
||||
226,
|
||||
190,
|
||||
154,
|
||||
118,
|
||||
82,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
45,
|
||||
39,
|
||||
33,
|
||||
27,
|
||||
21,
|
||||
57,
|
||||
93,
|
||||
129,
|
||||
165,
|
||||
201,
|
||||
200,
|
||||
199,
|
||||
198,
|
||||
197,
|
||||
]
|
||||
formatNamespace = namespace => {
|
||||
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
|
||||
let hash = 0
|
||||
for (let i = 0, n = namespace.length; i < n; ++i) {
|
||||
hash = ((hash << 5) - hash + namespace.charCodeAt(i)) | 0
|
||||
}
|
||||
// // select a hue (HSV)
|
||||
// const h = (Math.abs(hash) % 20) * 18
|
||||
// // convert to RGB
|
||||
// const f = (n, k = (n + h / 60) % 6) =>
|
||||
// Math.round(255 * (1 - Math.max(Math.min(k, 4 - k, 1), 0)))
|
||||
// const r = f(5)
|
||||
// const g = f(3)
|
||||
// const b = f(1)
|
||||
// return ansi(`38;2;${r};${g};${b}`, namespace)
|
||||
return ansi(
|
||||
`1;38;5;${NAMESPACE_COLORS[Math.abs(hash) % NAMESPACE_COLORS.length]}`,
|
||||
namespace
|
||||
)
|
||||
}
|
||||
} else {
|
||||
formatLevel = str => NAMES[str]
|
||||
formatNamespace = str => str
|
||||
}
|
||||
|
||||
const consoleTransport = ({ data, level, namespace, message, time }) => {
|
||||
const fn =
|
||||
@@ -14,7 +93,12 @@ const consoleTransport = ({ data, level, namespace, message, time }) => {
|
||||
: console.error
|
||||
/* eslint-enable no-console */
|
||||
|
||||
const args = [time.toISOString(), namespace, NAMES[level], message]
|
||||
const args = [
|
||||
time.toISOString(),
|
||||
formatNamespace(namespace),
|
||||
formatLevel(level),
|
||||
message,
|
||||
]
|
||||
if (data != null) {
|
||||
args.push(data)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/mixin",
|
||||
"version": "0.0.0",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "@xen-orchestra/template",
|
||||
"version": "0.1.0",
|
||||
"license": "ISC",
|
||||
|
||||
284
CHANGELOG.md
284
CHANGELOG.md
@@ -4,19 +4,186 @@
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [VM migration] Ability to choose network for migration within a pool [#2028](https://github.com/vatesfr/xen-orchestra/issues/2028) (PR [#4828](https://github.com/vatesfr/xen-orchestra/pull/4828))
|
||||
- [Support] Link to create a new support ticket [#4234](https://github.com/vatesfr/xen-orchestra/issues/4234) (PR [#4833](https://github.com/vatesfr/xen-orchestra/pull/4833))
|
||||
- [Proxies] Ability to redeploy a proxy VM [#4825](https://github.com/vatesfr/xen-orchestra/issues/4825) (PR [#4725](https://github.com/vatesfr/xen-orchestra/pull/4725))
|
||||
- [SR / Disks] Ability to migrate VDIs [#4455](https://github.com/vatesfr/xen-orchestra/issues/4455) (PR [#4696](https://github.com/vatesfr/xen-orchestra/pull/4696))
|
||||
- [Proxy / Deploy] Ability to select the destination network [#4825](https://github.com/vatesfr/xen-orchestra/issues/4825) (PR [#4855](https://github.com/vatesfr/xen-orchestra/pull/4855))
|
||||
- [Proxies/Deploy] Remove SRs not connected to an HVM-capable host from selection [#4825](https://github.com/vatesfr/xen-orchestra/issues/4825) (PR [#4849](https://github.com/vatesfr/xen-orchestra/pull/4849))
|
||||
- [Audit] Ability to export records [#4798](https://github.com/vatesfr/xen-orchestra/issues/4798) (PR [#4858](https://github.com/vatesfr/xen-orchestra/pull/4858))
|
||||
- [VM/backup] Show backup jobs [#4623](https://github.com/vatesfr/xen-orchestra/issues/4623) (PR [#4860](https://github.com/vatesfr/xen-orchestra/pull/4860))
|
||||
- [Audit] Improve integrity check feedback [#4798](https://github.com/vatesfr/xen-orchestra/issues/4798) (PR [#4879](https://github.com/vatesfr/xen-orchestra/pull/4879))
|
||||
- [XOA] Manage the XOA licenses from the xoa/licenses page (PR [#3717](https://github.com/vatesfr/xen-orchestra/pull/3717))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [XOSAN] Fix the installer (PR [#4839](https://github.com/vatesfr/xen-orchestra/pull/4839))
|
||||
- [OVA Import] Fix _no host available_ error when starting for imported VMs with low memory (PR [#4866](https://github.com/vatesfr/xen-orchestra/pull/4866))
|
||||
- [Self] When a Self Service related operation fails, always revert the quotas to what they were before the operation (PR [#4861](https://github.com/vatesfr/xen-orchestra/pull/4861))
|
||||
- [auth-{github,google,saml}] Don't require manually reloading the plugin for configuration changes to take effect [#4863](https://github.com/vatesfr/xen-orchestra/issues/4863) (PR [#4864](https://github.com/vatesfr/xen-orchestra/pull/4864))
|
||||
- [auth-ldap] Fix reading certificate authorities files [#3873](https://github.com/vatesfr/xen-orchestra/issues/3873)
|
||||
- [Backup NG / logs] Replace successful backup job status by failed status in case of missing VMs [#4857](https://github.com/vatesfr/xen-orchestra/issues/4857) (PR [#4862](https://github.com/vatesfr/xen-orchestra/pull/4862))
|
||||
- [Jobs] Fix "no value for `user_ip`" error on jobs execution (PR [#4878](https://github.com/vatesfr/xen-orchestra/pull/4878))
|
||||
- [Self] Properly take IP pools into account when computing quotas (PR [#4871](https://github.com/vatesfr/xen-orchestra/pull/4871))
|
||||
|
||||
### Released packages
|
||||
|
||||
- @xen-orchestra/audit-core 0.1.1
|
||||
- xo-server-audit 0.2.0
|
||||
- xo-server-auth-github 0.2.2
|
||||
- xo-server-auth-google 0.2.2
|
||||
- xo-server-auth-ldap 0.7.1
|
||||
- xo-server-auth-saml 0.7.2
|
||||
- xo-server 5.58.0
|
||||
- xo-web 5.58.0
|
||||
|
||||
## **5.42.0** (2019-12-20)
|
||||
## **5.44.1** (2020-03-05)
|
||||
|
||||

|
||||
|
||||
### Enhancements
|
||||
|
||||
- [Plugin] Show plugin description [#4569](https://github.com/vatesfr/xen-orchestra/issues/4569) (PR [#4832](https://github.com/vatesfr/xen-orchestra/pull/4832))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [Backup reports] Fix backup report not sent in case of interrupted backup job (PR [#4772](https://github.com/vatesfr/xen-orchestra/pull/4772))
|
||||
- Fix TLS error (`unsupported protocol`) with XenServer <= 6.5 and Node >= 12 (PR [#8437](https://github.com/vatesfr/xen-orchestra/pull/8437))
|
||||
- [Metadata backup] Fix timeout when lots of pools are backed up [#4819](https://github.com/vatesfr/xen-orchestra/issues/4819) (PR [#4831](https://github.com/vatesfr/xen-orchestra/pull/4831))
|
||||
|
||||
### Released packages
|
||||
|
||||
- @xen-orchestra/fs v0.10.3
|
||||
- xen-api v0.28.3
|
||||
- xo-server-backup-reports v0.16.5
|
||||
- xo-server-perf-alert v0.2.1
|
||||
- xo-server-sdn-controller v0.4.1
|
||||
- xo-server-transport-icinga2 v0.1.1
|
||||
- xo-server-transport-nagios v0.1.1
|
||||
- xo-server-usage-report v0.7.5
|
||||
- xo-server-web-hooks v0.1.1
|
||||
- xo-server v5.57.3
|
||||
- xo-web v5.57.1
|
||||
|
||||
## **5.44.0** (2020-02-28)
|
||||
|
||||
### Highlights
|
||||
|
||||
- [Audit log] Record side effects triggered by users [#4653](https://github.com/vatesfr/xen-orchestra/issues/4653) [#701](https://github.com/vatesfr/xen-orchestra/issues/701) (PR [#4740](https://github.com/vatesfr/xen-orchestra/pull/4740))
|
||||
- [SR/general] Clickable SR usage graph: shows the corresponding disks when you click on one of the sections [#4747](https://github.com/vatesfr/xen-orchestra/issues/4747) (PR [#4754](https://github.com/vatesfr/xen-orchestra/pull/4754))
|
||||
- [New VM] Ability to copy host BIOS strings [#4204](https://github.com/vatesfr/xen-orchestra/issues/4204) (PR [4755](https://github.com/vatesfr/xen-orchestra/pull/4755))
|
||||
- [New VM] Ability to set VM max vCPUS [#4703](https://github.com/vatesfr/xen-orchestra/issues/4703) (PR [#4729](https://github.com/vatesfr/xen-orchestra/pull/4729))
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [SDN Controller] Automatically handle new, connected and disconnected servers (PR [#4677](https://github.com/vatesfr/xen-orchestra/pull/4677))
|
||||
- [Proxy] Support network configuration for the deployed proxy (PR [#4810](https://github.com/vatesfr/xen-orchestra/pull/4810))
|
||||
- [Menu] Display a warning icon in case of missing patches [#4475](https://github.com/vatesfr/xen-orchestra/issues/4475) (PR [#4683](https://github.com/vatesfr/xen-orchestra/pull/4683))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [Usage Report] Fix wrong report date [#4779](https://github.com/vatesfr/xen-orchestra/issues/4779) (PR [#4799](https://github.com/vatesfr/xen-orchestra/pull/4799))
|
||||
- [SDN Controller] Fix plugin stuck loading [#4649](https://github.com/vatesfr/xen-orchestra/issues/4649) (PR [#4677](https://github.com/vatesfr/xen-orchestra/pull/4677))
|
||||
- [xo-server-logs] Fix `Cannot find module '../better-stacks'`
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-common v0.4.0
|
||||
- @xen-orchestra/audit-core v0.1.0
|
||||
- xo-server-audit v0.1.2
|
||||
- xo-server-auth-ldap v0.7.0
|
||||
- xo-server-usage-report v0.7.4
|
||||
- xo-server-sdn-controller v0.4.0
|
||||
- xo-server v5.57.2
|
||||
- xo-web v5.57.0
|
||||
|
||||
## **5.43.3** (2020-03-06)
|
||||
|
||||

|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [Backups] Fix an issue where DR and CR could stay stuck (commit [63739df](https://github.com/vatesfr/xen-orchestra/commit/63739df90369798f16b61bf96d1a89513c7edc77))
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-server v5.56.3
|
||||
|
||||
## **5.43.2** (2020-02-11)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [Proxy] Correctly call the proxy when running backups (PRs [#4791](https://github.com/vatesfr/xen-orchestra/pull/4791) & [#4792](https://github.com/vatesfr/xen-orchestra/pull/4792))
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-server v5.56.2
|
||||
|
||||
## **5.43.1** (2020-02-06)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [Self,IP pools] Fixed the creation being stuck and freezing XO (PR [#4776](https://github.com/vatesfr/xen-orchestra/pull/4776))
|
||||
- [SDN Controller] Ensure the correct PIF is used to create private networks tunnels [#4737](https://github.com/vatesfr/xen-orchestra/issues/4737) (PR [#4757](https://github.com/vatesfr/xen-orchestra/pull/4757))
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-server-sdn-controller v0.3.2
|
||||
- xo-server-auth-saml 0.7.1
|
||||
- xo-server v5.56.1
|
||||
- xo-web v5.56.2
|
||||
|
||||
## **5.43.0** (2020-01-31)
|
||||
|
||||
### Highlights
|
||||
|
||||
- [Home] Allow to change the number of items per page [#4535](https://github.com/vatesfr/xen-orchestra/issues/4535) (PR [#4708](https://github.com/vatesfr/xen-orchestra/pull/4708))
|
||||
- [Tag] Adding a tag: ability to select from existing tags [#2810](https://github.com/vatesfr/xen-orchestra/issues/2810) (PR [#4530](https://github.com/vatesfr/xen-orchestra/pull/4530))
|
||||
- [Smart backup] Ability to manually add custom tags [#2810](https://github.com/vatesfr/xen-orchestra/issues/2810) (PR [#4648](https://github.com/vatesfr/xen-orchestra/pull/4648))
|
||||
- [Proxy] Ability to backup VMs via registered proxy [#4254](https://github.com/vatesfr/xen-orchestra/issues/4254) (PR [#4495](https://github.com/vatesfr/xen-orchestra/pull/4495))
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [VM/Migrate] Ask user before forcing migration [#2136](https://github.com/vatesfr/xen-orchestra/issues/2136) (PR [#4364](https://github.com/vatesfr/xen-orchestra/pull/4364))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [New network] Fix bonded network not linked to the slave hosts [#4529](https://github.com/vatesfr/xen-orchestra/issues/4529) (PR [#4756](https://github.com/vatesfr/xen-orchestra/pull/4756))
|
||||
|
||||
### Dropped features
|
||||
|
||||
- [Backup / Overview] Job cancellation will be disabled until we find a way to make it work [#4657](https://github.com/vatesfr/xen-orchestra/issues/4657) (PR [#4688](https://github.com/vatesfr/xen-orchestra/pull/4688))
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-common v0.3.0
|
||||
- xo-server v5.56.0
|
||||
- xo-web v5.56.1
|
||||
|
||||
## **5.42.1** (2020-01-17)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [Snapshot] Fallback to normal snapshot if quiesce is not available [#4735](https://github.com/vatesfr/xen-orchestra/issues/4735) (PR [#4736](https://github.com/vatesfr/xen-orchestra/pull/4736)) \
|
||||
Fixes compatibility with **Citrix Hypervisor 8.1**.
|
||||
- [Uncompressed full backup] Quick healthcheck of downloaded XVAs in case there was an undetected issue (PR [#4741](https://github.com/vatesfr/xen-orchestra/pull/4741))
|
||||
- [Backup] Make built-in concurrency limits configurable (PR [#4743](https://github.com/vatesfr/xen-orchestra/pull/4743)) \
|
||||
Via the following entries in `xo-server`'s configuration file:
|
||||
- `xapiOptions.vdiExportConcurrency`
|
||||
- `xapiOptions.vmExportConcurrency`
|
||||
- `xapiOptions.vmSnapshotConcurrency`
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-server v5.55.0
|
||||
- xo-web v5.55.0
|
||||
|
||||
## **5.42.0** (2019-12-20)
|
||||
|
||||
### Highlights
|
||||
|
||||
- [SDN Controller] Allow private network creation on bond and VLAN (PR [#4682](https://github.com/vatesfr/xen-orchestra/pull/4682))
|
||||
- [Hub/recipes] [Ability to create a kubernetes cluster](https://xen-orchestra.com/blog/devblog-5-kubernetes-clutser-on-xo/) (PR [#4695](https://github.com/vatesfr/xen-orchestra/pull/4695))
|
||||
- [Hub/recipes][ability to create a kubernetes cluster](https://xen-orchestra.com/blog/devblog-5-kubernetes-clutser-on-xo/) (PR [#4695](https://github.com/vatesfr/xen-orchestra/pull/4695))
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -29,13 +196,11 @@
|
||||
|
||||
## **5.41.0** (2019-11-29)
|
||||
|
||||

|
||||
|
||||
### Highlights
|
||||
|
||||
- [Backup NG] Make report recipients configurable in the backup settings [#4581](https://github.com/vatesfr/xen-orchestra/issues/4581) (PR [#4646](https://github.com/vatesfr/xen-orchestra/pull/4646))
|
||||
- [Host] Advanced Live Telemetry (PR [#4680](https://github.com/vatesfr/xen-orchestra/pull/4680))
|
||||
- [Plugin] [Web hooks](https://xen-orchestra.com/docs/web-hooks.html) [#1946](https://github.com/vatesfr/xen-orchestra/issues/1946) (PR [#3155](https://github.com/vatesfr/xen-orchestra/pull/3155))
|
||||
- [Plugin][web hooks](https://xen-orchestra.com/docs/web-hooks.html) [#1946](https://github.com/vatesfr/xen-orchestra/issues/1946) (PR [#3155](https://github.com/vatesfr/xen-orchestra/pull/3155))
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -131,7 +296,6 @@
|
||||
|
||||
- xo-server-cloud : this package was useless for OpenSource installations because it required a complete XOA environment
|
||||
|
||||
|
||||
## **5.39.1** (2019-10-11)
|
||||
|
||||
### Enhancements
|
||||
@@ -147,7 +311,6 @@
|
||||
|
||||
- xo-web v5.50.3
|
||||
|
||||
|
||||
## **5.39.0** (2019-09-30)
|
||||
|
||||
### Highlights
|
||||
@@ -189,7 +352,6 @@
|
||||
- [Backup NG] Properly log and report if job is already running [#4497](https://github.com/vatesfr/xen-orchestra/issues/4497) (PR [4534](https://github.com/vatesfr/xen-orchestra/pull/4534))
|
||||
- [Host] Fix an issue where host was wrongly reporting time inconsistency (PR [#4540](https://github.com/vatesfr/xen-orchestra/pull/4540))
|
||||
|
||||
|
||||
### Released packages
|
||||
|
||||
- xen-api v0.27.2
|
||||
@@ -200,7 +362,6 @@
|
||||
- xo-server v5.50.1
|
||||
- xo-web v5.50.2
|
||||
|
||||
|
||||
## **5.38.0** (2019-08-29)
|
||||
|
||||
### Enhancements
|
||||
@@ -377,9 +538,9 @@
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [Continuous Replication] Fix VHD size guess for empty files [#4105](https://github.com/vatesfr/xen-orchestra/issues/4105) (PR [#4107](https://github.com/vatesfr/xen-orchestra/pull/4107))
|
||||
- [Continuous Replication] Fix VHD size guess for empty files [#4105](https://github.com/vatesfr/xen-orchestra/issues/4105) (PR [#4107](https://github.com/vatesfr/xen-orchestra/pull/4107))
|
||||
- [Backup NG] Only display full backup interval in case of a delta backup (PR [#4125](https://github.com/vatesfr/xen-orchestra/pull/4107))
|
||||
- [Dashboard/Health] fix 'an error has occurred' on the storage state table [#4128](https://github.com/vatesfr/xen-orchestra/issues/4128) (PR [#4132](https://github.com/vatesfr/xen-orchestra/pull/4132))
|
||||
- [Dashboard/Health] fix 'an error has occurred' on the storage state table [#4128](https://github.com/vatesfr/xen-orchestra/issues/4128) (PR [#4132](https://github.com/vatesfr/xen-orchestra/pull/4132))
|
||||
- [Menu] XOA: Fixed empty slot when menu is collapsed [#4012](https://github.com/vatesfr/xen-orchestra/issues/4012) (PR [#4068](https://github.com/vatesfr/xen-orchestra/pull/4068)
|
||||
- [Self/New VM] Fix missing templates when refreshing page [#3265](https://github.com/vatesfr/xen-orchestra/issues/3265) (PR [#3565](https://github.com/vatesfr/xen-orchestra/pull/3565))
|
||||
- [Home] No more false positives when select Tag on Home page [#4087](https://github.com/vatesfr/xen-orchestra/issues/4087) (PR [#4112](https://github.com/vatesfr/xen-orchestra/pull/4112))
|
||||
@@ -415,7 +576,7 @@
|
||||
- Enable compression for HTTP requests (and initial objects fetch)
|
||||
- [VDI migration] Display same-pool SRs first in the selector [#3945](https://github.com/vatesfr/xen-orchestra/issues/3945) (PR [#3996](https://github.com/vatesfr/xen-orchestra/pull/3996))
|
||||
- [Home] Save the current page in url [#3993](https://github.com/vatesfr/xen-orchestra/issues/3993) (PR [#3999](https://github.com/vatesfr/xen-orchestra/pull/3999))
|
||||
- [VDI] Ensure suspend VDI is destroyed when destroying a VM [#4027](https://github.com/vatesfr/xen-orchestra/issues/4027) (PR [#4038](https://github.com/vatesfr/xen-orchestra/pull/4038))
|
||||
- [VDI] Ensure suspend VDI is destroyed when destroying a VM [#4027](https://github.com/vatesfr/xen-orchestra/issues/4027) (PR [#4038](https://github.com/vatesfr/xen-orchestra/pull/4038))
|
||||
- [VM/disk]: Warning when 2 VDIs are on 2 different hosts' local SRs [#3911](https://github.com/vatesfr/xen-orchestra/issues/3911) (PR [#3969](https://github.com/vatesfr/xen-orchestra/pull/3969))
|
||||
- [Remotes] Benchmarks (read and write rate speed) added when remote is tested [#3991](https://github.com/vatesfr/xen-orchestra/issues/3991) (PR [#4015](https://github.com/vatesfr/xen-orchestra/pull/4015))
|
||||
- [Cloud Config] Support both NoCloud and Config Drive 2 datasources for maximum compatibility (PR [#4053](https://github.com/vatesfr/xen-orchestra/pull/4053))
|
||||
@@ -490,7 +651,7 @@
|
||||
- [Home/VM] Show creation date of the VM on if it available [#3953](https://github.com/vatesfr/xen-orchestra/issues/3953) (PR [#3959](https://github.com/vatesfr/xen-orchestra/pull/3959))
|
||||
- [Notifications] Fix invalid notifications when not registered (PR [#3966](https://github.com/vatesfr/xen-orchestra/pull/3966))
|
||||
- [Import] Fix import of some OVA files [#3962](https://github.com/vatesfr/xen-orchestra/issues/3962) (PR [#3974](https://github.com/vatesfr/xen-orchestra/pull/3974))
|
||||
- [Servers] Fix *already connected error* after a server has been removed during connection [#3976](https://github.com/vatesfr/xen-orchestra/issues/3976) (PR [#3977](https://github.com/vatesfr/xen-orchestra/pull/3977))
|
||||
- [Servers] Fix _already connected error_ after a server has been removed during connection [#3976](https://github.com/vatesfr/xen-orchestra/issues/3976) (PR [#3977](https://github.com/vatesfr/xen-orchestra/pull/3977))
|
||||
- [Backup] Fix random _mount_ issues with NFS/SMB remotes [#3973](https://github.com/vatesfr/xen-orchestra/issues/3973) (PR [#4003](https://github.com/vatesfr/xen-orchestra/pull/4003))
|
||||
|
||||
### Released packages
|
||||
@@ -567,7 +728,7 @@
|
||||
- [Self] Display sorted Resource Sets [#3818](https://github.com/vatesfr/xen-orchestra/issues/3818) (PR [#3823](https://github.com/vatesfr/xen-orchestra/pull/3823))
|
||||
- [Servers] Correctly report connecting status (PR [#3838](https://github.com/vatesfr/xen-orchestra/pull/3838))
|
||||
- [Servers] Fix cannot reconnect to a server after connection has been lost [#3839](https://github.com/vatesfr/xen-orchestra/issues/3839) (PR [#3841](https://github.com/vatesfr/xen-orchestra/pull/3841))
|
||||
- [New VM] Fix `NO_HOSTS_AVAILABLE()` error when creating a VM on a local SR from template on another local SR [#3084](https://github.com/vatesfr/xen-orchestra/issues/3084) (PR [#3827](https://github.com/vatesfr/xen-orchestra/pull/3827))
|
||||
- [New VM] Fix `NO_HOSTS_AVAILABLE()` error when creating a VM on a local SR from template on another local SR [#3084](https://github.com/vatesfr/xen-orchestra/issues/3084) (PR [#3827](https://github.com/vatesfr/xen-orchestra/pull/3827))
|
||||
- [Backup NG] Fix typo in the form [#3854](https://github.com/vatesfr/xen-orchestra/issues/3854) (PR [#3855](https://github.com/vatesfr/xen-orchestra/pull/3855))
|
||||
- [New SR] No warning when creating a NFS SR on a path that is already used as NFS SR [#3844](https://github.com/vatesfr/xen-orchestra/issues/3844) (PR [#3851](https://github.com/vatesfr/xen-orchestra/pull/3851))
|
||||
- [New SR] No redirection if the SR creation failed or canceled [#3843](https://github.com/vatesfr/xen-orchestra/issues/3843) (PR [#3853](https://github.com/vatesfr/xen-orchestra/pull/3853))
|
||||
@@ -579,7 +740,7 @@
|
||||
- [VM creation] Broken CloudInit config drive when VM created on local SR
|
||||
- [Legacy Backup] Fix error when restoring a backup
|
||||
- [Home] Fix `user.getAll` error when user is not admin [#3573](https://github.com/vatesfr/xen-orchestra/issues/3573) (PR [#3918](https://github.com/vatesfr/xen-orchestra/pull/3918))
|
||||
- [Backup NG] Fix restore issue when a disk has grown [#3910](https://github.com/vatesfr/xen-orchestra/issues/3910) (PR [#3920](https://github.com/vatesfr/xen-orchestra/pull/3920))
|
||||
- [Backup NG] Fix restore issue when a disk has grown [#3910](https://github.com/vatesfr/xen-orchestra/issues/3910) (PR [#3920](https://github.com/vatesfr/xen-orchestra/pull/3920))
|
||||
- [Backup NG] Delete _importing_ VMs due to interrupted CR/DR (PR [#3923](https://github.com/vatesfr/xen-orchestra/pull/3923))
|
||||
|
||||
### Released packages
|
||||
@@ -807,7 +968,7 @@
|
||||
### 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))
|
||||
- [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))
|
||||
@@ -824,7 +985,7 @@
|
||||
- [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))
|
||||
- [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))
|
||||
@@ -1004,7 +1165,7 @@
|
||||
- Make cloud config templates available for all users [3147](https://github.com/vatesfr/xen-orchestra/issues/3147) (PR [3148](https://github.com/vatesfr/xen-orchestra/pull/3148))
|
||||
- [New VM] Only create the cloud config drive when its option is enabled [3161](https://github.com/vatesfr/xen-orchestra/issues/3161) (PR [3162](https://github.com/vatesfr/xen-orchestra/pull/3162))
|
||||
- Fix error when installing patches from the host or without a default SR (PR [3166](https://github.com/vatesfr/xen-orchestra/pull/3166))
|
||||
- [Backup NG] Fix SMB *Not implemented* issue [#3149](](https://github.com/vatesfr/xen-orchestra/issues/3149) (PR [3175](https://github.com/vatesfr/xen-orchestra/pull/3175))
|
||||
- [Backup NG] Fix SMB _Not implemented_ issue [#3149](<](https://github.com/vatesfr/xen-orchestra/issues/3149)> 'PR [3175](https://github.com/vatesfr/xen-orchestra/pull/3175')
|
||||
|
||||
### Released packages
|
||||
|
||||
@@ -1118,10 +1279,9 @@
|
||||
- Hook/action if an export stream is cut [#1929](https://github.com/vatesfr/xen-orchestra/issues/1929)
|
||||
- Backup paths should not contain tags but job ids [#1854](https://github.com/vatesfr/xen-orchestra/issues/1854)
|
||||
- Add a button to delete a backup [#1751](https://github.com/vatesfr/xen-orchestra/issues/1751)
|
||||
- Dashboard available for Pool and Host level [#1631](https://github.com/vatesfr/xen-orchestra/issues/1631)
|
||||
- UI Enhancement - VM list - Allways show the Toolbar [#1581](https://github.com/vatesfr/xen-orchestra/issues/1581)
|
||||
- xoa-updater --register: unable to define proxy using the CLI [#873](https://github.com/vatesfr/xen-orchestra/issues/873)
|
||||
|
||||
- Dashboard available for Pool and Host level [#1631](https://github.com/vatesfr/xen-orchestra/issues/1631)
|
||||
- UI Enhancement - VM list - Allways show the Toolbar [#1581](https://github.com/vatesfr/xen-orchestra/issues/1581)
|
||||
- xoa-updater --register: unable to define proxy using the CLI [#873](https://github.com/vatesfr/xen-orchestra/issues/873)
|
||||
|
||||
### Bugs
|
||||
|
||||
@@ -1162,7 +1322,7 @@
|
||||
- Set a self-service VM at "share" after creation [#2589](https://github.com/vatesfr/xen-orchestra/issues/2589)
|
||||
- [Backup logs] Improve Unhealthy VDI Chain message [#2586](https://github.com/vatesfr/xen-orchestra/issues/2586)
|
||||
- [SortedTable] Put sort criteria in URL like the filter [#2584](https://github.com/vatesfr/xen-orchestra/issues/2584)
|
||||
- Cant attach XenTools on User side. [#2503](https://github.com/vatesfr/xen-orchestra/issues/2503)
|
||||
- Cant attach XenTools on User side. [#2503](https://github.com/vatesfr/xen-orchestra/issues/2503)
|
||||
- Pool filter for health view [#2302](https://github.com/vatesfr/xen-orchestra/issues/2302)
|
||||
- [Smart Backup] Improve feedback [#2253](https://github.com/vatesfr/xen-orchestra/issues/2253)
|
||||
- Backup jobs stuck if no space left on NFS remote [#2116](https://github.com/vatesfr/xen-orchestra/issues/2116)
|
||||
@@ -1193,7 +1353,7 @@
|
||||
- Move VM In to/Out of Self Service Group [#1913](https://github.com/vatesfr/xen-orchestra/issues/1913)
|
||||
- Two factor auth [#1897](https://github.com/vatesfr/xen-orchestra/issues/1897)
|
||||
- token.create should accept an expiration [#1769](https://github.com/vatesfr/xen-orchestra/issues/1769)
|
||||
- Self Service User - User don't have quota in his dashboard [#1538](https://github.com/vatesfr/xen-orchestra/issues/1538)
|
||||
- Self Service User - User don't have quota in his dashboard [#1538](https://github.com/vatesfr/xen-orchestra/issues/1538)
|
||||
- Remove CoffeeScript in xo-server [#189](https://github.com/vatesfr/xen-orchestra/issues/189)
|
||||
- Better Handling of suspending VMs from the Home screen [#2547](https://github.com/vatesfr/xen-orchestra/issues/2547)
|
||||
- [xen-api] Stronger reconnection policy [#2410](https://github.com/vatesfr/xen-orchestra/issues/2410)
|
||||
@@ -1212,7 +1372,6 @@
|
||||
- TZ selector is not used for backup schedule preview [#2464](https://github.com/vatesfr/xen-orchestra/issues/2464)
|
||||
- Remove filter in VM/network view [#2548](https://github.com/vatesfr/xen-orchestra/issues/2548)
|
||||
|
||||
|
||||
## **5.15.0** (2017-12-29)
|
||||
|
||||
### Enhancements
|
||||
@@ -1221,7 +1380,7 @@
|
||||
- Smart replace VDI.pool_migrate removed from XenServer 7.3 Free [#2541](https://github.com/vatesfr/xen-orchestra/issues/2541)
|
||||
- New memory constraints in XenServer 7.3 [#2540](https://github.com/vatesfr/xen-orchestra/issues/2540)
|
||||
- Link to Settings/Logs for admins in error notifications [#2516](https://github.com/vatesfr/xen-orchestra/issues/2516)
|
||||
- [Self Service] Do not use placehodlers to describe inputs [#2509](https://github.com/vatesfr/xen-orchestra/issues/2509)
|
||||
- [Self Service] Do not use placehodlers to describe inputs [#2509](https://github.com/vatesfr/xen-orchestra/issues/2509)
|
||||
- Obfuscate password in log in LDAP plugin test [#2506](https://github.com/vatesfr/xen-orchestra/issues/2506)
|
||||
- Log rotation [#2492](https://github.com/vatesfr/xen-orchestra/issues/2492)
|
||||
- Continuous Replication TAG [#2473](https://github.com/vatesfr/xen-orchestra/issues/2473)
|
||||
@@ -1238,7 +1397,6 @@
|
||||
- Cloud config drive create fail on XenServer < 7 [#2478](https://github.com/vatesfr/xen-orchestra/issues/2478)
|
||||
- VM create fails due to missing vGPU id [#2466](https://github.com/vatesfr/xen-orchestra/issues/2466)
|
||||
|
||||
|
||||
## **5.14.0** (2017-10-31)
|
||||
|
||||
### Enhancements
|
||||
@@ -1288,9 +1446,9 @@
|
||||
- Warning on SMB remote creation [#2316](https://github.com/vatesfr/xen-orchestra/issues/2316)
|
||||
- [Home | SortedTable] Add link to syntax doc in the filter input [#2305](https://github.com/vatesfr/xen-orchestra/issues/2305)
|
||||
- [SortedTable] Add optional binding of filter to an URL query [#2301](https://github.com/vatesfr/xen-orchestra/issues/2301)
|
||||
- [Home][Keyboard navigation] Allow selecting the objects [#2214](https://github.com/vatesfr/xen-orchestra/issues/2214)
|
||||
- [Home][keyboard navigation] Allow selecting the objects [#2214](https://github.com/vatesfr/xen-orchestra/issues/2214)
|
||||
- SR view / Disks: option to display non managed VDIs [#1724](https://github.com/vatesfr/xen-orchestra/issues/1724)
|
||||
- Continuous Replication Retention [#1692](https://github.com/vatesfr/xen-orchestra/issues/1692)
|
||||
- Continuous Replication Retention [#1692](https://github.com/vatesfr/xen-orchestra/issues/1692)
|
||||
|
||||
### Bugs
|
||||
|
||||
@@ -1298,7 +1456,7 @@
|
||||
- Errors in VM copy are not properly reported [#2347](https://github.com/vatesfr/xen-orchestra/issues/2347)
|
||||
- Removing a PIF IP fails [#2346](https://github.com/vatesfr/xen-orchestra/issues/2346)
|
||||
- Review and fix creating a VM from a snapshot [#2343](https://github.com/vatesfr/xen-orchestra/issues/2343)
|
||||
- iSCSI LUN Detection fails with authentification [#2339](https://github.com/vatesfr/xen-orchestra/issues/2339)
|
||||
- iSCSI LUN Detection fails with authentification [#2339](https://github.com/vatesfr/xen-orchestra/issues/2339)
|
||||
- Fix PoolActionBar to add a new SR [#2307](https://github.com/vatesfr/xen-orchestra/issues/2307)
|
||||
- [VM migration] Error if default SR not accessible to target host [#2180](https://github.com/vatesfr/xen-orchestra/issues/2180)
|
||||
- A job shouldn't executable more than once at the same time [#2053](https://github.com/vatesfr/xen-orchestra/issues/2053)
|
||||
@@ -1316,9 +1474,9 @@
|
||||
- [SortedTable] Add grouped actions feature [#2276](https://github.com/vatesfr/xen-orchestra/issues/2276)
|
||||
- Add a filter to the backups' log [#2246](https://github.com/vatesfr/xen-orchestra/issues/2246)
|
||||
- It should not be possible to migrate a halted VM. [#2233](https://github.com/vatesfr/xen-orchestra/issues/2233)
|
||||
- [Home][Keyboard navigation] Allow selecting the objects [#2214](https://github.com/vatesfr/xen-orchestra/issues/2214)
|
||||
- [Home][keyboard navigation] Allow selecting the objects [#2214](https://github.com/vatesfr/xen-orchestra/issues/2214)
|
||||
- Allow to set pool master [#2213](https://github.com/vatesfr/xen-orchestra/issues/2213)
|
||||
- Continuous Replication Retention [#1692](https://github.com/vatesfr/xen-orchestra/issues/1692)
|
||||
- Continuous Replication Retention [#1692](https://github.com/vatesfr/xen-orchestra/issues/1692)
|
||||
|
||||
### Bugs
|
||||
|
||||
@@ -1399,8 +1557,7 @@
|
||||
- Select is "moving" [\#2142](https://github.com/vatesfr/xen-orchestra/issues/2142)
|
||||
- Select issue for affinity host [\#2141](https://github.com/vatesfr/xen-orchestra/issues/2141)
|
||||
- Dashboard Storage Usage incorrect [\#2123](https://github.com/vatesfr/xen-orchestra/issues/2123)
|
||||
- Detect unmerged *base copy* and prevent too long chains [\#2047](https://github.com/vatesfr/xen-orchestra/issues/2047)
|
||||
|
||||
- Detect unmerged _base copy_ and prevent too long chains [\#2047](https://github.com/vatesfr/xen-orchestra/issues/2047)
|
||||
|
||||
## **5.8.0** (2017-04-28)
|
||||
|
||||
@@ -1462,7 +1619,7 @@
|
||||
|
||||
- Missing objects should be displayed in backup edition [\#2052](https://github.com/vatesfr/xen-orchestra/issues/2052)
|
||||
- Search bar content changes while typing [\#2035](https://github.com/vatesfr/xen-orchestra/issues/2035)
|
||||
- VM.$guest_metrics.PV_drivers_up_to_date is deprecated in XS 7.1 [\#2024](https://github.com/vatesfr/xen-orchestra/issues/2024)
|
||||
- VM.\$guest_metrics.PV_drivers_up_to_date is deprecated in XS 7.1 [\#2024](https://github.com/vatesfr/xen-orchestra/issues/2024)
|
||||
- Bootable flag selection checkbox for extra disk not fetched [\#1994](https://github.com/vatesfr/xen-orchestra/issues/1994)
|
||||
- Home view − Changing type must reset paging [\#1993](https://github.com/vatesfr/xen-orchestra/issues/1993)
|
||||
- XOSAN menu item should only be displayed to admins [\#1968](https://github.com/vatesfr/xen-orchestra/issues/1968)
|
||||
@@ -1548,7 +1705,7 @@ File level restore.
|
||||
- Use paginated table for backup jobs [\#1726](https://github.com/vatesfr/xen-orchestra/issues/1726)
|
||||
- SR view / Disks: should display snapshot VDIs [\#1723](https://github.com/vatesfr/xen-orchestra/issues/1723)
|
||||
- Restored VM should have an identifiable name [\#1719](https://github.com/vatesfr/xen-orchestra/issues/1719)
|
||||
- If host reboot action returns NO\_HOSTS\_AVAILABLE, ask to force [\#1717](https://github.com/vatesfr/xen-orchestra/issues/1717)
|
||||
- If host reboot action returns NO_HOSTS_AVAILABLE, ask to force [\#1717](https://github.com/vatesfr/xen-orchestra/issues/1717)
|
||||
- Hide xo-server timezone in backups [\#1706](https://github.com/vatesfr/xen-orchestra/issues/1706)
|
||||
- Enable hyperlink for Hostname for Issues [\#1700](https://github.com/vatesfr/xen-orchestra/issues/1700)
|
||||
- Pool/network - Modify column [\#1696](https://github.com/vatesfr/xen-orchestra/issues/1696)
|
||||
@@ -1572,15 +1729,15 @@ File level restore.
|
||||
|
||||
- Should jobs be accessible to non admins? [\#1759](https://github.com/vatesfr/xen-orchestra/issues/1759)
|
||||
- Schedules deletion is not working [\#1737](https://github.com/vatesfr/xen-orchestra/issues/1737)
|
||||
- Editing a job from the jobs overview page does not work [\#1736](https://github.com/vatesfr/xen-orchestra/issues/1736)
|
||||
- Editing a schedule from jobs overview does not work [\#1728](https://github.com/vatesfr/xen-orchestra/issues/1728)
|
||||
- Editing a job from the jobs overview page does not work [\#1736](https://github.com/vatesfr/xen-orchestra/issues/1736)
|
||||
- Editing a schedule from jobs overview does not work [\#1728](https://github.com/vatesfr/xen-orchestra/issues/1728)
|
||||
- ACLs not correctly imported [\#1722](https://github.com/vatesfr/xen-orchestra/issues/1722)
|
||||
- Some Bootstrap style broken [\#1721](https://github.com/vatesfr/xen-orchestra/issues/1721)
|
||||
- Not properly sign out on auth token expiration [\#1711](https://github.com/vatesfr/xen-orchestra/issues/1711)
|
||||
- Hosts/<UUID>/network status is incorrect [\#1702](https://github.com/vatesfr/xen-orchestra/issues/1702)
|
||||
- Patches application fails "Found : Moved Temporarily" [\#1701](https://github.com/vatesfr/xen-orchestra/issues/1701)
|
||||
- Password generation for user creation is not working [\#1678](https://github.com/vatesfr/xen-orchestra/issues/1678)
|
||||
- \#/dashboard/health Remove All Orphaned VDIs [\#1622](https://github.com/vatesfr/xen-orchestra/issues/1622)
|
||||
- \#/dashboard/health Remove All Orphaned VDIs [\#1622](https://github.com/vatesfr/xen-orchestra/issues/1622)
|
||||
- Create a new SR - CIFS/SAMBA Broken [\#1615](https://github.com/vatesfr/xen-orchestra/issues/1615)
|
||||
- xo-cli --list-objects: truncated output ? 64k buffer limitation ? [\#1356](https://github.com/vatesfr/xen-orchestra/issues/1356)
|
||||
|
||||
@@ -1759,7 +1916,7 @@ File level restore.
|
||||
- Button to recompute resource sets limits [\#1287](https://github.com/vatesfr/xen-orchestra/issues/1287)
|
||||
- Credit scheduler CAP and weight configuration [\#1283](https://github.com/vatesfr/xen-orchestra/issues/1283)
|
||||
- Migration form problem on the /v5/vms/\_\_UUID\_\_ page when doing xenmotion inside a pool [\#1254](https://github.com/vatesfr/xen-orchestra/issues/1254)
|
||||
- /v5/\#/pools/\_\_UUID\_\_: patch table improvement [\#1246](https://github.com/vatesfr/xen-orchestra/issues/1246)
|
||||
- /v5/\#/pools/\_\_UUID\_\_: patch table improvement [\#1246](https://github.com/vatesfr/xen-orchestra/issues/1246)
|
||||
- /v5/\#/hosts/\_\_UUID\_\_: patch list improvements ? [\#1245](https://github.com/vatesfr/xen-orchestra/issues/1245)
|
||||
- F\*cking patches, how do they work? [\#1236](https://github.com/vatesfr/xen-orchestra/issues/1236)
|
||||
- Change Default Filter [\#1235](https://github.com/vatesfr/xen-orchestra/issues/1235)
|
||||
@@ -1814,7 +1971,7 @@ File level restore.
|
||||
- Scheduled jobs seems use GMT since 5.0 [\#1258](https://github.com/vatesfr/xen-orchestra/issues/1258)
|
||||
- Can't create a VM with disks on 2 different SRs [\#1257](https://github.com/vatesfr/xen-orchestra/issues/1257)
|
||||
- Graph display bug [\#1247](https://github.com/vatesfr/xen-orchestra/issues/1247)
|
||||
- /v5/#/hosts/__UUID__: Patch list not limited to the current pool [\#1244](https://github.com/vatesfr/xen-orchestra/issues/1244)
|
||||
- /v5/#/hosts/**UUID**: Patch list not limited to the current pool [\#1244](https://github.com/vatesfr/xen-orchestra/issues/1244)
|
||||
- Replication issues [\#1233](https://github.com/vatesfr/xen-orchestra/issues/1233)
|
||||
- VM creation install method disabled fields [\#1198](https://github.com/vatesfr/xen-orchestra/issues/1198)
|
||||
- Update icon shouldn't be displayed when menu is collapsed [\#1188](https://github.com/vatesfr/xen-orchestra/issues/1188)
|
||||
@@ -1852,10 +2009,10 @@ File level restore.
|
||||
- Show HVM, PVM, PVHVM modes in guest details [\#806](https://github.com/vatesfr/xen-orchestra/issues/806)
|
||||
- Tree view: display cpu available/total for each host [\#696](https://github.com/vatesfr/xen-orchestra/issues/696)
|
||||
- Greenkeeper integration [\#667](https://github.com/vatesfr/xen-orchestra/issues/667)
|
||||
- Clarify vCPUs and RAM editor [\#658](https://github.com/vatesfr/xen-orchestra/issues/658)
|
||||
- Clarify vCPUs and RAM editor [\#658](https://github.com/vatesfr/xen-orchestra/issues/658)
|
||||
- Backup LZ4 compression [\#647](https://github.com/vatesfr/xen-orchestra/issues/647)
|
||||
- Support enum in plugins configuration [\#638](https://github.com/vatesfr/xen-orchestra/issues/638)
|
||||
- Add configuration option to disable xoa-updater [\#535](https://github.com/vatesfr/xen-orchestra/issues/535)
|
||||
- Add configuration option to disable xoa-updater [\#535](https://github.com/vatesfr/xen-orchestra/issues/535)
|
||||
- Use cursors to add more context to actions [\#523](https://github.com/vatesfr/xen-orchestra/issues/523)
|
||||
- Review UI for flat view [\#354](https://github.com/vatesfr/xen-orchestra/issues/354)
|
||||
- Review UI for the tree view [\#353](https://github.com/vatesfr/xen-orchestra/issues/353)
|
||||
@@ -1865,7 +2022,7 @@ File level restore.
|
||||
- Ability to collapse pools/hosts in main view [\#173](https://github.com/vatesfr/xen-orchestra/issues/173)
|
||||
- Issue importing .xva VM via xo-web [\#1022](https://github.com/vatesfr/xen-orchestra/issues/1022)
|
||||
- Enhancement Proposal - Cancel In Progress Backups [\#1003](https://github.com/vatesfr/xen-orchestra/issues/1003)
|
||||
- Can't create VM with CloudConfigDrive [\#917](https://github.com/vatesfr/xen-orchestra/issues/917)
|
||||
- Can't create VM with CloudConfigDrive [\#917](https://github.com/vatesfr/xen-orchestra/issues/917)
|
||||
- Auth: LDAP User causes problems [\#893](https://github.com/vatesfr/xen-orchestra/issues/893)
|
||||
- No tags in Continuous Replication [\#838](https://github.com/vatesfr/xen-orchestra/issues/838)
|
||||
- Delta backup Depth not working [\#802](https://github.com/vatesfr/xen-orchestra/issues/802)
|
||||
@@ -1881,20 +2038,20 @@ File level restore.
|
||||
|
||||
- vCPUs number when no tools installed [\#1089](https://github.com/vatesfr/xen-orchestra/issues/1089)
|
||||
- Config Drive textbox disappears when content is deleted [\#1012](https://github.com/vatesfr/xen-orchestra/issues/1012)
|
||||
- storage status not changed in host view page after disconnect/connect [\#1009](https://github.com/vatesfr/xen-orchestra/issues/1009)
|
||||
- storage status not changed in host view page after disconnect/connect [\#1009](https://github.com/vatesfr/xen-orchestra/issues/1009)
|
||||
- Cannot Delete Logs From Backup Overview [\#1004](https://github.com/vatesfr/xen-orchestra/issues/1004)
|
||||
- \[v5.x\] Plugins configuration: optional non-used objects are sent [\#1000](https://github.com/vatesfr/xen-orchestra/issues/1000)
|
||||
- "@" char in remote password break the remote view [\#997](https://github.com/vatesfr/xen-orchestra/issues/997)
|
||||
- Handle MEMORY\_CONSTRAINT\_VIOLATION correctly [\#970](https://github.com/vatesfr/xen-orchestra/issues/970)
|
||||
- Handle MEMORY_CONSTRAINT_VIOLATION correctly [\#970](https://github.com/vatesfr/xen-orchestra/issues/970)
|
||||
- VM creation error on XenServer Dundee [\#964](https://github.com/vatesfr/xen-orchestra/issues/964)
|
||||
- Copy VMs doesn't display all SRs [\#945](https://github.com/vatesfr/xen-orchestra/issues/945)
|
||||
- Autopower\_on wrong value [\#937](https://github.com/vatesfr/xen-orchestra/issues/937)
|
||||
- Autopower_on wrong value [\#937](https://github.com/vatesfr/xen-orchestra/issues/937)
|
||||
- Correctly handle unknown users in group view [\#900](https://github.com/vatesfr/xen-orchestra/issues/900)
|
||||
- Importing into Dundee [\#887](https://github.com/vatesfr/xen-orchestra/issues/887)
|
||||
- update status - gui resize issue [\#803](https://github.com/vatesfr/xen-orchestra/issues/803)
|
||||
- Backup Remote Stores Problem [\#751](https://github.com/vatesfr/xen-orchestra/issues/751)
|
||||
- VM view is broken when changing a disk SR twice [\#670](https://github.com/vatesfr/xen-orchestra/issues/670)
|
||||
- console mouse sync [\#280](https://github.com/vatesfr/xen-orchestra/issues/280)
|
||||
- console mouse sync [\#280](https://github.com/vatesfr/xen-orchestra/issues/280)
|
||||
|
||||
## **4.16.0** (2016-04-29)
|
||||
|
||||
@@ -1902,7 +2059,7 @@ Maintenance release
|
||||
|
||||
### Enhancements
|
||||
|
||||
- TOO\_MANY\_PENDING\_TASKS [\#861](https://github.com/vatesfr/xen-orchestra/issues/861)
|
||||
- TOO_MANY_PENDING_TASKS [\#861](https://github.com/vatesfr/xen-orchestra/issues/861)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -1943,7 +2100,7 @@ Load balancing, SMB delta support, advanced network operations...
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Broken link to backup remote [\#821](https://github.com/vatesfr/xen-orchestra/issues/821)
|
||||
- Broken link to backup remote [\#821](https://github.com/vatesfr/xen-orchestra/issues/821)
|
||||
- Issue with self-signed cert for email plugin [\#817](https://github.com/vatesfr/xen-orchestra/issues/817)
|
||||
- Plugins view, reset form and errors [\#815](https://github.com/vatesfr/xen-orchestra/issues/815)
|
||||
- HVM recovery mode is broken [\#794](https://github.com/vatesfr/xen-orchestra/issues/794)
|
||||
@@ -2097,7 +2254,7 @@ Delta backup, CloudInit...
|
||||
- Allow editing PV args even when empty \(but only for PV VMs\) [\#557](https://github.com/vatesfr/xen-orchestra/issues/557)
|
||||
- Crashes when using legacy event system [\#556](https://github.com/vatesfr/xen-orchestra/issues/556)
|
||||
- XenServer patches check error for 6.1 [\#555](https://github.com/vatesfr/xen-orchestra/issues/555)
|
||||
- activation plugin xo-server-transport-email [\#553](https://github.com/vatesfr/xen-orchestra/issues/553)
|
||||
- activation plugin xo-server-transport-email [\#553](https://github.com/vatesfr/xen-orchestra/issues/553)
|
||||
- Server error with JSON on 32 bits Dom0 [\#552](https://github.com/vatesfr/xen-orchestra/issues/552)
|
||||
- Cloud Config drive shouldn't be created on default SR [\#548](https://github.com/vatesfr/xen-orchestra/issues/548)
|
||||
- Deep properties cannot be edited in plugins configuration form [\#521](https://github.com/vatesfr/xen-orchestra/issues/521)
|
||||
@@ -2365,7 +2522,7 @@ An issue in `xo-server` with the password of default admin account and also a UI
|
||||
|
||||
## **4.0.1** (2015-05-30)
|
||||
|
||||
An issue with the updater in HTTPS was left in the *4.0.0*. This patch release fixed
|
||||
An issue with the updater in HTTPS was left in the _4.0.0_. This patch release fixed
|
||||
it.
|
||||
|
||||
### Bug fixes
|
||||
@@ -2397,10 +2554,9 @@ it.
|
||||
- VM stats behavior more robust ([xo-web#250](https://github.com/vatesfr/xen-orchestra/issues/250))
|
||||
- XO not on the root of domain ([xo-web#254](https://github.com/vatesfr/xen-orchestra/issues/254))
|
||||
|
||||
|
||||
## **3.9.1** (2015-04-21)
|
||||
|
||||
A few bugs hve made their way into *3.9.0*, this minor release fixes
|
||||
A few bugs hve made their way into _3.9.0_, this minor release fixes
|
||||
them.
|
||||
|
||||
### Bug fixes
|
||||
@@ -2451,13 +2607,13 @@ them.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- fix *Invalid parameter(s)* message on the settings page ([xo-server#49](https://github.com/vatesfr/xo-server/issues/49))
|
||||
- fix _Invalid parameter(s)_ message on the settings page ([xo-server#49](https://github.com/vatesfr/xo-server/issues/49))
|
||||
- fix mouse clicks in console ([xo-web#205](https://github.com/vatesfr/xen-orchestra/issues/205))
|
||||
- fix user editing on the settings page ([xo-web#206](https://github.com/vatesfr/xen-orchestra/issues/206))
|
||||
|
||||
## **3.7.0** (2015-03-06)
|
||||
|
||||
*Highlights in this release are the [initial ACLs implementation](https://xen-orchestra.com/blog/xen-orchestra-3-7-is-out-acls-in-early-access), [live migration for VDIs](https://xen-orchestra.com/blog/moving-vdi-in-live) and the ability to [create a new storage repository](https://xen-orchestra.com/blog/create-a-storage-repository-with-xen-orchestra/).*
|
||||
_Highlights in this release are the [initial ACLs implementation](https://xen-orchestra.com/blog/xen-orchestra-3-7-is-out-acls-in-early-access), [live migration for VDIs](https://xen-orchestra.com/blog/moving-vdi-in-live) and the ability to [create a new storage repository](https://xen-orchestra.com/blog/create-a-storage-repository-with-xen-orchestra/)._
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -2484,7 +2640,7 @@ them.
|
||||
- fix console view on IE ([xo-web#184](https://github.com/vatesfr/xen-orchestra/issues/184))
|
||||
- fix out of sync objects in XO-Web ([xo-web#142](https://github.com/vatesfr/xen-orchestra/issues/142))
|
||||
- fix incorrect connection status displayed in login view ([xo-web#193](https://github.com/vatesfr/xen-orchestra/issues/193))
|
||||
- fix *flickering* tree view ([xo-web#194](https://github.com/vatesfr/xen-orchestra/issues/194))
|
||||
- fix _flickering_ tree view ([xo-web#194](https://github.com/vatesfr/xen-orchestra/issues/194))
|
||||
- single host pools should not have a dropdown menu in tree view ([xo-web#198](https://github.com/vatesfr/xen-orchestra/issues/198))
|
||||
|
||||
## **3.6.0** (2014-11-28)
|
||||
@@ -2528,7 +2684,7 @@ them.
|
||||
|
||||
## **3.5.0** (2014-08-14)
|
||||
|
||||
*[XO-Web](https://www.npmjs.org/package/xo-web) and [XO-Server](https://www.npmjs.org/package/xo-server) are now available as npm packages!*
|
||||
_[XO-Web](https://www.npmjs.org/package/xo-web) and [XO-Server](https://www.npmjs.org/package/xo-server) are now available as npm packages!_
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -2554,9 +2710,9 @@ them.
|
||||
|
||||
## **3.4.0** (2014-05-22)
|
||||
|
||||
*Highlight in this release is the new events system between XO-Web
|
||||
_Highlight in this release is the new events system between XO-Web
|
||||
and XO-Server which results in less bandwidth consumption as well as
|
||||
better performance and reactivity.*
|
||||
better performance and reactivity._
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -2590,8 +2746,8 @@ better performance and reactivity.*
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- *Snapshot* not working in VM view ([#95](https://github.com/vatesfr/xen-orchestra/issues/95))
|
||||
- Host *Reboot*/*Restart toolstack*/*Shutdown* not working in main view ([#97](https://github.com/vatesfr/xen-orchestra/issues/97))
|
||||
- _Snapshot_ not working in VM view ([#95](https://github.com/vatesfr/xen-orchestra/issues/95))
|
||||
- Host _Reboot_/_Restart toolstack_/_Shutdown_ not working in main view ([#97](https://github.com/vatesfr/xen-orchestra/issues/97))
|
||||
- Bower cannot install `angular` automatically due to a version conflict ([#101](https://github.com/vatesfr/xen-orchestra/issues/101))
|
||||
- Bower installs an incorrect version of `angular-animate` ([#102](https://github.com/vatesfr/xen-orchestra/issues/102))
|
||||
|
||||
@@ -2619,7 +2775,7 @@ better performance and reactivity.*
|
||||
|
||||
- in VM view, interfaces' network should be displayed ([#64](https://github.com/vatesfr/xen-orchestra/issues/64))
|
||||
- middle-click or `Ctrl`+click should open new windows (even on pseudo-links) ([#66](https://github.com/vatesfr/xen-orchestra/issues/66))
|
||||
- lists should use natural sorting (e.g. *VM 2* before *VM 10*) ([#69](https://github.com/vatesfr/xen-orchestra/issues/69))
|
||||
- lists should use natural sorting (e.g. _VM 2_ before _VM 10_) ([#69](https://github.com/vatesfr/xen-orchestra/issues/69))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -2627,7 +2783,7 @@ better performance and reactivity.*
|
||||
- it makes no sense to remove a stand-alone host from a pool (58)
|
||||
- in VM view, the migrate button is not working ([#59](https://github.com/vatesfr/xen-orchestra/issues/59))
|
||||
- pool and host names overflow their box in the main view ([#63](https://github.com/vatesfr/xen-orchestra/issues/63))
|
||||
- in host view, interfaces incorrectly named *networks* and VLAN not shown ([#70](https://github.com/vatesfr/xen-orchestra/issues/70))
|
||||
- in host view, interfaces incorrectly named _networks_ and VLAN not shown ([#70](https://github.com/vatesfr/xen-orchestra/issues/70))
|
||||
- VM suspended state is not properly handled ([#71](https://github.com/vatesfr/xen-orchestra/issues/71))
|
||||
- unauthenticated users should not be able to access to consoles ([#73](https://github.com/vatesfr/xen-orchestra/issues/73))
|
||||
- incorrect scroll (under the navbar) when the view changes ([#74](https://github.com/vatesfr/xen-orchestra/issues/74))
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||
|
||||
- [Backup] **BETA** Ability to backup running VMs with their memory [#645](https://github.com/vatesfr/xen-orchestra/issues/645) (PR [#4252](https://github.com/vatesfr/xen-orchestra/pull/4252))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||
@@ -18,5 +20,5 @@
|
||||
>
|
||||
> Rule of thumb: add packages on top.
|
||||
|
||||
- xo-server v5.55.0
|
||||
- xo-web v5.55.0
|
||||
- xo-server minor
|
||||
- xo-web minor
|
||||
|
||||
@@ -8,7 +8,7 @@ XO is a web interface to visualize and administer your XenServer (or XAPI enable
|
||||
|
||||
It aims to be easy to use on any device supporting modern web technologies (HTML 5, CSS 3, JavaScript), such as your desktop computer or your smartphone.
|
||||
|
||||

|
||||

|
||||
|
||||
## XOA quick deploy
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
- [XOA](xoa.md)
|
||||
- [Updater](updater.md)
|
||||
- [Trial activation](trial_activation.md)
|
||||
- [License activation](license_activation.md)
|
||||
- [Plugins](plugins.md)
|
||||
- [Logs](logs.md)
|
||||
- [Compatibility](supported-version.md)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
BIN
docs/assets/force-start.jpg
Normal file
BIN
docs/assets/force-start.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/assets/logo.png
Normal file
BIN
docs/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
@@ -14,7 +14,7 @@ Xen Orchestra will fetch the content of the snapshot made in step 1. This operat
|
||||
|
||||
### 3. Snapshot removal
|
||||
|
||||
When it's done exporting, we'll remove the snapshot. Note: this operation will trigger a coalesce on your storage in the near future.
|
||||
When it's done exporting, we'll remove the snapshot. Note: this operation will trigger a coalesce on your storage in the near future (a coalesce is required every time a snapshot is removed).
|
||||
|
||||
## Concurrency
|
||||
|
||||
@@ -39,6 +39,7 @@ Each step has its own concurrency to fit its requirements:
|
||||
|
||||
- **snapshot process** needs to be performed with the lowest concurrency possible. 2 is a good compromise: one snapshot is fast, but a stuck snapshot won't block the whole job. That's why a concurrency of 2 is not too bad on your storage. Basically, at 3 AM, we'll do all the VM snapshots needed, 2 at a time.
|
||||
- **disk export process** is bottlenecked by XCP-ng/XenServer - so to get the most of it, you can use up to 12 in parallel. As soon a snapshot is done, the export process will start, until reaching 12 at once. Then as soon as one in those 12 is finished, another one will appear until there is nothing more to export.
|
||||
- **VM export process:** the 12 disk export limit mentioned above applies to VDI exports, which happen during delta exports. For full VM exports (for example, for full backup job types), there is a built in limit of 2. This means if you have a full backup job of 6 VMs, only 2 will be exported at once.
|
||||
- **snapshot deletion** can't happen all at once because the previous step durations are random - no need to implement concurrency on this one.
|
||||
|
||||
This is how it currently works in Xen Orchestra. But sometimes, you also want to have _sequential_ backups combined with the _parallel strategy_. That's why we introduced a sequential option in the advanced section of backup-ng:
|
||||
|
||||
@@ -77,6 +77,8 @@ Don't forget to reload `systemd` conf and restart `xo-server`:
|
||||
# systemctl restart xo-server.service
|
||||
```
|
||||
|
||||
**Note:** The `--use-openssl-ca` option is ignored by Node if Xen-Orchestra is run with Linux capabilities. Capabilities are commonly used to bind applications to privileged ports (<1024) (i.e. `CAP_NET_BIND_SERVICE`). Local NAT rules (`iptables`) or a reverse proxy would be required to use privileged ports and a custom certficate authority.
|
||||
|
||||
### Redis server
|
||||
|
||||
By default, XO-server will try to contact Redis server on `localhost`, with the port `6379`. But you can define whatever you want:
|
||||
|
||||
@@ -96,3 +96,11 @@ xo-cr-seed https://root:password@xen1.company.tld 4a21c1cd-e8bd-4466-910a-f7524e
|
||||
### Finished
|
||||
|
||||
Your backup job should now be working correctly! Manually run the job the first time to check if everything is OK. Then, enable the job. **Now, only the deltas are sent, your initial seed saved you a LOT of time if you have a slow network.**
|
||||
|
||||
### Failover process
|
||||
|
||||
In the situation where you need to failover to your destination host, you simply need to start all your VMs on the destination host.
|
||||
|
||||
> Note: If you want to start a VM on your destination host without breaking the CR jobs on the other side, you will need to make a copy of the VM and start the copy. Otherwise, you will be asked if you would like to force start the VMs.
|
||||
|
||||

|
||||
|
||||
@@ -14,13 +14,13 @@ As you may have seen in other parts of the documentation, XO is composed of two
|
||||
|
||||
### NodeJS
|
||||
|
||||
XO needs Node.js. **Please use Node 8**.
|
||||
XO needs Node.js. **Please use Node LTS**.
|
||||
|
||||
We'll consider at this point that you've got a working node on your box. E.g:
|
||||
|
||||
```
|
||||
$ node -v
|
||||
v8.16.2
|
||||
v12.16.1
|
||||
```
|
||||
|
||||
If not, see [this page](https://nodejs.org/en/download/package-manager/) for instructions on how to install Node.
|
||||
|
||||
25
docs/license_activation.md
Normal file
25
docs/license_activation.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# License activation
|
||||
|
||||
Once you purchased a license on the [website](https://xen-orchestra.com/#!/xo-home) you will need to activate it for your appliance.
|
||||
|
||||
The procedure is very simple:
|
||||
|
||||
1. Log to your Xen Orchestra appliance and go in the Licenses section
|
||||
|
||||

|
||||
|
||||
2. All the licenses you paid for should be visible on this page. Unbinded licenses should have a green activate license button.
|
||||
|
||||

|
||||
|
||||
3. By clicking on the activate license button, you will start the process of binding the license on the **current Xen Orchestra Appliance you are using.**
|
||||
|
||||

|
||||
|
||||
That's it, you are ready to use your Xen Orchestra Appliance with all the features you unlocked.
|
||||
|
||||
|
||||
## How to unbind and rebind a new Appliance
|
||||
|
||||
Sometimes, you might need to change the XO appliance to which you have bind the license you purchase (eg. If you lose the host on which you had your Xen Orchestra appliance).
|
||||
Do unbind an already registered license, you simply need to open a [support request](https://xen-orchestra.com/#!/member/support) and ask us to release the license.
|
||||
@@ -1,8 +1,8 @@
|
||||
# SDN Controller
|
||||
|
||||
> SDN Controller is available in XOA 5.44 and higher
|
||||
> SDN Controller is available in XOA 5.36.0 and higher
|
||||
|
||||
The SDN Controller enables a user to **create pool-wide and cross-pool** (since XOA 5.48.1) **private networks**.
|
||||
The SDN Controller enables a user to **create pool-wide and cross-pool** (since XOA 5.38.0) **private networks**.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ Then, you can define quotas on this set:
|
||||
- max RAM
|
||||
- max disk usage
|
||||
|
||||
> Note: Snapshotting a VM within a self-service will use the quota from the resource set. The same rule applies for backups and replication.
|
||||
> Note: Snapshotting a VM within a self-service will _not_ use the quota from the resource set. The same rule applies for backups and replication.
|
||||
|
||||
When you click on create, you can see the resource set and remove or edit it:
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ Xen Orchestra is designed to work exclusively on [XCP-ng](https://xcp-ng.org/) a
|
||||
|
||||
Backup restore for large VM disks (>1TiB usage) is [broken on all XenServer versions](https://bugs.xenserver.org/browse/XSO-868) until Citrix release a fix.
|
||||
|
||||
- Citrix Hypervisor 8.1
|
||||
- Citrix Hypervisor 8.0
|
||||
- XenServer 7.6
|
||||
- XenServer 7.5
|
||||
- [VDI I/O error](https://bugs.xenserver.org/browse/XSO-873), waiting for Citrix to release our fix
|
||||
@@ -27,6 +29,7 @@ Backup restore for large VM disks (>1TiB usage) is [broken on all XenServer vers
|
||||
|
||||
All the pending fixes are already integrated in the latest XCP-ng version. We strongly suggest people to keep using the latest XCP-ng version as far as possible.
|
||||
|
||||
- XCP-ng 8.0
|
||||
- XCP-ng 7.6
|
||||
- XCP-ng 7.5
|
||||
- XCP-ng 7.4.1
|
||||
|
||||
@@ -90,14 +90,12 @@ FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memo
|
||||
1: node::Abort() [node]
|
||||
```
|
||||
|
||||
In that case, you need to increase the memory allocated to the
|
||||
XOA VM (from 2GB to 4 or 8 GB), and then update the service file
|
||||
(`/etc/systemd/system/xo-server.service`) to increase the allocated
|
||||
memory to xo-server itself:
|
||||
In that case, you need to increase the memory allocated to the XOA VM (from 2GB to 4GB or 8GB). Note that simply increasing the RAM for the VM is not enough. You must also edit the service file (`/etc/systemd/system/xo-server.service`) to increase the memory allocated to the xo-server process itself.
|
||||
|
||||
**Note:** you should leave ~512MB for the debian OS itself. Meaning if your VM has 4096MB total RAM, you should use `3584` for the memory value below.
|
||||
```diff
|
||||
- ExecStart=/usr/local/bin/xo-server
|
||||
+ ExecStart=/usr/local/bin/node --max-old-space-size=8192 /usr/local/bin/xo-server
|
||||
+ ExecStart=/usr/local/bin/node --max-old-space-size=3584 /usr/local/bin/xo-server
|
||||
```
|
||||
|
||||
The last step is to refresh and restart the service:
|
||||
|
||||
@@ -43,13 +43,12 @@ curl -sS https://xoa.io/deploy | bash
|
||||
> ```
|
||||
> curl: (35) error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
|
||||
> ```
|
||||
|
||||
````
|
||||
>
|
||||
> It means that the secure HTTPS protocol is not supported, you can bypass this using the unsecure command instead:
|
||||
>
|
||||
> ```
|
||||
> curl -sS http://xoa.io/deploy | bash
|
||||
````
|
||||
> ```
|
||||
|
||||
Follow the instructions:
|
||||
|
||||
|
||||
@@ -6,29 +6,20 @@ XOSAN is a virtual SAN that allows you to create a shared SR (Storage Repository
|
||||
|
||||
## Introduction
|
||||
|
||||
This documentation will give you some advices and assistance in order to create a XOSAN storage on your XenServer infrastructure.
|
||||
This documentation will give you some advice and assistance in order to create an XOSAN storage on your XenServer or XCP-ng infrastructure.
|
||||
|
||||
## Objectives
|
||||
|
||||
XOSAN will "gather" all your local disks into a shared SR, that XenServer will just see as any other shared SR, without limitations on it (you can live migrate, snapshot, backup, whatever you need). **It's a fully software defined solution** that doesn't require to buy extra-hardware. It could even run on the disk where your XenServer is already installed!
|
||||
XOSAN will "gather" all your local disks (across multiple hosts) into a shared SR, that XenServer/XCP-ng will just see as any other shared SR, without limitations (you can live migrate, snapshot, backup, whatever you need). **It's a fully software defined solution** that doesn't require you to buy extra hardware. It can even run on the disk where your Citrix Hypervisor (Xenserver) or XCP-ng is already installed!
|
||||
|
||||

|
||||
|
||||
The objectives are to:
|
||||
|
||||
- protect your data thanks to replication of data on multiple hosts
|
||||
- provide XenServer high availability without buying a NAS nor a SAN
|
||||
- protect your data thanks to replication of data across multiple hosts
|
||||
- Unlock High Availability without buying a NAS nor a SAN
|
||||
- give you flexibility to grow your storage by adding new nodes
|
||||
- work on all kind of hardware, from HDDs to SSDs, with hardware RAID or not
|
||||
|
||||
## Beta access - Phase II
|
||||
|
||||
XOSAN is currently in beta phase II. Every user can access this beta stage and try the solution in its current version.
|
||||
To activate you XOSAN beta, just go in the **"XOSAN"** menu in your (up-to-date) XOA. Once on the menu, click on **"Register for Beta"**.
|
||||
|
||||

|
||||
|
||||
> Even a free version of XOA can access the XOSAN beta.
|
||||
- work on all kinds of hardware, HDDs or SSDs, with hardware RAID or not
|
||||
|
||||
## Deployment
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "complex-matcher",
|
||||
"version": "0.6.0",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "value-matcher",
|
||||
"version": "0.2.0",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vhd-cli",
|
||||
"version": "0.3.1",
|
||||
"license": "ISC",
|
||||
@@ -27,7 +28,7 @@
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xen-orchestra/fs": "^0.10.2",
|
||||
"@xen-orchestra/fs": "^0.10.3",
|
||||
"cli-progress": "^3.1.0",
|
||||
"exec-promise": "^0.7.0",
|
||||
"getopts": "^2.2.3",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vhd-lib",
|
||||
"version": "0.7.2",
|
||||
"license": "AGPL-3.0",
|
||||
@@ -24,7 +25,6 @@
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"async-iterator-to-stream": "^1.0.2",
|
||||
"core-js": "^3.0.0",
|
||||
"from2": "^2.3.0",
|
||||
"fs-extra": "^8.0.1",
|
||||
"limit-concurrency-decorator": "^0.4.0",
|
||||
"lodash": "^4.17.4",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"@xen-orchestra/fs": "^0.10.2",
|
||||
"@xen-orchestra/fs": "^0.10.3",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^6.0.3",
|
||||
"execa": "^3.2.0",
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import from2 from 'from2'
|
||||
|
||||
const constantStream = (data, n = 1) => {
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
data = Buffer.from(data)
|
||||
}
|
||||
|
||||
const { length } = data
|
||||
|
||||
if (!length) {
|
||||
throw new Error('data should not be empty')
|
||||
}
|
||||
|
||||
n *= length
|
||||
let currentLength = length
|
||||
|
||||
return from2((size, next) => {
|
||||
if (n <= 0) {
|
||||
return next(null, null)
|
||||
}
|
||||
|
||||
if (n < size) {
|
||||
size = n
|
||||
}
|
||||
|
||||
if (size < currentLength) {
|
||||
const m = Math.floor(size / length) * length || length
|
||||
n -= m
|
||||
return next(null, data.slice(0, m))
|
||||
}
|
||||
|
||||
// if more than twice the data length is requested, repeat the data
|
||||
if (size > currentLength * 2) {
|
||||
currentLength = Math.floor(size / length) * length
|
||||
data = Buffer.alloc(currentLength, data)
|
||||
}
|
||||
|
||||
n -= currentLength
|
||||
return next(null, data)
|
||||
})
|
||||
}
|
||||
export { constantStream as default }
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xapi-explore-sr",
|
||||
"version": "0.2.1",
|
||||
"license": "ISC",
|
||||
@@ -41,7 +42,7 @@
|
||||
"human-format": "^0.10.0",
|
||||
"lodash": "^4.17.4",
|
||||
"pw": "^0.0.4",
|
||||
"xen-api": "^0.27.3"
|
||||
"xen-api": "^0.28.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.1.5",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xen-api",
|
||||
"version": "0.27.3",
|
||||
"version": "0.28.3",
|
||||
"license": "ISC",
|
||||
"description": "Connector to the Xen API",
|
||||
"keywords": [
|
||||
@@ -38,7 +39,7 @@
|
||||
"debug": "^4.0.1",
|
||||
"event-to-promise": "^0.8.0",
|
||||
"exec-promise": "^0.7.0",
|
||||
"http-request-plus": "^0.9.1",
|
||||
"http-request-plus": "^0.8.0",
|
||||
"jest-diff": "^24.0.0",
|
||||
"json-rpc-protocol": "^0.13.1",
|
||||
"kindof": "^2.0.0",
|
||||
|
||||
@@ -39,6 +39,8 @@ const { defineProperties, defineProperty, freeze, keys: getKeys } = Object
|
||||
|
||||
export const NULL_REF = 'OpaqueRef:NULL'
|
||||
|
||||
export { isOpaqueRef }
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const RESERVED_FIELDS = {
|
||||
@@ -281,6 +283,17 @@ export class Xapi extends EventEmitter {
|
||||
return this.call(`${type}.set_${field}`, ref, value).then(noop)
|
||||
}
|
||||
|
||||
setFields(type, ref, fields) {
|
||||
return Promise.all(
|
||||
getKeys(fields).map(field => {
|
||||
const value = fields[field]
|
||||
if (value !== undefined) {
|
||||
return this.call(`${type}.set_${field}`, ref, value)
|
||||
}
|
||||
})
|
||||
).then(noop)
|
||||
}
|
||||
|
||||
setFieldEntries(type, ref, field, entries) {
|
||||
return Promise.all(
|
||||
getKeys(entries).map(entry => {
|
||||
@@ -506,7 +519,13 @@ export class Xapi extends EventEmitter {
|
||||
const { promise, resolve } = defer()
|
||||
eventWatchers[key] = resolve
|
||||
|
||||
await this._sessionCall('pool.add_to_other_config', [poolRef, key, ''])
|
||||
await this._sessionCall('pool.add_to_other_config', [
|
||||
poolRef,
|
||||
key,
|
||||
|
||||
// use ms timestamp as values to enable identification of stale entries
|
||||
String(Date.now()),
|
||||
])
|
||||
|
||||
await promise
|
||||
|
||||
@@ -758,7 +777,10 @@ export class Xapi extends EventEmitter {
|
||||
_setUrl(url) {
|
||||
this._humanId = `${this._auth.user}@${url.hostname}`
|
||||
this._transport = autoTransport({
|
||||
allowUnauthorized: this._allowUnauthorized,
|
||||
secureOptions: {
|
||||
minVersion: 'TLSv1',
|
||||
rejectUnauthorized: !this._allowUnauthorized,
|
||||
},
|
||||
url,
|
||||
})
|
||||
this._url = url
|
||||
@@ -981,6 +1003,8 @@ export class Xapi extends EventEmitter {
|
||||
this._processEvents(result.events)
|
||||
|
||||
// detect and fix disappearing tasks (e.g. when toolstack restarts)
|
||||
//
|
||||
// FIXME: only if 'task' in 'types
|
||||
if (result.valid_ref_counts.task !== this._nTasks) {
|
||||
await this._refreshCachedRecords(['task'])
|
||||
}
|
||||
@@ -1063,7 +1087,32 @@ export class Xapi extends EventEmitter {
|
||||
)
|
||||
|
||||
const getters = { $pool: getPool }
|
||||
const props = { $type: type }
|
||||
const props = {
|
||||
$call: function(method, ...args) {
|
||||
return xapi.call(`${type}.${method}`, this.$ref, ...args)
|
||||
},
|
||||
$callAsync: function(method, ...args) {
|
||||
return xapi.callAsync(`${type}.${method}`, this.$ref, ...args)
|
||||
},
|
||||
$type: type,
|
||||
}
|
||||
;(function addMethods(object) {
|
||||
Object.getOwnPropertyNames(object).forEach(name => {
|
||||
// dont trigger getters (eg sessionId)
|
||||
const fn = Object.getOwnPropertyDescriptor(object, name).value
|
||||
if (typeof fn === 'function' && name.startsWith(type + '_')) {
|
||||
const key = '$' + name.slice(type.length + 1)
|
||||
assert.strictEqual(props[key], undefined)
|
||||
props[key] = function(...args) {
|
||||
return xapi[name](this.$ref, ...args)
|
||||
}
|
||||
}
|
||||
})
|
||||
const proto = Object.getPrototypeOf(object)
|
||||
if (proto !== null) {
|
||||
addMethods(proto)
|
||||
}
|
||||
})(xapi)
|
||||
fields.forEach(field => {
|
||||
props[`set_${field}`] = function(value) {
|
||||
return xapi.setField(this.$type, this.$ref, field, value)
|
||||
|
||||
@@ -6,11 +6,11 @@ import XapiError from '../_XapiError'
|
||||
import UnsupportedTransport from './_UnsupportedTransport'
|
||||
|
||||
// https://github.com/xenserver/xenadmin/blob/0df39a9d83cd82713f32d24704852a0fd57b8a64/XenModel/XenAPI/Session.cs#L403-L433
|
||||
export default ({ allowUnauthorized, url }) => {
|
||||
export default ({ secureOptions, url }) => {
|
||||
return (method, args) =>
|
||||
httpRequestPlus
|
||||
.post(url, {
|
||||
rejectUnauthorized: !allowUnauthorized,
|
||||
...secureOptions,
|
||||
body: format.request(0, method, args),
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
|
||||
@@ -74,12 +74,13 @@ const parseResult = result => {
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
export default ({ allowUnauthorized, url: { hostname, port, protocol } }) => {
|
||||
const client = (protocol === 'https:' ? createSecureClient : createClient)({
|
||||
export default ({ secureOptions, url: { hostname, port, protocol } }) => {
|
||||
const secure = protocol === 'https:'
|
||||
const client = (secure ? createSecureClient : createClient)({
|
||||
...(secure ? secureOptions : undefined),
|
||||
host: hostname,
|
||||
path: '/json',
|
||||
port,
|
||||
rejectUnauthorized: !allowUnauthorized,
|
||||
})
|
||||
const call = promisify(client.methodCall, client)
|
||||
|
||||
|
||||
@@ -34,11 +34,12 @@ const parseResult = result => {
|
||||
return result.Value
|
||||
}
|
||||
|
||||
export default ({ allowUnauthorized, url: { hostname, port, protocol } }) => {
|
||||
const client = (protocol === 'https:' ? createSecureClient : createClient)({
|
||||
export default ({ secureOptions, url: { hostname, port, protocol } }) => {
|
||||
const secure = protocol === 'https:'
|
||||
const client = (secure ? createSecureClient : createClient)({
|
||||
...(secure ? secureOptions : undefined),
|
||||
host: hostname,
|
||||
port,
|
||||
rejectUnauthorized: !allowUnauthorized,
|
||||
})
|
||||
const call = promisify(client.methodCall, client)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-acl-resolver",
|
||||
"version": "0.4.1",
|
||||
"license": "ISC",
|
||||
@@ -24,7 +25,7 @@
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"xo-common": "^0.2.0"
|
||||
"xo-common": "^0.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "npm publish"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-cli",
|
||||
"version": "0.10.1",
|
||||
"version": "0.11.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Basic CLI for Xen-Orchestra",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-collection",
|
||||
"version": "0.4.1",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-common",
|
||||
"version": "0.2.0",
|
||||
"version": "0.4.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Code shared between [XO](https://xen-orchestra.com) server and clients",
|
||||
"keywords": [],
|
||||
|
||||
@@ -172,3 +172,28 @@ export const patchPrecheckFailed = create(20, ({ errorType, patch }) => ({
|
||||
},
|
||||
message: `patch precheck failed: ${errorType}`,
|
||||
}))
|
||||
|
||||
export const operationFailed = create(21, ({ objectId, code }) => ({
|
||||
data: {
|
||||
objectId,
|
||||
code,
|
||||
},
|
||||
message: 'operation failed',
|
||||
}))
|
||||
|
||||
export const missingAuditRecord = create(22, ({ id, nValid }) => ({
|
||||
data: {
|
||||
id,
|
||||
nValid,
|
||||
},
|
||||
message: 'missing record',
|
||||
}))
|
||||
|
||||
export const alteredAuditRecord = create(23, ({ id, record, nValid }) => ({
|
||||
data: {
|
||||
id,
|
||||
record,
|
||||
nValid,
|
||||
},
|
||||
message: 'altered record',
|
||||
}))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-import-servers-csv",
|
||||
"version": "1.1.0",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-lib",
|
||||
"version": "0.9.0",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "xo-remote-parser",
|
||||
"version": "0.5.0",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
3
packages/xo-server-audit/.babelrc.js
Normal file
3
packages/xo-server-audit/.babelrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = require('../../@xen-orchestra/babel-config')(
|
||||
require('./package.json')
|
||||
)
|
||||
10
packages/xo-server-audit/.npmignore
Normal file
10
packages/xo-server-audit/.npmignore
Normal file
@@ -0,0 +1,10 @@
|
||||
/examples/
|
||||
example.js
|
||||
example.js.map
|
||||
*.example.js
|
||||
*.example.js.map
|
||||
|
||||
/test/
|
||||
/tests/
|
||||
*.spec.js
|
||||
*.spec.js.map
|
||||
50
packages/xo-server-audit/README.md
Normal file
50
packages/xo-server-audit/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# xo-server-audit [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||
|
||||
> **TODO**
|
||||
|
||||
## Install
|
||||
|
||||
Installation of the [npm package](https://npmjs.org/package/xo-server-audit):
|
||||
|
||||
```
|
||||
> npm install --global xo-server-audit
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Like all other xo-server plugins, it can be configured directly via
|
||||
the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
> yarn
|
||||
|
||||
# Run the tests
|
||||
> yarn test
|
||||
|
||||
# Continuously compile
|
||||
> yarn dev
|
||||
|
||||
# Continuously run the tests
|
||||
> yarn dev-test
|
||||
|
||||
# Build for production
|
||||
> 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
|
||||
|
||||
AGPL3 © [Vates SAS](http://vates.fr)
|
||||
39
packages/xo-server-audit/config.toml
Normal file
39
packages/xo-server-audit/config.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
# List of skipped methods
|
||||
blockedList = [
|
||||
'acl.get',
|
||||
'acl.getCurrentPermissions',
|
||||
'audit.checkIntegrity',
|
||||
'audit.generateFingerprint',
|
||||
'audit.getRecords',
|
||||
'backupNg.getAllJobs',
|
||||
'backupNg.getAllLogs',
|
||||
'cloud.getResourceCatalog',
|
||||
'cloudConfig.getAll',
|
||||
'group.getAll',
|
||||
'host.stats',
|
||||
'ipPool.getAll',
|
||||
'job.getAll',
|
||||
'log.get',
|
||||
'metadataBackup.getAllJobs',
|
||||
'plugin.get',
|
||||
'pool.listMissingPatches',
|
||||
'proxy.getAll',
|
||||
'remote.getAll',
|
||||
'remote.getAllInfo',
|
||||
'resourceSet.getAll',
|
||||
'role.getAll',
|
||||
'schedule.getAll',
|
||||
'server.getAll',
|
||||
'session.getUser',
|
||||
'sr.getUnhealthyVdiChainsLength',
|
||||
'sr.stats',
|
||||
'system.getMethodsInfo',
|
||||
'system.getServerTimezone',
|
||||
'system.getServerVersion',
|
||||
'user.getAll',
|
||||
'vm.stats',
|
||||
'xo.getAllObjects',
|
||||
'xoa.supportTunnel.getState',
|
||||
'xosan.checkSrCurrentState',
|
||||
'xosan.getVolumeInfo',
|
||||
]
|
||||
62
packages/xo-server-audit/package.json
Normal file
62
packages/xo-server-audit/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "xo-server-audit",
|
||||
"version": "0.2.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Audit plugin for XO-Server",
|
||||
"keywords": [
|
||||
"audit",
|
||||
"log",
|
||||
"logs",
|
||||
"orchestra",
|
||||
"plugin",
|
||||
"xen-orchestra",
|
||||
"xen",
|
||||
"xo-server"
|
||||
],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-audit",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
"directory": "packages/xo-server-audit",
|
||||
"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": [
|
||||
"config.toml",
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.7.0",
|
||||
"@babel/core": "^7.7.2",
|
||||
"@babel/preset-env": "^7.7.1",
|
||||
"cross-env": "^6.0.3",
|
||||
"rimraf": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||
"prebuild": "rimraf dist/",
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.3",
|
||||
"@xen-orchestra/audit-core": "^0.1.1",
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"app-conf": "^0.7.1",
|
||||
"async-iterator-to-stream": "^1.1.0",
|
||||
"promise-toolbox": "^0.15.0",
|
||||
"readable-stream": "^3.5.0",
|
||||
"xo-common": "^0.4.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
252
packages/xo-server-audit/src/index.js
Normal file
252
packages/xo-server-audit/src/index.js
Normal file
@@ -0,0 +1,252 @@
|
||||
import appConf from 'app-conf'
|
||||
import asyncIteratorToStream from 'async-iterator-to-stream'
|
||||
import createLogger from '@xen-orchestra/log'
|
||||
import path from 'path'
|
||||
import { alteredAuditRecord, missingAuditRecord } from 'xo-common/api-errors'
|
||||
import { createGzip } from 'zlib'
|
||||
import { fromCallback } from 'promise-toolbox'
|
||||
import { pipeline } from 'readable-stream'
|
||||
import {
|
||||
AlteredRecordError,
|
||||
AuditCore,
|
||||
MissingRecordError,
|
||||
NULL_ID,
|
||||
Storage,
|
||||
} from '@xen-orchestra/audit-core'
|
||||
|
||||
const log = createLogger('xo:xo-server-audit')
|
||||
|
||||
const LAST_ID = 'lastId'
|
||||
class Db extends Storage {
|
||||
constructor(db) {
|
||||
super()
|
||||
this._db = db
|
||||
}
|
||||
|
||||
async put(record) {
|
||||
await this._db.put(record.id, record)
|
||||
}
|
||||
|
||||
async del(id) {
|
||||
await this._db.del(id)
|
||||
}
|
||||
|
||||
get(id) {
|
||||
return this._db.get(id).catch(error => {
|
||||
if (!error.notFound) {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async setLastId(id) {
|
||||
await this._db.put(LAST_ID, id)
|
||||
}
|
||||
|
||||
getLastId() {
|
||||
return this.get(LAST_ID)
|
||||
}
|
||||
}
|
||||
|
||||
const NAMESPACE = 'audit'
|
||||
class AuditXoPlugin {
|
||||
constructor({ xo }) {
|
||||
this._cleaners = []
|
||||
this._xo = xo
|
||||
|
||||
this._auditCore = undefined
|
||||
this._blockedList = undefined
|
||||
this._storage = undefined
|
||||
}
|
||||
|
||||
async load() {
|
||||
const cleaners = this._cleaners
|
||||
|
||||
try {
|
||||
const storage = (this._storage = new Db(
|
||||
await this._xo.getStore(NAMESPACE)
|
||||
))
|
||||
this._auditCore = new AuditCore(storage)
|
||||
this._blockedList = (
|
||||
await appConf.load('xo-server-audit', {
|
||||
appDir: path.join(__dirname, '..'),
|
||||
})
|
||||
).blockedList
|
||||
|
||||
cleaners.push(() => {
|
||||
this._auditCore = undefined
|
||||
this._blockedList = undefined
|
||||
this._storage = undefined
|
||||
})
|
||||
} catch (error) {
|
||||
this._auditCore = undefined
|
||||
this._blockedList = undefined
|
||||
this._storage = undefined
|
||||
throw error
|
||||
}
|
||||
|
||||
this._addListener('xo:postCall', this._handleEvent.bind(this, 'apiCall'))
|
||||
this._addListener('xo:audit', this._handleEvent.bind(this))
|
||||
|
||||
const exportRecords = this._exportRecords.bind(this)
|
||||
exportRecords.permission = 'admin'
|
||||
|
||||
const getRecords = this._getRecords.bind(this)
|
||||
getRecords.description = 'Get records from a passed record ID'
|
||||
getRecords.permission = 'admin'
|
||||
getRecords.params = {
|
||||
id: { type: 'string', optional: true },
|
||||
ndjson: { type: 'boolean', optional: true },
|
||||
}
|
||||
|
||||
const checkIntegrity = this._checkIntegrity.bind(this)
|
||||
checkIntegrity.description =
|
||||
'Check records integrity between oldest and newest'
|
||||
checkIntegrity.permission = 'admin'
|
||||
checkIntegrity.params = {
|
||||
newest: { type: 'string', optional: true },
|
||||
oldest: { type: 'string', optional: true },
|
||||
}
|
||||
|
||||
const generateFingerprint = this._generateFingerprint.bind(this)
|
||||
generateFingerprint.description =
|
||||
'Generate a fingerprint of the chain oldest-newest'
|
||||
generateFingerprint.permission = 'admin'
|
||||
generateFingerprint.params = {
|
||||
newest: { type: 'string', optional: true },
|
||||
oldest: { type: 'string', optional: true },
|
||||
}
|
||||
|
||||
cleaners.push(
|
||||
this._xo.addApiMethods({
|
||||
audit: {
|
||||
checkIntegrity,
|
||||
exportRecords,
|
||||
generateFingerprint,
|
||||
getRecords,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
unload() {
|
||||
this._cleaners.forEach(cleaner => cleaner())
|
||||
this._cleaners.length = 0
|
||||
}
|
||||
|
||||
_addListener(event, listener_) {
|
||||
const listener = async (...args) => {
|
||||
try {
|
||||
await listener_(...args)
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
}
|
||||
}
|
||||
const xo = this._xo
|
||||
xo.on(event, listener)
|
||||
this._cleaners.push(() => xo.removeListener(event, listener))
|
||||
}
|
||||
|
||||
_handleEvent(event, { userId, userIp, userName, ...data }) {
|
||||
if (event !== 'apiCall' || this._blockedList.indexOf(data.method) === -1) {
|
||||
return this._auditCore.add(
|
||||
{
|
||||
userId,
|
||||
userIp,
|
||||
userName,
|
||||
},
|
||||
event,
|
||||
data
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async _getRecords({ id, ndjson = false }) {
|
||||
if (ndjson) {
|
||||
return this._xo
|
||||
.registerHttpRequest((req, res) => {
|
||||
res.set('Content-Type', 'application/json')
|
||||
return fromCallback(pipeline, this._getRecordsStream(id), res)
|
||||
})
|
||||
.then($getFrom => ({
|
||||
$getFrom,
|
||||
}))
|
||||
}
|
||||
|
||||
const records = []
|
||||
for await (const record of this._auditCore.getFrom(id)) {
|
||||
records.push(record)
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
_exportRecords() {
|
||||
return this._xo
|
||||
.registerHttpRequest(
|
||||
(req, res) => {
|
||||
res.writeHead(200, {
|
||||
'content-disposition': 'attachment',
|
||||
'content-type': 'application/json',
|
||||
})
|
||||
return fromCallback(
|
||||
pipeline,
|
||||
this._getRecordsStream(),
|
||||
createGzip(),
|
||||
res
|
||||
)
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
suffix: `/audit-records-${new Date()
|
||||
.toISOString()
|
||||
.replace(/:/g, '_')}.gz`,
|
||||
}
|
||||
)
|
||||
.then($getFrom => ({
|
||||
$getFrom,
|
||||
}))
|
||||
}
|
||||
|
||||
async _checkIntegrity(props) {
|
||||
const { oldest = NULL_ID, newest = await this._storage.getLastId() } = props
|
||||
return this._auditCore.checkIntegrity(oldest, newest).catch(error => {
|
||||
if (error instanceof MissingRecordError) {
|
||||
throw missingAuditRecord(error)
|
||||
}
|
||||
if (error instanceof AlteredRecordError) {
|
||||
throw alteredAuditRecord(error)
|
||||
}
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
async _generateFingerprint(props) {
|
||||
const { oldest = NULL_ID, newest = await this._storage.getLastId() } = props
|
||||
try {
|
||||
return {
|
||||
fingerprint: `${oldest}|${newest}`,
|
||||
nValid: await this._checkIntegrity({ oldest, newest }),
|
||||
}
|
||||
} catch (error) {
|
||||
if (missingAuditRecord.is(error) || alteredAuditRecord.is(error)) {
|
||||
return {
|
||||
fingerprint: `${error.data.id}|${newest}`,
|
||||
nValid: error.data.nValid,
|
||||
error,
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AuditXoPlugin.prototype._getRecordsStream = asyncIteratorToStream(
|
||||
async function*(id) {
|
||||
for await (const record of this._auditCore.getFrom(id)) {
|
||||
yield JSON.stringify(record)
|
||||
yield '\n'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default opts => new AuditXoPlugin(opts)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-auth-github",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "GitHub authentication plugin for XO-Server",
|
||||
"keywords": [
|
||||
|
||||
@@ -23,8 +23,13 @@ class AuthGitHubXoPlugin {
|
||||
this._xo = xo
|
||||
}
|
||||
|
||||
configure(conf) {
|
||||
async configure(conf, { loaded }) {
|
||||
this._conf = conf
|
||||
|
||||
if (loaded) {
|
||||
await this.unload()
|
||||
await this.load()
|
||||
}
|
||||
}
|
||||
|
||||
load() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-auth-google",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Google authentication plugin for XO-Server",
|
||||
"keywords": [
|
||||
|
||||
@@ -35,8 +35,13 @@ class AuthGoogleXoPlugin {
|
||||
this._xo = xo
|
||||
}
|
||||
|
||||
configure(conf) {
|
||||
async configure(conf, { loaded }) {
|
||||
this._conf = conf
|
||||
|
||||
if (loaded) {
|
||||
await this.unload()
|
||||
await this.load()
|
||||
}
|
||||
}
|
||||
|
||||
load() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-auth-ldap",
|
||||
"version": "0.6.6",
|
||||
"version": "0.7.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "LDAP authentication plugin for XO-Server",
|
||||
"keywords": [
|
||||
@@ -34,18 +34,15 @@
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"event-to-promise": "^0.8.0",
|
||||
"exec-promise": "^0.7.0",
|
||||
"inquirer": "^7.0.0",
|
||||
"ldapjs": "^1.0.1",
|
||||
"lodash": "^4.17.4",
|
||||
"ldapts": "1.10.0",
|
||||
"promise-toolbox": "^0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^6.0.3",
|
||||
"rimraf": "^3.0.0"
|
||||
},
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/* eslint no-throw-literal: 0 */
|
||||
|
||||
import eventToPromise from 'event-to-promise'
|
||||
import noop from 'lodash/noop'
|
||||
import { createClient } from 'ldapjs'
|
||||
import { escape } from 'ldapjs/lib/filters/escape'
|
||||
import { promisify } from 'promise-toolbox'
|
||||
import fromCallback from 'promise-toolbox/fromCallback'
|
||||
import { Client } from 'ldapts'
|
||||
import { Filter } from 'ldapts/filters/Filter'
|
||||
import { readFile } from 'fs'
|
||||
|
||||
// ===================================================================
|
||||
@@ -14,6 +12,8 @@ const DEFAULTS = {
|
||||
filter: '(uid={{name}})',
|
||||
}
|
||||
|
||||
const { escape } = Filter.prototype
|
||||
|
||||
const VAR_RE = /\{\{([^}]+)\}\}/g
|
||||
const evalFilter = (filter, vars) =>
|
||||
filter.replace(VAR_RE, (_, name) => {
|
||||
@@ -26,6 +26,8 @@ const evalFilter = (filter, vars) =>
|
||||
return escape(value)
|
||||
})
|
||||
|
||||
const noop = Function.prototype
|
||||
|
||||
export const configurationSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -152,7 +154,7 @@ class AuthLdap {
|
||||
tlsOptions.rejectUnauthorized = checkCertificate
|
||||
if (certificateAuthorities) {
|
||||
tlsOptions.ca = await Promise.all(
|
||||
certificateAuthorities.map(path => readFile(path))
|
||||
certificateAuthorities.map(path => fromCallback(readFile, path))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -194,68 +196,48 @@ class AuthLdap {
|
||||
return null
|
||||
}
|
||||
|
||||
const client = createClient(this._clientOpts)
|
||||
const client = new Client(this._clientOpts)
|
||||
|
||||
try {
|
||||
// Promisify some methods.
|
||||
const bind = promisify(client.bind, client)
|
||||
const search = promisify(client.search, client)
|
||||
|
||||
await eventToPromise(client, 'connect')
|
||||
|
||||
// Bind if necessary.
|
||||
{
|
||||
const { _credentials: credentials } = this
|
||||
if (credentials) {
|
||||
logger(`attempting to bind with as ${credentials.dn}...`)
|
||||
await bind(credentials.dn, credentials.password)
|
||||
await client.bind(credentials.dn, credentials.password)
|
||||
logger(`successfully bound as ${credentials.dn}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Search for the user.
|
||||
const entries = []
|
||||
{
|
||||
logger('searching for entries...')
|
||||
const response = await search(this._searchBase, {
|
||||
scope: 'sub',
|
||||
filter: evalFilter(this._searchFilter, {
|
||||
name: username,
|
||||
}),
|
||||
})
|
||||
|
||||
response.on('searchEntry', entry => {
|
||||
logger('.')
|
||||
entries.push(entry.json)
|
||||
})
|
||||
|
||||
const { status } = await eventToPromise(response, 'end')
|
||||
if (status) {
|
||||
throw new Error('unexpected search response status: ' + status)
|
||||
}
|
||||
|
||||
logger(`${entries.length} entries found`)
|
||||
}
|
||||
logger('searching for entries...')
|
||||
const { searchEntries: entries } = await client.search(this._searchBase, {
|
||||
scope: 'sub',
|
||||
filter: evalFilter(this._searchFilter, {
|
||||
name: username,
|
||||
}),
|
||||
})
|
||||
logger(`${entries.length} entries found`)
|
||||
|
||||
// Try to find an entry which can be bind with the given password.
|
||||
for (const entry of entries) {
|
||||
try {
|
||||
logger(`attempting to bind as ${entry.objectName}`)
|
||||
await bind(entry.objectName, password)
|
||||
logger(`attempting to bind as ${entry.dn}`)
|
||||
await client.bind(entry.dn, password)
|
||||
logger(
|
||||
`successfully bound as ${entry.objectName} => ${username} authenticated`
|
||||
`successfully bound as ${entry.dn} => ${username} authenticated`
|
||||
)
|
||||
logger(JSON.stringify(entry, null, 2))
|
||||
return { username }
|
||||
} catch (error) {
|
||||
logger(`failed to bind as ${entry.objectName}: ${error.message}`)
|
||||
logger(`failed to bind as ${entry.dn}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
logger(`could not authenticate ${username}`)
|
||||
return null
|
||||
} finally {
|
||||
client.unbind()
|
||||
await client.unbind()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { forEach, isFinite, isInteger } from 'lodash'
|
||||
import { pForOwn } from 'promise-toolbox'
|
||||
import { prompt } from 'inquirer'
|
||||
|
||||
@@ -95,11 +94,12 @@ const promptByType = {
|
||||
}
|
||||
|
||||
n = schema.maxItems || Infinity
|
||||
const m = defaultValue.length
|
||||
while (
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
i < n &&
|
||||
(await confirm('additional item?', {
|
||||
default: false,
|
||||
default: i < m,
|
||||
}))
|
||||
) {
|
||||
await promptItem()
|
||||
@@ -122,7 +122,7 @@ const promptByType = {
|
||||
input(path, {
|
||||
default: defaultValue || schema.default,
|
||||
filter: input => +input,
|
||||
validate: input => isInteger(+input),
|
||||
validate: input => Number.isInteger(+input),
|
||||
}),
|
||||
|
||||
number: (schema, defaultValue, path) =>
|
||||
@@ -137,7 +137,7 @@ const promptByType = {
|
||||
|
||||
const required = {}
|
||||
schema.required &&
|
||||
forEach(schema.required, name => {
|
||||
schema.required.forEach(name => {
|
||||
required[name] = true
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-auth-saml",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.2",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "SAML authentication plugin for XO-Server",
|
||||
"keywords": [
|
||||
|
||||
@@ -38,6 +38,7 @@ You should try \`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddr
|
||||
title: "Don't request an authentication context",
|
||||
description: 'This is known to help when using Active Directory',
|
||||
default: DEFAULTS.disableRequestedAuthnContext,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
required: ['cert', 'entryPoint', 'issuer', 'usernameField'],
|
||||
@@ -53,7 +54,7 @@ class AuthSamlXoPlugin {
|
||||
this._xo = xo
|
||||
}
|
||||
|
||||
configure({ usernameField, ...conf }) {
|
||||
async configure({ usernameField, ...conf }, { loaded }) {
|
||||
this._usernameField = usernameField
|
||||
this._conf = {
|
||||
...DEFAULTS,
|
||||
@@ -62,6 +63,11 @@ class AuthSamlXoPlugin {
|
||||
// must match the callback URL
|
||||
path: '/signin/saml/callback',
|
||||
}
|
||||
|
||||
if (loaded) {
|
||||
await this.unload()
|
||||
await this.load()
|
||||
}
|
||||
}
|
||||
|
||||
load() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-backup-reports",
|
||||
"version": "0.16.4",
|
||||
"version": "0.16.5",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Backup reports plugin for XO-Server",
|
||||
"keywords": [
|
||||
|
||||
@@ -395,7 +395,13 @@ class BackupReportsXoPlugin {
|
||||
mailReceivers,
|
||||
markdown: toMarkdown(markdown),
|
||||
success: false,
|
||||
nagiosMarkdown: `[Xen Orchestra] [${log.status}] Backup report for ${jobName} - Error : ${log.result.message}`,
|
||||
nagiosMarkdown: `[Xen Orchestra] [${
|
||||
log.status
|
||||
}] Backup report for ${jobName}${
|
||||
log.result?.message !== undefined
|
||||
? ` - Error : ${log.result.message}`
|
||||
: ''
|
||||
}`,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "xo-server-perf-alert",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "",
|
||||
"description": "Sends alerts based on performance criteria",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-perf-alert",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-sdn-controller",
|
||||
"description": "Creates pool-wide and cross-pool private networks",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-sdn-controller",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
@@ -15,7 +16,7 @@
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"version": "0.3.1",
|
||||
"version": "0.4.1",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
@@ -30,7 +31,7 @@
|
||||
"dependencies": {
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"node-openssl-cert": "^0.0.116",
|
||||
"node-openssl-cert": "^0.0.117",
|
||||
"promise-toolbox": "^0.15.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
|
||||
@@ -214,11 +214,20 @@ async function generateCertificatesAndKey(dataDir) {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
async function createTunnel(host, network, pifDevice) {
|
||||
const hostPif = find(host.$PIFs, { device: pifDevice })
|
||||
async function createTunnel(host, network) {
|
||||
const otherConfig = network.other_config
|
||||
const pifDevice = otherConfig['xo:sdn-controller:pif-device']
|
||||
const pifVlan = otherConfig['xo:sdn-controller:vlan']
|
||||
const hostPif = host.$PIFs.find(
|
||||
pif =>
|
||||
pif.device === pifDevice &&
|
||||
pif.VLAN === +pifVlan &&
|
||||
pif.ip_configuration_mode !== 'None'
|
||||
)
|
||||
if (hostPif === undefined) {
|
||||
log.error("Can't create tunnel: no available PIF", {
|
||||
pif: pifDevice,
|
||||
pifDevice,
|
||||
pifVlan,
|
||||
network: network.name_label,
|
||||
host: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
@@ -232,7 +241,8 @@ async function createTunnel(host, network, pifDevice) {
|
||||
} catch (error) {
|
||||
log.error('Error while creating tunnel', {
|
||||
error,
|
||||
pif: pifDevice,
|
||||
pifDevice,
|
||||
pifVlan,
|
||||
network: network.name_label,
|
||||
host: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
@@ -241,7 +251,8 @@ async function createTunnel(host, network, pifDevice) {
|
||||
}
|
||||
|
||||
log.debug('New tunnel added', {
|
||||
pif: pifDevice,
|
||||
pifDevice,
|
||||
pifVlan,
|
||||
network: network.name_label,
|
||||
host: host.name_label,
|
||||
pool: host.$pool.name_label,
|
||||
@@ -249,7 +260,7 @@ async function createTunnel(host, network, pifDevice) {
|
||||
}
|
||||
|
||||
function getHostTunnelForNetwork(host, networkRef) {
|
||||
const pif = find(host.$PIFs, { network: networkRef })
|
||||
const pif = host.$PIFs.find(_ => _.network === networkRef)
|
||||
if (pif === undefined) {
|
||||
return
|
||||
}
|
||||
@@ -264,7 +275,7 @@ function getHostTunnelForNetwork(host, networkRef) {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function setControllerNeeded(xapi) {
|
||||
function isControllerNeeded(xapi) {
|
||||
const controller = find(xapi.objects.all, { $type: 'SDN_controller' })
|
||||
return !(
|
||||
controller?.protocol === PROTOCOL &&
|
||||
@@ -281,8 +292,9 @@ class SDNController extends EventEmitter {
|
||||
- `other_config`:
|
||||
- `xo:sdn-controller:encapsulation` : encapsulation protocol used for tunneling (either `gre` or `vxlan`)
|
||||
- `xo:sdn-controller:encrypted` : `true` if the network is encrypted
|
||||
- `xo:sdn-controller:pif-device` : PIF device on which the tunnels are created, must be physical and have an IP configuration
|
||||
- `xo:sdn-controller:pif-device` : PIF device on which the tunnels are created, must be physical or VLAN or bond master and have an IP configuration
|
||||
- `xo:sdn-controller:private-network-uuid`: UUID of the private network, same across pools
|
||||
- `xo:sdn-controller:vlan` : VLAN of the PIFs on which the network is created
|
||||
- `xo:sdn-controller:vni` : VxLAN Network Identifier,
|
||||
it is used by OpenVSwitch to route traffic of different networks in a single tunnel
|
||||
See: https://tools.ietf.org/html/rfc7348
|
||||
@@ -356,7 +368,7 @@ class SDNController extends EventEmitter {
|
||||
await Promise.all(
|
||||
map(this._privateNetworks, async privateNetworks => {
|
||||
await Promise.all(
|
||||
map(privateNetworks.getPools(), async pool => {
|
||||
privateNetworks.getPools().map(async pool => {
|
||||
if (!updatedPools.includes(pool)) {
|
||||
const xapi = this._xo.getXapi(pool)
|
||||
await this._installCaCertificateIfNeeded(xapi)
|
||||
@@ -406,109 +418,21 @@ class SDNController extends EventEmitter {
|
||||
},
|
||||
})
|
||||
|
||||
// FIXME: we should monitor when xapis are added/removed
|
||||
this._xapis = this._xo.getAllXapis()
|
||||
await Promise.all(
|
||||
map(this._xapis, async xapi => {
|
||||
await xapi.objectsFetched
|
||||
forOwn(this._xo.getAllXapis(), xapi => {
|
||||
if (xapi.status === 'connected') {
|
||||
this._handleConnectedXapi(xapi)
|
||||
}
|
||||
})
|
||||
|
||||
if (setControllerNeeded(xapi)) {
|
||||
return
|
||||
}
|
||||
|
||||
this._cleaners.push(await this._manageXapi(xapi))
|
||||
const hosts = filter(xapi.objects.all, { $type: 'host' })
|
||||
for (const host of hosts) {
|
||||
this._createOvsdbClient(host)
|
||||
}
|
||||
|
||||
// Add already existing private networks
|
||||
const networks = filter(xapi.objects.all, { $type: 'network' })
|
||||
const noVniNetworks = []
|
||||
await Promise.all(
|
||||
map(networks, async network => {
|
||||
// 2019-09-03
|
||||
// Compatibility code, to be removed in 1 year.
|
||||
await updateNetworkOtherConfig(network)
|
||||
network = await network.$xapi.barrier(network.$ref)
|
||||
let otherConfig = network.other_config
|
||||
|
||||
// 2019-10-01
|
||||
// To be removed in a year
|
||||
if (otherConfig['xo:sdn-controller:private-pool-wide'] === 'true') {
|
||||
await updateOldPrivateNetwork(network)
|
||||
}
|
||||
network = await network.$xapi.barrier(network.$ref)
|
||||
otherConfig = network.other_config
|
||||
|
||||
const uuid = otherConfig['xo:sdn-controller:private-network-uuid']
|
||||
if (uuid === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
let privateNetwork = this._privateNetworks[uuid]
|
||||
if (privateNetwork === undefined) {
|
||||
privateNetwork = new PrivateNetwork(this, uuid)
|
||||
this._privateNetworks[uuid] = privateNetwork
|
||||
}
|
||||
|
||||
const vni = otherConfig['xo:sdn-controller:vni']
|
||||
if (vni === undefined) {
|
||||
noVniNetworks.push(network)
|
||||
} else {
|
||||
this._prevVni = Math.max(this._prevVni, +vni)
|
||||
}
|
||||
|
||||
await privateNetwork.addNetwork(network)
|
||||
|
||||
// Previously created network didn't store `pif_device`
|
||||
//
|
||||
// 2019-08-22
|
||||
// This is used to add the pif_device to networks created before this version. (v0.1.2)
|
||||
// This will be removed in 1 year.
|
||||
if (otherConfig['xo:sdn-controller:pif-device'] === undefined) {
|
||||
const tunnel = getHostTunnelForNetwork(
|
||||
privateNetwork.center,
|
||||
network.$ref
|
||||
)
|
||||
const pif = xapi.getObjectByRef(tunnel.transport_PIF)
|
||||
await network.update_other_config(
|
||||
'xo:sdn-controller:pif-device',
|
||||
pif.device
|
||||
)
|
||||
}
|
||||
|
||||
this._networks.set(network.$id, network.$ref)
|
||||
if (privateNetwork.center !== undefined) {
|
||||
this._starCenters.set(
|
||||
privateNetwork.center.$id,
|
||||
privateNetwork.center.$ref
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Add VNI to other config of networks without VNI
|
||||
//
|
||||
// 2019-08-22
|
||||
// This is used to add the VNI to networks created before this version. (v0.1.3)
|
||||
// This will be removed in 1 year.
|
||||
await Promise.all(
|
||||
map(noVniNetworks, async network => {
|
||||
await network.update_other_config(
|
||||
'xo:sdn-controller:vni',
|
||||
String(++this._prevVni)
|
||||
)
|
||||
|
||||
// Re-elect a center to apply the VNI
|
||||
const privateNetwork = this._privateNetworks[
|
||||
network.other_config['private-network-uuid']
|
||||
]
|
||||
await this._electNewCenter(privateNetwork)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
const handleConnectedServer = ({ xapi }) => this._handleConnectedXapi(xapi)
|
||||
const handleDisconnectedServer = ({ xapi }) =>
|
||||
this._handleDisconnectedXapi(xapi)
|
||||
this._xo.on('server:connected', handleConnectedServer)
|
||||
this._xo.on('server:disconnected', handleDisconnectedServer)
|
||||
this._cleaners.push(() => {
|
||||
this._xo.removeListener('server:connected', handleConnectedServer)
|
||||
this._xo.removeListener('server:disconnected', handleDisconnectedServer)
|
||||
})
|
||||
}
|
||||
|
||||
async unload() {
|
||||
@@ -528,6 +452,159 @@ class SDNController extends EventEmitter {
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
async _handleConnectedXapi(xapi) {
|
||||
log.debug('xapi connected', { id: xapi.pool.uuid })
|
||||
try {
|
||||
await xapi.objectsFetched
|
||||
|
||||
if (isControllerNeeded(xapi)) {
|
||||
return
|
||||
}
|
||||
|
||||
this._cleaners.push(await this._manageXapi(xapi))
|
||||
const hosts = filter(xapi.objects.all, { $type: 'host' })
|
||||
for (const host of hosts) {
|
||||
this._createOvsdbClient(host)
|
||||
}
|
||||
|
||||
// Add already existing private networks
|
||||
const networks = filter(xapi.objects.all, { $type: 'network' })
|
||||
const noVniNetworks = []
|
||||
await Promise.all(
|
||||
networks.map(async network => {
|
||||
// 2019-09-03
|
||||
// Compatibility code, to be removed in 1 year.
|
||||
await updateNetworkOtherConfig(network)
|
||||
network = await network.$xapi.barrier(network.$ref)
|
||||
let otherConfig = network.other_config
|
||||
|
||||
// 2019-10-01
|
||||
// To be removed in a year
|
||||
if (otherConfig['xo:sdn-controller:private-pool-wide'] === 'true') {
|
||||
await updateOldPrivateNetwork(network)
|
||||
}
|
||||
network = await network.$xapi.barrier(network.$ref)
|
||||
otherConfig = network.other_config
|
||||
|
||||
const uuid = otherConfig['xo:sdn-controller:private-network-uuid']
|
||||
if (uuid === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
let privateNetwork = this._privateNetworks[uuid]
|
||||
if (privateNetwork === undefined) {
|
||||
privateNetwork = new PrivateNetwork(this, uuid)
|
||||
this._privateNetworks[uuid] = privateNetwork
|
||||
}
|
||||
|
||||
const vni = otherConfig['xo:sdn-controller:vni']
|
||||
if (vni === undefined) {
|
||||
noVniNetworks.push(network)
|
||||
} else {
|
||||
this._prevVni = Math.max(this._prevVni, +vni)
|
||||
}
|
||||
|
||||
await privateNetwork.addNetwork(network)
|
||||
|
||||
// Previously created network didn't store `pif-device`
|
||||
//
|
||||
// 2019-08-22
|
||||
// This is used to add the pif-device to networks created before this version. (v0.1.2)
|
||||
// This will be removed in 1 year.
|
||||
if (otherConfig['xo:sdn-controller:pif-device'] === undefined) {
|
||||
const tunnel = getHostTunnelForNetwork(
|
||||
privateNetwork.center,
|
||||
network.$ref
|
||||
)
|
||||
const pif = xapi.getObjectByRef(tunnel.transport_PIF)
|
||||
await network.update_other_config(
|
||||
'xo:sdn-controller:pif-device',
|
||||
pif.device
|
||||
)
|
||||
}
|
||||
|
||||
// Previously created network didn't store `vlan`
|
||||
//
|
||||
// 2020-01-27
|
||||
// This is used to add the `vlan` to networks created before this version. (v0.4.0)
|
||||
// This will be removed in 1 year.
|
||||
if (otherConfig['xo:sdn-controller:vlan'] === undefined) {
|
||||
const tunnel = getHostTunnelForNetwork(
|
||||
privateNetwork.center,
|
||||
network.$ref
|
||||
)
|
||||
const pif = xapi.getObjectByRef(tunnel.transport_PIF)
|
||||
await network.update_other_config(
|
||||
'xo:sdn-controller:vlan',
|
||||
String(pif.VLAN)
|
||||
)
|
||||
}
|
||||
|
||||
this._networks.set(network.$id, network.$ref)
|
||||
if (privateNetwork.center !== undefined) {
|
||||
this._starCenters.set(
|
||||
privateNetwork.center.$id,
|
||||
privateNetwork.center.$ref
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Add VNI to other config of networks without VNI
|
||||
//
|
||||
// 2019-08-22
|
||||
// This is used to add the VNI to networks created before this version. (v0.1.3)
|
||||
// This will be removed in 1 year.
|
||||
await Promise.all(
|
||||
noVniNetworks.map(async network => {
|
||||
await network.update_other_config(
|
||||
'xo:sdn-controller:vni',
|
||||
String(++this._prevVni)
|
||||
)
|
||||
|
||||
// Re-elect a center to apply the VNI
|
||||
const privateNetwork = this._privateNetworks[
|
||||
network.other_config['xo:sdn-controller:private-network-uuid']
|
||||
]
|
||||
await this._electNewCenter(privateNetwork)
|
||||
})
|
||||
)
|
||||
} catch (error) {
|
||||
log.error('Error while handling xapi connection', {
|
||||
id: xapi.pool.uuid,
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_handleDisconnectedXapi(xapi) {
|
||||
log.debug('xapi disconnected', { id: xapi.pool.uuid })
|
||||
try {
|
||||
forOwn(this._privateNetworks, privateNetwork => {
|
||||
privateNetwork.networks = omitBy(
|
||||
privateNetwork.networks,
|
||||
network => network.$pool.uuid === xapi.pool.uuid
|
||||
)
|
||||
|
||||
if (privateNetwork.center?.$pool.uuid === xapi.pool.uuid) {
|
||||
this._electNewCenter(privateNetwork)
|
||||
}
|
||||
})
|
||||
|
||||
this._privateNetworks = filter(
|
||||
this._privateNetworks,
|
||||
privateNetwork => Object.keys(privateNetwork.networks).length !== 0
|
||||
)
|
||||
} catch (error) {
|
||||
log.error('Error while handling xapi disconnection', {
|
||||
id: xapi.pool.uuid,
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
async _createPrivateNetwork({
|
||||
poolIds,
|
||||
pifIds,
|
||||
@@ -544,7 +621,7 @@ class SDNController extends EventEmitter {
|
||||
|
||||
await this._setPoolControllerIfNeeded(pool)
|
||||
|
||||
const pifId = find(pifIds, id => {
|
||||
const pifId = pifIds.find(id => {
|
||||
const pif = this._xo.getXapiObject(this._xo.getObject(id, 'PIF'))
|
||||
return pif.$pool.$ref === pool.$ref
|
||||
})
|
||||
@@ -563,6 +640,7 @@ class SDNController extends EventEmitter {
|
||||
'xo:sdn-controller:encrypted': encrypted ? 'true' : undefined,
|
||||
'xo:sdn-controller:pif-device': pif.device,
|
||||
'xo:sdn-controller:private-network-uuid': privateNetwork.uuid,
|
||||
'xo:sdn-controller:vlan': String(pif.VLAN),
|
||||
'xo:sdn-controller:vni': String(vni),
|
||||
},
|
||||
})
|
||||
@@ -581,7 +659,7 @@ class SDNController extends EventEmitter {
|
||||
const hosts = filter(pool.$xapi.objects.all, { $type: 'host' })
|
||||
await Promise.all(
|
||||
map(hosts, async host => {
|
||||
await createTunnel(host, createdNetwork, pif.device)
|
||||
await createTunnel(host, createdNetwork)
|
||||
this._createOvsdbClient(host)
|
||||
})
|
||||
)
|
||||
@@ -626,7 +704,7 @@ class SDNController extends EventEmitter {
|
||||
pool: object.$pool.name_label,
|
||||
})
|
||||
|
||||
if (find(this._newHosts, { $ref: object.$ref }) === undefined) {
|
||||
if (!this._newHosts.some(_ => _.$ref === object.$ref)) {
|
||||
this._newHosts.push(object)
|
||||
}
|
||||
this._createOvsdbClient(object)
|
||||
@@ -686,6 +764,11 @@ class SDNController extends EventEmitter {
|
||||
network => network.$ref === networkRef
|
||||
)
|
||||
})
|
||||
|
||||
this._privateNetworks = filter(
|
||||
this._privateNetworks,
|
||||
privateNetwork => Object.keys(privateNetwork.networks).length !== 0
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error in _objectsRemoved', {
|
||||
@@ -748,12 +831,16 @@ class SDNController extends EventEmitter {
|
||||
}
|
||||
|
||||
async _hostUpdated(host) {
|
||||
const newHost = find(this._newHosts, { $ref: host.$ref })
|
||||
if (!host.enabled || host.PIFs.length === 0 || newHost === undefined) {
|
||||
let i
|
||||
if (
|
||||
!host.enabled ||
|
||||
host.PIFs.length === 0 ||
|
||||
(i = this._newHosts.findIndex(_ => _.$ref === host.$ref)) === -1
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this._newHosts = this._newHosts.slice(this._newHosts.indexOf(newHost), 1)
|
||||
this._newHosts.splice(i, 1)
|
||||
|
||||
log.debug('Sync pool certificates', {
|
||||
newHost: host.name_label,
|
||||
@@ -779,9 +866,7 @@ class SDNController extends EventEmitter {
|
||||
continue
|
||||
}
|
||||
|
||||
const pifDevice =
|
||||
network.other_config['xo:sdn-controller:pif-device'] ?? 'eth0'
|
||||
await createTunnel(host, network, pifDevice)
|
||||
await createTunnel(host, network)
|
||||
}
|
||||
|
||||
await this._addHostToPrivateNetworks(host)
|
||||
@@ -803,7 +888,7 @@ class SDNController extends EventEmitter {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async _setPoolControllerIfNeeded(pool) {
|
||||
if (!setControllerNeeded(pool.$xapi)) {
|
||||
if (!isControllerNeeded(pool.$xapi)) {
|
||||
// Nothing to do
|
||||
return
|
||||
}
|
||||
@@ -989,9 +1074,8 @@ class SDNController extends EventEmitter {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
_createOvsdbClient(host) {
|
||||
const foundClient = this.ovsdbClients[host.$ref]
|
||||
if (foundClient !== undefined) {
|
||||
return foundClient
|
||||
if (this.ovsdbClients[host.$ref] !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const client = new OvsdbClient(
|
||||
@@ -1001,7 +1085,6 @@ class SDNController extends EventEmitter {
|
||||
this._caCert
|
||||
)
|
||||
this.ovsdbClients[host.$ref] = client
|
||||
return client
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import createLogger from '@xen-orchestra/log'
|
||||
import { filter, find, forOwn, map, sample } from 'lodash'
|
||||
import { filter, forOwn, sample } from 'lodash'
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -111,7 +111,7 @@ export class PrivateNetwork {
|
||||
|
||||
const hosts = filter(network.$pool.$xapi.objects.all, { $type: 'host' })
|
||||
return Promise.all(
|
||||
map(hosts, async host => {
|
||||
hosts.map(async host => {
|
||||
const hostClient = this.controller.ovsdbClients[host.$ref]
|
||||
const network = this.networks[host.$pool.uuid]
|
||||
await hostClient.resetForNetwork(network, this.uuid)
|
||||
@@ -126,9 +126,9 @@ export class PrivateNetwork {
|
||||
// TODO: make it random
|
||||
const hosts = this._getHosts()
|
||||
for (const host of hosts) {
|
||||
const pif = find(host.$PIFs, {
|
||||
network: this.networks[host.$pool.uuid].$ref,
|
||||
})
|
||||
const pif = host.$PIFs.find(
|
||||
_ => _.network === this.networks[host.$pool.uuid].$ref
|
||||
)
|
||||
if (pif?.currently_attached && host.$metrics.live) {
|
||||
this.center = host
|
||||
break
|
||||
@@ -145,7 +145,7 @@ export class PrivateNetwork {
|
||||
await this._reset()
|
||||
|
||||
// Recreate star topology
|
||||
await Promise.all(map(hosts, host => this.addHost(host)))
|
||||
await Promise.all(hosts.map(host => this.addHost(host)))
|
||||
|
||||
log.info('New star-center elected', {
|
||||
center: this.center.name_label,
|
||||
@@ -167,7 +167,7 @@ export class PrivateNetwork {
|
||||
|
||||
_reset() {
|
||||
return Promise.all(
|
||||
map(this._getHosts(), async host => {
|
||||
this._getHosts().map(async host => {
|
||||
// Clean old ports and interfaces
|
||||
const hostClient = this.controller.ovsdbClients[host.$ref]
|
||||
if (hostClient === undefined) {
|
||||
|
||||
@@ -131,7 +131,7 @@ export class OvsdbClient {
|
||||
table: 'Port',
|
||||
row: {
|
||||
name: portName,
|
||||
interfaces: ['set', [['named-uuid', 'new_iface']]],
|
||||
interfaces: ['named-uuid', 'new_iface'],
|
||||
other_config: toMap({
|
||||
'xo:sdn-controller:private-network-uuid': privateNetworkUuid,
|
||||
}),
|
||||
@@ -143,7 +143,7 @@ export class OvsdbClient {
|
||||
op: 'mutate',
|
||||
table: 'Bridge',
|
||||
where: [['_uuid', '==', ['uuid', bridge.uuid]]],
|
||||
mutations: [['ports', 'insert', ['set', [['named-uuid', 'new_port']]]]],
|
||||
mutations: [['ports', 'insert', ['named-uuid', 'new_port']]],
|
||||
}
|
||||
const params = [
|
||||
'Open_vSwitch',
|
||||
@@ -487,7 +487,7 @@ export class OvsdbClient {
|
||||
let resultRequestId
|
||||
do {
|
||||
try {
|
||||
result = await fromEvent(stream, 'data', {})
|
||||
result = await fromEvent(stream, 'data')
|
||||
} catch (error) {
|
||||
log.error('Error while waiting for stream data', {
|
||||
error,
|
||||
@@ -518,7 +518,7 @@ export class OvsdbClient {
|
||||
const socket = connect(options)
|
||||
|
||||
try {
|
||||
await fromEvent(socket, 'secureConnect', {})
|
||||
await fromEvent(socket, 'secureConnect')
|
||||
} catch (error) {
|
||||
log.error('TLS connection failed', {
|
||||
error,
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"lodash": "^4.17.11",
|
||||
"promise-toolbox": "^0.15.0",
|
||||
"xo-collection": "^0.4.1",
|
||||
"xo-common": "^0.2.0",
|
||||
"xo-common": "^0.4.0",
|
||||
"xo-lib": "^0.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "xo-server-transport-icinga2",
|
||||
"description": "Sends backup runs statuses to icinga2 server",
|
||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-transport-icinga2",
|
||||
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||
"repository": {
|
||||
@@ -15,7 +16,7 @@
|
||||
"predev": "yarn run prebuild",
|
||||
"prepublishOnly": "yarn run build"
|
||||
},
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"engines": {
|
||||
"node": ">=8.9.4"
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "xo-server-transport-nagios",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"description": "Send backup runs statuses to Nagios",
|
||||
"keywords": [
|
||||
"nagios",
|
||||
"orchestra",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "xo-server-usage-report",
|
||||
"version": "0.7.3",
|
||||
"version": "0.7.5",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "",
|
||||
"description": "Report resources usage with their evolution",
|
||||
"keywords": [
|
||||
"orchestra",
|
||||
"plugin",
|
||||
|
||||
@@ -30,8 +30,6 @@ const GRANULARITY = 'days'
|
||||
const pReadFile = promisify(readFile)
|
||||
const pWriteFile = promisify(writeFile)
|
||||
|
||||
const currDate = new Date().toISOString().slice(0, 10)
|
||||
|
||||
const compareOperators = {
|
||||
'>': (l, r) => l > r,
|
||||
}
|
||||
@@ -615,7 +613,7 @@ async function computeEvolution({ storedStatsPath, ...newStats }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function dataBuilder({ xo, storedStatsPath, all }) {
|
||||
async function dataBuilder({ currDate, xo, storedStatsPath, all }) {
|
||||
const xoObjects = values(xo.getObjects())
|
||||
const runningVms = filter(xoObjects, { type: 'VM', power_state: 'Running' })
|
||||
const haltedVms = filter(xoObjects, { type: 'VM', power_state: 'Halted' })
|
||||
@@ -766,7 +764,9 @@ class UsageReportPlugin {
|
||||
)
|
||||
}
|
||||
|
||||
const currDate = new Date().toISOString().slice(0, 10)
|
||||
const data = await dataBuilder({
|
||||
currDate,
|
||||
xo,
|
||||
storedStatsPath: this._storedStatsPath,
|
||||
all: this._conf.all,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "xo-server-web-hooks",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "",
|
||||
"description": "Sends HTTP requests on XO-Server API calls",
|
||||
"keywords": [
|
||||
"hooks",
|
||||
"orchestra",
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
Error.stackTraceLimit = 100
|
||||
|
||||
// Removes internal modules.
|
||||
try {
|
||||
const sep = require('path').sep
|
||||
|
||||
require('stack-chain').filter.attach(function(_, frames) {
|
||||
const filtered = frames.filter(function(frame) {
|
||||
const name = frame && frame.getFileName()
|
||||
|
||||
return (
|
||||
// has a filename
|
||||
name &&
|
||||
// contains a separator (no internal modules)
|
||||
name.indexOf(sep) !== -1 &&
|
||||
// does not start with `internal`
|
||||
name.lastIndexOf('internal', 0) !== -1
|
||||
)
|
||||
})
|
||||
|
||||
// depd (used amongst other by express requires at least 3 frames
|
||||
// in the stack.
|
||||
return filtered.length > 2 ? filtered : frames
|
||||
})
|
||||
} catch (_) {}
|
||||
|
||||
// Source maps.
|
||||
try {
|
||||
require('julien-f-source-map-support/register')
|
||||
} catch (_) {}
|
||||
@@ -10,7 +10,11 @@ if (process.env.NODE_ENV === undefined) {
|
||||
}
|
||||
|
||||
// Better stack traces if possible.
|
||||
require('../better-stacks')
|
||||
try {
|
||||
require('source-map-support').install({
|
||||
handleUncaughtExceptions: false
|
||||
});
|
||||
} catch (_) {}
|
||||
|
||||
// Use Bluebird for all promises as it provides better performance and
|
||||
// less memory usage.
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
// ===================================================================
|
||||
|
||||
// Better stack traces if possible.
|
||||
require('../better-stacks')
|
||||
try {
|
||||
require('source-map-support').install({
|
||||
handleUncaughtExceptions: false,
|
||||
})
|
||||
} catch (_) {}
|
||||
|
||||
require('exec-promise')(require('../dist/logs-cli').default)
|
||||
|
||||
@@ -74,6 +74,7 @@ poolMetadataTimeout = '10 minutes'
|
||||
|
||||
# https://github.com/naugtur/blocked-at#params-and-return-value
|
||||
[blockedAtOptions]
|
||||
enabled = false
|
||||
threshold = 1000
|
||||
|
||||
# Helmet handles HTTP security via headers
|
||||
@@ -120,3 +121,25 @@ timeout = 600e3
|
||||
|
||||
[xapiOptions]
|
||||
maxUncoalescedVdis = 1
|
||||
vdiExportConcurrency = 12
|
||||
vmExportConcurrency = 2
|
||||
vmSnapshotConcurrency = 2
|
||||
|
||||
["xo-proxy"]
|
||||
callTimeout = '1 min'
|
||||
|
||||
channel = 'xo-proxy-appliance'
|
||||
|
||||
namespace = 'xoProxyAppliance'
|
||||
|
||||
proxyName = 'Proxy {date}'
|
||||
|
||||
vmName = 'XOA Proxy {date}'
|
||||
|
||||
# The duration for which we can wait for the VM networks to be defined
|
||||
vmNetworksTimeout = '5 min'
|
||||
|
||||
vmTag = 'XOA Proxy'
|
||||
|
||||
# The duration for which we can wait for the XOA to be upgraded
|
||||
xoaUpgradeTimeout = '5 min'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-server",
|
||||
"version": "5.54.0",
|
||||
"version": "5.58.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Server part of Xen-Orchestra",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,6 @@
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"files": [
|
||||
"better-stacks.js",
|
||||
"bin/",
|
||||
"dist/",
|
||||
"config.toml",
|
||||
@@ -35,12 +34,14 @@
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.1",
|
||||
"@xen-orchestra/async-map": "^0.0.0",
|
||||
"@xen-orchestra/backups": "^0.1.1",
|
||||
"@xen-orchestra/cron": "^1.0.6",
|
||||
"@xen-orchestra/defined": "^0.0.0",
|
||||
"@xen-orchestra/emit-async": "^0.0.0",
|
||||
"@xen-orchestra/fs": "^0.10.2",
|
||||
"@xen-orchestra/fs": "^0.10.3",
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"@xen-orchestra/mixin": "^0.0.0",
|
||||
"@xen-orchestra/template": "^0.1.0",
|
||||
"ajv": "^6.1.1",
|
||||
"app-conf": "^0.7.0",
|
||||
"archiver": "^3.0.0",
|
||||
@@ -68,11 +69,11 @@
|
||||
"fs-extra": "^8.0.1",
|
||||
"get-stream": "^5.1.0",
|
||||
"golike-defer": "^0.4.1",
|
||||
"hashy": "^0.7.1",
|
||||
"hashy": "^0.8.0",
|
||||
"helmet": "^3.9.0",
|
||||
"highland": "^2.11.1",
|
||||
"http-proxy": "^1.16.2",
|
||||
"http-request-plus": "^0.9.1",
|
||||
"http-request-plus": "^0.8.0",
|
||||
"http-server-plus": "^0.11.0",
|
||||
"human-format": "^0.10.0",
|
||||
"is-redirect": "^1.0.0",
|
||||
@@ -81,12 +82,8 @@
|
||||
"js-yaml": "^3.10.0",
|
||||
"json-rpc-peer": "^0.15.3",
|
||||
"json5": "^2.0.1",
|
||||
"julien-f-source-map-support": "0.1.0",
|
||||
"julien-f-unzip": "^0.2.1",
|
||||
"kindof": "^2.0.0",
|
||||
"level": "^6.0.0",
|
||||
"level-party": "^4.0.0",
|
||||
"level-sublevel": "^6.6.1",
|
||||
"limit-concurrency-decorator": "^0.4.0",
|
||||
"lodash": "^4.17.4",
|
||||
"make-error": "^1",
|
||||
@@ -106,29 +103,35 @@
|
||||
"proxy-agent": "^3.0.0",
|
||||
"pug": "^2.0.0-rc.4",
|
||||
"pump": "^3.0.0",
|
||||
"pumpify": "^2.0.0",
|
||||
"pw": "^0.0.4",
|
||||
"readable-stream": "^3.2.0",
|
||||
"redis": "^2.8.0",
|
||||
"schema-inspector": "^1.6.8",
|
||||
"semver": "^6.0.0",
|
||||
"serve-static": "^1.13.1",
|
||||
"set-cookie-parser": "^2.3.5",
|
||||
"source-map-support": "^0.5.16",
|
||||
"split-lines": "^2.0.0",
|
||||
"split2": "^3.1.1",
|
||||
"stack-chain": "^2.0.0",
|
||||
"stoppable": "^1.0.5",
|
||||
"strict-timeout": "^1.0.0",
|
||||
"struct-fu": "^1.2.0",
|
||||
"subleveldown": "^4.1.4",
|
||||
"tar-stream": "^2.0.1",
|
||||
"through2": "^3.0.0",
|
||||
"tmp": "^0.1.0",
|
||||
"unzipper": "^0.10.5",
|
||||
"uuid": "^3.0.1",
|
||||
"value-matcher": "^0.2.0",
|
||||
"vhd-lib": "^0.7.2",
|
||||
"ws": "^7.1.2",
|
||||
"xen-api": "^0.27.3",
|
||||
"xen-api": "^0.28.3",
|
||||
"xml2js": "^0.4.19",
|
||||
"xo-acl-resolver": "^0.4.1",
|
||||
"xo-collection": "^0.4.1",
|
||||
"xo-common": "^0.2.0",
|
||||
"xo-common": "^0.4.0",
|
||||
"xo-remote-parser": "^0.5.0",
|
||||
"xo-vmdk-to-vhd": "^0.1.8",
|
||||
"yazl": "^2.4.3"
|
||||
|
||||
38
packages/xo-server/src/_readStreamChunk.js
Normal file
38
packages/xo-server/src/_readStreamChunk.js
Normal file
@@ -0,0 +1,38 @@
|
||||
function onEnd() {
|
||||
this.onError(new Error('unexpected end of stream'))
|
||||
}
|
||||
|
||||
function onError(reject, error) {
|
||||
reject(error)
|
||||
removeListeners.call(this)
|
||||
}
|
||||
|
||||
function onReadable(resolve, size) {
|
||||
const data = this.stream.read(size)
|
||||
if (data !== null) {
|
||||
resolve(data)
|
||||
removeListeners.call(this)
|
||||
}
|
||||
}
|
||||
|
||||
function removeListeners() {
|
||||
const { onEnd, onError, onReadable, stream } = this
|
||||
stream.removeListener('end', onEnd)
|
||||
stream.removeListener('error', onError)
|
||||
stream.removeListener('readable', onReadable)
|
||||
}
|
||||
|
||||
function Reader(stream, size, resolve, reject) {
|
||||
stream.on('end', (this.onEnd = onEnd.bind(this, reject)))
|
||||
stream.on('error', (this.onError = onError.bind(this, reject)))
|
||||
stream.on(
|
||||
'readable',
|
||||
(this.onReadable = onReadable.bind(this, resolve, size))
|
||||
)
|
||||
this.stream = stream
|
||||
}
|
||||
|
||||
export default (stream, size) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Reader(stream, size, resolve, reject).onReadable()
|
||||
})
|
||||
@@ -24,6 +24,10 @@ createJob.params = {
|
||||
type: 'string',
|
||||
optional: true,
|
||||
},
|
||||
proxy: {
|
||||
type: 'string',
|
||||
optional: true,
|
||||
},
|
||||
remotes: {
|
||||
type: 'object',
|
||||
optional: true,
|
||||
@@ -85,6 +89,10 @@ editJob.params = {
|
||||
type: 'string',
|
||||
optional: true,
|
||||
},
|
||||
proxy: {
|
||||
type: ['string', 'null'],
|
||||
optional: true,
|
||||
},
|
||||
remotes: {
|
||||
type: 'object',
|
||||
optional: true,
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import createLogger from '@xen-orchestra/log'
|
||||
import pump from 'pump'
|
||||
import convertVmdkToVhdStream from 'xo-vmdk-to-vhd'
|
||||
import createLogger from '@xen-orchestra/log'
|
||||
import defer from 'golike-defer'
|
||||
import pump from 'pump'
|
||||
import { format, JsonRpcError } from 'json-rpc-peer'
|
||||
import { noSuchObject } from 'xo-common/api-errors'
|
||||
import { peekFooterFromVhdStream } from 'vhd-lib'
|
||||
|
||||
import { parseSize } from '../utils'
|
||||
import { VDI_FORMAT_VHD } from '../xapi'
|
||||
|
||||
const log = createLogger('xo:disk')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export async function create({ name, size, sr, vm, bootable, position, mode }) {
|
||||
export const create = defer(async function(
|
||||
$defer,
|
||||
{ name, size, sr, vm, bootable, position, mode }
|
||||
) {
|
||||
const attach = vm !== undefined
|
||||
|
||||
do {
|
||||
@@ -23,6 +26,9 @@ export async function create({ name, size, sr, vm, bootable, position, mode }) {
|
||||
sr.id,
|
||||
])
|
||||
await this.allocateLimitsInResourceSet({ disk: size }, resourceSet)
|
||||
$defer.onFailure(() =>
|
||||
this.releaseLimitsInResourceSet({ disk: size }, resourceSet)
|
||||
)
|
||||
|
||||
break
|
||||
} catch (error) {
|
||||
@@ -43,6 +49,7 @@ export async function create({ name, size, sr, vm, bootable, position, mode }) {
|
||||
size,
|
||||
sr: sr._xapiId,
|
||||
})
|
||||
$defer.onFailure(() => xapi.deleteVdi(vdi.$id))
|
||||
|
||||
if (attach) {
|
||||
await xapi.createVbd({
|
||||
@@ -55,7 +62,7 @@ export async function create({ name, size, sr, vm, bootable, position, mode }) {
|
||||
}
|
||||
|
||||
return vdi.$id
|
||||
}
|
||||
})
|
||||
|
||||
create.description = 'create a new disk on a SR'
|
||||
|
||||
@@ -154,21 +161,6 @@ importContent.resolve = {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export async function resize({ vdi, size }) {
|
||||
await this.getXapi(vdi).resizeVdi(vdi._xapiId, parseSize(size))
|
||||
}
|
||||
|
||||
resize.description = 'resize an existing VDI'
|
||||
|
||||
resize.params = {
|
||||
id: { type: 'string' },
|
||||
size: { type: ['integer', 'string'] },
|
||||
}
|
||||
|
||||
resize.resolve = {
|
||||
vdi: ['id', ['VDI', 'VDI-snapshot'], 'administrate'],
|
||||
}
|
||||
|
||||
async function handleImport(
|
||||
req,
|
||||
res,
|
||||
|
||||
@@ -46,7 +46,6 @@ export async function createBonded({
|
||||
description,
|
||||
pifs,
|
||||
mtu = 1500,
|
||||
mac,
|
||||
bondMode,
|
||||
}) {
|
||||
return this.getXapi(pool).createBondedNetwork({
|
||||
@@ -54,7 +53,6 @@ export async function createBonded({
|
||||
description,
|
||||
pifIds: mapToArray(pifs, pif => this.getObject(pif, 'PIF')._xapiId),
|
||||
mtu: +mtu,
|
||||
mac,
|
||||
bondMode,
|
||||
})
|
||||
}
|
||||
@@ -70,7 +68,6 @@ createBonded.params = {
|
||||
},
|
||||
},
|
||||
mtu: { type: ['integer', 'string'], optional: true },
|
||||
mac: { type: 'string', optional: true },
|
||||
// RegExp since schema-inspector does not provide a param check based on an enumeration
|
||||
bondMode: {
|
||||
type: 'string',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user