chore: format all code (#2632)
This commit is contained in:
parent
8bf35b2a63
commit
7cb720b11f
@ -18,9 +18,10 @@ class Job {
|
|||||||
}
|
}
|
||||||
const scheduleNext = () => {
|
const scheduleNext = () => {
|
||||||
const delay = schedule._nextDelay()
|
const delay = schedule._nextDelay()
|
||||||
this._timeout = delay < MAX_DELAY
|
this._timeout =
|
||||||
? setTimeout(wrapper, delay)
|
delay < MAX_DELAY
|
||||||
: setTimeout(scheduleNext, MAX_DELAY)
|
? setTimeout(wrapper, delay)
|
||||||
|
: setTimeout(scheduleNext, MAX_DELAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._scheduleNext = scheduleNext
|
this._scheduleNext = scheduleNext
|
||||||
|
@ -9,7 +9,7 @@ const NEXT_MAPPING = {
|
|||||||
minute: { hour: 1 },
|
minute: { hour: 1 },
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFirst = values => values !== undefined ? values[0] : 0
|
const getFirst = values => (values !== undefined ? values[0] : 0)
|
||||||
|
|
||||||
const setFirstAvailable = (date, unit, values) => {
|
const setFirstAvailable = (date, unit, values) => {
|
||||||
if (values === undefined) {
|
if (values === undefined) {
|
||||||
|
@ -90,7 +90,7 @@ const createParser = ({ fields: [...fields], presets: { ...presets } }) => {
|
|||||||
if (!match('/')) {
|
if (!match('/')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
[start, end] = field.range
|
;[start, end] = field.range
|
||||||
step = parseInteger()
|
step = parseInteger()
|
||||||
} else {
|
} else {
|
||||||
start = parseValue()
|
start = parseValue()
|
||||||
|
@ -28,9 +28,7 @@ describe('parse()', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('reports missing integer', () => {
|
it('reports missing integer', () => {
|
||||||
expect(() => parse('*/a')).toThrow(
|
expect(() => parse('*/a')).toThrow('minute: missing integer at character 2')
|
||||||
'minute: missing integer at character 2'
|
|
||||||
)
|
|
||||||
expect(() => parse('*')).toThrow('hour: missing integer at character 1')
|
expect(() => parse('*')).toThrow('hour: missing integer at character 1')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -325,7 +325,10 @@ class P {
|
|||||||
value.push(result.value)
|
value.push(result.value)
|
||||||
pos = result.pos
|
pos = result.pos
|
||||||
}
|
}
|
||||||
while (i < max && (result = this._parse(input, pos, end)) instanceof Success) {
|
while (
|
||||||
|
i < max &&
|
||||||
|
(result = this._parse(input, pos, end)) instanceof Success
|
||||||
|
) {
|
||||||
++i
|
++i
|
||||||
value.push(result.value)
|
value.push(result.value)
|
||||||
pos = result.pos
|
pos = result.pos
|
||||||
@ -359,8 +362,9 @@ P.eof = new P(
|
|||||||
|
|
||||||
const parser = P.grammar({
|
const parser = P.grammar({
|
||||||
default: r =>
|
default: r =>
|
||||||
P.seq(r.ws, r.term.repeat(), P.eof)
|
P.seq(r.ws, r.term.repeat(), P.eof).map(
|
||||||
.map(([, terms]) => (terms.length === 0 ? new Null() : new And(terms))),
|
([, terms]) => (terms.length === 0 ? new Null() : new And(terms))
|
||||||
|
),
|
||||||
quotedString: new P((input, pos, end) => {
|
quotedString: new P((input, pos, end) => {
|
||||||
if (input[pos] !== '"') {
|
if (input[pos] !== '"') {
|
||||||
return new Failure(pos, '"')
|
return new Failure(pos, '"')
|
||||||
@ -416,7 +420,7 @@ const parser = P.grammar({
|
|||||||
? new StringNode(str)
|
? new StringNode(str)
|
||||||
: new NumberNode(asNum)
|
: new NumberNode(asNum)
|
||||||
})
|
})
|
||||||
),
|
)
|
||||||
).skip(r.ws),
|
).skip(r.ws),
|
||||||
ws: P.regex(/\s*/),
|
ws: P.regex(/\s*/),
|
||||||
}).default
|
}).default
|
||||||
@ -476,17 +480,19 @@ export const getPropertyClausesStrings = node => {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const setPropertyClause = (node, name, child) => {
|
export const setPropertyClause = (node, name, child) => {
|
||||||
const property = child && new Property(
|
const property =
|
||||||
name,
|
child &&
|
||||||
typeof child === 'string' ? new StringNode(child) : child
|
new Property(
|
||||||
)
|
name,
|
||||||
|
typeof child === 'string' ? new StringNode(child) : child
|
||||||
|
)
|
||||||
|
|
||||||
if (node === undefined) {
|
if (node === undefined) {
|
||||||
return property
|
return property
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = (node instanceof And ? node.children : [node]).filter(child =>
|
const children = (node instanceof And ? node.children : [node]).filter(
|
||||||
!(child instanceof Property && child.name === name)
|
child => !(child instanceof Property && child.name === name)
|
||||||
)
|
)
|
||||||
if (property !== undefined) {
|
if (property !== undefined) {
|
||||||
children.push(property)
|
children.push(property)
|
||||||
|
@ -49,13 +49,15 @@ describe('Number', () => {
|
|||||||
|
|
||||||
describe('setPropertyClause', () => {
|
describe('setPropertyClause', () => {
|
||||||
it('creates a node if none passed', () => {
|
it('creates a node if none passed', () => {
|
||||||
expect(setPropertyClause(undefined, 'foo', 'bar').toString()).toBe('foo:bar')
|
expect(setPropertyClause(undefined, 'foo', 'bar').toString()).toBe(
|
||||||
|
'foo:bar'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a property clause if there was none', () => {
|
it('adds a property clause if there was none', () => {
|
||||||
expect(
|
expect(setPropertyClause(parse('baz'), 'foo', 'bar').toString()).toBe(
|
||||||
setPropertyClause(parse('baz'), 'foo', 'bar').toString()
|
'baz foo:bar'
|
||||||
).toBe('baz foo:bar')
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('replaces the property clause if there was one', () => {
|
it('replaces the property clause if there was one', () => {
|
||||||
|
@ -26,13 +26,16 @@ type ObjectPattern = { [string]: Pattern }
|
|||||||
type ArrayPattern = Array<Pattern>
|
type ArrayPattern = Array<Pattern>
|
||||||
|
|
||||||
// value equals the pattern
|
// value equals the pattern
|
||||||
type ValuePattern = bool | number | string
|
type ValuePattern = boolean | number | string
|
||||||
|
|
||||||
const match = (pattern: Pattern, value: any) => {
|
const match = (pattern: Pattern, value: any) => {
|
||||||
if (Array.isArray(pattern)) {
|
if (Array.isArray(pattern)) {
|
||||||
return Array.isArray(value) && pattern.every((subpattern, i) =>
|
return (
|
||||||
// FIXME: subpatterns should match different subvalues
|
Array.isArray(value) &&
|
||||||
value.some(subvalue => match(subpattern, subvalue))
|
pattern.every((subpattern, i) =>
|
||||||
|
// FIXME: subpatterns should match different subvalues
|
||||||
|
value.some(subvalue => match(subpattern, subvalue))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +44,7 @@ const match = (pattern: Pattern, value: any) => {
|
|||||||
const { length } = keys
|
const { length } = keys
|
||||||
|
|
||||||
if (length === 1) {
|
if (length === 1) {
|
||||||
const [ key ] = keys
|
const [key] = keys
|
||||||
if (key === '__and') {
|
if (key === '__and') {
|
||||||
const andPattern: AndPattern = (pattern: any)
|
const andPattern: AndPattern = (pattern: any)
|
||||||
return andPattern.__and.every(subpattern => match(subpattern, value))
|
return andPattern.__and.every(subpattern => match(subpattern, value))
|
||||||
@ -74,4 +77,5 @@ const match = (pattern: Pattern, value: any) => {
|
|||||||
return pattern === value
|
return pattern === value
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createPredicate = (pattern: Pattern) => (value: any) => match(pattern, value)
|
export const createPredicate = (pattern: Pattern) => (value: any) =>
|
||||||
|
match(pattern, value)
|
||||||
|
@ -36,11 +36,13 @@ const fuFooter = fu.struct([
|
|||||||
fu.char('creatorApplication', 4), // 28
|
fu.char('creatorApplication', 4), // 28
|
||||||
fu.uint32('creatorVersion'), // 32
|
fu.uint32('creatorVersion'), // 32
|
||||||
fu.uint32('creatorHostOs'), // 36
|
fu.uint32('creatorHostOs'), // 36
|
||||||
fu.struct('originalSize', [ // At the creation, current size of the hard disk.
|
fu.struct('originalSize', [
|
||||||
|
// At the creation, current size of the hard disk.
|
||||||
fu.uint32('high'), // 40
|
fu.uint32('high'), // 40
|
||||||
fu.uint32('low'), // 44
|
fu.uint32('low'), // 44
|
||||||
]),
|
]),
|
||||||
fu.struct('currentSize', [ // Current size of the virtual disk. At the creation: currentSize = originalSize.
|
fu.struct('currentSize', [
|
||||||
|
// Current size of the virtual disk. At the creation: currentSize = originalSize.
|
||||||
fu.uint32('high'), // 48
|
fu.uint32('high'), // 48
|
||||||
fu.uint32('low'), // 52
|
fu.uint32('low'), // 52
|
||||||
]),
|
]),
|
||||||
@ -60,11 +62,9 @@ const FOOTER_SIZE = fuFooter.size
|
|||||||
|
|
||||||
const fuHeader = fu.struct([
|
const fuHeader = fu.struct([
|
||||||
fu.char('cookie', 8),
|
fu.char('cookie', 8),
|
||||||
fu.struct('dataOffset', [
|
fu.struct('dataOffset', [fu.uint32('high'), fu.uint32('low')]),
|
||||||
fu.uint32('high'),
|
fu.struct('tableOffset', [
|
||||||
fu.uint32('low'),
|
// Absolute byte offset of the Block Allocation Table.
|
||||||
]),
|
|
||||||
fu.struct('tableOffset', [ // Absolute byte offset of the Block Allocation Table.
|
|
||||||
fu.uint32('high'),
|
fu.uint32('high'),
|
||||||
fu.uint32('low'),
|
fu.uint32('low'),
|
||||||
]),
|
]),
|
||||||
@ -76,16 +76,21 @@ const fuHeader = fu.struct([
|
|||||||
fu.uint32('parentTimestamp'),
|
fu.uint32('parentTimestamp'),
|
||||||
fu.byte('reserved1', 4),
|
fu.byte('reserved1', 4),
|
||||||
fu.char16be('parentUnicodeName', 512),
|
fu.char16be('parentUnicodeName', 512),
|
||||||
fu.struct('parentLocatorEntry', [
|
fu.struct(
|
||||||
fu.uint32('platformCode'),
|
'parentLocatorEntry',
|
||||||
fu.uint32('platformDataSpace'),
|
[
|
||||||
fu.uint32('platformDataLength'),
|
fu.uint32('platformCode'),
|
||||||
fu.uint32('reserved'),
|
fu.uint32('platformDataSpace'),
|
||||||
fu.struct('platformDataOffset', [ // Absolute byte offset of the locator data.
|
fu.uint32('platformDataLength'),
|
||||||
fu.uint32('high'),
|
fu.uint32('reserved'),
|
||||||
fu.uint32('low'),
|
fu.struct('platformDataOffset', [
|
||||||
]),
|
// Absolute byte offset of the locator data.
|
||||||
], 8),
|
fu.uint32('high'),
|
||||||
|
fu.uint32('low'),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
8
|
||||||
|
),
|
||||||
fu.byte('reserved2', 256),
|
fu.byte('reserved2', 256),
|
||||||
])
|
])
|
||||||
const HEADER_SIZE = fuHeader.size
|
const HEADER_SIZE = fuHeader.size
|
||||||
@ -98,10 +103,10 @@ const SIZE_OF_32_BITS = Math.pow(2, 32)
|
|||||||
const uint32ToUint64 = fu => fu.high * SIZE_OF_32_BITS + fu.low
|
const uint32ToUint64 = fu => fu.high * SIZE_OF_32_BITS + fu.low
|
||||||
|
|
||||||
// Returns a 32 bits integer corresponding to a Vhd version.
|
// Returns a 32 bits integer corresponding to a Vhd version.
|
||||||
const getVhdVersion = (major, minor) => (major << 16) | (minor & 0x0000FFFF)
|
const getVhdVersion = (major, minor) => (major << 16) | (minor & 0x0000ffff)
|
||||||
|
|
||||||
// bytes[] bit manipulation
|
// bytes[] bit manipulation
|
||||||
const testBit = (map, bit) => map[bit >> 3] & 1 << (bit & 7)
|
const testBit = (map, bit) => map[bit >> 3] & (1 << (bit & 7))
|
||||||
const setBit = (map, bit) => {
|
const setBit = (map, bit) => {
|
||||||
map[bit >> 3] |= 1 << (bit & 7)
|
map[bit >> 3] |= 1 << (bit & 7)
|
||||||
}
|
}
|
||||||
@ -109,98 +114,95 @@ const unsetBit = (map, bit) => {
|
|||||||
map[bit >> 3] &= ~(1 << (bit & 7))
|
map[bit >> 3] &= ~(1 << (bit & 7))
|
||||||
}
|
}
|
||||||
|
|
||||||
const addOffsets = (...offsets) => offsets.reduce(
|
const addOffsets = (...offsets) =>
|
||||||
(a, b) => b == null
|
offsets.reduce(
|
||||||
? a
|
(a, b) =>
|
||||||
: typeof b === 'object'
|
b == null
|
||||||
? { bytes: a.bytes + b.bytes, bits: a.bits + b.bits }
|
? a
|
||||||
: { bytes: a.bytes + b, bits: a.bits },
|
: typeof b === 'object'
|
||||||
{ bytes: 0, bits: 0 }
|
? { bytes: a.bytes + b.bytes, bits: a.bits + b.bits }
|
||||||
)
|
: { bytes: a.bytes + b, bits: a.bits },
|
||||||
|
{ bytes: 0, bits: 0 }
|
||||||
|
)
|
||||||
|
|
||||||
const pack = (field, value, buf, offset) => {
|
const pack = (field, value, buf, offset) => {
|
||||||
field.pack(
|
field.pack(value, buf, addOffsets(field.offset, offset))
|
||||||
value,
|
|
||||||
buf,
|
|
||||||
addOffsets(field.offset, offset)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unpack = (field, buf, offset) =>
|
const unpack = (field, buf, offset) =>
|
||||||
field.unpack(
|
field.unpack(buf, addOffsets(field.offset, offset))
|
||||||
buf,
|
|
||||||
addOffsets(field.offset, offset)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const streamToNewBuffer = stream => new Promise((resolve, reject) => {
|
const streamToNewBuffer = stream =>
|
||||||
const chunks = []
|
new Promise((resolve, reject) => {
|
||||||
let length = 0
|
const chunks = []
|
||||||
|
let length = 0
|
||||||
|
|
||||||
const onData = chunk => {
|
const onData = chunk => {
|
||||||
chunks.push(chunk)
|
chunks.push(chunk)
|
||||||
length += chunk.length
|
length += chunk.length
|
||||||
}
|
}
|
||||||
stream.on('data', onData)
|
stream.on('data', onData)
|
||||||
|
|
||||||
const clean = () => {
|
const clean = () => {
|
||||||
stream.removeListener('data', onData)
|
stream.removeListener('data', onData)
|
||||||
stream.removeListener('end', onEnd)
|
stream.removeListener('end', onEnd)
|
||||||
stream.removeListener('error', onError)
|
stream.removeListener('error', onError)
|
||||||
}
|
}
|
||||||
const onEnd = () => {
|
const onEnd = () => {
|
||||||
resolve(Buffer.concat(chunks, length))
|
resolve(Buffer.concat(chunks, length))
|
||||||
clean()
|
clean()
|
||||||
}
|
}
|
||||||
stream.on('end', onEnd)
|
stream.on('end', onEnd)
|
||||||
const onError = error => {
|
const onError = error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
clean()
|
clean()
|
||||||
}
|
}
|
||||||
stream.on('error', onError)
|
stream.on('error', onError)
|
||||||
})
|
})
|
||||||
|
|
||||||
const streamToExistingBuffer = (
|
const streamToExistingBuffer = (
|
||||||
stream,
|
stream,
|
||||||
buffer,
|
buffer,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
end = buffer.length
|
end = buffer.length
|
||||||
) => new Promise((resolve, reject) => {
|
) =>
|
||||||
assert(offset >= 0)
|
new Promise((resolve, reject) => {
|
||||||
assert(end > offset)
|
assert(offset >= 0)
|
||||||
assert(end <= buffer.length)
|
assert(end > offset)
|
||||||
|
assert(end <= buffer.length)
|
||||||
|
|
||||||
let i = offset
|
let i = offset
|
||||||
|
|
||||||
const onData = chunk => {
|
const onData = chunk => {
|
||||||
const prev = i
|
const prev = i
|
||||||
i += chunk.length
|
i += chunk.length
|
||||||
|
|
||||||
if (i > end) {
|
if (i > end) {
|
||||||
return onError(new Error('too much data'))
|
return onError(new Error('too much data'))
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk.copy(buffer, prev)
|
||||||
}
|
}
|
||||||
|
stream.on('data', onData)
|
||||||
|
|
||||||
chunk.copy(buffer, prev)
|
const clean = () => {
|
||||||
}
|
stream.removeListener('data', onData)
|
||||||
stream.on('data', onData)
|
stream.removeListener('end', onEnd)
|
||||||
|
stream.removeListener('error', onError)
|
||||||
const clean = () => {
|
}
|
||||||
stream.removeListener('data', onData)
|
const onEnd = () => {
|
||||||
stream.removeListener('end', onEnd)
|
resolve(i - offset)
|
||||||
stream.removeListener('error', onError)
|
clean()
|
||||||
}
|
}
|
||||||
const onEnd = () => {
|
stream.on('end', onEnd)
|
||||||
resolve(i - offset)
|
const onError = error => {
|
||||||
clean()
|
reject(error)
|
||||||
}
|
clean()
|
||||||
stream.on('end', onEnd)
|
}
|
||||||
const onError = error => {
|
stream.on('error', onError)
|
||||||
reject(error)
|
})
|
||||||
clean()
|
|
||||||
}
|
|
||||||
stream.on('error', onError)
|
|
||||||
})
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -214,7 +216,11 @@ const computeChecksum = (struct, buf, offset = 0) => {
|
|||||||
for (let i = offset, n = checksumOffset; i < n; ++i) {
|
for (let i = offset, n = checksumOffset; i < n; ++i) {
|
||||||
sum += buf[i]
|
sum += buf[i]
|
||||||
}
|
}
|
||||||
for (let i = checksumOffset + checksumField.size, n = offset + struct.size; i < n; ++i) {
|
for (
|
||||||
|
let i = checksumOffset + checksumField.size, n = offset + struct.size;
|
||||||
|
i < n;
|
||||||
|
++i
|
||||||
|
) {
|
||||||
sum += buf[i]
|
sum += buf[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +228,8 @@ const computeChecksum = (struct, buf, offset = 0) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const verifyChecksum = (struct, buf, offset) =>
|
const verifyChecksum = (struct, buf, offset) =>
|
||||||
unpack(struct.fields.checksum, buf, offset) === computeChecksum(struct, buf, offset)
|
unpack(struct.fields.checksum, buf, offset) ===
|
||||||
|
computeChecksum(struct, buf, offset)
|
||||||
|
|
||||||
const getParentLocatorSize = parentLocatorEntry => {
|
const getParentLocatorSize = parentLocatorEntry => {
|
||||||
const { platformDataSpace } = parentLocatorEntry
|
const { platformDataSpace } = parentLocatorEntry
|
||||||
@ -231,15 +238,13 @@ const getParentLocatorSize = parentLocatorEntry => {
|
|||||||
return platformDataSpace * SECTOR_SIZE
|
return platformDataSpace * SECTOR_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
return (platformDataSpace % SECTOR_SIZE === 0)
|
return platformDataSpace % SECTOR_SIZE === 0 ? platformDataSpace : 0
|
||||||
? platformDataSpace
|
|
||||||
: 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
// Euclidean division, returns the quotient and the remainder of a / b.
|
// Euclidean division, returns the quotient and the remainder of a / b.
|
||||||
const div = (a, b) => [ Math.floor(a / b), a % b ]
|
const div = (a, b) => [Math.floor(a / b), a % b]
|
||||||
|
|
||||||
export default class Vhd {
|
export default class Vhd {
|
||||||
constructor (handler, path) {
|
constructor (handler, path) {
|
||||||
@ -263,13 +268,22 @@ export default class Vhd {
|
|||||||
assert(begin >= 0)
|
assert(begin >= 0)
|
||||||
assert(length > 0)
|
assert(length > 0)
|
||||||
|
|
||||||
return this._handler.createReadStream(this._path, {
|
return this._handler
|
||||||
end: begin + length - 1,
|
.createReadStream(this._path, {
|
||||||
start: begin,
|
end: begin + length - 1,
|
||||||
}).then(buf
|
start: begin,
|
||||||
? stream => streamToExistingBuffer(stream, buf, offset, (offset || 0) + length)
|
})
|
||||||
: streamToNewBuffer
|
.then(
|
||||||
)
|
buf
|
||||||
|
? stream =>
|
||||||
|
streamToExistingBuffer(
|
||||||
|
stream,
|
||||||
|
buf,
|
||||||
|
offset,
|
||||||
|
(offset || 0) + length
|
||||||
|
)
|
||||||
|
: streamToNewBuffer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// - if `buffer`: it is filled with 0 starting from `offset`, and
|
// - if `buffer`: it is filled with 0 starting from `offset`, and
|
||||||
@ -296,7 +310,7 @@ export default class Vhd {
|
|||||||
assert(block < this._header.maxTableEntries)
|
assert(block < this._header.maxTableEntries)
|
||||||
|
|
||||||
const blockAddr = this._blockAllocationTable[block]
|
const blockAddr = this._blockAllocationTable[block]
|
||||||
if (blockAddr !== 0xFFFFFFFF) {
|
if (blockAddr !== 0xffffffff) {
|
||||||
return blockAddr * SECTOR_SIZE
|
return blockAddr * SECTOR_SIZE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,7 +339,8 @@ export default class Vhd {
|
|||||||
assert(sectorsPerBlock % 1 === 0)
|
assert(sectorsPerBlock % 1 === 0)
|
||||||
|
|
||||||
// 1 bit per sector, rounded up to full sectors
|
// 1 bit per sector, rounded up to full sectors
|
||||||
this._blockBitmapSize = Math.ceil(sectorsPerBlock / 8 / SECTOR_SIZE) * SECTOR_SIZE
|
this._blockBitmapSize =
|
||||||
|
Math.ceil(sectorsPerBlock / 8 / SECTOR_SIZE) * SECTOR_SIZE
|
||||||
assert(this._blockBitmapSize === SECTOR_SIZE)
|
assert(this._blockBitmapSize === SECTOR_SIZE)
|
||||||
|
|
||||||
this._footer = footer
|
this._footer = footer
|
||||||
@ -368,10 +383,10 @@ export default class Vhd {
|
|||||||
const blockBitmapSize = this._blockBitmapSize
|
const blockBitmapSize = this._blockBitmapSize
|
||||||
const parent = this._parent
|
const parent = this._parent
|
||||||
|
|
||||||
if (blockAddr && (
|
if (
|
||||||
!parent ||
|
blockAddr &&
|
||||||
testBit(await this._read(blockAddr, blockBitmapSize), sector)
|
(!parent || testBit(await this._read(blockAddr, blockBitmapSize), sector))
|
||||||
)) {
|
) {
|
||||||
return this._read(
|
return this._read(
|
||||||
blockAddr + blockBitmapSize + sector * SECTOR_SIZE + begin,
|
blockAddr + blockBitmapSize + sector * SECTOR_SIZE + begin,
|
||||||
length,
|
length,
|
||||||
@ -402,12 +417,17 @@ export default class Vhd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return this._read(blockAddr + this._blockBitmapSize + begin, length, buf, offset)
|
return this._read(
|
||||||
|
blockAddr + this._blockBitmapSize + begin,
|
||||||
|
length,
|
||||||
|
buf,
|
||||||
|
offset
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: we should read as many sectors in a single pass as
|
// FIXME: we should read as many sectors in a single pass as
|
||||||
// possible for maximum perf.
|
// possible for maximum perf.
|
||||||
const [ sector, beginInSector ] = div(begin, SECTOR_SIZE)
|
const [sector, beginInSector] = div(begin, SECTOR_SIZE)
|
||||||
return this._readBlockSector(
|
return this._readBlockSector(
|
||||||
block,
|
block,
|
||||||
sector,
|
sector,
|
||||||
@ -428,7 +448,7 @@ export default class Vhd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { blockSize } = this._header
|
const { blockSize } = this._header
|
||||||
const [ block, beginInBlock ] = div(begin, blockSize)
|
const [block, beginInBlock] = div(begin, blockSize)
|
||||||
|
|
||||||
return this._readBlock(
|
return this._readBlock(
|
||||||
block,
|
block,
|
||||||
|
@ -29,13 +29,15 @@ exports.createOutputStream = path => {
|
|||||||
exports.resolveRef = (xapi, type, refOrUuidOrNameLabel) =>
|
exports.resolveRef = (xapi, type, refOrUuidOrNameLabel) =>
|
||||||
isOpaqueRef(refOrUuidOrNameLabel)
|
isOpaqueRef(refOrUuidOrNameLabel)
|
||||||
? refOrUuidOrNameLabel
|
? refOrUuidOrNameLabel
|
||||||
: xapi.call(`${type}.get_by_uuid`, refOrUuidOrNameLabel).catch(
|
: xapi.call(`${type}.get_by_uuid`, refOrUuidOrNameLabel).catch(() =>
|
||||||
() => xapi.call(`${type}.get_by_name_label`, refOrUuidOrNameLabel).then(
|
xapi
|
||||||
refs => {
|
.call(`${type}.get_by_name_label`, refOrUuidOrNameLabel)
|
||||||
|
.then(refs => {
|
||||||
if (refs.length === 1) {
|
if (refs.length === 1) {
|
||||||
return refs[0]
|
return refs[0]
|
||||||
}
|
}
|
||||||
throw new Error(`no single match for ${type} with name label ${refOrUuidOrNameLabel}`)
|
throw new Error(
|
||||||
}
|
`no single match for ${type} with name label ${refOrUuidOrNameLabel}`
|
||||||
)
|
)
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
@ -56,7 +56,7 @@ const main = async args => {
|
|||||||
|
|
||||||
let auth
|
let auth
|
||||||
if (opts._.length > 1) {
|
if (opts._.length > 1) {
|
||||||
const [ , user, password = await askPassword() ] = opts._
|
const [, user, password = await askPassword()] = opts._
|
||||||
auth = { user, password }
|
auth = { user, password }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,11 +86,11 @@ const main = async args => {
|
|||||||
|
|
||||||
// Make the REPL waits for promise completion.
|
// Make the REPL waits for promise completion.
|
||||||
repl.eval = (evaluate => (cmd, context, filename, cb) => {
|
repl.eval = (evaluate => (cmd, context, filename, cb) => {
|
||||||
fromCallback(cb => {
|
;fromCallback(cb => {
|
||||||
evaluate.call(repl, cmd, context, filename, cb)
|
evaluate.call(repl, cmd, context, filename, cb)
|
||||||
}).then(value =>
|
})
|
||||||
isArray(value) ? Promise.all(value) : value
|
.then(value => (isArray(value) ? Promise.all(value) : value))
|
||||||
)::asCallback(cb)
|
::asCallback(cb)
|
||||||
})(repl.eval)
|
})(repl.eval)
|
||||||
|
|
||||||
await eventToPromise(repl, 'exit')
|
await eventToPromise(repl, 'exit')
|
||||||
|
@ -55,7 +55,7 @@ const NETWORK_ERRORS = {
|
|||||||
ETIMEDOUT: true,
|
ETIMEDOUT: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNetworkError = ({code}) => NETWORK_ERRORS[code]
|
const isNetworkError = ({ code }) => NETWORK_ERRORS[code]
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
@ -64,17 +64,17 @@ const XAPI_NETWORK_ERRORS = {
|
|||||||
HOST_HAS_NO_MANAGEMENT_IP: true,
|
HOST_HAS_NO_MANAGEMENT_IP: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const isXapiNetworkError = ({code}) => XAPI_NETWORK_ERRORS[code]
|
const isXapiNetworkError = ({ code }) => XAPI_NETWORK_ERRORS[code]
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
const areEventsLost = ({code}) => code === 'EVENTS_LOST'
|
const areEventsLost = ({ code }) => code === 'EVENTS_LOST'
|
||||||
|
|
||||||
const isHostSlave = ({code}) => code === 'HOST_IS_SLAVE'
|
const isHostSlave = ({ code }) => code === 'HOST_IS_SLAVE'
|
||||||
|
|
||||||
const isMethodUnknown = ({code}) => code === 'MESSAGE_METHOD_UNKNOWN'
|
const isMethodUnknown = ({ code }) => code === 'MESSAGE_METHOD_UNKNOWN'
|
||||||
|
|
||||||
const isSessionInvalid = ({code}) => code === 'SESSION_INVALID'
|
const isSessionInvalid = ({ code }) => code === 'SESSION_INVALID'
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
@ -93,8 +93,9 @@ class XapiError extends BaseError {
|
|||||||
|
|
||||||
export const wrapError = error => {
|
export const wrapError = error => {
|
||||||
let code, params
|
let code, params
|
||||||
if (isArray(error)) { // < XenServer 7.3
|
if (isArray(error)) {
|
||||||
[ code, ...params ] = error
|
// < XenServer 7.3
|
||||||
|
;[code, ...params] = error
|
||||||
} else {
|
} else {
|
||||||
code = error.message
|
code = error.message
|
||||||
params = error.data
|
params = error.data
|
||||||
@ -111,7 +112,7 @@ const parseUrl = url => {
|
|||||||
throw new Error('invalid URL: ' + url)
|
throw new Error('invalid URL: ' + url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ , protocol = 'https:', username, password, hostname, port ] = matches
|
const [, protocol = 'https:', username, password, hostname, port] = matches
|
||||||
return { protocol, username, password, hostname, port }
|
return { protocol, username, password, hostname, port }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,17 +129,13 @@ const {
|
|||||||
|
|
||||||
const OPAQUE_REF_PREFIX = 'OpaqueRef:'
|
const OPAQUE_REF_PREFIX = 'OpaqueRef:'
|
||||||
export const isOpaqueRef = value =>
|
export const isOpaqueRef = value =>
|
||||||
typeof value === 'string' &&
|
typeof value === 'string' && startsWith(value, OPAQUE_REF_PREFIX)
|
||||||
startsWith(value, OPAQUE_REF_PREFIX)
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
const RE_READ_ONLY_METHOD = /^[^.]+\.get_/
|
const RE_READ_ONLY_METHOD = /^[^.]+\.get_/
|
||||||
const isReadOnlyCall = (method, args) => (
|
const isReadOnlyCall = (method, args) =>
|
||||||
args.length === 1 &&
|
args.length === 1 && isOpaqueRef(args[0]) && RE_READ_ONLY_METHOD.test(method)
|
||||||
isOpaqueRef(args[0]) &&
|
|
||||||
RE_READ_ONLY_METHOD.test(method)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Prepare values before passing them to the XenAPI:
|
// Prepare values before passing them to the XenAPI:
|
||||||
//
|
//
|
||||||
@ -178,17 +175,17 @@ const EMPTY_ARRAY = freezeObject([])
|
|||||||
const getTaskResult = (task, onSuccess, onFailure) => {
|
const getTaskResult = (task, onSuccess, onFailure) => {
|
||||||
const { status } = task
|
const { status } = task
|
||||||
if (status === 'cancelled') {
|
if (status === 'cancelled') {
|
||||||
return [ onFailure(new Cancel('task canceled')) ]
|
return [onFailure(new Cancel('task canceled'))]
|
||||||
}
|
}
|
||||||
if (status === 'failure') {
|
if (status === 'failure') {
|
||||||
return [ onFailure(wrapError(task.error_info)) ]
|
return [onFailure(wrapError(task.error_info))]
|
||||||
}
|
}
|
||||||
if (status === 'success') {
|
if (status === 'success') {
|
||||||
// the result might be:
|
// the result might be:
|
||||||
// - empty string
|
// - empty string
|
||||||
// - an opaque reference
|
// - an opaque reference
|
||||||
// - an XML-RPC value
|
// - an XML-RPC value
|
||||||
return [ onSuccess(task.result) ]
|
return [onSuccess(task.result)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +206,7 @@ export class Xapi extends EventEmitter {
|
|||||||
this._pool = null
|
this._pool = null
|
||||||
this._readOnly = Boolean(opts.readOnly)
|
this._readOnly = Boolean(opts.readOnly)
|
||||||
this._sessionId = null
|
this._sessionId = null
|
||||||
const url = this._url = parseUrl(opts.url)
|
const url = (this._url = parseUrl(opts.url))
|
||||||
|
|
||||||
if (this._auth === undefined) {
|
if (this._auth === undefined) {
|
||||||
const user = url.username
|
const user = url.username
|
||||||
@ -224,9 +221,7 @@ export class Xapi extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts.watchEvents !== false) {
|
if (opts.watchEvents !== false) {
|
||||||
this._debounce = opts.debounce == null
|
this._debounce = opts.debounce == null ? 200 : opts.debounce
|
||||||
? 200
|
|
||||||
: opts.debounce
|
|
||||||
|
|
||||||
this._eventWatchers = createObject(null)
|
this._eventWatchers = createObject(null)
|
||||||
|
|
||||||
@ -237,7 +232,7 @@ export class Xapi extends EventEmitter {
|
|||||||
|
|
||||||
this._nTasks = 0
|
this._nTasks = 0
|
||||||
|
|
||||||
const objects = this._objects = new Collection()
|
const objects = (this._objects = new Collection())
|
||||||
objects.getKey = getKey
|
objects.getKey = getKey
|
||||||
|
|
||||||
this._objectsByRefs = createObject(null)
|
this._objectsByRefs = createObject(null)
|
||||||
@ -286,13 +281,7 @@ export class Xapi extends EventEmitter {
|
|||||||
get status () {
|
get status () {
|
||||||
const id = this._sessionId
|
const id = this._sessionId
|
||||||
|
|
||||||
return id
|
return id ? (id === CONNECTING ? CONNECTING : CONNECTED) : DISCONNECTED
|
||||||
? (
|
|
||||||
id === CONNECTING
|
|
||||||
? CONNECTING
|
|
||||||
: CONNECTED
|
|
||||||
)
|
|
||||||
: DISCONNECTED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get _humanId () {
|
get _humanId () {
|
||||||
@ -305,36 +294,46 @@ export class Xapi extends EventEmitter {
|
|||||||
barrier (ref) {
|
barrier (ref) {
|
||||||
const eventWatchers = this._eventWatchers
|
const eventWatchers = this._eventWatchers
|
||||||
if (eventWatchers === undefined) {
|
if (eventWatchers === undefined) {
|
||||||
return Promise.reject(new Error('Xapi#barrier() requires events watching'))
|
return Promise.reject(
|
||||||
|
new Error('Xapi#barrier() requires events watching')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = `xo:barrier:${Math.random().toString(36).slice(2)}`
|
const key = `xo:barrier:${Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.slice(2)}`
|
||||||
const poolRef = this._pool.$ref
|
const poolRef = this._pool.$ref
|
||||||
|
|
||||||
const { promise, resolve } = defer()
|
const { promise, resolve } = defer()
|
||||||
eventWatchers[key] = resolve
|
eventWatchers[key] = resolve
|
||||||
|
|
||||||
return this._sessionCall(
|
return this._sessionCall('pool.add_to_other_config', [
|
||||||
'pool.add_to_other_config',
|
poolRef,
|
||||||
[ poolRef, key, '' ]
|
key,
|
||||||
).then(() => promise.then(() => {
|
'',
|
||||||
this._sessionCall('pool.remove_from_other_config', [ poolRef, key ]).catch(noop)
|
]).then(() =>
|
||||||
|
promise.then(() => {
|
||||||
|
this._sessionCall('pool.remove_from_other_config', [
|
||||||
|
poolRef,
|
||||||
|
key,
|
||||||
|
]).catch(noop)
|
||||||
|
|
||||||
if (ref === undefined) {
|
if (ref === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// support legacy params (type, ref)
|
// support legacy params (type, ref)
|
||||||
if (arguments.length === 2) {
|
if (arguments.length === 2) {
|
||||||
ref = arguments[1]
|
ref = arguments[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getObjectByRef(ref)
|
return this.getObjectByRef(ref)
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
connect () {
|
connect () {
|
||||||
const {status} = this
|
const { status } = this
|
||||||
|
|
||||||
if (status === CONNECTED) {
|
if (status === CONNECTED) {
|
||||||
return Promise.reject(new Error('already connected'))
|
return Promise.reject(new Error('already connected'))
|
||||||
@ -378,7 +377,7 @@ export class Xapi extends EventEmitter {
|
|||||||
return Promise.reject(new Error('already disconnected'))
|
return Promise.reject(new Error('already disconnected'))
|
||||||
}
|
}
|
||||||
|
|
||||||
this._transportCall('session.logout', [ this._sessionId ]).catch(noop)
|
this._transportCall('session.logout', [this._sessionId]).catch(noop)
|
||||||
|
|
||||||
this._sessionId = null
|
this._sessionId = null
|
||||||
|
|
||||||
@ -434,13 +433,10 @@ export class Xapi extends EventEmitter {
|
|||||||
// this lib), UUID (unique identifier that some objects have) or
|
// this lib), UUID (unique identifier that some objects have) or
|
||||||
// opaque reference (internal to XAPI).
|
// opaque reference (internal to XAPI).
|
||||||
getObject (idOrUuidOrRef, defaultValue) {
|
getObject (idOrUuidOrRef, defaultValue) {
|
||||||
const object = typeof idOrUuidOrRef === 'string'
|
const object =
|
||||||
? (
|
typeof idOrUuidOrRef === 'string'
|
||||||
// if there is an UUID, it is also the $id.
|
? this._objects.all[idOrUuidOrRef] || this._objectsByRefs[idOrUuidOrRef]
|
||||||
this._objects.all[idOrUuidOrRef] ||
|
: this._objects.all[idOrUuidOrRef.$id]
|
||||||
this._objectsByRefs[idOrUuidOrRef]
|
|
||||||
)
|
|
||||||
: this._objects.all[idOrUuidOrRef.$id]
|
|
||||||
|
|
||||||
if (object) return object
|
if (object) return object
|
||||||
|
|
||||||
@ -479,158 +475,147 @@ export class Xapi extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@cancelable
|
@cancelable
|
||||||
getResource ($cancelToken, pathname, {
|
getResource ($cancelToken, pathname, { host, query, task }) {
|
||||||
host,
|
return this._autoTask(task, `Xapi#getResource ${pathname}`).then(
|
||||||
query,
|
taskRef => {
|
||||||
task,
|
query = { ...query, session_id: this.sessionId }
|
||||||
}) {
|
let taskResult
|
||||||
return this._autoTask(
|
if (taskRef !== undefined) {
|
||||||
task,
|
query.task_id = taskRef
|
||||||
`Xapi#getResource ${pathname}`
|
taskResult = this.watchTask(taskRef)
|
||||||
).then(taskRef => {
|
|
||||||
query = { ...query, session_id: this.sessionId }
|
|
||||||
let taskResult
|
|
||||||
if (taskRef !== undefined) {
|
|
||||||
query.task_id = taskRef
|
|
||||||
taskResult = this.watchTask(taskRef)
|
|
||||||
|
|
||||||
if (typeof $cancelToken.addHandler === 'function') {
|
if (typeof $cancelToken.addHandler === 'function') {
|
||||||
$cancelToken.addHandler(() => taskResult)
|
$cancelToken.addHandler(() => taskResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let promise = httpRequest(
|
let promise = httpRequest(
|
||||||
$cancelToken,
|
$cancelToken,
|
||||||
this._url,
|
this._url,
|
||||||
host && {
|
host && {
|
||||||
hostname: this.getObject(host).address,
|
hostname: this.getObject(host).address,
|
||||||
},
|
|
||||||
{
|
|
||||||
pathname,
|
|
||||||
query,
|
|
||||||
rejectUnauthorized: !this._allowUnauthorized,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (taskResult !== undefined) {
|
|
||||||
promise = promise.then(response => {
|
|
||||||
response.task = taskResult
|
|
||||||
return response
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@cancelable
|
|
||||||
putResource ($cancelToken, body, pathname, {
|
|
||||||
host,
|
|
||||||
query,
|
|
||||||
task,
|
|
||||||
} = {}) {
|
|
||||||
if (this._readOnly) {
|
|
||||||
return Promise.reject(new Error(new Error('cannot put resource in read only mode')))
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._autoTask(
|
|
||||||
task,
|
|
||||||
`Xapi#putResource ${pathname}`
|
|
||||||
).then(taskRef => {
|
|
||||||
query = { ...query, session_id: this.sessionId }
|
|
||||||
|
|
||||||
let taskResult
|
|
||||||
if (taskRef !== undefined) {
|
|
||||||
query.task_id = taskRef
|
|
||||||
taskResult = this.watchTask(taskRef)
|
|
||||||
|
|
||||||
if (typeof $cancelToken.addHandler === 'function') {
|
|
||||||
$cancelToken.addHandler(() => taskResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = {}
|
|
||||||
|
|
||||||
// Xen API does not support chunk encoding.
|
|
||||||
const isStream = typeof body.pipe === 'function'
|
|
||||||
const { length } = body
|
|
||||||
if (isStream && length === undefined) {
|
|
||||||
// add a fake huge content length (1 PiB)
|
|
||||||
headers['content-length'] = '1125899906842624'
|
|
||||||
}
|
|
||||||
|
|
||||||
const doRequest = override => httpRequest.put(
|
|
||||||
$cancelToken,
|
|
||||||
this._url,
|
|
||||||
host && {
|
|
||||||
hostname: this.getObject(host).address,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
body,
|
|
||||||
headers,
|
|
||||||
pathname,
|
|
||||||
query,
|
|
||||||
rejectUnauthorized: !this._allowUnauthorized,
|
|
||||||
},
|
|
||||||
override
|
|
||||||
)
|
|
||||||
|
|
||||||
const promise = isStream
|
|
||||||
|
|
||||||
// dummy request to probe for a redirection before consuming body
|
|
||||||
? doRequest({
|
|
||||||
body: '',
|
|
||||||
|
|
||||||
// omit task_id because this request will fail on purpose
|
|
||||||
query: 'task_id' in query
|
|
||||||
? omit(query, 'task_id')
|
|
||||||
: query,
|
|
||||||
|
|
||||||
maxRedirects: 0,
|
|
||||||
}).then(
|
|
||||||
response => {
|
|
||||||
response.req.abort()
|
|
||||||
return doRequest()
|
|
||||||
},
|
},
|
||||||
error => {
|
{
|
||||||
let response
|
pathname,
|
||||||
if (error != null && (response = error.response) != null) {
|
query,
|
||||||
response.req.abort()
|
rejectUnauthorized: !this._allowUnauthorized,
|
||||||
|
|
||||||
const { headers: { location }, statusCode } = response
|
|
||||||
if (statusCode === 302 && location !== undefined) {
|
|
||||||
return doRequest(location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// http-request-plus correctly handle redirects if body is not a stream
|
|
||||||
: doRequest()
|
|
||||||
|
|
||||||
return promise.then(response => {
|
|
||||||
const { req } = response
|
|
||||||
|
|
||||||
if (taskResult !== undefined) {
|
if (taskResult !== undefined) {
|
||||||
taskResult = taskResult.catch(error => {
|
promise = promise.then(response => {
|
||||||
error.url = response.url
|
response.task = taskResult
|
||||||
throw error
|
return response
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.finished) {
|
return promise
|
||||||
req.abort()
|
}
|
||||||
return taskResult
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@cancelable
|
||||||
|
putResource ($cancelToken, body, pathname, { host, query, task } = {}) {
|
||||||
|
if (this._readOnly) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(new Error('cannot put resource in read only mode'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._autoTask(task, `Xapi#putResource ${pathname}`).then(
|
||||||
|
taskRef => {
|
||||||
|
query = { ...query, session_id: this.sessionId }
|
||||||
|
|
||||||
|
let taskResult
|
||||||
|
if (taskRef !== undefined) {
|
||||||
|
query.task_id = taskRef
|
||||||
|
taskResult = this.watchTask(taskRef)
|
||||||
|
|
||||||
|
if (typeof $cancelToken.addHandler === 'function') {
|
||||||
|
$cancelToken.addHandler(() => taskResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fromEvents(req, ['close', 'finish']).then(() => {
|
const headers = {}
|
||||||
req.abort()
|
|
||||||
return taskResult
|
// Xen API does not support chunk encoding.
|
||||||
|
const isStream = typeof body.pipe === 'function'
|
||||||
|
const { length } = body
|
||||||
|
if (isStream && length === undefined) {
|
||||||
|
// add a fake huge content length (1 PiB)
|
||||||
|
headers['content-length'] = '1125899906842624'
|
||||||
|
}
|
||||||
|
|
||||||
|
const doRequest = override =>
|
||||||
|
httpRequest.put(
|
||||||
|
$cancelToken,
|
||||||
|
this._url,
|
||||||
|
host && {
|
||||||
|
hostname: this.getObject(host).address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body,
|
||||||
|
headers,
|
||||||
|
pathname,
|
||||||
|
query,
|
||||||
|
rejectUnauthorized: !this._allowUnauthorized,
|
||||||
|
},
|
||||||
|
override
|
||||||
|
)
|
||||||
|
|
||||||
|
// if a stream, sends a dummy request to probe for a
|
||||||
|
// redirection before consuming body
|
||||||
|
const promise = isStream
|
||||||
|
? doRequest({
|
||||||
|
body: '',
|
||||||
|
|
||||||
|
// omit task_id because this request will fail on purpose
|
||||||
|
query: 'task_id' in query ? omit(query, 'task_id') : query,
|
||||||
|
|
||||||
|
maxRedirects: 0,
|
||||||
|
}).then(
|
||||||
|
response => {
|
||||||
|
response.req.abort()
|
||||||
|
return doRequest()
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
let response
|
||||||
|
if (error != null && (response = error.response) != null) {
|
||||||
|
response.req.abort()
|
||||||
|
|
||||||
|
const { headers: { location }, statusCode } = response
|
||||||
|
if (statusCode === 302 && location !== undefined) {
|
||||||
|
return doRequest(location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: doRequest()
|
||||||
|
|
||||||
|
return promise.then(response => {
|
||||||
|
const { req } = response
|
||||||
|
|
||||||
|
if (taskResult !== undefined) {
|
||||||
|
taskResult = taskResult.catch(error => {
|
||||||
|
error.url = response.url
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.finished) {
|
||||||
|
req.abort()
|
||||||
|
return taskResult
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromEvents(req, ['close', 'finish']).then(() => {
|
||||||
|
req.abort()
|
||||||
|
return taskResult
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
watchTask (ref) {
|
watchTask (ref) {
|
||||||
@ -692,22 +677,24 @@ export class Xapi extends EventEmitter {
|
|||||||
newArgs.push.apply(newArgs, args)
|
newArgs.push.apply(newArgs, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._transportCall(method, newArgs)
|
return this._transportCall(method, newArgs)::pCatch(
|
||||||
::pCatch(isSessionInvalid, () => {
|
isSessionInvalid,
|
||||||
|
() => {
|
||||||
// XAPI is sometimes reinitialized and sessions are lost.
|
// XAPI is sometimes reinitialized and sessions are lost.
|
||||||
// Try to login again.
|
// Try to login again.
|
||||||
debug('%s: the session has been reinitialized', this._humanId)
|
debug('%s: the session has been reinitialized', this._humanId)
|
||||||
|
|
||||||
this._sessionId = null
|
this._sessionId = null
|
||||||
return this.connect().then(() => this._sessionCall(method, args))
|
return this.connect().then(() => this._sessionCall(method, args))
|
||||||
})
|
}
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_addObject (type, ref, object) {
|
_addObject (type, ref, object) {
|
||||||
const {_objectsByRefs: objectsByRefs} = this
|
const { _objectsByRefs: objectsByRefs } = this
|
||||||
|
|
||||||
const reservedKeys = {
|
const reservedKeys = {
|
||||||
id: true,
|
id: true,
|
||||||
@ -715,9 +702,8 @@ export class Xapi extends EventEmitter {
|
|||||||
ref: true,
|
ref: true,
|
||||||
type: true,
|
type: true,
|
||||||
}
|
}
|
||||||
const getKey = (key, obj) => reservedKeys[key] && obj === object
|
const getKey = (key, obj) =>
|
||||||
? `$$${key}`
|
reservedKeys[key] && obj === object ? `$$${key}` : `$${key}`
|
||||||
: `$${key}`
|
|
||||||
|
|
||||||
// Creates resolved properties.
|
// Creates resolved properties.
|
||||||
forEach(object, function resolveObject (value, key, object) {
|
forEach(object, function resolveObject (value, key, object) {
|
||||||
@ -736,7 +722,7 @@ export class Xapi extends EventEmitter {
|
|||||||
} else if (isOpaqueRef(value[0])) {
|
} else if (isOpaqueRef(value[0])) {
|
||||||
// This is an array of refs.
|
// This is an array of refs.
|
||||||
defineProperty(object, getKey(key, object), {
|
defineProperty(object, getKey(key, object), {
|
||||||
get: () => freezeObject(map(value, (ref) => objectsByRefs[ref])),
|
get: () => freezeObject(map(value, ref => objectsByRefs[ref])),
|
||||||
})
|
})
|
||||||
|
|
||||||
freezeObject(value)
|
freezeObject(value)
|
||||||
@ -836,38 +822,40 @@ export class Xapi extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_watchEvents () {
|
_watchEvents () {
|
||||||
const loop = () => this.status === CONNECTED && this._sessionCall('event.from', [
|
const loop = () =>
|
||||||
['*'],
|
this.status === CONNECTED &&
|
||||||
this._fromToken,
|
this._sessionCall('event.from', [
|
||||||
60 + 0.1, // Force float.
|
['*'],
|
||||||
]).then(onSuccess, onFailure)
|
this._fromToken,
|
||||||
|
60 + 0.1, // Force float.
|
||||||
|
]).then(onSuccess, onFailure)
|
||||||
|
|
||||||
const onSuccess = ({ events, token, valid_ref_counts: { task } }) => {
|
const onSuccess = ({ events, token, valid_ref_counts: { task } }) => {
|
||||||
this._fromToken = token
|
this._fromToken = token
|
||||||
this._processEvents(events)
|
this._processEvents(events)
|
||||||
|
|
||||||
if (task !== this._nTasks) {
|
if (task !== this._nTasks) {
|
||||||
this._sessionCall('task.get_all_records').then(tasks => {
|
this._sessionCall('task.get_all_records')
|
||||||
const toRemove = new Set()
|
.then(tasks => {
|
||||||
forEach(this.objects.all, object => {
|
const toRemove = new Set()
|
||||||
if (object.$type === 'task') {
|
forEach(this.objects.all, object => {
|
||||||
toRemove.add(object.$ref)
|
if (object.$type === 'task') {
|
||||||
}
|
toRemove.add(object.$ref)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
forEach(tasks, (task, ref) => {
|
||||||
|
toRemove.delete(ref)
|
||||||
|
this._addObject('task', ref, task)
|
||||||
|
})
|
||||||
|
toRemove.forEach(ref => {
|
||||||
|
this._removeObject('task', ref)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
forEach(tasks, (task, ref) => {
|
.catch(noop)
|
||||||
toRemove.delete(ref)
|
|
||||||
this._addObject('task', ref, task)
|
|
||||||
})
|
|
||||||
toRemove.forEach(ref => {
|
|
||||||
this._removeObject('task', ref)
|
|
||||||
})
|
|
||||||
}).catch(noop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const debounce = this._debounce
|
const debounce = this._debounce
|
||||||
return debounce != null
|
return debounce != null ? pDelay(debounce).then(loop) : loop()
|
||||||
? pDelay(debounce).then(loop)
|
|
||||||
: loop()
|
|
||||||
}
|
}
|
||||||
const onFailure = error => {
|
const onFailure = error => {
|
||||||
if (areEventsLost(error)) {
|
if (areEventsLost(error)) {
|
||||||
@ -906,41 +894,43 @@ export class Xapi extends EventEmitter {
|
|||||||
::/\.get_all_records$/.test
|
::/\.get_all_records$/.test
|
||||||
)
|
)
|
||||||
|
|
||||||
return Promise.all(map(
|
return Promise.all(
|
||||||
getAllRecordsMethods,
|
map(getAllRecordsMethods, method =>
|
||||||
method => this._sessionCall(method).then(
|
this._sessionCall(method).then(
|
||||||
objects => {
|
objects => {
|
||||||
const type = method.slice(0, method.indexOf('.')).toLowerCase()
|
const type = method.slice(0, method.indexOf('.')).toLowerCase()
|
||||||
forEach(objects, (object, ref) => {
|
forEach(objects, (object, ref) => {
|
||||||
this._addObject(type, ref, object)
|
this._addObject(type, ref, object)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
if (error.code !== 'MESSAGE_REMOVED') {
|
if (error.code !== 'MESSAGE_REMOVED') {
|
||||||
throw error
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const watchEvents = () => this._sessionCall('event.register', [ ['*'] ]).then(loop)
|
const watchEvents = () =>
|
||||||
|
this._sessionCall('event.register', [['*']]).then(loop)
|
||||||
|
|
||||||
const loop = () => this.status === CONNECTED && this._sessionCall('event.next').then(onSuccess, onFailure)
|
const loop = () =>
|
||||||
|
this.status === CONNECTED &&
|
||||||
|
this._sessionCall('event.next').then(onSuccess, onFailure)
|
||||||
|
|
||||||
const onSuccess = events => {
|
const onSuccess = events => {
|
||||||
this._processEvents(events)
|
this._processEvents(events)
|
||||||
|
|
||||||
const debounce = this._debounce
|
const debounce = this._debounce
|
||||||
return debounce == null
|
return debounce == null ? loop() : pDelay(debounce).then(loop)
|
||||||
? loop()
|
|
||||||
: pDelay(debounce).then(loop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFailure = error => {
|
const onFailure = error => {
|
||||||
if (areEventsLost(error)) {
|
if (areEventsLost(error)) {
|
||||||
return this._sessionCall('event.unregister', [ ['*'] ]).then(watchEvents)
|
return this._sessionCall('event.unregister', [['*']]).then(watchEvents)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
@ -950,85 +940,106 @@ export class Xapi extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Xapi.prototype._transportCall = reduce([
|
Xapi.prototype._transportCall = reduce(
|
||||||
function (method, args) {
|
[
|
||||||
return this._call(method, args).catch(error => {
|
function (method, args) {
|
||||||
if (!(error instanceof Error)) {
|
return this._call(method, args).catch(error => {
|
||||||
error = wrapError(error)
|
if (!(error instanceof Error)) {
|
||||||
}
|
error = wrapError(error)
|
||||||
|
|
||||||
error.method = method
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
},
|
|
||||||
call => function () {
|
|
||||||
let iterator // lazily created
|
|
||||||
const loop = () => call.apply(this, arguments)
|
|
||||||
::pCatch(isNetworkError, isXapiNetworkError, error => {
|
|
||||||
if (iterator === undefined) {
|
|
||||||
iterator = fibonacci().clamp(undefined, 60).take(10).toMs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cursor = iterator.next()
|
error.method = method
|
||||||
if (!cursor.done) {
|
|
||||||
// TODO: ability to cancel the connection
|
|
||||||
// TODO: ability to force immediate reconnection
|
|
||||||
|
|
||||||
const delay = cursor.value
|
|
||||||
debug('%s: network error %s, next try in %s ms', this._humanId, error.code, delay)
|
|
||||||
return pDelay(delay).then(loop)
|
|
||||||
}
|
|
||||||
|
|
||||||
debug('%s: network error %s, aborting', this._humanId, error.code)
|
|
||||||
|
|
||||||
// mark as disconnected
|
|
||||||
this.disconnect()::pCatch(noop)
|
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
return loop()
|
},
|
||||||
},
|
call =>
|
||||||
call => function loop () {
|
function () {
|
||||||
return call.apply(this, arguments)
|
let iterator // lazily created
|
||||||
::pCatch(isHostSlave, ({params: [master]}) => {
|
const loop = () =>
|
||||||
debug('%s: host is slave, attempting to connect at %s', this._humanId, master)
|
call
|
||||||
|
.apply(this, arguments)
|
||||||
|
::pCatch(isNetworkError, isXapiNetworkError, error => {
|
||||||
|
if (iterator === undefined) {
|
||||||
|
iterator = fibonacci()
|
||||||
|
.clamp(undefined, 60)
|
||||||
|
.take(10)
|
||||||
|
.toMs()
|
||||||
|
}
|
||||||
|
|
||||||
const newUrl = {
|
const cursor = iterator.next()
|
||||||
...this._url,
|
if (!cursor.done) {
|
||||||
hostname: master,
|
// TODO: ability to cancel the connection
|
||||||
}
|
// TODO: ability to force immediate reconnection
|
||||||
this.emit('redirect', newUrl)
|
|
||||||
this._url = newUrl
|
|
||||||
|
|
||||||
return loop.apply(this, arguments)
|
const delay = cursor.value
|
||||||
})
|
debug(
|
||||||
},
|
'%s: network error %s, next try in %s ms',
|
||||||
call => function (method) {
|
this._humanId,
|
||||||
const startTime = Date.now()
|
error.code,
|
||||||
return call.apply(this, arguments).then(
|
delay
|
||||||
result => {
|
)
|
||||||
debug(
|
return pDelay(delay).then(loop)
|
||||||
'%s: %s(...) [%s] ==> %s',
|
}
|
||||||
this._humanId,
|
|
||||||
method,
|
debug('%s: network error %s, aborting', this._humanId, error.code)
|
||||||
ms(Date.now() - startTime),
|
|
||||||
kindOf(result)
|
// mark as disconnected
|
||||||
)
|
this.disconnect()::pCatch(noop)
|
||||||
return result
|
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
return loop()
|
||||||
},
|
},
|
||||||
error => {
|
call =>
|
||||||
debug(
|
function loop () {
|
||||||
'%s: %s(...) [%s] =!> %s',
|
return call
|
||||||
this._humanId,
|
.apply(this, arguments)
|
||||||
method,
|
::pCatch(isHostSlave, ({ params: [master] }) => {
|
||||||
ms(Date.now() - startTime),
|
debug(
|
||||||
error
|
'%s: host is slave, attempting to connect at %s',
|
||||||
|
this._humanId,
|
||||||
|
master
|
||||||
|
)
|
||||||
|
|
||||||
|
const newUrl = {
|
||||||
|
...this._url,
|
||||||
|
hostname: master,
|
||||||
|
}
|
||||||
|
this.emit('redirect', newUrl)
|
||||||
|
this._url = newUrl
|
||||||
|
|
||||||
|
return loop.apply(this, arguments)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
call =>
|
||||||
|
function (method) {
|
||||||
|
const startTime = Date.now()
|
||||||
|
return call.apply(this, arguments).then(
|
||||||
|
result => {
|
||||||
|
debug(
|
||||||
|
'%s: %s(...) [%s] ==> %s',
|
||||||
|
this._humanId,
|
||||||
|
method,
|
||||||
|
ms(Date.now() - startTime),
|
||||||
|
kindOf(result)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
debug(
|
||||||
|
'%s: %s(...) [%s] =!> %s',
|
||||||
|
this._humanId,
|
||||||
|
method,
|
||||||
|
ms(Date.now() - startTime),
|
||||||
|
error
|
||||||
|
)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
)
|
)
|
||||||
throw error
|
},
|
||||||
}
|
],
|
||||||
)
|
(call, decorator) => decorator(call)
|
||||||
},
|
)
|
||||||
], (call, decorator) => decorator(call))
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { delay as pDelay } from 'promise-toolbox'
|
|||||||
import { createClient } from './'
|
import { createClient } from './'
|
||||||
|
|
||||||
const xapi = (() => {
|
const xapi = (() => {
|
||||||
const [ , , url, user, password ] = process.argv
|
const [, , url, user, password] = process.argv
|
||||||
|
|
||||||
return createClient({
|
return createClient({
|
||||||
auth: { user, password },
|
auth: { user, password },
|
||||||
@ -14,16 +14,19 @@ const xapi = (() => {
|
|||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
|
|
||||||
xapi.connect()
|
xapi
|
||||||
|
.connect()
|
||||||
|
|
||||||
// Get the pool record's ref.
|
// Get the pool record's ref.
|
||||||
.then(() => xapi.call('pool.get_all'))
|
.then(() => xapi.call('pool.get_all'))
|
||||||
|
|
||||||
// Injects lots of events.
|
// Injects lots of events.
|
||||||
.then(([ poolRef ]) => {
|
.then(([poolRef]) => {
|
||||||
const loop = () => xapi.call('event.inject', 'pool', poolRef)
|
const loop = () =>
|
||||||
::pDelay(10) // A small delay is required to avoid overloading the Xen API.
|
xapi
|
||||||
.then(loop)
|
.call('event.inject', 'pool', poolRef)
|
||||||
|
::pDelay(10) // A small delay is required to avoid overloading the Xen API.
|
||||||
|
.then(loop)
|
||||||
|
|
||||||
return loop()
|
return loop()
|
||||||
})
|
})
|
||||||
|
@ -14,7 +14,7 @@ setInterval(() => {
|
|||||||
)
|
)
|
||||||
}, 1e2)
|
}, 1e2)
|
||||||
|
|
||||||
const [ , , url, user, password ] = process.argv
|
const [, , url, user, password] = process.argv
|
||||||
createClient({
|
createClient({
|
||||||
auth: { user, password },
|
auth: { user, password },
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
|
@ -3,7 +3,7 @@ import xmlRpc from './xml-rpc'
|
|||||||
import xmlRpcJson from './xml-rpc-json'
|
import xmlRpcJson from './xml-rpc-json'
|
||||||
import { UnsupportedTransport } from './_utils'
|
import { UnsupportedTransport } from './_utils'
|
||||||
|
|
||||||
const factories = [ jsonRpc, xmlRpcJson, xmlRpc ]
|
const factories = [jsonRpc, xmlRpcJson, xmlRpc]
|
||||||
const { length } = factories
|
const { length } = factories
|
||||||
|
|
||||||
export default opts => {
|
export default opts => {
|
||||||
@ -14,18 +14,18 @@ export default opts => {
|
|||||||
const current = factories[i++](opts)
|
const current = factories[i++](opts)
|
||||||
if (i < length) {
|
if (i < length) {
|
||||||
const currentI = i
|
const currentI = i
|
||||||
call = (method, args) => current(method, args).catch(
|
call = (method, args) =>
|
||||||
error => {
|
current(method, args).catch(error => {
|
||||||
if (error instanceof UnsupportedTransport) {
|
if (error instanceof UnsupportedTransport) {
|
||||||
if (currentI === i) { // not changed yet
|
if (currentI === i) {
|
||||||
|
// not changed yet
|
||||||
create()
|
create()
|
||||||
}
|
}
|
||||||
return call(method, args)
|
return call(method, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
}
|
})
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
call = current
|
call = current
|
||||||
}
|
}
|
||||||
|
@ -4,35 +4,40 @@ import { format, parse } from 'json-rpc-protocol'
|
|||||||
import { UnsupportedTransport } from './_utils'
|
import { UnsupportedTransport } from './_utils'
|
||||||
|
|
||||||
export default ({ allowUnauthorized, url }) => {
|
export default ({ allowUnauthorized, url }) => {
|
||||||
return (method, args) => httpRequestPlus.post(url, {
|
return (method, args) =>
|
||||||
rejectUnauthorized: !allowUnauthorized,
|
httpRequestPlus
|
||||||
body: format.request(0, method, args),
|
.post(url, {
|
||||||
headers: {
|
rejectUnauthorized: !allowUnauthorized,
|
||||||
'Accept': 'application/json',
|
body: format.request(0, method, args),
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
},
|
Accept: 'application/json',
|
||||||
path: '/jsonrpc',
|
'Content-Type': 'application/json',
|
||||||
}).readAll('utf8').then(
|
},
|
||||||
text => {
|
path: '/jsonrpc',
|
||||||
let response
|
})
|
||||||
try {
|
.readAll('utf8')
|
||||||
response = parse(text)
|
.then(
|
||||||
} catch (error) {
|
text => {
|
||||||
throw new UnsupportedTransport()
|
let response
|
||||||
}
|
try {
|
||||||
|
response = parse(text)
|
||||||
|
} catch (error) {
|
||||||
|
throw new UnsupportedTransport()
|
||||||
|
}
|
||||||
|
|
||||||
if (response.type === 'response') {
|
if (response.type === 'response') {
|
||||||
return response.result
|
return response.result
|
||||||
}
|
}
|
||||||
|
|
||||||
throw response.error
|
throw response.error
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
if (error.response !== undefined) { // HTTP error
|
if (error.response !== undefined) {
|
||||||
throw new UnsupportedTransport()
|
// HTTP error
|
||||||
}
|
throw new UnsupportedTransport()
|
||||||
|
}
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,7 @@ const SPECIAL_CHARS = {
|
|||||||
'\r': '\\r',
|
'\r': '\\r',
|
||||||
'\t': '\\t',
|
'\t': '\\t',
|
||||||
}
|
}
|
||||||
const SPECIAL_CHARS_RE = new RegExp(
|
const SPECIAL_CHARS_RE = new RegExp(Object.keys(SPECIAL_CHARS).join('|'), 'g')
|
||||||
Object.keys(SPECIAL_CHARS).join('|'),
|
|
||||||
'g'
|
|
||||||
)
|
|
||||||
|
|
||||||
const parseResult = result => {
|
const parseResult = result => {
|
||||||
const status = result.Status
|
const status = result.Status
|
||||||
@ -78,11 +75,7 @@ export default ({
|
|||||||
allowUnauthorized,
|
allowUnauthorized,
|
||||||
url: { hostname, path, port, protocol },
|
url: { hostname, path, port, protocol },
|
||||||
}) => {
|
}) => {
|
||||||
const client = (
|
const client = (protocol === 'https:' ? createSecureClient : createClient)({
|
||||||
protocol === 'https:'
|
|
||||||
? createSecureClient
|
|
||||||
: createClient
|
|
||||||
)({
|
|
||||||
host: hostname,
|
host: hostname,
|
||||||
path: '/json',
|
path: '/json',
|
||||||
port,
|
port,
|
||||||
@ -90,8 +83,5 @@ export default ({
|
|||||||
})
|
})
|
||||||
const call = promisify(client.methodCall, client)
|
const call = promisify(client.methodCall, client)
|
||||||
|
|
||||||
return (method, args) => call(method, args).then(
|
return (method, args) => call(method, args).then(parseResult, logError)
|
||||||
parseResult,
|
|
||||||
logError
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -34,19 +34,12 @@ export default ({
|
|||||||
allowUnauthorized,
|
allowUnauthorized,
|
||||||
url: { hostname, path, port, protocol },
|
url: { hostname, path, port, protocol },
|
||||||
}) => {
|
}) => {
|
||||||
const client = (
|
const client = (protocol === 'https:' ? createSecureClient : createClient)({
|
||||||
protocol === 'https:'
|
|
||||||
? createSecureClient
|
|
||||||
: createClient
|
|
||||||
)({
|
|
||||||
host: hostname,
|
host: hostname,
|
||||||
port,
|
port,
|
||||||
rejectUnauthorized: !allowUnauthorized,
|
rejectUnauthorized: !allowUnauthorized,
|
||||||
})
|
})
|
||||||
const call = promisify(client.methodCall, client)
|
const call = promisify(client.methodCall, client)
|
||||||
|
|
||||||
return (method, args) => call(method, args).then(
|
return (method, args) => call(method, args).then(parseResult, logError)
|
||||||
parseResult,
|
|
||||||
logError
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@ let getObject
|
|||||||
const authorized = () => true // eslint-disable-line no-unused-vars
|
const authorized = () => true // eslint-disable-line no-unused-vars
|
||||||
const forbiddden = () => false // eslint-disable-line no-unused-vars
|
const forbiddden = () => false // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
const and = (...checkers) => (object, permission) => { // eslint-disable-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const and = (...checkers) => (object, permission) => {
|
||||||
for (const checker of checkers) {
|
for (const checker of checkers) {
|
||||||
if (!checker(object, permission)) {
|
if (!checker(object, permission)) {
|
||||||
return false
|
return false
|
||||||
@ -17,7 +18,8 @@ const and = (...checkers) => (object, permission) => { // eslint-disable-line no
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const or = (...checkers) => (object, permission) => { // eslint-disable-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const or = (...checkers) => (object, permission) => {
|
||||||
for (const checker of checkers) {
|
for (const checker of checkers) {
|
||||||
if (checker(object, permission)) {
|
if (checker(object, permission)) {
|
||||||
return true
|
return true
|
||||||
@ -28,7 +30,7 @@ const or = (...checkers) => (object, permission) => { // eslint-disable-line no-
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
const checkMember = (memberName) => (object, permission) => {
|
const checkMember = memberName => (object, permission) => {
|
||||||
const member = object[memberName]
|
const member = object[memberName]
|
||||||
return member !== object.id && checkAuthorization(member, permission)
|
return member !== object.id && checkAuthorization(member, permission)
|
||||||
}
|
}
|
||||||
@ -36,10 +38,7 @@ const checkMember = (memberName) => (object, permission) => {
|
|||||||
const checkSelf = ({ id }, permission) => {
|
const checkSelf = ({ id }, permission) => {
|
||||||
const permissionsForObject = permissionsByObject[id]
|
const permissionsForObject = permissionsByObject[id]
|
||||||
|
|
||||||
return (
|
return permissionsForObject && permissionsForObject[permission]
|
||||||
permissionsForObject &&
|
|
||||||
permissionsForObject[permission]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
@ -102,12 +101,7 @@ function checkAuthorization (objectId, permission) {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export default (
|
export default (permissionsByObject_, getObject_, permissions, permission) => {
|
||||||
permissionsByObject_,
|
|
||||||
getObject_,
|
|
||||||
permissions,
|
|
||||||
permission
|
|
||||||
) => {
|
|
||||||
// Assign global variables.
|
// Assign global variables.
|
||||||
permissionsByObject = permissionsByObject_
|
permissionsByObject = permissionsByObject_
|
||||||
getObject = getObject_
|
getObject = getObject_
|
||||||
|
@ -5,7 +5,7 @@ const __TEST__ = NODE_ENV === 'test'
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
comments: !__PROD__,
|
comments: !__PROD__,
|
||||||
compact: __PROD__,
|
compact: __PROD__,
|
||||||
ignore: __TEST__ ? undefined : [ /\.spec\.js$/ ],
|
ignore: __TEST__ ? undefined : [/\.spec\.js$/],
|
||||||
plugins: ['lodash'],
|
plugins: ['lodash'],
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
@ -14,9 +14,7 @@ module.exports = {
|
|||||||
debug: !__TEST__,
|
debug: !__TEST__,
|
||||||
loose: true,
|
loose: true,
|
||||||
shippedProposals: true,
|
shippedProposals: true,
|
||||||
targets: __PROD__
|
targets: __PROD__ ? { node: '6' } : { node: 'current' },
|
||||||
? { node: '6' }
|
|
||||||
: { node: 'current' },
|
|
||||||
useBuiltIns: 'usage',
|
useBuiltIns: 'usage',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -19,11 +19,13 @@ const configFile = configPath + '/config.json'
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const load = exports.load = function () {
|
const load = (exports.load = function () {
|
||||||
return readFile(configFile).then(JSON.parse).catch(function () {
|
return readFile(configFile)
|
||||||
return {}
|
.then(JSON.parse)
|
||||||
})
|
.catch(function () {
|
||||||
}
|
return {}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
exports.get = function (path) {
|
exports.get = function (path) {
|
||||||
return load().then(function (config) {
|
return load().then(function (config) {
|
||||||
@ -31,11 +33,11 @@ exports.get = function (path) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const save = exports.save = function (config) {
|
const save = (exports.save = function (config) {
|
||||||
return mkdirp(configPath).then(function () {
|
return mkdirp(configPath).then(function () {
|
||||||
return writeFile(configFile, JSON.stringify(config))
|
return writeFile(configFile, JSON.stringify(config))
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
exports.set = function (data) {
|
exports.set = function (data) {
|
||||||
return load().then(function (config) {
|
return load().then(function (config) {
|
||||||
|
@ -108,14 +108,16 @@ const humanFormatOpts = {
|
|||||||
|
|
||||||
function printProgress (progress) {
|
function printProgress (progress) {
|
||||||
if (progress.length) {
|
if (progress.length) {
|
||||||
console.warn('%s% of %s @ %s/s - ETA %s',
|
console.warn(
|
||||||
|
'%s% of %s @ %s/s - ETA %s',
|
||||||
Math.round(progress.percentage),
|
Math.round(progress.percentage),
|
||||||
humanFormat(progress.length, humanFormatOpts),
|
humanFormat(progress.length, humanFormatOpts),
|
||||||
humanFormat(progress.speed, humanFormatOpts),
|
humanFormat(progress.speed, humanFormatOpts),
|
||||||
prettyMs(progress.eta * 1e3)
|
prettyMs(progress.eta * 1e3)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
console.warn('%s @ %s/s',
|
console.warn(
|
||||||
|
'%s @ %s/s',
|
||||||
humanFormat(progress.transferred, humanFormatOpts),
|
humanFormat(progress.transferred, humanFormatOpts),
|
||||||
humanFormat(progress.speed, humanFormatOpts)
|
humanFormat(progress.speed, humanFormatOpts)
|
||||||
)
|
)
|
||||||
@ -130,8 +132,10 @@ function wrap (val) {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const help = wrap((function (pkg) {
|
const help = wrap(
|
||||||
return require('strip-indent')(`
|
(function (pkg) {
|
||||||
|
return require('strip-indent')(
|
||||||
|
`
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
$name --register [--expiresIn duration] <XO-Server URL> <username> [<password>]
|
$name --register [--expiresIn duration] <XO-Server URL> <username> [<password>]
|
||||||
@ -162,18 +166,20 @@ const help = wrap((function (pkg) {
|
|||||||
Executes a command on the current XO instance.
|
Executes a command on the current XO instance.
|
||||||
|
|
||||||
$name v$version
|
$name v$version
|
||||||
`).replace(/<([^>]+)>|\$(\w+)/g, function (_, arg, key) {
|
`
|
||||||
if (arg) {
|
).replace(/<([^>]+)>|\$(\w+)/g, function (_, arg, key) {
|
||||||
return '<' + chalk.yellow(arg) + '>'
|
if (arg) {
|
||||||
}
|
return '<' + chalk.yellow(arg) + '>'
|
||||||
|
}
|
||||||
|
|
||||||
if (key === 'name') {
|
if (key === 'name') {
|
||||||
return chalk.bold(pkg[key])
|
return chalk.bold(pkg[key])
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkg[key]
|
return pkg[key]
|
||||||
})
|
})
|
||||||
})(require('../package')))
|
})(require('../package'))
|
||||||
|
)
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
@ -230,10 +236,7 @@ async function register (args) {
|
|||||||
exports.register = register
|
exports.register = register
|
||||||
|
|
||||||
function unregister () {
|
function unregister () {
|
||||||
return config.unset([
|
return config.unset(['server', 'token'])
|
||||||
'server',
|
|
||||||
'token',
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
exports.unregister = unregister
|
exports.unregister = unregister
|
||||||
|
|
||||||
@ -284,11 +287,7 @@ async function listCommands (args) {
|
|||||||
str.push(
|
str.push(
|
||||||
name,
|
name,
|
||||||
'=<',
|
'=<',
|
||||||
type == null
|
type == null ? 'unknown type' : isArray(type) ? type.join('|') : type,
|
||||||
? 'unknown type'
|
|
||||||
: isArray(type)
|
|
||||||
? type.join('|')
|
|
||||||
: type,
|
|
||||||
'>'
|
'>'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -347,10 +346,7 @@ async function call (args) {
|
|||||||
|
|
||||||
const result = await xo.call(method, params)
|
const result = await xo.call(method, params)
|
||||||
let keys, key, url
|
let keys, key, url
|
||||||
if (
|
if (isObject(result) && (keys = getKeys(result)).length === 1) {
|
||||||
isObject(result) &&
|
|
||||||
(keys = getKeys(result)).length === 1
|
|
||||||
) {
|
|
||||||
key = keys[0]
|
key = keys[0]
|
||||||
|
|
||||||
if (key === '$getFrom') {
|
if (key === '$getFrom') {
|
||||||
@ -359,16 +355,19 @@ async function call (args) {
|
|||||||
|
|
||||||
const progress = progressStream({ time: 1e3 }, printProgress)
|
const progress = progressStream({ time: 1e3 }, printProgress)
|
||||||
|
|
||||||
return eventToPromise(nicePipe([
|
return eventToPromise(
|
||||||
got.stream(url).on('response', function (response) {
|
nicePipe([
|
||||||
const length = response.headers['content-length']
|
got.stream(url).on('response', function (response) {
|
||||||
if (length !== undefined) {
|
const length = response.headers['content-length']
|
||||||
progress.length(length)
|
if (length !== undefined) {
|
||||||
}
|
progress.length(length)
|
||||||
}),
|
}
|
||||||
progress,
|
}),
|
||||||
output,
|
progress,
|
||||||
]), 'finish')
|
output,
|
||||||
|
]),
|
||||||
|
'finish'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === '$sendTo') {
|
if (key === '$sendTo') {
|
||||||
@ -379,10 +378,13 @@ async function call (args) {
|
|||||||
|
|
||||||
const input = nicePipe([
|
const input = nicePipe([
|
||||||
createReadStream(file),
|
createReadStream(file),
|
||||||
progressStream({
|
progressStream(
|
||||||
length: length,
|
{
|
||||||
time: 1e3,
|
length: length,
|
||||||
}, printProgress),
|
time: 1e3,
|
||||||
|
},
|
||||||
|
printProgress
|
||||||
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
const response = await got.post(url, {
|
const response = await got.post(url, {
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import kindOf from 'kindof'
|
import kindOf from 'kindof'
|
||||||
import {BaseError} from 'make-error'
|
import { BaseError } from 'make-error'
|
||||||
import {EventEmitter} from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import {forEach} from 'lodash'
|
import { forEach } from 'lodash'
|
||||||
|
|
||||||
import isEmpty from './is-empty'
|
import isEmpty from './is-empty'
|
||||||
import isObject from './is-object'
|
import isObject from './is-object'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const {
|
const { create: createObject, prototype: { hasOwnProperty } } = Object
|
||||||
create: createObject,
|
|
||||||
prototype: { hasOwnProperty },
|
|
||||||
} = Object
|
|
||||||
|
|
||||||
export const ACTION_ADD = 'add'
|
export const ACTION_ADD = 'add'
|
||||||
export const ACTION_UPDATE = 'update'
|
export const ACTION_UPDATE = 'update'
|
||||||
@ -189,7 +186,7 @@ export default class Collection extends EventEmitter {
|
|||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
createIndex (name, index) {
|
createIndex (name, index) {
|
||||||
const {_indexes: indexes} = this
|
const { _indexes: indexes } = this
|
||||||
if (hasOwnProperty.call(indexes, name)) {
|
if (hasOwnProperty.call(indexes, name)) {
|
||||||
throw new DuplicateIndex(name)
|
throw new DuplicateIndex(name)
|
||||||
}
|
}
|
||||||
@ -201,7 +198,7 @@ export default class Collection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteIndex (name) {
|
deleteIndex (name) {
|
||||||
const {_indexes: indexes} = this
|
const { _indexes: indexes } = this
|
||||||
if (!hasOwnProperty.call(indexes, name)) {
|
if (!hasOwnProperty.call(indexes, name)) {
|
||||||
throw new NoSuchIndex(name)
|
throw new NoSuchIndex(name)
|
||||||
}
|
}
|
||||||
@ -218,7 +215,7 @@ export default class Collection extends EventEmitter {
|
|||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
* [Symbol.iterator] () {
|
* [Symbol.iterator] () {
|
||||||
const {_items: items} = this
|
const { _items: items } = this
|
||||||
|
|
||||||
for (const key in items) {
|
for (const key in items) {
|
||||||
yield [key, items[key]]
|
yield [key, items[key]]
|
||||||
@ -226,7 +223,7 @@ export default class Collection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
* keys () {
|
* keys () {
|
||||||
const {_items: items} = this
|
const { _items: items } = this
|
||||||
|
|
||||||
for (const key in items) {
|
for (const key in items) {
|
||||||
yield key
|
yield key
|
||||||
@ -234,7 +231,7 @@ export default class Collection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
* values () {
|
* values () {
|
||||||
const {_items: items} = this
|
const { _items: items } = this
|
||||||
|
|
||||||
for (const key in items) {
|
for (const key in items) {
|
||||||
yield items[key]
|
yield items[key]
|
||||||
@ -259,7 +256,7 @@ export default class Collection extends EventEmitter {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const {_buffer: buffer} = this
|
const { _buffer: buffer } = this
|
||||||
|
|
||||||
// Due to deduplication there could be nothing in the buffer.
|
// Due to deduplication there could be nothing in the buffer.
|
||||||
if (isEmpty(buffer)) {
|
if (isEmpty(buffer)) {
|
||||||
@ -354,7 +351,8 @@ export default class Collection extends EventEmitter {
|
|||||||
} else {
|
} else {
|
||||||
this._buffer[key] = ACTION_REMOVE
|
this._buffer[key] = ACTION_REMOVE
|
||||||
}
|
}
|
||||||
} else { // update
|
} else {
|
||||||
|
// update
|
||||||
if (!this._buffer[key]) {
|
if (!this._buffer[key]) {
|
||||||
this._buffer[key] = ACTION_UPDATE
|
this._buffer[key] = ACTION_UPDATE
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
import eventToPromise from 'event-to-promise'
|
import eventToPromise from 'event-to-promise'
|
||||||
import { forEach } from 'lodash'
|
import { forEach } from 'lodash'
|
||||||
|
|
||||||
import Collection, {DuplicateItem, NoSuchItem} from './collection'
|
import Collection, { DuplicateItem, NoSuchItem } from './collection'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
function waitTicks (n = 2) {
|
function waitTicks (n = 2) {
|
||||||
const {nextTick} = process
|
const { nextTick } = process
|
||||||
|
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
(function waitNextTick () {
|
;(function waitNextTick () {
|
||||||
// The first tick is handled by Promise#then()
|
// The first tick is handled by Promise#then()
|
||||||
if (--n) {
|
if (--n) {
|
||||||
nextTick(waitNextTick)
|
nextTick(waitNextTick)
|
||||||
@ -34,16 +34,16 @@ describe('Collection', function () {
|
|||||||
it('is iterable', function () {
|
it('is iterable', function () {
|
||||||
const iterator = col[Symbol.iterator]()
|
const iterator = col[Symbol.iterator]()
|
||||||
|
|
||||||
expect(iterator.next()).toEqual({done: false, value: ['bar', 0]})
|
expect(iterator.next()).toEqual({ done: false, value: ['bar', 0] })
|
||||||
expect(iterator.next()).toEqual({done: true, value: undefined})
|
expect(iterator.next()).toEqual({ done: true, value: undefined })
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#keys()', function () {
|
describe('#keys()', function () {
|
||||||
it('returns an iterator over the keys', function () {
|
it('returns an iterator over the keys', function () {
|
||||||
const iterator = col.keys()
|
const iterator = col.keys()
|
||||||
|
|
||||||
expect(iterator.next()).toEqual({done: false, value: 'bar'})
|
expect(iterator.next()).toEqual({ done: false, value: 'bar' })
|
||||||
expect(iterator.next()).toEqual({done: true, value: undefined})
|
expect(iterator.next()).toEqual({ done: true, value: undefined })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -51,8 +51,8 @@ describe('Collection', function () {
|
|||||||
it('returns an iterator over the values', function () {
|
it('returns an iterator over the values', function () {
|
||||||
const iterator = col.values()
|
const iterator = col.values()
|
||||||
|
|
||||||
expect(iterator.next()).toEqual({done: false, value: 0})
|
expect(iterator.next()).toEqual({ done: false, value: 0 })
|
||||||
expect(iterator.next()).toEqual({done: true, value: undefined})
|
expect(iterator.next()).toEqual({ done: true, value: undefined })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ describe('Collection', function () {
|
|||||||
|
|
||||||
// Async event.
|
// Async event.
|
||||||
return eventToPromise(col, 'add').then(function (added) {
|
return eventToPromise(col, 'add').then(function (added) {
|
||||||
expect(Object.keys(added)).toEqual([ 'foo' ])
|
expect(Object.keys(added)).toEqual(['foo'])
|
||||||
expect(added.foo).toBe(true)
|
expect(added.foo).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -216,7 +216,7 @@ describe('Collection', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('accepts an object with an id property', function () {
|
it('accepts an object with an id property', function () {
|
||||||
col.unset({id: 'bar'})
|
col.unset({ id: 'bar' })
|
||||||
|
|
||||||
expect(col.has('bar')).toBe(false)
|
expect(col.has('bar')).toBe(false)
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ describe('Collection', function () {
|
|||||||
return waitTicks().then(() => {
|
return waitTicks().then(() => {
|
||||||
col.touch(foo)
|
col.touch(foo)
|
||||||
|
|
||||||
return eventToPromise(col, 'update', (items) => {
|
return eventToPromise(col, 'update', items => {
|
||||||
expect(Object.keys(items)).toEqual(['foo'])
|
expect(Object.keys(items)).toEqual(['foo'])
|
||||||
expect(items.foo).toBe(foo)
|
expect(items.foo).toBe(foo)
|
||||||
})
|
})
|
||||||
@ -249,7 +249,7 @@ describe('Collection', function () {
|
|||||||
|
|
||||||
expect(col.size).toBe(0)
|
expect(col.size).toBe(0)
|
||||||
|
|
||||||
return eventToPromise(col, 'remove').then((items) => {
|
return eventToPromise(col, 'remove').then(items => {
|
||||||
expect(Object.keys(items)).toEqual(['bar'])
|
expect(Object.keys(items)).toEqual(['bar'])
|
||||||
expect(items.bar).toBeUndefined()
|
expect(items.bar).toBeUndefined()
|
||||||
})
|
})
|
||||||
@ -257,84 +257,69 @@ describe('Collection', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('deduplicates events', function () {
|
describe('deduplicates events', function () {
|
||||||
forEach({
|
forEach(
|
||||||
'add & update → add': [
|
{
|
||||||
[
|
'add & update → add': [
|
||||||
['add', 'foo', 0],
|
[['add', 'foo', 0], ['update', 'foo', 1]],
|
||||||
['update', 'foo', 1],
|
{
|
||||||
],
|
add: {
|
||||||
{
|
foo: 1,
|
||||||
add: {
|
},
|
||||||
foo: 1,
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
'add & remove → ∅': [
|
|
||||||
[
|
|
||||||
['add', 'foo', 0],
|
|
||||||
['remove', 'foo'],
|
|
||||||
],
|
],
|
||||||
{},
|
|
||||||
],
|
|
||||||
|
|
||||||
'update & update → update': [
|
'add & remove → ∅': [[['add', 'foo', 0], ['remove', 'foo']], {}],
|
||||||
[
|
|
||||||
['update', 'bar', 1],
|
'update & update → update': [
|
||||||
['update', 'bar', 2],
|
[['update', 'bar', 1], ['update', 'bar', 2]],
|
||||||
],
|
{
|
||||||
{
|
update: {
|
||||||
update: {
|
bar: 2,
|
||||||
bar: 2,
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
'update & remove → remove': [
|
|
||||||
[
|
|
||||||
['update', 'bar', 1],
|
|
||||||
['remove', 'bar'],
|
|
||||||
],
|
],
|
||||||
{
|
|
||||||
remove: {
|
|
||||||
bar: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
'remove & add → update': [
|
'update & remove → remove': [
|
||||||
[
|
[['update', 'bar', 1], ['remove', 'bar']],
|
||||||
['remove', 'bar'],
|
{
|
||||||
['add', 'bar', 0],
|
remove: {
|
||||||
|
bar: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
{
|
|
||||||
update: {
|
'remove & add → update': [
|
||||||
bar: 0,
|
[['remove', 'bar'], ['add', 'bar', 0]],
|
||||||
|
{
|
||||||
|
update: {
|
||||||
|
bar: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
}, ([operations, results], label) => {
|
([operations, results], label) => {
|
||||||
it(label, function () {
|
it(label, function () {
|
||||||
forEach(operations, ([method, ...args]) => {
|
forEach(operations, ([method, ...args]) => {
|
||||||
col[method](...args)
|
col[method](...args)
|
||||||
})
|
})
|
||||||
|
|
||||||
const spies = Object.create(null)
|
const spies = Object.create(null)
|
||||||
forEach(['add', 'update', 'remove'], event => {
|
forEach(['add', 'update', 'remove'], event => {
|
||||||
col.on(event, (spies[event] = jest.fn()))
|
col.on(event, (spies[event] = jest.fn()))
|
||||||
})
|
})
|
||||||
|
|
||||||
return waitTicks().then(() => {
|
return waitTicks().then(() => {
|
||||||
forEach(spies, (spy, event) => {
|
forEach(spies, (spy, event) => {
|
||||||
const items = results[event]
|
const items = results[event]
|
||||||
if (items) {
|
if (items) {
|
||||||
expect(spy.mock.calls).toEqual([ [ items ] ])
|
expect(spy.mock.calls).toEqual([[items]])
|
||||||
} else {
|
} else {
|
||||||
expect(spy).not.toHaveBeenCalled()
|
expect(spy).not.toHaveBeenCalled()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -3,11 +3,7 @@ import { bind, iteratee } from 'lodash'
|
|||||||
import clearObject from './clear-object'
|
import clearObject from './clear-object'
|
||||||
import isEmpty from './is-empty'
|
import isEmpty from './is-empty'
|
||||||
import NotImplemented from './not-implemented'
|
import NotImplemented from './not-implemented'
|
||||||
import {
|
import { ACTION_ADD, ACTION_UPDATE, ACTION_REMOVE } from './collection'
|
||||||
ACTION_ADD,
|
|
||||||
ACTION_UPDATE,
|
|
||||||
ACTION_REMOVE,
|
|
||||||
} from './collection'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -34,7 +30,7 @@ export default class Index {
|
|||||||
|
|
||||||
// Remove empty items lists.
|
// Remove empty items lists.
|
||||||
sweep () {
|
sweep () {
|
||||||
const {_itemsByHash: itemsByHash} = this
|
const { _itemsByHash: itemsByHash } = this
|
||||||
for (const hash in itemsByHash) {
|
for (const hash in itemsByHash) {
|
||||||
if (isEmpty(itemsByHash[hash])) {
|
if (isEmpty(itemsByHash[hash])) {
|
||||||
delete itemsByHash[hash]
|
delete itemsByHash[hash]
|
||||||
@ -86,14 +82,11 @@ export default class Index {
|
|||||||
const hash = computeHash(value, key)
|
const hash = computeHash(value, key)
|
||||||
|
|
||||||
if (hash != null) {
|
if (hash != null) {
|
||||||
(
|
;(itemsByHash[hash] ||
|
||||||
itemsByHash[hash] ||
|
|
||||||
|
|
||||||
// FIXME: We do not use objects without prototype for now
|
// FIXME: We do not use objects without prototype for now
|
||||||
// because it breaks Angular in xo-web, change it back when
|
// because it breaks Angular in xo-web, change it back when
|
||||||
// this is fixed.
|
// this is fixed.
|
||||||
(itemsByHash[hash] = {})
|
(itemsByHash[hash] = {}))[key] = value
|
||||||
)[key] = value
|
|
||||||
|
|
||||||
keysToHash[key] = hash
|
keysToHash[key] = hash
|
||||||
}
|
}
|
||||||
@ -118,12 +111,9 @@ export default class Index {
|
|||||||
|
|
||||||
// Inserts item into the new hash's list if any.
|
// Inserts item into the new hash's list if any.
|
||||||
if (hash != null) {
|
if (hash != null) {
|
||||||
(
|
;(itemsByHash[hash] ||
|
||||||
itemsByHash[hash] ||
|
|
||||||
|
|
||||||
// FIXME: idem: change back to Object.create(null)
|
// FIXME: idem: change back to Object.create(null)
|
||||||
(itemsByHash[hash] = {})
|
(itemsByHash[hash] = {}))[key] = value
|
||||||
)[key] = value
|
|
||||||
|
|
||||||
keysToHash[key] = hash
|
keysToHash[key] = hash
|
||||||
} else {
|
} else {
|
||||||
@ -133,10 +123,7 @@ export default class Index {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onRemove (items) {
|
_onRemove (items) {
|
||||||
const {
|
const { _itemsByHash: itemsByHash, _keysToHash: keysToHash } = this
|
||||||
_itemsByHash: itemsByHash,
|
|
||||||
_keysToHash: keysToHash,
|
|
||||||
} = this
|
|
||||||
|
|
||||||
for (const key in items) {
|
for (const key in items) {
|
||||||
const prev = keysToHash[key]
|
const prev = keysToHash[key]
|
||||||
|
@ -9,10 +9,10 @@ import Index from './index'
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const waitTicks = (n = 2) => {
|
const waitTicks = (n = 2) => {
|
||||||
const {nextTick} = process
|
const { nextTick } = process
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
(function waitNextTick () {
|
;(function waitNextTick () {
|
||||||
// The first tick is handled by Promise#then()
|
// The first tick is handled by Promise#then()
|
||||||
if (--n) {
|
if (--n) {
|
||||||
nextTick(waitNextTick)
|
nextTick(waitNextTick)
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export default function isObject (value) {
|
export default function isObject (value) {
|
||||||
return (value !== null) && (typeof value === 'object')
|
return value !== null && typeof value === 'object'
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {BaseError} from 'make-error'
|
import { BaseError } from 'make-error'
|
||||||
|
|
||||||
export default class NotImplemented extends BaseError {
|
export default class NotImplemented extends BaseError {
|
||||||
constructor (message) {
|
constructor (message) {
|
||||||
|
@ -2,11 +2,7 @@ import { bind, iteratee } from 'lodash'
|
|||||||
|
|
||||||
import clearObject from './clear-object'
|
import clearObject from './clear-object'
|
||||||
import NotImplemented from './not-implemented'
|
import NotImplemented from './not-implemented'
|
||||||
import {
|
import { ACTION_ADD, ACTION_UPDATE, ACTION_REMOVE } from './collection'
|
||||||
ACTION_ADD,
|
|
||||||
ACTION_UPDATE,
|
|
||||||
ACTION_REMOVE,
|
|
||||||
} from './collection'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -108,10 +104,7 @@ export default class UniqueIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onRemove (items) {
|
_onRemove (items) {
|
||||||
const {
|
const { _itemByHash: itemByHash, _keysToHash: keysToHash } = this
|
||||||
_itemByHash: itemByHash,
|
|
||||||
_keysToHash: keysToHash,
|
|
||||||
} = this
|
|
||||||
|
|
||||||
for (const key in items) {
|
for (const key in items) {
|
||||||
const prev = keysToHash[key]
|
const prev = keysToHash[key]
|
||||||
|
@ -9,10 +9,10 @@ import Index from './unique-index'
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const waitTicks = (n = 2) => {
|
const waitTicks = (n = 2) => {
|
||||||
const {nextTick} = process
|
const { nextTick } = process
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
(function waitNextTick () {
|
;(function waitNextTick () {
|
||||||
// The first tick is handled by Promise#then()
|
// The first tick is handled by Promise#then()
|
||||||
if (--n) {
|
if (--n) {
|
||||||
nextTick(waitNextTick)
|
nextTick(waitNextTick)
|
||||||
|
@ -7,7 +7,7 @@ import View from './view'
|
|||||||
|
|
||||||
// Create the collection.
|
// Create the collection.
|
||||||
const users = new Collection()
|
const users = new Collection()
|
||||||
users.getKey = (user) => user.name
|
users.getKey = user => user.name
|
||||||
|
|
||||||
// Inserts some data.
|
// Inserts some data.
|
||||||
users.add({
|
users.add({
|
||||||
|
@ -54,7 +54,7 @@ export default class View extends Collection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onAdd (items) {
|
_onAdd (items) {
|
||||||
const {_predicate: predicate} = this
|
const { _predicate: predicate } = this
|
||||||
|
|
||||||
forEach(items, (value, key) => {
|
forEach(items, (value, key) => {
|
||||||
if (predicate(value, key, this)) {
|
if (predicate(value, key, this)) {
|
||||||
@ -67,7 +67,7 @@ export default class View extends Collection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onUpdate (items) {
|
_onUpdate (items) {
|
||||||
const {_predicate: predicate} = this
|
const { _predicate: predicate } = this
|
||||||
|
|
||||||
forEach(items, (value, key) => {
|
forEach(items, (value, key) => {
|
||||||
if (predicate(value, key, this)) {
|
if (predicate(value, key, this)) {
|
||||||
|
@ -10,36 +10,53 @@ const xo = new Xo({
|
|||||||
url: 'localhost:9000',
|
url: 'localhost:9000',
|
||||||
})
|
})
|
||||||
|
|
||||||
xo.open().then(function () {
|
xo
|
||||||
return xo.call('acl.get', {}).then(function (result) {
|
.open()
|
||||||
console.log('success:', result)
|
.then(function () {
|
||||||
}).catch(function (error) {
|
return xo
|
||||||
console.log('failure:', error)
|
.call('acl.get', {})
|
||||||
|
.then(function (result) {
|
||||||
|
console.log('success:', result)
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log('failure:', error)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}).then(function () {
|
.then(function () {
|
||||||
return xo.signIn({
|
return xo
|
||||||
email: 'admin@admin.net',
|
.signIn({
|
||||||
password: 'admin',
|
email: 'admin@admin.net',
|
||||||
}).then(function () {
|
password: 'admin',
|
||||||
console.log('connected as ', xo.user)
|
})
|
||||||
}).catch(function (error) {
|
.then(function () {
|
||||||
console.log('failure:', error)
|
console.log('connected as ', xo.user)
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log('failure:', error)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}).then(function () {
|
.then(function () {
|
||||||
return xo.signIn({
|
return xo
|
||||||
email: 'tom',
|
.signIn({
|
||||||
password: 'tom',
|
email: 'tom',
|
||||||
}).then(function () {
|
password: 'tom',
|
||||||
console.log('connected as', xo.user)
|
})
|
||||||
|
.then(function () {
|
||||||
|
console.log('connected as', xo.user)
|
||||||
|
|
||||||
return xo.call('acl.get', {}).then(function (result) {
|
return xo
|
||||||
console.log('success:', result)
|
.call('acl.get', {})
|
||||||
}).catch(function (error) {
|
.then(function (result) {
|
||||||
console.log('failure:', error)
|
console.log('success:', result)
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
.catch(function (error) {
|
||||||
console.log('failure', error)
|
console.log('failure:', error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log('failure', error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return xo.close()
|
||||||
})
|
})
|
||||||
}).then(function () {
|
|
||||||
return xo.close()
|
|
||||||
})
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import JsonRpcWebSocketClient, {
|
import JsonRpcWebSocketClient, { OPEN, CLOSED } from 'jsonrpc-websocket-client'
|
||||||
OPEN,
|
|
||||||
CLOSED,
|
|
||||||
} from 'jsonrpc-websocket-client'
|
|
||||||
import { BaseError } from 'make-error'
|
import { BaseError } from 'make-error'
|
||||||
import { startsWith } from 'lodash'
|
import { startsWith } from 'lodash'
|
||||||
|
|
||||||
@ -20,7 +17,7 @@ export default class Xo extends JsonRpcWebSocketClient {
|
|||||||
const url = opts != null ? opts.url : '.'
|
const url = opts != null ? opts.url : '.'
|
||||||
super(`${url === '/' ? '' : url}/api/`)
|
super(`${url === '/' ? '' : url}/api/`)
|
||||||
|
|
||||||
this._credentials = (opts != null ? opts.credentials : null)
|
this._credentials = opts != null ? opts.credentials : null
|
||||||
this._user = null
|
this._user = null
|
||||||
|
|
||||||
this.on(OPEN, () => {
|
this.on(OPEN, () => {
|
||||||
@ -45,12 +42,13 @@ export default class Xo extends JsonRpcWebSocketClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const promise = super.call(method, args)
|
const promise = super.call(method, args)
|
||||||
promise.retry = (predicate) => promise.catch((error) => {
|
promise.retry = predicate =>
|
||||||
i = (i || 0) + 1
|
promise.catch(error => {
|
||||||
if (predicate(error, i)) {
|
i = (i || 0) + 1
|
||||||
return this.call(method, args, i)
|
if (predicate(error, i)) {
|
||||||
}
|
return this.call(method, args, i)
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,13 @@ import map from 'lodash/map'
|
|||||||
import trim from 'lodash/trim'
|
import trim from 'lodash/trim'
|
||||||
import trimStart from 'lodash/trimStart'
|
import trimStart from 'lodash/trimStart'
|
||||||
|
|
||||||
const sanitizePath = (...paths) => filter(map(paths, s => s && filter(map(s.split('/'), trim)).join('/'))).join('/')
|
const sanitizePath = (...paths) =>
|
||||||
|
filter(map(paths, s => s && filter(map(s.split('/'), trim)).join('/'))).join(
|
||||||
|
'/'
|
||||||
|
)
|
||||||
|
|
||||||
export const parse = string => {
|
export const parse = string => {
|
||||||
const object = { }
|
const object = {}
|
||||||
|
|
||||||
const [type, rest] = string.split('://')
|
const [type, rest] = string.split('://')
|
||||||
if (type === 'file') {
|
if (type === 'file') {
|
||||||
@ -36,7 +39,7 @@ export const parse = string => {
|
|||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
export const format = ({type, host, path, username, password, domain}) => {
|
export const format = ({ type, host, path, username, password, domain }) => {
|
||||||
type === 'local' && (type = 'file')
|
type === 'local' && (type = 'file')
|
||||||
let string = `${type}://`
|
let string = `${type}://`
|
||||||
if (type === 'nfs') {
|
if (type === 'nfs') {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Strategy} from 'passport-github'
|
import { Strategy } from 'passport-github'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -27,18 +27,23 @@ class AuthGitHubXoPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
load () {
|
load () {
|
||||||
const {_xo: xo} = this
|
const { _xo: xo } = this
|
||||||
|
|
||||||
xo.registerPassportStrategy(new Strategy(this._conf, async (accessToken, refreshToken, profile, done) => {
|
xo.registerPassportStrategy(
|
||||||
try {
|
new Strategy(
|
||||||
done(null, await xo.registerUser('github', profile.username))
|
this._conf,
|
||||||
} catch (error) {
|
async (accessToken, refreshToken, profile, done) => {
|
||||||
done(error.message)
|
try {
|
||||||
}
|
done(null, await xo.registerUser('github', profile.username))
|
||||||
}))
|
} catch (error) {
|
||||||
|
done(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export default ({xo}) => new AuthGitHubXoPlugin(xo)
|
export default ({ xo }) => new AuthGitHubXoPlugin(xo)
|
||||||
|
@ -7,7 +7,8 @@ export const configurationSchema = {
|
|||||||
properties: {
|
properties: {
|
||||||
callbackURL: {
|
callbackURL: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Must be exactly the same as specified on the Google developer console.',
|
description:
|
||||||
|
'Must be exactly the same as specified on the Google developer console.',
|
||||||
},
|
},
|
||||||
clientID: {
|
clientID: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -18,8 +19,8 @@ export const configurationSchema = {
|
|||||||
scope: {
|
scope: {
|
||||||
default: 'https://www.googleapis.com/auth/plus.login',
|
default: 'https://www.googleapis.com/auth/plus.login',
|
||||||
description: 'Note that changing this value will break existing users.',
|
description: 'Note that changing this value will break existing users.',
|
||||||
enum: [ 'https://www.googleapis.com/auth/plus.login', 'email' ],
|
enum: ['https://www.googleapis.com/auth/plus.login', 'email'],
|
||||||
enumNames: [ 'Google+ name', 'Simple email address' ],
|
enumNames: ['Google+ name', 'Simple email address'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ['callbackURL', 'clientID', 'clientSecret'],
|
required: ['callbackURL', 'clientID', 'clientSecret'],
|
||||||
@ -41,18 +42,23 @@ class AuthGoogleXoPlugin {
|
|||||||
const conf = this._conf
|
const conf = this._conf
|
||||||
const xo = this._xo
|
const xo = this._xo
|
||||||
|
|
||||||
xo.registerPassportStrategy(new Strategy(conf, async (accessToken, refreshToken, profile, done) => {
|
xo.registerPassportStrategy(
|
||||||
try {
|
new Strategy(conf, async (accessToken, refreshToken, profile, done) => {
|
||||||
done(null, await xo.registerUser(
|
try {
|
||||||
'google',
|
done(
|
||||||
conf.scope === 'email'
|
null,
|
||||||
? profile.emails[0].value
|
await xo.registerUser(
|
||||||
: profile.displayName
|
'google',
|
||||||
))
|
conf.scope === 'email'
|
||||||
} catch (error) {
|
? profile.emails[0].value
|
||||||
done(error.message)
|
: profile.displayName
|
||||||
}
|
)
|
||||||
}))
|
)
|
||||||
|
} catch (error) {
|
||||||
|
done(error.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,15 +10,16 @@ import { readFile } from 'fs'
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const VAR_RE = /\{\{([^}]+)\}\}/g
|
const VAR_RE = /\{\{([^}]+)\}\}/g
|
||||||
const evalFilter = (filter, vars) => filter.replace(VAR_RE, (_, name) => {
|
const evalFilter = (filter, vars) =>
|
||||||
const value = vars[name]
|
filter.replace(VAR_RE, (_, name) => {
|
||||||
|
const value = vars[name]
|
||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
throw new Error('invalid variable: ' + name)
|
throw new Error('invalid variable: ' + name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return escape(value)
|
return escape(value)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const configurationSchema = {
|
export const configurationSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -39,7 +40,8 @@ If not specified, it will use a default set of well-known CAs.
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
checkCertificate: {
|
checkCertificate: {
|
||||||
description: 'Enforce the validity of the server\'s certificates. You can disable it when connecting to servers that use a self-signed certificate.',
|
description:
|
||||||
|
"Enforce the validity of the server's certificates. You can disable it when connecting to servers that use a self-signed certificate.",
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
@ -58,14 +60,16 @@ For Microsoft Active Directory, it can also be \`<user>@<domain>\`.
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
description: 'Password of the user permitted of search the LDAP directory.',
|
description:
|
||||||
|
'Password of the user permitted of search the LDAP directory.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ['dn', 'password'],
|
required: ['dn', 'password'],
|
||||||
},
|
},
|
||||||
base: {
|
base: {
|
||||||
description: 'The base is the part of the description tree where the users are looked for.',
|
description:
|
||||||
|
'The base is the part of the description tree where the users are looked for.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
@ -116,25 +120,21 @@ class AuthLdap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async configure (conf) {
|
async configure (conf) {
|
||||||
const clientOpts = this._clientOpts = {
|
const clientOpts = (this._clientOpts = {
|
||||||
url: conf.uri,
|
url: conf.uri,
|
||||||
maxConnections: 5,
|
maxConnections: 5,
|
||||||
tlsOptions: {},
|
tlsOptions: {},
|
||||||
}
|
})
|
||||||
|
|
||||||
{
|
{
|
||||||
const {
|
const { bind, checkCertificate = true, certificateAuthorities } = conf
|
||||||
bind,
|
|
||||||
checkCertificate = true,
|
|
||||||
certificateAuthorities,
|
|
||||||
} = conf
|
|
||||||
|
|
||||||
if (bind) {
|
if (bind) {
|
||||||
clientOpts.bindDN = bind.dn
|
clientOpts.bindDN = bind.dn
|
||||||
clientOpts.bindCredentials = bind.password
|
clientOpts.bindCredentials = bind.password
|
||||||
}
|
}
|
||||||
|
|
||||||
const {tlsOptions} = clientOpts
|
const { tlsOptions } = clientOpts
|
||||||
|
|
||||||
tlsOptions.rejectUnauthorized = checkCertificate
|
tlsOptions.rejectUnauthorized = checkCertificate
|
||||||
if (certificateAuthorities) {
|
if (certificateAuthorities) {
|
||||||
@ -192,7 +192,7 @@ class AuthLdap {
|
|||||||
|
|
||||||
// Bind if necessary.
|
// Bind if necessary.
|
||||||
{
|
{
|
||||||
const {_credentials: credentials} = this
|
const { _credentials: credentials } = this
|
||||||
if (credentials) {
|
if (credentials) {
|
||||||
logger(`attempting to bind with as ${credentials.dn}...`)
|
logger(`attempting to bind with as ${credentials.dn}...`)
|
||||||
await bind(credentials.dn, credentials.password)
|
await bind(credentials.dn, credentials.password)
|
||||||
@ -216,7 +216,7 @@ class AuthLdap {
|
|||||||
entries.push(entry.json)
|
entries.push(entry.json)
|
||||||
})
|
})
|
||||||
|
|
||||||
const {status} = await eventToPromise(response, 'end')
|
const { status } = await eventToPromise(response, 'end')
|
||||||
if (status) {
|
if (status) {
|
||||||
throw new Error('unexpected search response status: ' + status)
|
throw new Error('unexpected search response status: ' + status)
|
||||||
}
|
}
|
||||||
@ -229,7 +229,11 @@ class AuthLdap {
|
|||||||
try {
|
try {
|
||||||
logger(`attempting to bind as ${entry.objectName}`)
|
logger(`attempting to bind as ${entry.objectName}`)
|
||||||
await bind(entry.objectName, password)
|
await bind(entry.objectName, password)
|
||||||
logger(`successfully bound as ${entry.objectName} => ${username} authenticated`)
|
logger(
|
||||||
|
`successfully bound as ${
|
||||||
|
entry.objectName
|
||||||
|
} => ${username} authenticated`
|
||||||
|
)
|
||||||
return { username }
|
return { username }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger(`failed to bind as ${entry.objectName}: ${error.message}`)
|
logger(`failed to bind as ${entry.objectName}: ${error.message}`)
|
||||||
@ -246,4 +250,4 @@ class AuthLdap {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export default ({xo}) => new AuthLdap(xo)
|
export default ({ xo }) => new AuthLdap(xo)
|
||||||
|
@ -8,48 +8,61 @@ const EMPTY_OBJECT = Object.freeze({ __proto__: null })
|
|||||||
|
|
||||||
const _extractValue = ({ value }) => value
|
const _extractValue = ({ value }) => value
|
||||||
|
|
||||||
export const confirm = (message, {
|
export const confirm = (
|
||||||
default: defaultValue = null,
|
|
||||||
} = EMPTY_OBJECT) => prompt({
|
|
||||||
default: defaultValue,
|
|
||||||
message,
|
message,
|
||||||
name: 'value',
|
{ default: defaultValue = null } = EMPTY_OBJECT
|
||||||
type: 'confirm',
|
) =>
|
||||||
}).then(_extractValue)
|
prompt({
|
||||||
|
default: defaultValue,
|
||||||
|
message,
|
||||||
|
name: 'value',
|
||||||
|
type: 'confirm',
|
||||||
|
}).then(_extractValue)
|
||||||
|
|
||||||
export const input = (message, {
|
export const input = (
|
||||||
default: defaultValue = null,
|
|
||||||
filter = undefined,
|
|
||||||
validate = undefined,
|
|
||||||
} = EMPTY_OBJECT) => prompt({
|
|
||||||
default: defaultValue,
|
|
||||||
message,
|
message,
|
||||||
name: 'value',
|
{
|
||||||
type: 'input',
|
default: defaultValue = null,
|
||||||
validate,
|
filter = undefined,
|
||||||
}).then(_extractValue)
|
validate = undefined,
|
||||||
|
} = EMPTY_OBJECT
|
||||||
|
) =>
|
||||||
|
prompt({
|
||||||
|
default: defaultValue,
|
||||||
|
message,
|
||||||
|
name: 'value',
|
||||||
|
type: 'input',
|
||||||
|
validate,
|
||||||
|
}).then(_extractValue)
|
||||||
|
|
||||||
export const list = (message, choices, {
|
export const list = (
|
||||||
default: defaultValue = null,
|
message,
|
||||||
} = EMPTY_OBJECT) => prompt({
|
|
||||||
default: defaultValue,
|
|
||||||
choices,
|
choices,
|
||||||
message,
|
{ default: defaultValue = null } = EMPTY_OBJECT
|
||||||
name: 'value',
|
) =>
|
||||||
type: 'list',
|
prompt({
|
||||||
}).then(_extractValue)
|
default: defaultValue,
|
||||||
|
choices,
|
||||||
|
message,
|
||||||
|
name: 'value',
|
||||||
|
type: 'list',
|
||||||
|
}).then(_extractValue)
|
||||||
|
|
||||||
export const password = (message, {
|
export const password = (
|
||||||
default: defaultValue = null,
|
|
||||||
filter = undefined,
|
|
||||||
validate = undefined,
|
|
||||||
} = EMPTY_OBJECT) => prompt({
|
|
||||||
default: defaultValue,
|
|
||||||
message,
|
message,
|
||||||
name: 'value',
|
{
|
||||||
type: 'password',
|
default: defaultValue = null,
|
||||||
validate,
|
filter = undefined,
|
||||||
}).then(_extractValue)
|
validate = undefined,
|
||||||
|
} = EMPTY_OBJECT
|
||||||
|
) =>
|
||||||
|
prompt({
|
||||||
|
default: defaultValue,
|
||||||
|
message,
|
||||||
|
name: 'value',
|
||||||
|
type: 'password',
|
||||||
|
validate,
|
||||||
|
}).then(_extractValue)
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -69,25 +82,25 @@ const promptByType = {
|
|||||||
items[i] = await promptGeneric(
|
items[i] = await promptGeneric(
|
||||||
itemSchema,
|
itemSchema,
|
||||||
defaultValue[i],
|
defaultValue[i],
|
||||||
path
|
path ? `${path} [${i}]` : `[${i}]`
|
||||||
? `${path} [${i}]`
|
|
||||||
: `[${i}]`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
++i
|
++i
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = schema.minItems || 0
|
let n = schema.minItems || 0
|
||||||
while (i < n) { // eslint-disable-line no-unmodified-loop-condition
|
// eslint-disable-next-line no-unmodified-loop-condition
|
||||||
|
while (i < n) {
|
||||||
await promptItem()
|
await promptItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
n = schema.maxItems || Infinity
|
n = schema.maxItems || Infinity
|
||||||
while (
|
while (
|
||||||
i < n && // eslint-disable-line no-unmodified-loop-condition
|
// eslint-disable-next-line no-unmodified-loop-condition
|
||||||
await confirm('additional item?', {
|
i < n &&
|
||||||
|
(await confirm('additional item?', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
}))
|
||||||
) {
|
) {
|
||||||
await promptItem()
|
await promptItem()
|
||||||
}
|
}
|
||||||
@ -95,33 +108,38 @@ const promptByType = {
|
|||||||
return items
|
return items
|
||||||
},
|
},
|
||||||
|
|
||||||
boolean: (schema, defaultValue, path) => confirm(path, {
|
boolean: (schema, defaultValue, path) =>
|
||||||
default: defaultValue != null ? defaultValue : schema.default,
|
confirm(path, {
|
||||||
}),
|
default: defaultValue != null ? defaultValue : schema.default,
|
||||||
|
}),
|
||||||
|
|
||||||
enum: (schema, defaultValue, path) => list(path, schema.enum, {
|
enum: (schema, defaultValue, path) =>
|
||||||
defaultValue: defaultValue || schema.defaultValue,
|
list(path, schema.enum, {
|
||||||
}),
|
defaultValue: defaultValue || schema.defaultValue,
|
||||||
|
}),
|
||||||
|
|
||||||
integer: (schema, defaultValue, path) => input(path, {
|
integer: (schema, defaultValue, path) =>
|
||||||
default: defaultValue || schema.default,
|
input(path, {
|
||||||
filter: input => +input,
|
default: defaultValue || schema.default,
|
||||||
validate: input => isInteger(+input),
|
filter: input => +input,
|
||||||
}),
|
validate: input => isInteger(+input),
|
||||||
|
}),
|
||||||
|
|
||||||
number: (schema, defaultValue, path) => input(path, {
|
number: (schema, defaultValue, path) =>
|
||||||
default: defaultValue || schema.default,
|
input(path, {
|
||||||
filter: input => +input,
|
default: defaultValue || schema.default,
|
||||||
validate: input => isFinite(+input),
|
filter: input => +input,
|
||||||
}),
|
validate: input => isFinite(+input),
|
||||||
|
}),
|
||||||
|
|
||||||
object: async (schema, defaultValue, path) => {
|
object: async (schema, defaultValue, path) => {
|
||||||
const value = {}
|
const value = {}
|
||||||
|
|
||||||
const required = {}
|
const required = {}
|
||||||
schema.required && forEach(schema.required, name => {
|
schema.required &&
|
||||||
required[name] = true
|
forEach(schema.required, name => {
|
||||||
})
|
required[name] = true
|
||||||
|
})
|
||||||
|
|
||||||
const promptProperty = async (schema, name) => {
|
const promptProperty = async (schema, name) => {
|
||||||
const subpath = path
|
const subpath = path
|
||||||
@ -130,9 +148,9 @@ const promptByType = {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
required[name] ||
|
required[name] ||
|
||||||
await confirm(`fill optional ${subpath}?`, {
|
(await confirm(`fill optional ${subpath}?`, {
|
||||||
default: Boolean(defaultValue && name in defaultValue),
|
default: Boolean(defaultValue && name in defaultValue),
|
||||||
})
|
}))
|
||||||
) {
|
) {
|
||||||
value[name] = await promptGeneric(
|
value[name] = await promptGeneric(
|
||||||
schema,
|
schema,
|
||||||
@ -147,15 +165,14 @@ const promptByType = {
|
|||||||
return value
|
return value
|
||||||
},
|
},
|
||||||
|
|
||||||
string: (schema, defaultValue, path) => input(path, {
|
string: (schema, defaultValue, path) =>
|
||||||
default: defaultValue || schema.default,
|
input(path, {
|
||||||
}),
|
default: defaultValue || schema.default,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function promptGeneric (schema, defaultValue, path) {
|
export default function promptGeneric (schema, defaultValue, path) {
|
||||||
const type = schema.enum
|
const type = schema.enum ? 'enum' : schema.type
|
||||||
? 'enum'
|
|
||||||
: schema.type
|
|
||||||
|
|
||||||
const prompt = promptByType[type.toLowerCase()]
|
const prompt = promptByType[type.toLowerCase()]
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
|
@ -5,13 +5,8 @@ import { bind } from 'lodash'
|
|||||||
import { fromCallback } from 'promise-toolbox'
|
import { fromCallback } from 'promise-toolbox'
|
||||||
import { readFile, writeFile } from 'fs'
|
import { readFile, writeFile } from 'fs'
|
||||||
|
|
||||||
import promptSchema, {
|
import promptSchema, { input, password } from './prompt-schema'
|
||||||
input,
|
import createPlugin, { configurationSchema } from './'
|
||||||
password,
|
|
||||||
} from './prompt-schema'
|
|
||||||
import createPlugin, {
|
|
||||||
configurationSchema,
|
|
||||||
} from './'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -27,7 +22,9 @@ execPromise(async args => {
|
|||||||
() => ({})
|
() => ({})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await fromCallback(cb => writeFile(CACHE_FILE, JSON.stringify(config, null, 2), cb)).then(
|
await fromCallback(cb =>
|
||||||
|
writeFile(CACHE_FILE, JSON.stringify(config, null, 2), cb)
|
||||||
|
).then(
|
||||||
() => {
|
() => {
|
||||||
console.log('configuration saved in %s', CACHE_FILE)
|
console.log('configuration saved in %s', CACHE_FILE)
|
||||||
},
|
},
|
||||||
@ -40,10 +37,13 @@ execPromise(async args => {
|
|||||||
const plugin = createPlugin({})
|
const plugin = createPlugin({})
|
||||||
await plugin.configure(config)
|
await plugin.configure(config)
|
||||||
|
|
||||||
await plugin._authenticate({
|
await plugin._authenticate(
|
||||||
username: await input('Username', {
|
{
|
||||||
validate: input => !!input.length,
|
username: await input('Username', {
|
||||||
}),
|
validate: input => !!input.length,
|
||||||
password: await password('Password'),
|
}),
|
||||||
}, bind(console.log, console))
|
password: await password('Password'),
|
||||||
|
},
|
||||||
|
bind(console.log, console)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Strategy} from 'passport-saml'
|
import { Strategy } from 'passport-saml'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -38,19 +38,21 @@ class AuthSamlXoPlugin {
|
|||||||
load () {
|
load () {
|
||||||
const xo = this._xo
|
const xo = this._xo
|
||||||
|
|
||||||
xo.registerPassportStrategy(new Strategy(this._conf, async (profile, done) => {
|
xo.registerPassportStrategy(
|
||||||
const name = profile[this._usernameField]
|
new Strategy(this._conf, async (profile, done) => {
|
||||||
if (!name) {
|
const name = profile[this._usernameField]
|
||||||
done('no name found for this user')
|
if (!name) {
|
||||||
return
|
done('no name found for this user')
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
done(null, await xo.registerUser('saml', name))
|
done(null, await xo.registerUser('saml', name))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
done(error.message)
|
done(error.message)
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,15 +37,17 @@ const ICON_FAILURE = '🚨'
|
|||||||
const ICON_SUCCESS = '✔'
|
const ICON_SUCCESS = '✔'
|
||||||
|
|
||||||
const DATE_FORMAT = 'dddd, MMMM Do YYYY, h:mm:ss a'
|
const DATE_FORMAT = 'dddd, MMMM Do YYYY, h:mm:ss a'
|
||||||
const createDateFormater = timezone => timezone !== undefined
|
const createDateFormater = timezone =>
|
||||||
? timestamp => moment(timestamp).tz(timezone).format(DATE_FORMAT)
|
timezone !== undefined
|
||||||
: timestamp => moment(timestamp).format(DATE_FORMAT)
|
? timestamp =>
|
||||||
|
moment(timestamp)
|
||||||
|
.tz(timezone)
|
||||||
|
.format(DATE_FORMAT)
|
||||||
|
: timestamp => moment(timestamp).format(DATE_FORMAT)
|
||||||
|
|
||||||
const formatDuration = milliseconds =>
|
const formatDuration = milliseconds => moment.duration(milliseconds).humanize()
|
||||||
moment.duration(milliseconds).humanize()
|
|
||||||
|
|
||||||
const formatMethod = method =>
|
const formatMethod = method => startCase(method.slice(method.indexOf('.') + 1))
|
||||||
startCase(method.slice(method.indexOf('.') + 1))
|
|
||||||
|
|
||||||
const formatSize = bytes =>
|
const formatSize = bytes =>
|
||||||
humanFormat(bytes, {
|
humanFormat(bytes, {
|
||||||
@ -83,7 +85,9 @@ class BackupReportsXoPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_wrapper (status) {
|
_wrapper (status) {
|
||||||
return new Promise(resolve => resolve(this._listener(status))).catch(logError)
|
return new Promise(resolve => resolve(this._listener(status))).catch(
|
||||||
|
logError
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_listener (status) {
|
_listener (status) {
|
||||||
@ -114,8 +118,7 @@ class BackupReportsXoPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const reportOnFailure =
|
const reportOnFailure =
|
||||||
reportWhen === 'fail' || // xo-web < 5
|
reportWhen === 'fail' || reportWhen === 'failure' // xo-web < 5 // xo-web >= 5
|
||||||
reportWhen === 'failure' // xo-web >= 5
|
|
||||||
|
|
||||||
let globalMergeSize = 0
|
let globalMergeSize = 0
|
||||||
let globalTransferSize = 0
|
let globalTransferSize = 0
|
||||||
@ -152,11 +155,7 @@ class BackupReportsXoPlugin {
|
|||||||
|
|
||||||
const { message } = error
|
const { message } = error
|
||||||
|
|
||||||
failedBackupsText.push(
|
failedBackupsText.push(...text, `- **Error**: ${message}`, '')
|
||||||
...text,
|
|
||||||
`- **Error**: ${message}`,
|
|
||||||
''
|
|
||||||
)
|
|
||||||
|
|
||||||
nagiosText.push(
|
nagiosText.push(
|
||||||
`[ ${vm !== undefined ? vm.name_label : 'undefined'} : ${message} ]`
|
`[ ${vm !== undefined ? vm.name_label : 'undefined'} : ${message} ]`
|
||||||
@ -169,22 +168,25 @@ class BackupReportsXoPlugin {
|
|||||||
globalTransferSize += transferSize
|
globalTransferSize += transferSize
|
||||||
text.push(
|
text.push(
|
||||||
`- **Transfer size**: ${formatSize(transferSize)}`,
|
`- **Transfer size**: ${formatSize(transferSize)}`,
|
||||||
`- **Transfer speed**: ${formatSpeed(transferSize, returnedValue.transferDuration)}`
|
`- **Transfer speed**: ${formatSpeed(
|
||||||
|
transferSize,
|
||||||
|
returnedValue.transferDuration
|
||||||
|
)}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (mergeSize !== undefined) {
|
if (mergeSize !== undefined) {
|
||||||
globalMergeSize += mergeSize
|
globalMergeSize += mergeSize
|
||||||
text.push(
|
text.push(
|
||||||
`- **Merge size**: ${formatSize(mergeSize)}`,
|
`- **Merge size**: ${formatSize(mergeSize)}`,
|
||||||
`- **Merge speed**: ${formatSpeed(mergeSize, returnedValue.mergeDuration)}`
|
`- **Merge speed**: ${formatSpeed(
|
||||||
|
mergeSize,
|
||||||
|
returnedValue.mergeDuration
|
||||||
|
)}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
successfulBackupText.push(
|
successfulBackupText.push(...text, '')
|
||||||
...text,
|
|
||||||
''
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -208,14 +210,10 @@ class BackupReportsXoPlugin {
|
|||||||
`- **Successes**: ${nSuccesses} / ${nCalls}`,
|
`- **Successes**: ${nSuccesses} / ${nCalls}`,
|
||||||
]
|
]
|
||||||
if (globalTransferSize !== 0) {
|
if (globalTransferSize !== 0) {
|
||||||
markdown.push(
|
markdown.push(`- **Transfer size**: ${formatSize(globalTransferSize)}`)
|
||||||
`- **Transfer size**: ${formatSize(globalTransferSize)}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (globalMergeSize !== 0) {
|
if (globalMergeSize !== 0) {
|
||||||
markdown.push(
|
markdown.push(`- **Merge size**: ${formatSize(globalMergeSize)}`)
|
||||||
`- **Merge size**: ${formatSize(globalMergeSize)}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
markdown.push('')
|
markdown.push('')
|
||||||
|
|
||||||
@ -239,38 +237,40 @@ class BackupReportsXoPlugin {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
markdown.push(
|
markdown.push('---', '', `*${pkg.name} v${pkg.version}*`)
|
||||||
'---',
|
|
||||||
'',
|
|
||||||
`*${pkg.name} v${pkg.version}*`
|
|
||||||
)
|
|
||||||
|
|
||||||
markdown = markdown.join('\n')
|
markdown = markdown.join('\n')
|
||||||
|
|
||||||
const xo = this._xo
|
const xo = this._xo
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
xo.sendEmail !== undefined && xo.sendEmail({
|
xo.sendEmail !== undefined &&
|
||||||
to: this._mailsReceivers,
|
xo.sendEmail({
|
||||||
subject: `[Xen Orchestra] ${
|
to: this._mailsReceivers,
|
||||||
globalSuccess ? 'Success' : 'Failure'
|
subject: `[Xen Orchestra] ${
|
||||||
} − Backup report for ${tag} ${
|
globalSuccess ? 'Success' : 'Failure'
|
||||||
globalSuccess ? ICON_SUCCESS : ICON_FAILURE
|
} − Backup report for ${tag} ${
|
||||||
}`,
|
globalSuccess ? ICON_SUCCESS : ICON_FAILURE
|
||||||
markdown,
|
}`,
|
||||||
}),
|
markdown,
|
||||||
xo.sendToXmppClient !== undefined && xo.sendToXmppClient({
|
}),
|
||||||
to: this._xmppReceivers,
|
xo.sendToXmppClient !== undefined &&
|
||||||
message: markdown,
|
xo.sendToXmppClient({
|
||||||
}),
|
to: this._xmppReceivers,
|
||||||
xo.sendSlackMessage !== undefined && xo.sendSlackMessage({
|
message: markdown,
|
||||||
message: markdown,
|
}),
|
||||||
}),
|
xo.sendSlackMessage !== undefined &&
|
||||||
xo.sendPassiveCheck !== undefined && xo.sendPassiveCheck({
|
xo.sendSlackMessage({
|
||||||
status: globalSuccess ? 0 : 2,
|
message: markdown,
|
||||||
message: globalSuccess
|
}),
|
||||||
? `[Xen Orchestra] [Success] Backup report for ${tag}`
|
xo.sendPassiveCheck !== undefined &&
|
||||||
: `[Xen Orchestra] [Failure] Backup report for ${tag} - VMs : ${nagiosText.join(' ')}`,
|
xo.sendPassiveCheck({
|
||||||
}),
|
status: globalSuccess ? 0 : 2,
|
||||||
|
message: globalSuccess
|
||||||
|
? `[Xen Orchestra] [Success] Backup report for ${tag}`
|
||||||
|
: `[Xen Orchestra] [Failure] Backup report for ${tag} - VMs : ${nagiosText.join(
|
||||||
|
' '
|
||||||
|
)}`,
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ class XoServerCloud {
|
|||||||
getResourceCatalog.description = 'Get the list of all available resources'
|
getResourceCatalog.description = 'Get the list of all available resources'
|
||||||
getResourceCatalog.permission = 'admin'
|
getResourceCatalog.permission = 'admin'
|
||||||
|
|
||||||
const registerResource = ({ namespace }) => this._registerResource(namespace)
|
const registerResource = ({ namespace }) =>
|
||||||
|
this._registerResource(namespace)
|
||||||
registerResource.description = 'Register a resource via cloud plugin'
|
registerResource.description = 'Register a resource via cloud plugin'
|
||||||
registerResource.params = {
|
registerResource.params = {
|
||||||
namespace: {
|
namespace: {
|
||||||
@ -42,21 +43,22 @@ class XoServerCloud {
|
|||||||
registerResource,
|
registerResource,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
this._unsetRequestResource = this._xo.defineProperty('requestResource', this._requestResource, this)
|
this._unsetRequestResource = this._xo.defineProperty(
|
||||||
|
'requestResource',
|
||||||
|
this._requestResource,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
|
||||||
const updater = this._updater = new Client(`${UPDATER_URL}:${WS_PORT}`)
|
const updater = (this._updater = new Client(`${UPDATER_URL}:${WS_PORT}`))
|
||||||
const connect = () => updater.open(createBackoff()).catch(
|
const connect = () =>
|
||||||
error => {
|
updater.open(createBackoff()).catch(error => {
|
||||||
console.error('xo-server-cloud: fail to connect to updater', error)
|
console.error('xo-server-cloud: fail to connect to updater', error)
|
||||||
|
|
||||||
return connect()
|
return connect()
|
||||||
}
|
|
||||||
)
|
|
||||||
updater
|
|
||||||
.on('closed', connect)
|
|
||||||
.on('scheduledAttempt', ({ delay }) => {
|
|
||||||
console.warn('xo-server-cloud: next attempt in %s ms', delay)
|
|
||||||
})
|
})
|
||||||
|
updater.on('closed', connect).on('scheduledAttempt', ({ delay }) => {
|
||||||
|
console.warn('xo-server-cloud: next attempt in %s ms', delay)
|
||||||
|
})
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,13 +140,15 @@ class XoServerCloud {
|
|||||||
throw new Error('cannot get download token')
|
throw new Error('cannot get download token')
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = request.get(`${UPDATER_URL}:${HTTP_PORT}/`)
|
const req = request
|
||||||
|
.get(`${UPDATER_URL}:${HTTP_PORT}/`)
|
||||||
.set('Authorization', `Bearer ${downloadToken}`)
|
.set('Authorization', `Bearer ${downloadToken}`)
|
||||||
|
|
||||||
// Impossible to pipe the response directly: https://github.com/visionmedia/superagent/issues/1187
|
// Impossible to pipe the response directly: https://github.com/visionmedia/superagent/issues/1187
|
||||||
const pt = new PassThrough()
|
const pt = new PassThrough()
|
||||||
req.pipe(pt)
|
req.pipe(pt)
|
||||||
pt.length = (await eventToPromise(req, 'response')).headers['content-length']
|
const { headers } = await eventToPromise(req, 'response')
|
||||||
|
pt.length = headers['content-length']
|
||||||
|
|
||||||
return pt
|
return pt
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ import { debug } from './utils'
|
|||||||
|
|
||||||
export default class DensityPlan extends Plan {
|
export default class DensityPlan extends Plan {
|
||||||
_checkRessourcesThresholds (objects, averages) {
|
_checkRessourcesThresholds (objects, averages) {
|
||||||
return filter(objects, object =>
|
return filter(
|
||||||
averages[object.id].memoryFree > this._thresholds.memoryFree.low
|
objects,
|
||||||
|
object => averages[object.id].memoryFree > this._thresholds.memoryFree.low
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,27 +20,17 @@ export default class DensityPlan extends Plan {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { hosts, toOptimize } = results
|
||||||
hosts,
|
|
||||||
toOptimize,
|
|
||||||
} = results
|
|
||||||
|
|
||||||
let {
|
let { averages: hostsAverages } = results
|
||||||
averages: hostsAverages,
|
|
||||||
} = results
|
|
||||||
|
|
||||||
const pools = await this._getPlanPools()
|
const pools = await this._getPlanPools()
|
||||||
let optimizationsCount = 0
|
let optimizationsCount = 0
|
||||||
|
|
||||||
for (const hostToOptimize of toOptimize) {
|
for (const hostToOptimize of toOptimize) {
|
||||||
const {
|
const { id: hostId, $poolId: poolId } = hostToOptimize
|
||||||
id: hostId,
|
|
||||||
$poolId: poolId,
|
|
||||||
} = hostToOptimize
|
|
||||||
|
|
||||||
const {
|
const { master: masterId } = pools[poolId]
|
||||||
master: masterId,
|
|
||||||
} = pools[poolId]
|
|
||||||
|
|
||||||
// Avoid master optimization.
|
// Avoid master optimization.
|
||||||
if (masterId === hostId) {
|
if (masterId === hostId) {
|
||||||
@ -58,10 +49,7 @@ export default class DensityPlan extends Plan {
|
|||||||
const otherHosts = []
|
const otherHosts = []
|
||||||
|
|
||||||
for (const dest of hosts) {
|
for (const dest of hosts) {
|
||||||
const {
|
const { id: destId, $poolId: destPoolId } = dest
|
||||||
id: destId,
|
|
||||||
$poolId: destPoolId,
|
|
||||||
} = dest
|
|
||||||
|
|
||||||
// Destination host != Host to optimize!
|
// Destination host != Host to optimize!
|
||||||
if (destId === hostId) {
|
if (destId === hostId) {
|
||||||
@ -83,12 +71,7 @@ export default class DensityPlan extends Plan {
|
|||||||
|
|
||||||
const simulResults = await this._simulate({
|
const simulResults = await this._simulate({
|
||||||
host: hostToOptimize,
|
host: hostToOptimize,
|
||||||
destinations: [
|
destinations: [[poolMaster], poolHosts, masters, otherHosts],
|
||||||
[ poolMaster ],
|
|
||||||
poolHosts,
|
|
||||||
masters,
|
|
||||||
otherHosts,
|
|
||||||
],
|
|
||||||
hostsAverages: clone(hostsAverages),
|
hostsAverages: clone(hostsAverages),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -115,15 +98,15 @@ export default class DensityPlan extends Plan {
|
|||||||
|
|
||||||
for (const vm of vms) {
|
for (const vm of vms) {
|
||||||
if (!vm.xenTools) {
|
if (!vm.xenTools) {
|
||||||
debug(`VM (${vm.id}) of Host (${hostId}) does not support pool migration.`)
|
debug(
|
||||||
|
`VM (${vm.id}) of Host (${hostId}) does not support pool migration.`
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort vms by amount of memory. (+ -> -)
|
// Sort vms by amount of memory. (+ -> -)
|
||||||
vms.sort((a, b) =>
|
vms.sort((a, b) => vmsAverages[b.id].memory - vmsAverages[a.id].memory)
|
||||||
vmsAverages[b.id].memory - vmsAverages[a.id].memory
|
|
||||||
)
|
|
||||||
|
|
||||||
const simulResults = {
|
const simulResults = {
|
||||||
hostsAverages,
|
hostsAverages,
|
||||||
@ -162,15 +145,11 @@ export default class DensityPlan extends Plan {
|
|||||||
|
|
||||||
// Test if a VM migration on a destination (of a destinations set) is possible.
|
// Test if a VM migration on a destination (of a destinations set) is possible.
|
||||||
_testMigration ({ vm, destinations, hostsAverages, vmsAverages }) {
|
_testMigration ({ vm, destinations, hostsAverages, vmsAverages }) {
|
||||||
const {
|
const { _thresholds: { critical: criticalThreshold } } = this
|
||||||
_thresholds: {
|
|
||||||
critical: criticalThreshold,
|
|
||||||
},
|
|
||||||
} = this
|
|
||||||
|
|
||||||
// Sort the destinations by available memory. (- -> +)
|
// Sort the destinations by available memory. (- -> +)
|
||||||
destinations.sort((a, b) =>
|
destinations.sort(
|
||||||
hostsAverages[a.id].memoryFree - hostsAverages[b.id].memoryFree
|
(a, b) => hostsAverages[a.id].memoryFree - hostsAverages[b.id].memoryFree
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const destination of destinations) {
|
for (const destination of destinations) {
|
||||||
@ -204,13 +183,18 @@ export default class DensityPlan extends Plan {
|
|||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
mapToArray(moves, move => {
|
mapToArray(moves, move => {
|
||||||
const {
|
const { vm, destination } = move
|
||||||
vm,
|
|
||||||
destination,
|
|
||||||
} = move
|
|
||||||
const xapiDest = this.xo.getXapi(destination)
|
const xapiDest = this.xo.getXapi(destination)
|
||||||
debug(`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${vm.$container}).`)
|
debug(
|
||||||
return xapiDest.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
|
`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${
|
||||||
|
vm.$container
|
||||||
|
}).`
|
||||||
|
)
|
||||||
|
return xapiDest.migrateVm(
|
||||||
|
vm._xapiId,
|
||||||
|
this.xo.getXapi(destination),
|
||||||
|
destination._xapiId
|
||||||
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,10 +9,7 @@ import {
|
|||||||
DEFAULT_CRITICAL_THRESHOLD_CPU,
|
DEFAULT_CRITICAL_THRESHOLD_CPU,
|
||||||
DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE,
|
DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE,
|
||||||
} from './plan'
|
} from './plan'
|
||||||
import {
|
import { EXECUTION_DELAY, debug } from './utils'
|
||||||
EXECUTION_DELAY,
|
|
||||||
debug,
|
|
||||||
} from './utils'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -41,7 +38,7 @@ export const configurationSchema = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
mode: {
|
mode: {
|
||||||
enum: [ 'Performance mode', 'Density mode' ],
|
enum: ['Performance mode', 'Density mode'],
|
||||||
title: 'Mode',
|
title: 'Mode',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -85,7 +82,7 @@ export const configurationSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
required: [ 'name', 'mode', 'pools' ],
|
required: ['name', 'mode', 'pools'],
|
||||||
},
|
},
|
||||||
|
|
||||||
minItems: 1,
|
minItems: 1,
|
||||||
@ -115,7 +112,10 @@ const makeJob = (cronPattern, fn) => {
|
|||||||
try {
|
try {
|
||||||
await fn()
|
await fn()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[WARN] scheduled function:', (error && error.stack) || error)
|
console.error(
|
||||||
|
'[WARN] scheduled function:',
|
||||||
|
(error && error.stack) || error
|
||||||
|
)
|
||||||
} finally {
|
} finally {
|
||||||
job.running = false
|
job.running = false
|
||||||
job.emitter.emit('finish')
|
job.emitter.emit('finish')
|
||||||
@ -133,7 +133,10 @@ const makeJob = (cronPattern, fn) => {
|
|||||||
class LoadBalancerPlugin {
|
class LoadBalancerPlugin {
|
||||||
constructor (xo) {
|
constructor (xo) {
|
||||||
this.xo = xo
|
this.xo = xo
|
||||||
this._job = makeJob(`*/${EXECUTION_DELAY} * * * *`, this._executePlans.bind(this))
|
this._job = makeJob(
|
||||||
|
`*/${EXECUTION_DELAY} * * * *`,
|
||||||
|
this._executePlans.bind(this)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async configure ({ plans }) {
|
async configure ({ plans }) {
|
||||||
@ -154,7 +157,10 @@ class LoadBalancerPlugin {
|
|||||||
|
|
||||||
if (plans) {
|
if (plans) {
|
||||||
for (const plan of plans) {
|
for (const plan of plans) {
|
||||||
this._addPlan(plan.mode === 'Performance mode' ? PERFORMANCE_MODE : DENSITY_MODE, plan)
|
this._addPlan(
|
||||||
|
plan.mode === 'Performance mode' ? PERFORMANCE_MODE : DENSITY_MODE,
|
||||||
|
plan
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,18 +186,17 @@ class LoadBalancerPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._poolIds = this._poolIds.concat(pools)
|
this._poolIds = this._poolIds.concat(pools)
|
||||||
this._plans.push(mode === PERFORMANCE_MODE
|
this._plans.push(
|
||||||
? new PerformancePlan(this.xo, name, pools, options)
|
mode === PERFORMANCE_MODE
|
||||||
: new DensityPlan(this.xo, name, pools, options)
|
? new PerformancePlan(this.xo, name, pools, options)
|
||||||
|
: new DensityPlan(this.xo, name, pools, options)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_executePlans () {
|
_executePlans () {
|
||||||
debug('Execute plans!')
|
debug('Execute plans!')
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(mapToArray(this._plans, plan => plan.execute()))
|
||||||
mapToArray(this._plans, plan => plan.execute())
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,10 @@ export default class PerformancePlan extends Plan {
|
|||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
mapToArray(
|
mapToArray(
|
||||||
filter(this._getHosts({ powerState: 'Halted' }), host => host.powerOnMode !== ''),
|
filter(
|
||||||
|
this._getHosts({ powerState: 'Halted' }),
|
||||||
|
host => host.powerOnMode !== ''
|
||||||
|
),
|
||||||
host => {
|
host => {
|
||||||
const { id } = host
|
const { id } = host
|
||||||
return this.xo.getXapi(id).powerOnHost(id)
|
return this.xo.getXapi(id).powerOnHost(id)
|
||||||
@ -52,17 +55,14 @@ export default class PerformancePlan extends Plan {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { averages, toOptimize } = results
|
||||||
averages,
|
|
||||||
toOptimize,
|
|
||||||
} = results
|
|
||||||
let { hosts } = results
|
let { hosts } = results
|
||||||
|
|
||||||
toOptimize.sort((a, b) => {
|
toOptimize.sort((a, b) => {
|
||||||
a = averages[a.id]
|
a = averages[a.id]
|
||||||
b = averages[b.id]
|
b = averages[b.id]
|
||||||
|
|
||||||
return (b.cpu - a.cpu) || (a.memoryFree - b.memoryFree)
|
return b.cpu - a.cpu || a.memoryFree - b.memoryFree
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const exceededHost of toOptimize) {
|
for (const exceededHost of toOptimize) {
|
||||||
@ -85,9 +85,7 @@ export default class PerformancePlan extends Plan {
|
|||||||
const vmsAverages = await this._getVmsAverages(vms, exceededHost)
|
const vmsAverages = await this._getVmsAverages(vms, exceededHost)
|
||||||
|
|
||||||
// Sort vms by cpu usage. (lower to higher)
|
// Sort vms by cpu usage. (lower to higher)
|
||||||
vms.sort((a, b) =>
|
vms.sort((a, b) => vmsAverages[b.id].cpu - vmsAverages[a.id].cpu)
|
||||||
vmsAverages[b.id].cpu - vmsAverages[a.id].cpu
|
|
||||||
)
|
|
||||||
|
|
||||||
const exceededAverages = hostsAverages[exceededHost.id]
|
const exceededAverages = hostsAverages[exceededHost.id]
|
||||||
const promises = []
|
const promises = []
|
||||||
@ -95,11 +93,15 @@ export default class PerformancePlan extends Plan {
|
|||||||
const xapiSrc = this.xo.getXapi(exceededHost)
|
const xapiSrc = this.xo.getXapi(exceededHost)
|
||||||
let optimizationsCount = 0
|
let optimizationsCount = 0
|
||||||
|
|
||||||
const searchFunction = (a, b) => hostsAverages[b.id].cpu - hostsAverages[a.id].cpu
|
const searchFunction = (a, b) =>
|
||||||
|
hostsAverages[b.id].cpu - hostsAverages[a.id].cpu
|
||||||
|
|
||||||
for (const vm of vms) {
|
for (const vm of vms) {
|
||||||
// Search host with lower cpu usage in the same pool first. In other pool if necessary.
|
// Search host with lower cpu usage in the same pool first. In other pool if necessary.
|
||||||
let destination = searchBestObject(find(hosts, host => host.$poolId === vm.$poolId), searchFunction)
|
let destination = searchBestObject(
|
||||||
|
find(hosts, host => host.$poolId === vm.$poolId),
|
||||||
|
searchFunction
|
||||||
|
)
|
||||||
|
|
||||||
if (!destination) {
|
if (!destination) {
|
||||||
destination = searchBestObject(hosts, searchFunction)
|
destination = searchBestObject(hosts, searchFunction)
|
||||||
@ -110,7 +112,8 @@ export default class PerformancePlan extends Plan {
|
|||||||
|
|
||||||
// Unable to move the vm.
|
// Unable to move the vm.
|
||||||
if (
|
if (
|
||||||
exceededAverages.cpu - vmAverages.cpu < destinationAverages.cpu + vmAverages.cpu ||
|
exceededAverages.cpu - vmAverages.cpu <
|
||||||
|
destinationAverages.cpu + vmAverages.cpu ||
|
||||||
destinationAverages.memoryFree > vmAverages.memory
|
destinationAverages.memoryFree > vmAverages.memory
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
@ -122,15 +125,27 @@ export default class PerformancePlan extends Plan {
|
|||||||
exceededAverages.memoryFree += vmAverages.memory
|
exceededAverages.memoryFree += vmAverages.memory
|
||||||
destinationAverages.memoryFree -= vmAverages.memory
|
destinationAverages.memoryFree -= vmAverages.memory
|
||||||
|
|
||||||
debug(`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${exceededHost.id}).`)
|
debug(
|
||||||
|
`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${
|
||||||
|
exceededHost.id
|
||||||
|
}).`
|
||||||
|
)
|
||||||
optimizationsCount++
|
optimizationsCount++
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
xapiSrc.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
|
xapiSrc.migrateVm(
|
||||||
|
vm._xapiId,
|
||||||
|
this.xo.getXapi(destination),
|
||||||
|
destination._xapiId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
debug(`Performance mode: ${optimizationsCount} optimizations for Host (${exceededHost.id}).`)
|
debug(
|
||||||
|
`Performance mode: ${optimizationsCount} optimizations for Host (${
|
||||||
|
exceededHost.id
|
||||||
|
}).`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import { filter, includes, map as mapToArray } from 'lodash'
|
import { filter, includes, map as mapToArray } from 'lodash'
|
||||||
|
|
||||||
import {
|
import { EXECUTION_DELAY, debug } from './utils'
|
||||||
EXECUTION_DELAY,
|
|
||||||
debug,
|
|
||||||
} from './utils'
|
|
||||||
|
|
||||||
const MINUTES_OF_HISTORICAL_DATA = 30
|
const MINUTES_OF_HISTORICAL_DATA = 30
|
||||||
|
|
||||||
@ -20,7 +17,7 @@ const LOW_THRESHOLD_FACTOR = 0.25
|
|||||||
const HIGH_THRESHOLD_MEMORY_FREE_FACTOR = 1.25
|
const HIGH_THRESHOLD_MEMORY_FREE_FACTOR = 1.25
|
||||||
const LOW_THRESHOLD_MEMORY_FREE_FACTOR = 20.0
|
const LOW_THRESHOLD_MEMORY_FREE_FACTOR = 20.0
|
||||||
|
|
||||||
const numberOrDefault = (value, def) => (value >= 0) ? value : def
|
const numberOrDefault = (value, def) => (value >= 0 ? value : def)
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Averages.
|
// Averages.
|
||||||
@ -69,10 +66,12 @@ function computeRessourcesAverageWithWeight (averages1, averages2, ratio) {
|
|||||||
const averages = {}
|
const averages = {}
|
||||||
|
|
||||||
for (const id in averages1) {
|
for (const id in averages1) {
|
||||||
const objectAverages = averages[id] = {}
|
const objectAverages = (averages[id] = {})
|
||||||
|
|
||||||
for (const averageName in averages1[id]) {
|
for (const averageName in averages1[id]) {
|
||||||
objectAverages[averageName] = averages1[id][averageName] * ratio + averages2[id][averageName] * (1 - ratio)
|
objectAverages[averageName] =
|
||||||
|
averages1[id][averageName] * ratio +
|
||||||
|
averages2[id][averageName] * (1 - ratio)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,20 +88,24 @@ function setRealCpuAverageOfVms (vms, vmsAverages, nCpus) {
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export default class Plan {
|
export default class Plan {
|
||||||
constructor (xo, name, poolIds, {
|
constructor (xo, name, poolIds, { excludedHosts, thresholds } = {}) {
|
||||||
excludedHosts,
|
|
||||||
thresholds,
|
|
||||||
} = {}) {
|
|
||||||
this.xo = xo
|
this.xo = xo
|
||||||
this._name = name
|
this._name = name
|
||||||
this._poolIds = poolIds
|
this._poolIds = poolIds
|
||||||
this._excludedHosts = excludedHosts
|
this._excludedHosts = excludedHosts
|
||||||
this._thresholds = {
|
this._thresholds = {
|
||||||
cpu: {
|
cpu: {
|
||||||
critical: numberOrDefault(thresholds && thresholds.cpu, DEFAULT_CRITICAL_THRESHOLD_CPU),
|
critical: numberOrDefault(
|
||||||
|
thresholds && thresholds.cpu,
|
||||||
|
DEFAULT_CRITICAL_THRESHOLD_CPU
|
||||||
|
),
|
||||||
},
|
},
|
||||||
memoryFree: {
|
memoryFree: {
|
||||||
critical: numberOrDefault(thresholds && thresholds.memoryFree, DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE) * 1024,
|
critical:
|
||||||
|
numberOrDefault(
|
||||||
|
thresholds && thresholds.memoryFree,
|
||||||
|
DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE
|
||||||
|
) * 1024,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,8 +146,16 @@ export default class Plan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check in the last 30 min interval with ratio.
|
// Check in the last 30 min interval with ratio.
|
||||||
const avgBefore = computeRessourcesAverage(hosts, hostsStats, MINUTES_OF_HISTORICAL_DATA)
|
const avgBefore = computeRessourcesAverage(
|
||||||
const avgWithRatio = computeRessourcesAverageWithWeight(avgNow, avgBefore, 0.75)
|
hosts,
|
||||||
|
hostsStats,
|
||||||
|
MINUTES_OF_HISTORICAL_DATA
|
||||||
|
)
|
||||||
|
const avgWithRatio = computeRessourcesAverageWithWeight(
|
||||||
|
avgNow,
|
||||||
|
avgBefore,
|
||||||
|
0.75
|
||||||
|
)
|
||||||
|
|
||||||
toOptimize = this._checkRessourcesThresholds(toOptimize, avgWithRatio)
|
toOptimize = this._checkRessourcesThresholds(toOptimize, avgWithRatio)
|
||||||
|
|
||||||
@ -185,19 +196,23 @@ export default class Plan {
|
|||||||
|
|
||||||
// Compute hosts for each pool. They can change over time.
|
// Compute hosts for each pool. They can change over time.
|
||||||
_getHosts ({ powerState = 'Running' } = {}) {
|
_getHosts ({ powerState = 'Running' } = {}) {
|
||||||
return filter(this.xo.getObjects(), object => (
|
return filter(
|
||||||
object.type === 'host' &&
|
this.xo.getObjects(),
|
||||||
includes(this._poolIds, object.$poolId) &&
|
object =>
|
||||||
object.power_state === powerState &&
|
object.type === 'host' &&
|
||||||
!includes(this._excludedHosts, object.id)
|
includes(this._poolIds, object.$poolId) &&
|
||||||
))
|
object.power_state === powerState &&
|
||||||
|
!includes(this._excludedHosts, object.id)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getVms (hostId) {
|
async _getVms (hostId) {
|
||||||
return filter(this.xo.getObjects(), object =>
|
return filter(
|
||||||
object.type === 'VM' &&
|
this.xo.getObjects(),
|
||||||
object.power_state === 'Running' &&
|
object =>
|
||||||
object.$container === hostId
|
object.type === 'VM' &&
|
||||||
|
object.power_state === 'Running' &&
|
||||||
|
object.$container === hostId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,15 +223,17 @@ export default class Plan {
|
|||||||
async _getHostsStats (hosts, granularity) {
|
async _getHostsStats (hosts, granularity) {
|
||||||
const hostsStats = {}
|
const hostsStats = {}
|
||||||
|
|
||||||
await Promise.all(mapToArray(hosts, host =>
|
await Promise.all(
|
||||||
this.xo.getXapiHostStats(host, granularity).then(hostStats => {
|
mapToArray(hosts, host =>
|
||||||
hostsStats[host.id] = {
|
this.xo.getXapiHostStats(host, granularity).then(hostStats => {
|
||||||
nPoints: hostStats.stats.cpus[0].length,
|
hostsStats[host.id] = {
|
||||||
stats: hostStats.stats,
|
nPoints: hostStats.stats.cpus[0].length,
|
||||||
averages: {},
|
stats: hostStats.stats,
|
||||||
}
|
averages: {},
|
||||||
})
|
}
|
||||||
))
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return hostsStats
|
return hostsStats
|
||||||
}
|
}
|
||||||
@ -224,15 +241,17 @@ export default class Plan {
|
|||||||
async _getVmsStats (vms, granularity) {
|
async _getVmsStats (vms, granularity) {
|
||||||
const vmsStats = {}
|
const vmsStats = {}
|
||||||
|
|
||||||
await Promise.all(mapToArray(vms, vm =>
|
await Promise.all(
|
||||||
this.xo.getXapiVmStats(vm, granularity).then(vmStats => {
|
mapToArray(vms, vm =>
|
||||||
vmsStats[vm.id] = {
|
this.xo.getXapiVmStats(vm, granularity).then(vmStats => {
|
||||||
nPoints: vmStats.stats.cpus[0].length,
|
vmsStats[vm.id] = {
|
||||||
stats: vmStats.stats,
|
nPoints: vmStats.stats.cpus[0].length,
|
||||||
averages: {},
|
stats: vmStats.stats,
|
||||||
}
|
averages: {},
|
||||||
})
|
}
|
||||||
))
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return vmsStats
|
return vmsStats
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,7 @@ module.exports = {
|
|||||||
debug: !__TEST__,
|
debug: !__TEST__,
|
||||||
loose: true,
|
loose: true,
|
||||||
shippedProposals: true,
|
shippedProposals: true,
|
||||||
targets: __PROD__
|
targets: __PROD__ ? { node: nodeCompat } : { node: 'current' },
|
||||||
? { node: nodeCompat }
|
|
||||||
: { node: 'current' },
|
|
||||||
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
|
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -91,9 +91,7 @@ const HOST_FUNCTIONS = {
|
|||||||
unit: '% used',
|
unit: '% used',
|
||||||
comparator: '>',
|
comparator: '>',
|
||||||
createParser: (legend, threshold) => {
|
createParser: (legend, threshold) => {
|
||||||
const memoryKBytesLegend = legend.find(
|
const memoryKBytesLegend = legend.find(l => l.name === 'memory_total_kib')
|
||||||
l => l.name === 'memory_total_kib'
|
|
||||||
)
|
|
||||||
const memoryKBytesFreeLegend = legend.find(
|
const memoryKBytesFreeLegend = legend.find(
|
||||||
l => l.name === 'memory_free_kib'
|
l => l.name === 'memory_free_kib'
|
||||||
)
|
)
|
||||||
|
@ -37,7 +37,6 @@ exports.default = function (opts) {
|
|||||||
// For simplicity's sake, this plugin returns a plain object, but
|
// For simplicity's sake, this plugin returns a plain object, but
|
||||||
// usually it returns a new instance of an existing class.
|
// usually it returns a new instance of an existing class.
|
||||||
return {
|
return {
|
||||||
|
|
||||||
// This (optional) method is called each time the plugin is
|
// This (optional) method is called each time the plugin is
|
||||||
// (re-)configured.
|
// (re-)configured.
|
||||||
//
|
//
|
||||||
|
@ -16,7 +16,10 @@ const removeUndefined = obj => {
|
|||||||
const markdownCompiler = nodemailerMarkdown()
|
const markdownCompiler = nodemailerMarkdown()
|
||||||
|
|
||||||
const logAndRethrow = error => {
|
const logAndRethrow = error => {
|
||||||
console.error('[WARN] plugin transport-email:', (error && error.stack) || error)
|
console.error(
|
||||||
|
'[WARN] plugin transport-email:',
|
||||||
|
(error && error.stack) || error
|
||||||
|
)
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@ -59,7 +62,7 @@ export const configurationSchema = {
|
|||||||
},
|
},
|
||||||
secure: {
|
secure: {
|
||||||
default: false,
|
default: false,
|
||||||
enum: [ false, 'force', 'disabled', true ],
|
enum: [false, 'force', 'disabled', true],
|
||||||
enumNames: [
|
enumNames: [
|
||||||
'auto (uses STARTTLS if available)',
|
'auto (uses STARTTLS if available)',
|
||||||
'force (requires STARTTLS or fail)',
|
'force (requires STARTTLS or fail)',
|
||||||
@ -70,7 +73,8 @@ export const configurationSchema = {
|
|||||||
},
|
},
|
||||||
ignoreUnauthorized: {
|
ignoreUnauthorized: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'ignore certificates error (e.g. self-signed certificate)',
|
description:
|
||||||
|
'ignore certificates error (e.g. self-signed certificate)',
|
||||||
},
|
},
|
||||||
|
|
||||||
// FIXME: xo-web does not support edition of too nested
|
// FIXME: xo-web does not support edition of too nested
|
||||||
@ -138,18 +142,11 @@ class TransportEmailPlugin {
|
|||||||
|
|
||||||
configure ({
|
configure ({
|
||||||
from,
|
from,
|
||||||
transport: {
|
transport: { ignoreUnauthorized, password, secure, user, ...transportConf },
|
||||||
ignoreUnauthorized,
|
|
||||||
password,
|
|
||||||
secure,
|
|
||||||
user,
|
|
||||||
...transportConf
|
|
||||||
},
|
|
||||||
}) {
|
}) {
|
||||||
if (ignoreUnauthorized != null) {
|
if (ignoreUnauthorized != null) {
|
||||||
(
|
;(
|
||||||
transportConf.tls ||
|
transportConf.tls || (transportConf.tls = {})
|
||||||
(transportConf.tls = {})
|
|
||||||
).rejectUnauthorized = !ignoreUnauthorized
|
).rejectUnauthorized = !ignoreUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,11 +156,14 @@ class TransportEmailPlugin {
|
|||||||
|
|
||||||
switch (secure) {
|
switch (secure) {
|
||||||
case true:
|
case true:
|
||||||
transportConf.secure = true; break
|
transportConf.secure = true
|
||||||
|
break
|
||||||
case 'disabled':
|
case 'disabled':
|
||||||
transportConf.ignoreTLS = true; break
|
transportConf.ignoreTLS = true
|
||||||
|
break
|
||||||
case 'required':
|
case 'required':
|
||||||
transportConf.requireTLS = true; break
|
transportConf.requireTLS = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const transport = createTransport(transportConf, { from })
|
const transport = createTransport(transportConf, { from })
|
||||||
@ -180,7 +180,7 @@ class TransportEmailPlugin {
|
|||||||
this._unset()
|
this._unset()
|
||||||
}
|
}
|
||||||
|
|
||||||
test ({to}) {
|
test ({ to }) {
|
||||||
return this._sendEmail({
|
return this._sendEmail({
|
||||||
to,
|
to,
|
||||||
subject: '[Xen Orchestra] Test of transport-email plugin',
|
subject: '[Xen Orchestra] Test of transport-email plugin',
|
||||||
@ -188,29 +188,27 @@ class TransportEmailPlugin {
|
|||||||
|
|
||||||
The transport-email plugin for Xen Orchestra server seems to be working fine, nicely done :)
|
The transport-email plugin for Xen Orchestra server seems to be working fine, nicely done :)
|
||||||
`,
|
`,
|
||||||
attachments: [ {
|
attachments: [
|
||||||
filename: 'example.txt',
|
{
|
||||||
content: 'Attachments are working too, great!\n',
|
filename: 'example.txt',
|
||||||
} ],
|
content: 'Attachments are working too, great!\n',
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendEmail ({
|
_sendEmail ({ from, to, cc, bcc, subject, markdown, attachments }) {
|
||||||
from,
|
return this._send(
|
||||||
to, cc, bcc,
|
removeUndefined({
|
||||||
subject,
|
from,
|
||||||
markdown,
|
to,
|
||||||
attachments,
|
cc,
|
||||||
}) {
|
bcc,
|
||||||
return this._send(removeUndefined({
|
subject,
|
||||||
from,
|
markdown,
|
||||||
to,
|
attachments,
|
||||||
cc,
|
})
|
||||||
bcc,
|
).catch(logAndRethrow)
|
||||||
subject,
|
|
||||||
markdown,
|
|
||||||
attachments,
|
|
||||||
})).catch(logAndRethrow)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,18 +35,12 @@ export const configurationSchema = {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const bind = (fn, thisArg) => function __bound__ () {
|
const bind = (fn, thisArg) =>
|
||||||
return fn.apply(thisArg, arguments)
|
function __bound__ () {
|
||||||
}
|
return fn.apply(thisArg, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
function nscaPacketBuilder ({
|
function nscaPacketBuilder ({ host, iv, message, service, status, timestamp }) {
|
||||||
host,
|
|
||||||
iv,
|
|
||||||
message,
|
|
||||||
service,
|
|
||||||
status,
|
|
||||||
timestamp,
|
|
||||||
}) {
|
|
||||||
// Building NSCA packet
|
// Building NSCA packet
|
||||||
const SIZE = 720
|
const SIZE = 720
|
||||||
const packet = Buffer.alloc(SIZE)
|
const packet = Buffer.alloc(SIZE)
|
||||||
@ -112,15 +106,13 @@ class XoServerNagios {
|
|||||||
|
|
||||||
test () {
|
test () {
|
||||||
return this._sendPassiveCheck({
|
return this._sendPassiveCheck({
|
||||||
message: 'The server-nagios plugin for Xen Orchestra server seems to be working fine, nicely done :)',
|
message:
|
||||||
|
'The server-nagios plugin for Xen Orchestra server seems to be working fine, nicely done :)',
|
||||||
status: OK,
|
status: OK,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendPassiveCheck ({
|
_sendPassiveCheck ({ message, status }) {
|
||||||
message,
|
|
||||||
status,
|
|
||||||
}) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (/\r|\n/.test(message)) {
|
if (/\r|\n/.test(message)) {
|
||||||
throw new Error('the message must not contain a line break')
|
throw new Error('the message must not contain a line break')
|
||||||
@ -145,13 +137,7 @@ class XoServerNagios {
|
|||||||
|
|
||||||
// 1) Using xor between the NSCA packet and the initialization vector
|
// 1) Using xor between the NSCA packet and the initialization vector
|
||||||
// 2) Using xor between the result of the first operation and the encryption key
|
// 2) Using xor between the result of the first operation and the encryption key
|
||||||
const xorPacketBuffer = xor(
|
const xorPacketBuffer = xor(xor(packet, iv), this._key)
|
||||||
xor(
|
|
||||||
packet,
|
|
||||||
iv
|
|
||||||
),
|
|
||||||
this._key
|
|
||||||
)
|
|
||||||
|
|
||||||
client.write(xorPacketBuffer, res => {
|
client.write(xorPacketBuffer, res => {
|
||||||
client.destroy()
|
client.destroy()
|
||||||
|
@ -4,7 +4,10 @@ import { promisify } from 'promise-toolbox'
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const logAndRethrow = error => {
|
const logAndRethrow = error => {
|
||||||
console.error('[WARN] plugin transport-slack:', (error != null && error.stack) || error)
|
console.error(
|
||||||
|
'[WARN] plugin transport-slack:',
|
||||||
|
(error != null && error.stack) || error
|
||||||
|
)
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@ -48,10 +51,7 @@ class XoServerTransportSlack {
|
|||||||
this._send = null
|
this._send = null
|
||||||
}
|
}
|
||||||
|
|
||||||
configure ({
|
configure ({ webhookUri, ...conf }) {
|
||||||
webhookUri,
|
|
||||||
...conf
|
|
||||||
}) {
|
|
||||||
const slack = new Slack()
|
const slack = new Slack()
|
||||||
slack.setWebhook(webhookUri)
|
slack.setWebhook(webhookUri)
|
||||||
this._conf = conf
|
this._conf = conf
|
||||||
@ -74,9 +74,7 @@ The transport-slack plugin for Xen Orchestra server seems to be working fine, ni
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendSlack ({
|
_sendSlack ({ message }) {
|
||||||
message,
|
|
||||||
}) {
|
|
||||||
// TODO: handle errors
|
// TODO: handle errors
|
||||||
return this._send({ ...this._conf, text: message }).catch(logAndRethrow)
|
return this._send({ ...this._conf, text: message }).catch(logAndRethrow)
|
||||||
}
|
}
|
||||||
|
@ -68,13 +68,15 @@ class TransportXmppPlugin {
|
|||||||
this._unset = this._client = null
|
this._unset = this._client = null
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendToXmppClient ({to, message}) {
|
_sendToXmppClient ({ to, message }) {
|
||||||
for (const receiver of to) {
|
for (const receiver of to) {
|
||||||
this._client.send(
|
this._client.send(
|
||||||
new XmppClient.Stanza('message', {
|
new XmppClient.Stanza('message', {
|
||||||
to: receiver,
|
to: receiver,
|
||||||
type: 'chat',
|
type: 'chat',
|
||||||
}).c('body').t(message)
|
})
|
||||||
|
.c('body')
|
||||||
|
.t(message)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,8 @@ import {
|
|||||||
values,
|
values,
|
||||||
zipObject,
|
zipObject,
|
||||||
} from 'lodash'
|
} from 'lodash'
|
||||||
import {
|
import { promisify } from 'promise-toolbox'
|
||||||
promisify,
|
import { readFile, writeFile } from 'fs'
|
||||||
} from 'promise-toolbox'
|
|
||||||
import {
|
|
||||||
readFile,
|
|
||||||
writeFile,
|
|
||||||
} from 'fs'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -41,9 +36,9 @@ const mibPower = Math.pow(2, 20)
|
|||||||
const kibPower = Math.pow(2, 10)
|
const kibPower = Math.pow(2, 10)
|
||||||
|
|
||||||
let template = null
|
let template = null
|
||||||
pReadFile(`${__dirname}/../report.html.tpl`, 'utf8')
|
pReadFile(`${__dirname}/../report.html.tpl`, 'utf8').then(tpl => {
|
||||||
.then(tpl => {
|
template = Handlebars.compile(
|
||||||
template = Handlebars.compile(minify(tpl, {
|
minify(tpl, {
|
||||||
collapseBooleanAttributes: true,
|
collapseBooleanAttributes: true,
|
||||||
collapseWhitespace: true,
|
collapseWhitespace: true,
|
||||||
minifyCSS: true,
|
minifyCSS: true,
|
||||||
@ -51,14 +46,14 @@ pReadFile(`${__dirname}/../report.html.tpl`, 'utf8')
|
|||||||
removeComments: true,
|
removeComments: true,
|
||||||
removeOptionalTags: true,
|
removeOptionalTags: true,
|
||||||
removeRedundantAttributes: true,
|
removeRedundantAttributes: true,
|
||||||
}))
|
})
|
||||||
})
|
)
|
||||||
|
})
|
||||||
|
|
||||||
let imgXo = null
|
let imgXo = null
|
||||||
pReadFile(`${__dirname}/../images/xo.png`, 'base64')
|
pReadFile(`${__dirname}/../images/xo.png`, 'base64').then(data => {
|
||||||
.then(data => {
|
imgXo = `data:image/png;base64,${data}`
|
||||||
imgXo = `data:image/png;base64,${data}`
|
})
|
||||||
})
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -75,26 +70,36 @@ export const configurationSchema = {
|
|||||||
periodicity: {
|
periodicity: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
enum: ['monthly', 'weekly'],
|
enum: ['monthly', 'weekly'],
|
||||||
description: 'If you choose weekly you will receive the report every sunday and if you choose monthly you will receive it every first day of the month.',
|
description:
|
||||||
|
'If you choose weekly you will receive the report every sunday and if you choose monthly you will receive it every first day of the month.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
required: [ 'emails', 'periodicity' ],
|
required: ['emails', 'periodicity'],
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
Handlebars.registerHelper('compare', function (lvalue, operator, rvalue, options) {
|
Handlebars.registerHelper('compare', function (
|
||||||
|
lvalue,
|
||||||
|
operator,
|
||||||
|
rvalue,
|
||||||
|
options
|
||||||
|
) {
|
||||||
if (arguments.length < 3) {
|
if (arguments.length < 3) {
|
||||||
throw new Error('Handlerbars Helper "compare" needs 2 parameters')
|
throw new Error('Handlerbars Helper "compare" needs 2 parameters')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compareOperators[operator]) {
|
if (!compareOperators[operator]) {
|
||||||
throw new Error(`Handlerbars Helper "compare" doesn't know the operator ${operator}`)
|
throw new Error(
|
||||||
|
`Handlerbars Helper "compare" doesn't know the operator ${operator}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return compareOperators[operator](lvalue, rvalue) ? options.fn(this) : options.inverse(this)
|
return compareOperators[operator](lvalue, rvalue)
|
||||||
|
? options.fn(this)
|
||||||
|
: options.inverse(this)
|
||||||
})
|
})
|
||||||
|
|
||||||
Handlebars.registerHelper('math', function (lvalue, operator, rvalue, options) {
|
Handlebars.registerHelper('math', function (lvalue, operator, rvalue, options) {
|
||||||
@ -103,7 +108,9 @@ Handlebars.registerHelper('math', function (lvalue, operator, rvalue, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mathOperators[operator]) {
|
if (!mathOperators[operator]) {
|
||||||
throw new Error(`Handlerbars Helper "math" doesn't know the operator ${operator}`)
|
throw new Error(
|
||||||
|
`Handlerbars Helper "math" doesn't know the operator ${operator}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return mathOperators[operator](+lvalue, +rvalue)
|
return mathOperators[operator](+lvalue, +rvalue)
|
||||||
@ -135,24 +142,24 @@ const computeDoubleMean = val => computeMean(val.map(computeMean))
|
|||||||
function computeMeans (objects, options) {
|
function computeMeans (objects, options) {
|
||||||
return zipObject(
|
return zipObject(
|
||||||
options,
|
options,
|
||||||
map(
|
map(options, opt => round(computeMean(map(objects, opt)), 2))
|
||||||
options,
|
|
||||||
opt => round(computeMean(map(objects, opt)), 2)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTop (objects, options) {
|
function getTop (objects, options) {
|
||||||
return zipObject(
|
return zipObject(
|
||||||
options,
|
options,
|
||||||
map(
|
map(options, opt =>
|
||||||
options,
|
map(
|
||||||
opt => map(
|
orderBy(
|
||||||
orderBy(objects, object => {
|
objects,
|
||||||
const value = object[opt]
|
object => {
|
||||||
|
const value = object[opt]
|
||||||
|
|
||||||
return isNaN(value) ? -Infinity : value
|
return isNaN(value) ? -Infinity : value
|
||||||
}, 'desc').slice(0, 3),
|
},
|
||||||
|
'desc'
|
||||||
|
).slice(0, 3),
|
||||||
obj => ({
|
obj => ({
|
||||||
uuid: obj.uuid,
|
uuid: obj.uuid,
|
||||||
name: obj.name,
|
name: obj.name,
|
||||||
@ -168,7 +175,10 @@ function conputePercentage (curr, prev, options) {
|
|||||||
options,
|
options,
|
||||||
map(
|
map(
|
||||||
options,
|
options,
|
||||||
opt => prev[opt] === 0 ? 'NONE' : `${round((curr[opt] - prev[opt]) * 100 / prev[opt], 2)}`
|
opt =>
|
||||||
|
prev[opt] === 0
|
||||||
|
? 'NONE'
|
||||||
|
: `${round((curr[opt] - prev[opt]) * 100 / prev[opt], 2)}`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -182,48 +192,42 @@ function getDiff (oldElements, newElements) {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
function getVmsStats ({
|
function getVmsStats ({ runningVms, xo }) {
|
||||||
runningVms,
|
return Promise.all(
|
||||||
xo,
|
map(runningVms, async vm => {
|
||||||
}) {
|
const vmStats = await xo.getXapiVmStats(vm, 'days')
|
||||||
return Promise.all(map(runningVms, async vm => {
|
return {
|
||||||
const vmStats = await xo.getXapiVmStats(vm, 'days')
|
uuid: vm.uuid,
|
||||||
return {
|
name: vm.name_label,
|
||||||
uuid: vm.uuid,
|
cpu: computeDoubleMean(vmStats.stats.cpus),
|
||||||
name: vm.name_label,
|
ram: computeMean(vmStats.stats.memoryUsed) / gibPower,
|
||||||
cpu: computeDoubleMean(vmStats.stats.cpus),
|
diskRead: computeDoubleMean(values(vmStats.stats.xvds.r)) / mibPower,
|
||||||
ram: computeMean(vmStats.stats.memoryUsed) / gibPower,
|
diskWrite: computeDoubleMean(values(vmStats.stats.xvds.w)) / mibPower,
|
||||||
diskRead: computeDoubleMean(values(vmStats.stats.xvds.r)) / mibPower,
|
netReception: computeDoubleMean(vmStats.stats.vifs.rx) / kibPower,
|
||||||
diskWrite: computeDoubleMean(values(vmStats.stats.xvds.w)) / mibPower,
|
netTransmission: computeDoubleMean(vmStats.stats.vifs.tx) / kibPower,
|
||||||
netReception: computeDoubleMean(vmStats.stats.vifs.rx) / kibPower,
|
}
|
||||||
netTransmission: computeDoubleMean(vmStats.stats.vifs.tx) / kibPower,
|
})
|
||||||
}
|
)
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHostsStats ({
|
function getHostsStats ({ runningHosts, xo }) {
|
||||||
runningHosts,
|
return Promise.all(
|
||||||
xo,
|
map(runningHosts, async host => {
|
||||||
}) {
|
const hostStats = await xo.getXapiHostStats(host, 'days')
|
||||||
return Promise.all(map(runningHosts, async host => {
|
return {
|
||||||
const hostStats = await xo.getXapiHostStats(host, 'days')
|
uuid: host.uuid,
|
||||||
return {
|
name: host.name_label,
|
||||||
uuid: host.uuid,
|
cpu: computeDoubleMean(hostStats.stats.cpus),
|
||||||
name: host.name_label,
|
ram: computeMean(hostStats.stats.memoryUsed) / gibPower,
|
||||||
cpu: computeDoubleMean(hostStats.stats.cpus),
|
load: computeMean(hostStats.stats.load),
|
||||||
ram: computeMean(hostStats.stats.memoryUsed) / gibPower,
|
netReception: computeDoubleMean(hostStats.stats.pifs.rx) / kibPower,
|
||||||
load: computeMean(hostStats.stats.load),
|
netTransmission: computeDoubleMean(hostStats.stats.pifs.tx) / kibPower,
|
||||||
netReception: computeDoubleMean(hostStats.stats.pifs.rx) / kibPower,
|
}
|
||||||
netTransmission: computeDoubleMean(hostStats.stats.pifs.tx) / kibPower,
|
})
|
||||||
}
|
)
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeGlobalVmsStats ({
|
function computeGlobalVmsStats ({ haltedVms, vmsStats, xo }) {
|
||||||
haltedVms,
|
|
||||||
vmsStats,
|
|
||||||
xo,
|
|
||||||
}) {
|
|
||||||
const allVms = concat(
|
const allVms = concat(
|
||||||
map(vmsStats, vm => ({
|
map(vmsStats, vm => ({
|
||||||
uuid: vm.uuid,
|
uuid: vm.uuid,
|
||||||
@ -235,17 +239,23 @@ function computeGlobalVmsStats ({
|
|||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
return assign(computeMeans(vmsStats, ['cpu', 'ram', 'diskRead', 'diskWrite', 'netReception', 'netTransmission']), {
|
return assign(
|
||||||
number: allVms.length,
|
computeMeans(vmsStats, [
|
||||||
allVms,
|
'cpu',
|
||||||
})
|
'ram',
|
||||||
|
'diskRead',
|
||||||
|
'diskWrite',
|
||||||
|
'netReception',
|
||||||
|
'netTransmission',
|
||||||
|
]),
|
||||||
|
{
|
||||||
|
number: allVms.length,
|
||||||
|
allVms,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeGlobalHostsStats ({
|
function computeGlobalHostsStats ({ haltedHosts, hostsStats, xo }) {
|
||||||
haltedHosts,
|
|
||||||
hostsStats,
|
|
||||||
xo,
|
|
||||||
}) {
|
|
||||||
const allHosts = concat(
|
const allHosts = concat(
|
||||||
map(hostsStats, host => ({
|
map(hostsStats, host => ({
|
||||||
uuid: host.uuid,
|
uuid: host.uuid,
|
||||||
@ -257,52 +267,65 @@ function computeGlobalHostsStats ({
|
|||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
return assign(computeMeans(hostsStats, ['cpu', 'ram', 'load', 'netReception', 'netTransmission']), {
|
return assign(
|
||||||
number: allHosts.length,
|
computeMeans(hostsStats, [
|
||||||
allHosts,
|
'cpu',
|
||||||
})
|
'ram',
|
||||||
}
|
'load',
|
||||||
|
'netReception',
|
||||||
function getTopVms ({
|
'netTransmission',
|
||||||
vmsStats,
|
]),
|
||||||
xo,
|
{
|
||||||
}) {
|
number: allHosts.length,
|
||||||
return getTop(vmsStats, ['cpu', 'ram', 'diskRead', 'diskWrite', 'netReception', 'netTransmission'])
|
allHosts,
|
||||||
}
|
|
||||||
|
|
||||||
function getTopHosts ({
|
|
||||||
hostsStats,
|
|
||||||
xo,
|
|
||||||
}) {
|
|
||||||
return getTop(hostsStats, ['cpu', 'ram', 'load', 'netReception', 'netTransmission'])
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMostAllocatedSpaces ({
|
|
||||||
disks,
|
|
||||||
xo,
|
|
||||||
}) {
|
|
||||||
return map(
|
|
||||||
orderBy(disks, ['size'], ['desc']).slice(0, 3), disk => ({
|
|
||||||
uuid: disk.uuid,
|
|
||||||
name: disk.name_label,
|
|
||||||
size: round(disk.size / gibPower, 2),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getHostsMissingPatches ({
|
|
||||||
runningHosts,
|
|
||||||
xo,
|
|
||||||
}) {
|
|
||||||
const hostsMissingPatches = await Promise.all(map(runningHosts, async host => {
|
|
||||||
const hostsPatches = await xo.getXapi(host).listMissingPoolPatchesOnHost(host._xapiId)
|
|
||||||
if (hostsPatches.length > 0) {
|
|
||||||
return {
|
|
||||||
uuid: host.uuid,
|
|
||||||
name: host.name_label,
|
|
||||||
patches: map(hostsPatches, 'name'),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTopVms ({ vmsStats, xo }) {
|
||||||
|
return getTop(vmsStats, [
|
||||||
|
'cpu',
|
||||||
|
'ram',
|
||||||
|
'diskRead',
|
||||||
|
'diskWrite',
|
||||||
|
'netReception',
|
||||||
|
'netTransmission',
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTopHosts ({ hostsStats, xo }) {
|
||||||
|
return getTop(hostsStats, [
|
||||||
|
'cpu',
|
||||||
|
'ram',
|
||||||
|
'load',
|
||||||
|
'netReception',
|
||||||
|
'netTransmission',
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMostAllocatedSpaces ({ disks, xo }) {
|
||||||
|
return map(orderBy(disks, ['size'], ['desc']).slice(0, 3), disk => ({
|
||||||
|
uuid: disk.uuid,
|
||||||
|
name: disk.name_label,
|
||||||
|
size: round(disk.size / gibPower, 2),
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getHostsMissingPatches ({ runningHosts, xo }) {
|
||||||
|
const hostsMissingPatches = await Promise.all(
|
||||||
|
map(runningHosts, async host => {
|
||||||
|
const hostsPatches = await xo
|
||||||
|
.getXapi(host)
|
||||||
|
.listMissingPoolPatchesOnHost(host._xapiId)
|
||||||
|
if (hostsPatches.length > 0) {
|
||||||
|
return {
|
||||||
|
uuid: host.uuid,
|
||||||
|
name: host.name_label,
|
||||||
|
patches: map(hostsPatches, 'name'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
return filter(hostsMissingPatches, host => host !== undefined)
|
return filter(hostsMissingPatches, host => host !== undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,17 +333,11 @@ function getAllUsersEmail (users) {
|
|||||||
return map(users, 'email')
|
return map(users, 'email')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeStats ({
|
async function storeStats ({ data, storedStatsPath }) {
|
||||||
data,
|
|
||||||
storedStatsPath,
|
|
||||||
}) {
|
|
||||||
await pWriteFile(storedStatsPath, JSON.stringify(data))
|
await pWriteFile(storedStatsPath, JSON.stringify(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function computeEvolution ({
|
async function computeEvolution ({ storedStatsPath, ...newStats }) {
|
||||||
storedStatsPath,
|
|
||||||
...newStats
|
|
||||||
}) {
|
|
||||||
try {
|
try {
|
||||||
const oldStats = JSON.parse(await pReadFile(storedStatsPath, 'utf8'))
|
const oldStats = JSON.parse(await pReadFile(storedStatsPath, 'utf8'))
|
||||||
const newStatsVms = newStats.vms
|
const newStatsVms = newStats.vms
|
||||||
@ -332,16 +349,35 @@ async function computeEvolution ({
|
|||||||
|
|
||||||
const vmsEvolution = {
|
const vmsEvolution = {
|
||||||
number: newStatsVms.number - oldStatsVms.number,
|
number: newStatsVms.number - oldStatsVms.number,
|
||||||
...conputePercentage(newStatsVms, oldStatsVms, ['cpu', 'ram', 'diskRead', 'diskWrite', 'netReception', 'netTransmission']),
|
...conputePercentage(newStatsVms, oldStatsVms, [
|
||||||
|
'cpu',
|
||||||
|
'ram',
|
||||||
|
'diskRead',
|
||||||
|
'diskWrite',
|
||||||
|
'netReception',
|
||||||
|
'netTransmission',
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
const hostsEvolution = {
|
const hostsEvolution = {
|
||||||
number: newStatsHosts.number - oldStatsHosts.number,
|
number: newStatsHosts.number - oldStatsHosts.number,
|
||||||
...conputePercentage(newStatsHosts, oldStatsHosts, ['cpu', 'ram', 'load', 'netReception', 'netTransmission']),
|
...conputePercentage(newStatsHosts, oldStatsHosts, [
|
||||||
|
'cpu',
|
||||||
|
'ram',
|
||||||
|
'load',
|
||||||
|
'netReception',
|
||||||
|
'netTransmission',
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
const vmsRessourcesEvolution = getDiff(oldStatsVms.allVms, newStatsVms.allVms)
|
const vmsRessourcesEvolution = getDiff(
|
||||||
const hostsRessourcesEvolution = getDiff(oldStatsHosts.allHosts, newStatsHosts.allHosts)
|
oldStatsVms.allVms,
|
||||||
|
newStatsVms.allVms
|
||||||
|
)
|
||||||
|
const hostsRessourcesEvolution = getDiff(
|
||||||
|
oldStatsHosts.allHosts,
|
||||||
|
newStatsHosts.allHosts
|
||||||
|
)
|
||||||
|
|
||||||
const usersEvolution = getDiff(oldStats.users, newStats.users)
|
const usersEvolution = getDiff(oldStats.users, newStats.users)
|
||||||
|
|
||||||
@ -358,29 +394,41 @@ async function computeEvolution ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function dataBuilder ({
|
async function dataBuilder ({ xo, storedStatsPath }) {
|
||||||
xo,
|
|
||||||
storedStatsPath,
|
|
||||||
}) {
|
|
||||||
const xoObjects = values(xo.getObjects())
|
const xoObjects = values(xo.getObjects())
|
||||||
const runningVms = filter(xoObjects, {type: 'VM', power_state: 'Running'})
|
const runningVms = filter(xoObjects, { type: 'VM', power_state: 'Running' })
|
||||||
const haltedVms = filter(xoObjects, {type: 'VM', power_state: 'Halted'})
|
const haltedVms = filter(xoObjects, { type: 'VM', power_state: 'Halted' })
|
||||||
const runningHosts = filter(xoObjects, {type: 'host', power_state: 'Running'})
|
const runningHosts = filter(xoObjects, {
|
||||||
const haltedHosts = filter(xoObjects, {type: 'host', power_state: 'Halted'})
|
type: 'host',
|
||||||
const disks = filter(xoObjects, {type: 'SR'})
|
power_state: 'Running',
|
||||||
const [users, vmsStats, hostsStats, topAllocation, hostsMissingPatches] = await Promise.all([
|
})
|
||||||
|
const haltedHosts = filter(xoObjects, { type: 'host', power_state: 'Halted' })
|
||||||
|
const disks = filter(xoObjects, { type: 'SR' })
|
||||||
|
const [
|
||||||
|
users,
|
||||||
|
vmsStats,
|
||||||
|
hostsStats,
|
||||||
|
topAllocation,
|
||||||
|
hostsMissingPatches,
|
||||||
|
] = await Promise.all([
|
||||||
xo.getAllUsers(),
|
xo.getAllUsers(),
|
||||||
getVmsStats({xo, runningVms}),
|
getVmsStats({ xo, runningVms }),
|
||||||
getHostsStats({xo, runningHosts}),
|
getHostsStats({ xo, runningHosts }),
|
||||||
getMostAllocatedSpaces({xo, disks}),
|
getMostAllocatedSpaces({ xo, disks }),
|
||||||
getHostsMissingPatches({xo, runningHosts}),
|
getHostsMissingPatches({ xo, runningHosts }),
|
||||||
])
|
])
|
||||||
|
|
||||||
const [globalVmsStats, globalHostsStats, topVms, topHosts, usersEmail] = await Promise.all([
|
const [
|
||||||
computeGlobalVmsStats({xo, vmsStats, haltedVms}),
|
globalVmsStats,
|
||||||
computeGlobalHostsStats({xo, hostsStats, haltedHosts}),
|
globalHostsStats,
|
||||||
getTopVms({xo, vmsStats}),
|
topVms,
|
||||||
getTopHosts({xo, hostsStats}),
|
topHosts,
|
||||||
|
usersEmail,
|
||||||
|
] = await Promise.all([
|
||||||
|
computeGlobalVmsStats({ xo, vmsStats, haltedVms }),
|
||||||
|
computeGlobalHostsStats({ xo, hostsStats, haltedHosts }),
|
||||||
|
getTopVms({ xo, vmsStats }),
|
||||||
|
getTopHosts({ xo, hostsStats }),
|
||||||
getAllUsersEmail(users),
|
getAllUsersEmail(users),
|
||||||
])
|
])
|
||||||
const evolution = await computeEvolution({
|
const evolution = await computeEvolution({
|
||||||
@ -419,7 +467,7 @@ async function dataBuilder ({
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
class UsageReportPlugin {
|
class UsageReportPlugin {
|
||||||
constructor ({xo, getDataDir}) {
|
constructor ({ xo, getDataDir }) {
|
||||||
this._xo = xo
|
this._xo = xo
|
||||||
this._dir = getDataDir
|
this._dir = getDataDir
|
||||||
// Defined in configure().
|
// Defined in configure().
|
||||||
@ -430,7 +478,8 @@ class UsageReportPlugin {
|
|||||||
this._conf = configuration
|
this._conf = configuration
|
||||||
|
|
||||||
this._job = new CronJob({
|
this._job = new CronJob({
|
||||||
cronTime: configuration.periodicity === 'monthly' ? '00 06 1 * *' : '00 06 * * 0',
|
cronTime:
|
||||||
|
configuration.periodicity === 'monthly' ? '00 06 1 * *' : '00 06 * * 0',
|
||||||
onTick: () => this._sendReport(),
|
onTick: () => this._sendReport(),
|
||||||
start: false,
|
start: false,
|
||||||
})
|
})
|
||||||
@ -467,10 +516,12 @@ class UsageReportPlugin {
|
|||||||
Please, find the attached report.
|
Please, find the attached report.
|
||||||
|
|
||||||
best regards.`,
|
best regards.`,
|
||||||
attachments: [{
|
attachments: [
|
||||||
filename: `xoReport_${currDate}.html`,
|
{
|
||||||
content: template(data),
|
filename: `xoReport_${currDate}.html`,
|
||||||
}],
|
content: template(data),
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
storeStats({
|
storeStats({
|
||||||
data,
|
data,
|
||||||
|
@ -8,25 +8,21 @@ try {
|
|||||||
const filtered = frames.filter(function (frame) {
|
const filtered = frames.filter(function (frame) {
|
||||||
const name = frame && frame.getFileName()
|
const name = frame && frame.getFileName()
|
||||||
|
|
||||||
return (
|
return (// has a filename
|
||||||
// has a filename
|
|
||||||
name &&
|
name &&
|
||||||
|
|
||||||
// contains a separator (no internal modules)
|
// contains a separator (no internal modules)
|
||||||
name.indexOf(sep) !== -1 &&
|
name.indexOf(sep) !== -1 &&
|
||||||
|
|
||||||
// does not start with `internal`
|
// does not start with `internal`
|
||||||
name.lastIndexOf('internal', 0) !== -1
|
name.lastIndexOf('internal', 0) !== -1)
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// depd (used amongst other by express requires at least 3 frames
|
// depd (used amongst other by express requires at least 3 frames
|
||||||
// in the stack.
|
// in the stack.
|
||||||
return filtered.length > 2
|
return filtered.length > 2 ? filtered : frames
|
||||||
? filtered
|
|
||||||
: frames
|
|
||||||
})
|
})
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
// Source maps.
|
// Source maps.
|
||||||
try { require('julien-f-source-map-support/register') } catch (_) {}
|
try {
|
||||||
|
require('julien-f-source-map-support/register')
|
||||||
|
} catch (_) {}
|
||||||
|
@ -14,11 +14,12 @@ export async function getCurrentPermissions () {
|
|||||||
|
|
||||||
getCurrentPermissions.permission = ''
|
getCurrentPermissions.permission = ''
|
||||||
|
|
||||||
getCurrentPermissions.description = 'get (explicit) permissions by object for the current user'
|
getCurrentPermissions.description =
|
||||||
|
'get (explicit) permissions by object for the current user'
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function add ({subject, object, action}) {
|
export async function add ({ subject, object, action }) {
|
||||||
await this.addAcl(subject, object, action)
|
await this.addAcl(subject, object, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ add.description = 'add a new ACL entry'
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function remove ({subject, object, action}) {
|
export async function remove ({ subject, object, action }) {
|
||||||
await this.removeAcl(subject, object, action)
|
await this.removeAcl(subject, object, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,46 +42,57 @@ scanFiles.params = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
function handleFetchFiles (req, res, { remote, disk, partition, paths, format: archiveFormat }) {
|
function handleFetchFiles (
|
||||||
this.fetchFilesInDiskBackup(remote, disk, partition, paths).then(files => {
|
req,
|
||||||
res.setHeader('content-disposition', 'attachment')
|
res,
|
||||||
res.setHeader('content-type', 'application/octet-stream')
|
{ remote, disk, partition, paths, format: archiveFormat }
|
||||||
|
) {
|
||||||
|
this.fetchFilesInDiskBackup(remote, disk, partition, paths)
|
||||||
|
.then(files => {
|
||||||
|
res.setHeader('content-disposition', 'attachment')
|
||||||
|
res.setHeader('content-type', 'application/octet-stream')
|
||||||
|
|
||||||
const nFiles = paths.length
|
const nFiles = paths.length
|
||||||
|
|
||||||
// Send lone file directly
|
// Send lone file directly
|
||||||
if (nFiles === 1) {
|
if (nFiles === 1) {
|
||||||
files[0].pipe(res)
|
files[0].pipe(res)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const archive = archiver(archiveFormat)
|
const archive = archiver(archiveFormat)
|
||||||
archive.on('error', error => {
|
archive.on('error', error => {
|
||||||
|
console.error(error)
|
||||||
|
res.end(format.error(0, error))
|
||||||
|
})
|
||||||
|
|
||||||
|
forEach(files, file => {
|
||||||
|
archive.append(file, { name: basename(file.path) })
|
||||||
|
})
|
||||||
|
archive.finalize()
|
||||||
|
|
||||||
|
archive.pipe(res)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
res.writeHead(500)
|
||||||
res.end(format.error(0, error))
|
res.end(format.error(0, error))
|
||||||
})
|
})
|
||||||
|
|
||||||
forEach(files, file => {
|
|
||||||
archive.append(file, { name: basename(file.path) })
|
|
||||||
})
|
|
||||||
archive.finalize()
|
|
||||||
|
|
||||||
archive.pipe(res)
|
|
||||||
}).catch(error => {
|
|
||||||
console.error(error)
|
|
||||||
res.writeHead(500)
|
|
||||||
res.end(format.error(0, error))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchFiles ({ format = 'zip', ...params }) {
|
export async function fetchFiles ({ format = 'zip', ...params }) {
|
||||||
const fileName = params.paths.length > 1
|
const fileName =
|
||||||
? `restore_${new Date().toJSON()}.${format}`
|
params.paths.length > 1
|
||||||
: basename(params.paths[0])
|
? `restore_${new Date().toJSON()}.${format}`
|
||||||
|
: basename(params.paths[0])
|
||||||
|
|
||||||
return this.registerHttpRequest(handleFetchFiles, { ...params, format }, {
|
return this.registerHttpRequest(
|
||||||
suffix: encodeURI(`/${fileName}`),
|
handleFetchFiles,
|
||||||
}).then(url => ({ $getFrom: url }))
|
{ ...params, format },
|
||||||
|
{
|
||||||
|
suffix: encodeURI(`/${fileName}`),
|
||||||
|
}
|
||||||
|
).then(url => ({ $getFrom: url }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchFiles.permission = 'admin'
|
fetchFiles.permission = 'admin'
|
||||||
|
@ -8,9 +8,11 @@ export async function create ({ name, size, sr, vm, bootable, position, mode })
|
|||||||
|
|
||||||
let resourceSet
|
let resourceSet
|
||||||
if (attach && (resourceSet = vm.resourceSet) != null) {
|
if (attach && (resourceSet = vm.resourceSet) != null) {
|
||||||
await this.checkResourceSetConstraints(resourceSet, this.user.id, [ sr.id ])
|
await this.checkResourceSetConstraints(resourceSet, this.user.id, [sr.id])
|
||||||
await this.allocateLimitsInResourceSet({ disk: size }, resourceSet)
|
await this.allocateLimitsInResourceSet({ disk: size }, resourceSet)
|
||||||
} else if (!(await this.hasPermissions(this.user.id, [ [ sr.id, 'administrate' ] ]))) {
|
} else if (
|
||||||
|
!await this.hasPermissions(this.user.id, [[sr.id, 'administrate']])
|
||||||
|
) {
|
||||||
throw unauthorized()
|
throw unauthorized()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export async function register ({vm}) {
|
export async function register ({ vm }) {
|
||||||
await this.getXapi(vm).registerDockerContainer(vm._xapiId)
|
await this.getXapi(vm).registerDockerContainer(vm._xapiId)
|
||||||
}
|
}
|
||||||
register.description = 'Register the VM for Docker management'
|
register.description = 'Register the VM for Docker management'
|
||||||
@ -13,7 +13,7 @@ register.resolve = {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
export async function deregister ({vm}) {
|
export async function deregister ({ vm }) {
|
||||||
await this.getXapi(vm).unregisterDockerContainer(vm._xapiId)
|
await this.getXapi(vm).unregisterDockerContainer(vm._xapiId)
|
||||||
}
|
}
|
||||||
deregister.description = 'Deregister the VM for Docker management'
|
deregister.description = 'Deregister the VM for Docker management'
|
||||||
@ -28,23 +28,23 @@ deregister.resolve = {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
export async function start ({vm, container}) {
|
export async function start ({ vm, container }) {
|
||||||
await this.getXapi(vm).startDockerContainer(vm._xapiId, container)
|
await this.getXapi(vm).startDockerContainer(vm._xapiId, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stop ({vm, container}) {
|
export async function stop ({ vm, container }) {
|
||||||
await this.getXapi(vm).stopDockerContainer(vm._xapiId, container)
|
await this.getXapi(vm).stopDockerContainer(vm._xapiId, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function restart ({vm, container}) {
|
export async function restart ({ vm, container }) {
|
||||||
await this.getXapi(vm).restartDockerContainer(vm._xapiId, container)
|
await this.getXapi(vm).restartDockerContainer(vm._xapiId, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pause ({vm, container}) {
|
export async function pause ({ vm, container }) {
|
||||||
await this.getXapi(vm).pauseDockerContainer(vm._xapiId, container)
|
await this.getXapi(vm).pauseDockerContainer(vm._xapiId, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unpause ({vm, container}) {
|
export async function unpause ({ vm, container }) {
|
||||||
await this.getXapi(vm).unpauseDockerContainer(vm._xapiId, container)
|
await this.getXapi(vm).unpauseDockerContainer(vm._xapiId, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
export async function create ({name}) {
|
export async function create ({ name }) {
|
||||||
return (await this.createGroup({name})).id
|
return (await this.createGroup({ name })).id
|
||||||
}
|
}
|
||||||
|
|
||||||
create.description = 'creates a new group'
|
create.description = 'creates a new group'
|
||||||
create.permission = 'admin'
|
create.permission = 'admin'
|
||||||
create.params = {
|
create.params = {
|
||||||
name: {type: 'string'},
|
name: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// Deletes an existing group.
|
// Deletes an existing group.
|
||||||
async function delete_ ({id}) {
|
async function delete_ ({ id }) {
|
||||||
await this.deleteGroup(id)
|
await this.deleteGroup(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete is not a valid identifier.
|
// delete is not a valid identifier.
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
delete_.description = 'deletes an existing group'
|
delete_.description = 'deletes an existing group'
|
||||||
delete_.permission = 'admin'
|
delete_.permission = 'admin'
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@ -36,35 +36,35 @@ getAll.permission = 'admin'
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// sets group.users with an array of user ids
|
// sets group.users with an array of user ids
|
||||||
export async function setUsers ({id, userIds}) {
|
export async function setUsers ({ id, userIds }) {
|
||||||
await this.setGroupUsers(id, userIds)
|
await this.setGroupUsers(id, userIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
setUsers.description = 'sets the users belonging to a group'
|
setUsers.description = 'sets the users belonging to a group'
|
||||||
setUsers.permission = 'admin'
|
setUsers.permission = 'admin'
|
||||||
setUsers.params = {
|
setUsers.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
userIds: {},
|
userIds: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// adds the user id to group.users
|
// adds the user id to group.users
|
||||||
export async function addUser ({id, userId}) {
|
export async function addUser ({ id, userId }) {
|
||||||
await this.addUserToGroup(userId, id)
|
await this.addUserToGroup(userId, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
addUser.description = 'adds a user to a group'
|
addUser.description = 'adds a user to a group'
|
||||||
addUser.permission = 'admin'
|
addUser.permission = 'admin'
|
||||||
addUser.params = {
|
addUser.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
userId: {type: 'string'},
|
userId: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// remove the user id from group.users
|
// remove the user id from group.users
|
||||||
export async function removeUser ({id, userId}) {
|
export async function removeUser ({ id, userId }) {
|
||||||
await this.removeUserFromGroup(userId, id)
|
await this.removeUserFromGroup(userId, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,14 +73,14 @@ export async function removeUser ({id, userId}) {
|
|||||||
removeUser.description = 'removes a user from a group'
|
removeUser.description = 'removes a user from a group'
|
||||||
removeUser.permission = 'admin'
|
removeUser.permission = 'admin'
|
||||||
removeUser.params = {
|
removeUser.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
userId: {type: 'string'},
|
userId: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function set ({id, name}) {
|
export async function set ({ id, name }) {
|
||||||
await this.updateGroup(id, {name})
|
await this.updateGroup(id, { name })
|
||||||
}
|
}
|
||||||
|
|
||||||
set.description = 'changes the properties of an existing group'
|
set.description = 'changes the properties of an existing group'
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
|
import { format } from 'json-rpc-peer'
|
||||||
import {format} from 'json-rpc-peer'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ restart.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function restartAgent ({host}) {
|
export function restartAgent ({ host }) {
|
||||||
return this.getXapi(host).restartHostAgent(host._xapiId)
|
return this.getXapi(host).restartHostAgent(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +76,7 @@ export { restartAgent as restart_agent } // eslint-disable-line camelcase
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function start ({host}) {
|
export function start ({ host }) {
|
||||||
return this.getXapi(host).powerOnHost(host._xapiId)
|
return this.getXapi(host).powerOnHost(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +92,7 @@ start.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function stop ({host}) {
|
export function stop ({ host }) {
|
||||||
return this.getXapi(host).shutdownHost(host._xapiId)
|
return this.getXapi(host).shutdownHost(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +108,7 @@ stop.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function detach ({host}) {
|
export function detach ({ host }) {
|
||||||
return this.getXapi(host).ejectHostFromPool(host._xapiId)
|
return this.getXapi(host).ejectHostFromPool(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +124,7 @@ detach.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function enable ({host}) {
|
export function enable ({ host }) {
|
||||||
return this.getXapi(host).enableHost(host._xapiId)
|
return this.getXapi(host).enableHost(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +140,7 @@ enable.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function disable ({host}) {
|
export function disable ({ host }) {
|
||||||
return this.getXapi(host).disableHost(host._xapiId)
|
return this.getXapi(host).disableHost(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +156,7 @@ disable.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function forget ({host}) {
|
export function forget ({ host }) {
|
||||||
return this.getXapi(host).forgetHost(host._xapiId)
|
return this.getXapi(host).forgetHost(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,11 +175,12 @@ forget.resolve = {
|
|||||||
// Returns an array of missing new patches in the host
|
// Returns an array of missing new patches in the host
|
||||||
// Returns an empty array if up-to-date
|
// Returns an empty array if up-to-date
|
||||||
// Throws an error if the host is not running the latest XS version
|
// Throws an error if the host is not running the latest XS version
|
||||||
export function listMissingPatches ({host}) {
|
export function listMissingPatches ({ host }) {
|
||||||
return this.getXapi(host).listMissingPoolPatchesOnHost(host._xapiId)
|
return this.getXapi(host).listMissingPoolPatchesOnHost(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
listMissingPatches.description = 'return an array of missing new patches in the host'
|
listMissingPatches.description =
|
||||||
|
'return an array of missing new patches in the host'
|
||||||
|
|
||||||
listMissingPatches.params = {
|
listMissingPatches.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
@ -192,7 +192,7 @@ listMissingPatches.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function installPatch ({host, patch: patchUuid}) {
|
export function installPatch ({ host, patch: patchUuid }) {
|
||||||
return this.getXapi(host).installPoolPatchOnHost(patchUuid, host._xapiId)
|
return this.getXapi(host).installPoolPatchOnHost(patchUuid, host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ installPatch.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function installAllPatches ({host}) {
|
export function installAllPatches ({ host }) {
|
||||||
return this.getXapi(host).installAllPoolPatchesOnHost(host._xapiId)
|
return this.getXapi(host).installAllPoolPatchesOnHost(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +225,7 @@ installAllPatches.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function emergencyShutdownHost ({host}) {
|
export function emergencyShutdownHost ({ host }) {
|
||||||
return this.getXapi(host).emergencyShutdownHost(host._xapiId)
|
return this.getXapi(host).emergencyShutdownHost(host._xapiId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +241,7 @@ emergencyShutdownHost.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function stats ({host, granularity}) {
|
export function stats ({ host, granularity }) {
|
||||||
return this.getXapiHostStats(host, granularity)
|
return this.getXapiHostStats(host, granularity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,9 +278,11 @@ async function handleInstallSupplementalPack (req, res, { hostId }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installSupplementalPack ({host}) {
|
export async function installSupplementalPack ({ host }) {
|
||||||
return {
|
return {
|
||||||
$sendTo: (await this.registerHttpRequest(handleInstallSupplementalPack, { hostId: host.id })),
|
$sendTo: await this.registerHttpRequest(handleInstallSupplementalPack, {
|
||||||
|
hostId: host.id,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,9 +26,8 @@ export function getAll (params) {
|
|||||||
throw unauthorized()
|
throw unauthorized()
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getAllIpPools(user.permission === 'admin'
|
return this.getAllIpPools(
|
||||||
? params && params.userId
|
user.permission === 'admin' ? params && params.userId : user.id
|
||||||
: user.id
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +14,10 @@ export async function get (id) {
|
|||||||
get.permission = 'admin'
|
get.permission = 'admin'
|
||||||
get.description = 'Gets an existing job'
|
get.description = 'Gets an existing job'
|
||||||
get.params = {
|
get.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create ({job}) {
|
export async function create ({ job }) {
|
||||||
if (!job.userId) {
|
if (!job.userId) {
|
||||||
job.userId = this.session.get('user_id')
|
job.userId = this.session.get('user_id')
|
||||||
}
|
}
|
||||||
@ -31,16 +31,16 @@ create.params = {
|
|||||||
job: {
|
job: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
userId: {type: 'string', optional: true},
|
userId: { type: 'string', optional: true },
|
||||||
name: {type: 'string', optional: true},
|
name: { type: 'string', optional: true },
|
||||||
timeout: {type: 'number', optional: true},
|
timeout: { type: 'number', optional: true },
|
||||||
type: {type: 'string'},
|
type: { type: 'string' },
|
||||||
key: {type: 'string'},
|
key: { type: 'string' },
|
||||||
method: {type: 'string'},
|
method: { type: 'string' },
|
||||||
paramsVector: {
|
paramsVector: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
type: {type: 'string'},
|
type: { type: 'string' },
|
||||||
items: {
|
items: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
@ -54,7 +54,7 @@ create.params = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function set ({job}) {
|
export async function set ({ job }) {
|
||||||
await this.updateJob(job)
|
await this.updateJob(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,16 +64,16 @@ set.params = {
|
|||||||
job: {
|
job: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
name: {type: 'string', optional: true},
|
name: { type: 'string', optional: true },
|
||||||
timeout: {type: ['number', 'null'], optional: true},
|
timeout: { type: ['number', 'null'], optional: true },
|
||||||
type: {type: 'string', optional: true},
|
type: { type: 'string', optional: true },
|
||||||
key: {type: 'string', optional: true},
|
key: { type: 'string', optional: true },
|
||||||
method: {type: 'string', optional: true},
|
method: { type: 'string', optional: true },
|
||||||
paramsVector: {
|
paramsVector: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
type: {type: 'string'},
|
type: { type: 'string' },
|
||||||
items: {
|
items: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
@ -87,24 +87,24 @@ set.params = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async function delete_ ({id}) {
|
async function delete_ ({ id }) {
|
||||||
await this.removeJob(id)
|
await this.removeJob(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_.permission = 'admin'
|
delete_.permission = 'admin'
|
||||||
delete_.description = 'Deletes an existing job'
|
delete_.description = 'Deletes an existing job'
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
export async function runSequence ({idSequence}) {
|
export async function runSequence ({ idSequence }) {
|
||||||
await this.runJobSequence(idSequence)
|
await this.runJobSequence(idSequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
runSequence.permission = 'admin'
|
runSequence.permission = 'admin'
|
||||||
runSequence.description = 'Runs jobs sequentially, in the provided order'
|
runSequence.description = 'Runs jobs sequentially, in the provided order'
|
||||||
runSequence.params = {
|
runSequence.params = {
|
||||||
idSequence: {type: 'array', items: {type: 'string'}},
|
idSequence: { type: 'array', items: { type: 'string' } },
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
export async function get ({namespace}) {
|
export async function get ({ namespace }) {
|
||||||
const logger = await this.getLogger(namespace)
|
const logger = await this.getLogger(namespace)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const logs = {}
|
const logs = {}
|
||||||
|
|
||||||
logger.createReadStream()
|
logger
|
||||||
.on('data', (data) => {
|
.createReadStream()
|
||||||
|
.on('data', data => {
|
||||||
logs[data.key] = data.value
|
logs[data.key] = data.value
|
||||||
})
|
})
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
@ -23,16 +24,16 @@ get.permission = 'admin'
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
async function delete_ ({namespace, id}) {
|
async function delete_ ({ namespace, id }) {
|
||||||
const logger = await this.getLogger(namespace)
|
const logger = await this.getLogger(namespace)
|
||||||
logger.del(id)
|
logger.del(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_.description = 'deletes one or several logs from a namespace'
|
delete_.description = 'deletes one or several logs from a namespace'
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: { type: [ 'array', 'string' ] },
|
id: { type: ['array', 'string'] },
|
||||||
namespace: { type: 'string' },
|
namespace: { type: 'string' },
|
||||||
}
|
}
|
||||||
delete_.permission = 'admin'
|
delete_.permission = 'admin'
|
||||||
|
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
async function delete_ ({ message }) {
|
async function delete_ ({ message }) {
|
||||||
await this.getXapi(message).call('message.destroy', message._xapiRef)
|
await this.getXapi(message).call('message.destroy', message._xapiRef)
|
||||||
}
|
}
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' },
|
||||||
|
@ -4,7 +4,14 @@ export function getBondModes () {
|
|||||||
return ['balance-slb', 'active-backup', 'lacp']
|
return ['balance-slb', 'active-backup', 'lacp']
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create ({ pool, name, description, pif, mtu = 1500, vlan = 0 }) {
|
export async function create ({
|
||||||
|
pool,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
pif,
|
||||||
|
mtu = 1500,
|
||||||
|
vlan = 0,
|
||||||
|
}) {
|
||||||
return this.getXapi(pool).createNetwork({
|
return this.getXapi(pool).createNetwork({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@ -30,13 +37,19 @@ create.permission = 'admin'
|
|||||||
|
|
||||||
// =================================================================
|
// =================================================================
|
||||||
|
|
||||||
export async function createBonded ({ pool, name, description, pifs, mtu = 1500, mac, bondMode }) {
|
export async function createBonded ({
|
||||||
|
pool,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
pifs,
|
||||||
|
mtu = 1500,
|
||||||
|
mac,
|
||||||
|
bondMode,
|
||||||
|
}) {
|
||||||
return this.getXapi(pool).createBondedNetwork({
|
return this.getXapi(pool).createBondedNetwork({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
pifIds: mapToArray(pifs, pif =>
|
pifIds: mapToArray(pifs, pif => this.getObject(pif, 'PIF')._xapiId),
|
||||||
this.getObject(pif, 'PIF')._xapiId
|
|
||||||
),
|
|
||||||
mtu: +mtu,
|
mtu: +mtu,
|
||||||
mac,
|
mac,
|
||||||
bondMode,
|
bondMode,
|
||||||
@ -56,14 +69,18 @@ createBonded.params = {
|
|||||||
mtu: { type: ['integer', 'string'], optional: true },
|
mtu: { type: ['integer', 'string'], optional: true },
|
||||||
mac: { type: 'string', optional: true },
|
mac: { type: 'string', optional: true },
|
||||||
// RegExp since schema-inspector does not provide a param check based on an enumeration
|
// RegExp since schema-inspector does not provide a param check based on an enumeration
|
||||||
bondMode: { type: 'string', pattern: new RegExp(`^(${getBondModes().join('|')})$`) },
|
bondMode: {
|
||||||
|
type: 'string',
|
||||||
|
pattern: new RegExp(`^(${getBondModes().join('|')})$`),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
createBonded.resolve = {
|
createBonded.resolve = {
|
||||||
pool: ['pool', 'pool', 'administrate'],
|
pool: ['pool', 'pool', 'administrate'],
|
||||||
}
|
}
|
||||||
createBonded.permission = 'admin'
|
createBonded.permission = 'admin'
|
||||||
createBonded.description = 'Create a bonded network. bondMode can be balance-slb, active-backup or lacp'
|
createBonded.description =
|
||||||
|
'Create a bonded network. bondMode can be balance-slb, active-backup or lacp'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -109,7 +126,7 @@ set.resolve = {
|
|||||||
export async function delete_ ({ network }) {
|
export async function delete_ ({ network }) {
|
||||||
return this.getXapi(network).deleteNetwork(network._xapiId)
|
return this.getXapi(network).deleteNetwork(network._xapiId)
|
||||||
}
|
}
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' },
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Delete
|
// Delete
|
||||||
|
|
||||||
async function delete_ ({PBD}) {
|
async function delete_ ({ PBD }) {
|
||||||
// TODO: check if PBD is attached before
|
// TODO: check if PBD is attached before
|
||||||
await this.getXapi(PBD).call('PBD.destroy', PBD._xapiRef)
|
await this.getXapi(PBD).call('PBD.destroy', PBD._xapiRef)
|
||||||
}
|
}
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' },
|
||||||
@ -35,7 +35,7 @@ disconnect.resolve = {
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Connect
|
// Connect
|
||||||
|
|
||||||
export async function connect ({PBD}) {
|
export async function connect ({ PBD }) {
|
||||||
// TODO: check if PBD is attached before
|
// TODO: check if PBD is attached before
|
||||||
await this.getXapi(PBD).call('PBD.plug', PBD._xapiRef)
|
await this.getXapi(PBD).call('PBD.plug', PBD._xapiRef)
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,11 @@ export function getIpv6ConfigurationModes () {
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Delete
|
// Delete
|
||||||
|
|
||||||
async function delete_ ({pif}) {
|
async function delete_ ({ pif }) {
|
||||||
// TODO: check if PIF is attached before
|
// TODO: check if PIF is attached before
|
||||||
await this.getXapi(pif).call('PIF.destroy', pif._xapiRef)
|
await this.getXapi(pif).call('PIF.destroy', pif._xapiRef)
|
||||||
}
|
}
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' },
|
||||||
@ -30,7 +30,7 @@ delete_.resolve = {
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Disconnect
|
// Disconnect
|
||||||
|
|
||||||
export async function disconnect ({pif}) {
|
export async function disconnect ({ pif }) {
|
||||||
// TODO: check if PIF is attached before
|
// TODO: check if PIF is attached before
|
||||||
await this.getXapi(pif).call('PIF.unplug', pif._xapiRef)
|
await this.getXapi(pif).call('PIF.unplug', pif._xapiRef)
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ disconnect.resolve = {
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Connect
|
// Connect
|
||||||
|
|
||||||
export async function connect ({pif}) {
|
export async function connect ({ pif }) {
|
||||||
// TODO: check if PIF is attached before
|
// TODO: check if PIF is attached before
|
||||||
await this.getXapi(pif).call('PIF.plug', pif._xapiRef)
|
await this.getXapi(pif).call('PIF.plug', pif._xapiRef)
|
||||||
}
|
}
|
||||||
@ -60,8 +60,23 @@ connect.resolve = {
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Reconfigure IP
|
// Reconfigure IP
|
||||||
|
|
||||||
export async function reconfigureIp ({ pif, mode = 'DHCP', ip = '', netmask = '', gateway = '', dns = '' }) {
|
export async function reconfigureIp ({
|
||||||
await this.getXapi(pif).call('PIF.reconfigure_ip', pif._xapiRef, mode, ip, netmask, gateway, dns)
|
pif,
|
||||||
|
mode = 'DHCP',
|
||||||
|
ip = '',
|
||||||
|
netmask = '',
|
||||||
|
gateway = '',
|
||||||
|
dns = '',
|
||||||
|
}) {
|
||||||
|
await this.getXapi(pif).call(
|
||||||
|
'PIF.reconfigure_ip',
|
||||||
|
pif._xapiRef,
|
||||||
|
mode,
|
||||||
|
ip,
|
||||||
|
netmask,
|
||||||
|
gateway,
|
||||||
|
dns
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
reconfigureIp.params = {
|
reconfigureIp.params = {
|
||||||
|
@ -38,7 +38,7 @@ set.resolve = {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function setDefaultSr ({ sr }) {
|
export async function setDefaultSr ({ sr }) {
|
||||||
await this.hasPermissions(this.user.id, [ [ sr.$pool, 'administrate' ] ])
|
await this.hasPermissions(this.user.id, [[sr.$pool, 'administrate']])
|
||||||
|
|
||||||
await this.getXapi(sr).setDefaultSr(sr._xapiId)
|
await this.getXapi(sr).setDefaultSr(sr._xapiId)
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ setDefaultSr.resolve = {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function setPoolMaster ({ host }) {
|
export async function setPoolMaster ({ host }) {
|
||||||
await this.hasPermissions(this.user.id, [ [ host.$pool, 'administrate' ] ])
|
await this.hasPermissions(this.user.id, [[host.$pool, 'administrate']])
|
||||||
|
|
||||||
await this.getXapi(host).setPoolMaster(host._xapiId)
|
await this.getXapi(host).setPoolMaster(host._xapiId)
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ setPoolMaster.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function installPatch ({pool, patch: patchUuid}) {
|
export async function installPatch ({ pool, patch: patchUuid }) {
|
||||||
await this.getXapi(pool).installPoolPatchOnAllHosts(patchUuid)
|
await this.getXapi(pool).installPoolPatchOnAllHosts(patchUuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,11 +107,12 @@ installAllPatches.resolve = {
|
|||||||
pool: ['pool', 'pool', 'administrate'],
|
pool: ['pool', 'pool', 'administrate'],
|
||||||
}
|
}
|
||||||
|
|
||||||
installAllPatches.description = 'Install automatically all patches for every hosts of a pool'
|
installAllPatches.description =
|
||||||
|
'Install automatically all patches for every hosts of a pool'
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
async function handlePatchUpload (req, res, {pool}) {
|
async function handlePatchUpload (req, res, { pool }) {
|
||||||
const contentLength = req.headers['content-length']
|
const contentLength = req.headers['content-length']
|
||||||
if (!contentLength) {
|
if (!contentLength) {
|
||||||
res.writeHead(411)
|
res.writeHead(411)
|
||||||
@ -122,9 +123,9 @@ async function handlePatchUpload (req, res, {pool}) {
|
|||||||
await this.getXapi(pool).uploadPoolPatch(req, contentLength)
|
await this.getXapi(pool).uploadPoolPatch(req, contentLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadPatch ({pool}) {
|
export async function uploadPatch ({ pool }) {
|
||||||
return {
|
return {
|
||||||
$sendTo: await this.registerHttpRequest(handlePatchUpload, {pool}),
|
$sendTo: await this.registerHttpRequest(handlePatchUpload, { pool }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ uploadPatch.resolve = {
|
|||||||
// Compatibility
|
// Compatibility
|
||||||
//
|
//
|
||||||
// TODO: remove when no longer used in xo-web
|
// TODO: remove when no longer used in xo-web
|
||||||
export {uploadPatch as patch}
|
export { uploadPatch as patch }
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
@ -177,11 +178,8 @@ mergeInto.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function getLicenseState ({pool}) {
|
export async function getLicenseState ({ pool }) {
|
||||||
return this.getXapi(pool).call(
|
return this.getXapi(pool).call('pool.get_license_state', pool._xapiId.$ref)
|
||||||
'pool.get_license_state',
|
|
||||||
pool._xapiId.$ref
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLicenseState.params = {
|
getLicenseState.params = {
|
||||||
@ -215,11 +213,14 @@ async function handleInstallSupplementalPack (req, res, { poolId }) {
|
|||||||
|
|
||||||
export async function installSupplementalPack ({ pool }) {
|
export async function installSupplementalPack ({ pool }) {
|
||||||
return {
|
return {
|
||||||
$sendTo: await this.registerHttpRequest(handleInstallSupplementalPack, { poolId: pool.id }),
|
$sendTo: await this.registerHttpRequest(handleInstallSupplementalPack, {
|
||||||
|
poolId: pool.id,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
installSupplementalPack.description = 'installs supplemental pack from ISO file on all hosts'
|
installSupplementalPack.description =
|
||||||
|
'installs supplemental pack from ISO file on all hosts'
|
||||||
|
|
||||||
installSupplementalPack.params = {
|
installSupplementalPack.params = {
|
||||||
pool: { type: 'string' },
|
pool: { type: 'string' },
|
||||||
|
@ -5,68 +5,68 @@ export async function getAll () {
|
|||||||
getAll.permission = 'admin'
|
getAll.permission = 'admin'
|
||||||
getAll.description = 'Gets all existing fs remote points'
|
getAll.description = 'Gets all existing fs remote points'
|
||||||
|
|
||||||
export async function get ({id}) {
|
export async function get ({ id }) {
|
||||||
return this.getRemote(id)
|
return this.getRemote(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
get.permission = 'admin'
|
get.permission = 'admin'
|
||||||
get.description = 'Gets an existing fs remote point'
|
get.description = 'Gets an existing fs remote point'
|
||||||
get.params = {
|
get.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function test ({id}) {
|
export async function test ({ id }) {
|
||||||
return this.testRemote(id)
|
return this.testRemote(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
test.permission = 'admin'
|
test.permission = 'admin'
|
||||||
test.description = 'Performs a read/write matching test on a remote point'
|
test.description = 'Performs a read/write matching test on a remote point'
|
||||||
test.params = {
|
test.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function list ({id}) {
|
export async function list ({ id }) {
|
||||||
return this.listRemoteBackups(id)
|
return this.listRemoteBackups(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
list.permission = 'admin'
|
list.permission = 'admin'
|
||||||
list.description = 'Lists the files found in a remote point'
|
list.description = 'Lists the files found in a remote point'
|
||||||
list.params = {
|
list.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create ({name, url}) {
|
export async function create ({ name, url }) {
|
||||||
return this.createRemote({name, url})
|
return this.createRemote({ name, url })
|
||||||
}
|
}
|
||||||
|
|
||||||
create.permission = 'admin'
|
create.permission = 'admin'
|
||||||
create.description = 'Creates a new fs remote point'
|
create.description = 'Creates a new fs remote point'
|
||||||
create.params = {
|
create.params = {
|
||||||
name: {type: 'string'},
|
name: { type: 'string' },
|
||||||
url: {type: 'string'},
|
url: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function set ({id, name, url, enabled}) {
|
export async function set ({ id, name, url, enabled }) {
|
||||||
await this.updateRemote(id, {name, url, enabled})
|
await this.updateRemote(id, { name, url, enabled })
|
||||||
}
|
}
|
||||||
|
|
||||||
set.permission = 'admin'
|
set.permission = 'admin'
|
||||||
set.description = 'Modifies an existing fs remote point'
|
set.description = 'Modifies an existing fs remote point'
|
||||||
set.params = {
|
set.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
name: {type: 'string', optional: true},
|
name: { type: 'string', optional: true },
|
||||||
url: {type: 'string', optional: true},
|
url: { type: 'string', optional: true },
|
||||||
enabled: {type: 'boolean', optional: true},
|
enabled: { type: 'boolean', optional: true },
|
||||||
}
|
}
|
||||||
|
|
||||||
async function delete_ ({id}) {
|
async function delete_ ({ id }) {
|
||||||
await this.removeRemote(id)
|
await this.removeRemote(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_.permission = 'admin'
|
delete_.permission = 'admin'
|
||||||
delete_.description = 'Deletes an existing fs remote point'
|
delete_.description = 'Deletes an existing fs remote point'
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import {
|
import { unauthorized } from 'xo-common/api-errors'
|
||||||
unauthorized,
|
|
||||||
} from 'xo-common/api-errors'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -237,4 +235,5 @@ export function recomputeAllLimits () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recomputeAllLimits.permission = 'admin'
|
recomputeAllLimits.permission = 'admin'
|
||||||
recomputeAllLimits.description = 'Recompute manually the current resource set usage'
|
recomputeAllLimits.description =
|
||||||
|
'Recompute manually the current resource set usage'
|
||||||
|
@ -14,20 +14,26 @@ export async function get (id) {
|
|||||||
get.permission = 'admin'
|
get.permission = 'admin'
|
||||||
get.description = 'Gets an existing schedule'
|
get.description = 'Gets an existing schedule'
|
||||||
get.params = {
|
get.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create ({ jobId, cron, enabled, name, timezone }) {
|
export async function create ({ jobId, cron, enabled, name, timezone }) {
|
||||||
return /* await */ this.createSchedule(this.session.get('user_id'), { job: jobId, cron, enabled, name, timezone })
|
return /* await */ this.createSchedule(this.session.get('user_id'), {
|
||||||
|
job: jobId,
|
||||||
|
cron,
|
||||||
|
enabled,
|
||||||
|
name,
|
||||||
|
timezone,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
create.permission = 'admin'
|
create.permission = 'admin'
|
||||||
create.description = 'Creates a new schedule'
|
create.description = 'Creates a new schedule'
|
||||||
create.params = {
|
create.params = {
|
||||||
jobId: {type: 'string'},
|
jobId: { type: 'string' },
|
||||||
cron: {type: 'string'},
|
cron: { type: 'string' },
|
||||||
enabled: {type: 'boolean', optional: true},
|
enabled: { type: 'boolean', optional: true },
|
||||||
name: {type: 'string', optional: true},
|
name: { type: 'string', optional: true },
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function set ({ id, jobId, cron, enabled, name, timezone }) {
|
export async function set ({ id, jobId, cron, enabled, name, timezone }) {
|
||||||
@ -37,21 +43,21 @@ export async function set ({ id, jobId, cron, enabled, name, timezone }) {
|
|||||||
set.permission = 'admin'
|
set.permission = 'admin'
|
||||||
set.description = 'Modifies an existing schedule'
|
set.description = 'Modifies an existing schedule'
|
||||||
set.params = {
|
set.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
jobId: {type: 'string', optional: true},
|
jobId: { type: 'string', optional: true },
|
||||||
cron: {type: 'string', optional: true},
|
cron: { type: 'string', optional: true },
|
||||||
enabled: {type: 'boolean', optional: true},
|
enabled: { type: 'boolean', optional: true },
|
||||||
name: {type: 'string', optional: true},
|
name: { type: 'string', optional: true },
|
||||||
}
|
}
|
||||||
|
|
||||||
async function delete_ ({id}) {
|
async function delete_ ({ id }) {
|
||||||
await this.removeSchedule(id)
|
await this.removeSchedule(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_.permission = 'admin'
|
delete_.permission = 'admin'
|
||||||
delete_.description = 'Deletes an existing schedule'
|
delete_.description = 'Deletes an existing schedule'
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
export async function enable ({id}) {
|
export async function enable ({ id }) {
|
||||||
const schedule = await this.getSchedule(id)
|
const schedule = await this.getSchedule(id)
|
||||||
schedule.enabled = true
|
schedule.enabled = true
|
||||||
await this.updateSchedule(id, schedule)
|
await this.updateSchedule(id, schedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
enable.permission = 'admin'
|
enable.permission = 'admin'
|
||||||
enable.description = 'Enables a schedule to run it\'s job as scheduled'
|
enable.description = "Enables a schedule to run it's job as scheduled"
|
||||||
enable.params = {
|
enable.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function disable ({id}) {
|
export async function disable ({ id }) {
|
||||||
const schedule = await this.getSchedule(id)
|
const schedule = await this.getSchedule(id)
|
||||||
schedule.enabled = false
|
schedule.enabled = false
|
||||||
await this.updateSchedule(id, schedule)
|
await this.updateSchedule(id, schedule)
|
||||||
@ -19,7 +19,7 @@ export async function disable ({id}) {
|
|||||||
disable.permission = 'admin'
|
disable.permission = 'admin'
|
||||||
disable.description = 'Disables a schedule'
|
disable.description = 'Disables a schedule'
|
||||||
disable.params = {
|
disable.params = {
|
||||||
id: {type: 'string'},
|
id: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScheduleTable () {
|
export function getScheduleTable () {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { ignoreErrors } from 'promise-toolbox'
|
import { ignoreErrors } from 'promise-toolbox'
|
||||||
|
|
||||||
export async function add ({autoConnect = true, ...props}) {
|
export async function add ({ autoConnect = true, ...props }) {
|
||||||
const server = await this.registerXenServer(props)
|
const server = await this.registerXenServer(props)
|
||||||
|
|
||||||
if (autoConnect) {
|
if (autoConnect) {
|
||||||
this.connectXenServer(server.id)::ignoreErrors()
|
;this.connectXenServer(server.id)::ignoreErrors()
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.id
|
return server.id
|
||||||
@ -40,7 +40,7 @@ add.params = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function remove ({id}) {
|
export async function remove ({ id }) {
|
||||||
await this.unregisterXenServer(id)
|
await this.unregisterXenServer(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ getAll.permission = 'admin'
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function set ({id, ...props}) {
|
export async function set ({ id, ...props }) {
|
||||||
await this.updateXenServer(id, props)
|
await this.updateXenServer(id, props)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,8 +104,8 @@ set.params = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function connect ({id}) {
|
export async function connect ({ id }) {
|
||||||
this.updateXenServer(id, {enabled: true})::ignoreErrors()
|
;this.updateXenServer(id, { enabled: true })::ignoreErrors()
|
||||||
await this.connectXenServer(id)
|
await this.connectXenServer(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,8 +121,8 @@ connect.params = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function disconnect ({id}) {
|
export async function disconnect ({ id }) {
|
||||||
this.updateXenServer(id, {enabled: false})::ignoreErrors()
|
;this.updateXenServer(id, { enabled: false })::ignoreErrors()
|
||||||
await this.disconnectXenServer(id)
|
await this.disconnectXenServer(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {deprecate} from 'util'
|
import { deprecate } from 'util'
|
||||||
|
|
||||||
import { getUserPublicProperties } from '../utils'
|
import { getUserPublicProperties } from '../utils'
|
||||||
import {invalidCredentials} from 'xo-common/api-errors'
|
import { invalidCredentials } from 'xo-common/api-errors'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -19,7 +19,10 @@ signIn.description = 'sign in'
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const signInWithPassword = deprecate(signIn, 'use session.signIn() instead')
|
export const signInWithPassword = deprecate(
|
||||||
|
signIn,
|
||||||
|
'use session.signIn() instead'
|
||||||
|
)
|
||||||
|
|
||||||
signInWithPassword.params = {
|
signInWithPassword.params = {
|
||||||
email: { type: 'string' },
|
email: { type: 'string' },
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import { some } from 'lodash'
|
import { some } from 'lodash'
|
||||||
|
|
||||||
import { asInteger } from '../xapi/utils'
|
import { asInteger } from '../xapi/utils'
|
||||||
import {
|
import { asyncMap, ensureArray, forEach, parseXml } from '../utils'
|
||||||
asyncMap,
|
|
||||||
ensureArray,
|
|
||||||
forEach,
|
|
||||||
parseXml,
|
|
||||||
} from '../utils'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -50,10 +45,11 @@ scan.resolve = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
const srIsBackingHa = (sr) => sr.$pool.ha_enabled && some(sr.$pool.$ha_statefiles, f => f.$SR === sr)
|
const srIsBackingHa = sr =>
|
||||||
|
sr.$pool.ha_enabled && some(sr.$pool.$ha_statefiles, f => f.$SR === sr)
|
||||||
|
|
||||||
// TODO: find a way to call this "delete" and not destroy
|
// TODO: find a way to call this "delete" and not destroy
|
||||||
export async function destroy ({sr}) {
|
export async function destroy ({ sr }) {
|
||||||
const xapi = this.getXapi(sr)
|
const xapi = this.getXapi(sr)
|
||||||
if (sr.SR_type !== 'xosan') {
|
if (sr.SR_type !== 'xosan') {
|
||||||
await xapi.destroySr(sr._xapiId)
|
await xapi.destroySr(sr._xapiId)
|
||||||
@ -61,7 +57,9 @@ export async function destroy ({sr}) {
|
|||||||
}
|
}
|
||||||
const xapiSr = xapi.getObject(sr)
|
const xapiSr = xapi.getObject(sr)
|
||||||
if (srIsBackingHa(xapiSr)) {
|
if (srIsBackingHa(xapiSr)) {
|
||||||
throw new Error('You tried to remove a SR the High Availability is relying on. Please disable HA first.')
|
throw new Error(
|
||||||
|
'You tried to remove a SR the High Availability is relying on. Please disable HA first.'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const config = xapi.xo.getData(sr, 'xosan_config')
|
const config = xapi.xo.getData(sr, 'xosan_config')
|
||||||
// we simply forget because the hosted disks are being destroyed with the VMs
|
// we simply forget because the hosted disks are being destroyed with the VMs
|
||||||
@ -239,12 +237,7 @@ createNfs.resolve = {
|
|||||||
|
|
||||||
// This functions creates an HBA SR
|
// This functions creates an HBA SR
|
||||||
|
|
||||||
export async function createHba ({
|
export async function createHba ({ host, nameLabel, nameDescription, scsiId }) {
|
||||||
host,
|
|
||||||
nameLabel,
|
|
||||||
nameDescription,
|
|
||||||
scsiId,
|
|
||||||
}) {
|
|
||||||
const xapi = this.getXapi(host)
|
const xapi = this.getXapi(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
@ -284,12 +277,7 @@ createHba.resolve = {
|
|||||||
|
|
||||||
// This functions creates a local LVM SR
|
// This functions creates a local LVM SR
|
||||||
|
|
||||||
export async function createLvm ({
|
export async function createLvm ({ host, nameLabel, nameDescription, device }) {
|
||||||
host,
|
|
||||||
nameLabel,
|
|
||||||
nameDescription,
|
|
||||||
device,
|
|
||||||
}) {
|
|
||||||
const xapi = this.getXapi(host)
|
const xapi = this.getXapi(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
@ -328,10 +316,7 @@ createLvm.resolve = {
|
|||||||
// This function helps to detect all NFS shares (exports) on a NFS server
|
// This function helps to detect all NFS shares (exports) on a NFS server
|
||||||
// Return a table of exports with their paths and ACLs
|
// Return a table of exports with their paths and ACLs
|
||||||
|
|
||||||
export async function probeNfs ({
|
export async function probeNfs ({ host, server }) {
|
||||||
host,
|
|
||||||
server,
|
|
||||||
}) {
|
|
||||||
const xapi = this.getXapi(host)
|
const xapi = this.getXapi(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
@ -341,13 +326,7 @@ export async function probeNfs ({
|
|||||||
let xml
|
let xml
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await xapi.call(
|
await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'nfs', {})
|
||||||
'SR.probe',
|
|
||||||
host._xapiRef,
|
|
||||||
deviceConfig,
|
|
||||||
'nfs',
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
throw new Error('the call above should have thrown an error')
|
throw new Error('the call above should have thrown an error')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -381,20 +360,13 @@ probeNfs.resolve = {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// This function helps to detect all HBA devices on the host
|
// This function helps to detect all HBA devices on the host
|
||||||
|
|
||||||
export async function probeHba ({
|
export async function probeHba ({ host }) {
|
||||||
host,
|
|
||||||
}) {
|
|
||||||
const xapi = this.getXapi(host)
|
const xapi = this.getXapi(host)
|
||||||
|
|
||||||
let xml
|
let xml
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await xapi.call(
|
await xapi.call('SR.probe', host._xapiRef, 'type', {})
|
||||||
'SR.probe',
|
|
||||||
host._xapiRef,
|
|
||||||
'type',
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
throw new Error('the call above should have thrown an error')
|
throw new Error('the call above should have thrown an error')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -527,13 +499,7 @@ export async function probeIscsiIqns ({
|
|||||||
let xml
|
let xml
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await xapi.call(
|
await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'lvmoiscsi', {})
|
||||||
'SR.probe',
|
|
||||||
host._xapiRef,
|
|
||||||
deviceConfig,
|
|
||||||
'lvmoiscsi',
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
throw new Error('the call above should have thrown an error')
|
throw new Error('the call above should have thrown an error')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -605,13 +571,7 @@ export async function probeIscsiLuns ({
|
|||||||
let xml
|
let xml
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await xapi.call(
|
await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'lvmoiscsi', {})
|
||||||
'SR.probe',
|
|
||||||
host._xapiRef,
|
|
||||||
deviceConfig,
|
|
||||||
'lvmoiscsi',
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
throw new Error('the call above should have thrown an error')
|
throw new Error('the call above should have thrown an error')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -681,7 +641,9 @@ export async function probeIscsiExists ({
|
|||||||
deviceConfig.port = asInteger(port)
|
deviceConfig.port = asInteger(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
const xml = parseXml(await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'lvmoiscsi', {}))
|
const xml = parseXml(
|
||||||
|
await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'lvmoiscsi', {})
|
||||||
|
)
|
||||||
|
|
||||||
const srs = []
|
const srs = []
|
||||||
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
||||||
@ -710,11 +672,7 @@ probeIscsiExists.resolve = {
|
|||||||
// This function helps to detect if this NFS SR already exists in XAPI
|
// This function helps to detect if this NFS SR already exists in XAPI
|
||||||
// It returns a table of SR UUID, empty if no existing connections
|
// It returns a table of SR UUID, empty if no existing connections
|
||||||
|
|
||||||
export async function probeNfsExists ({
|
export async function probeNfsExists ({ host, server, serverPath }) {
|
||||||
host,
|
|
||||||
server,
|
|
||||||
serverPath,
|
|
||||||
}) {
|
|
||||||
const xapi = this.getXapi(host)
|
const xapi = this.getXapi(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
@ -722,7 +680,9 @@ export async function probeNfsExists ({
|
|||||||
serverpath: serverPath,
|
serverpath: serverPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
const xml = parseXml(await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'nfs', {}))
|
const xml = parseXml(
|
||||||
|
await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'nfs', {})
|
||||||
|
)
|
||||||
|
|
||||||
const srs = []
|
const srs = []
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ export function getMethodsInfo () {
|
|||||||
|
|
||||||
return methods
|
return methods
|
||||||
}
|
}
|
||||||
getMethodsInfo.description = 'returns the signatures of all available API methods'
|
getMethodsInfo.description =
|
||||||
|
'returns the signatures of all available API methods'
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ listMethods.description = 'returns the name of all available API methods'
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function methodSignature ({method: name}) {
|
export function methodSignature ({ method: name }) {
|
||||||
const method = this.apiMethods[name]
|
const method = this.apiMethods[name]
|
||||||
|
|
||||||
if (!method) {
|
if (!method) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export async function add ({tag, object}) {
|
export async function add ({ tag, object }) {
|
||||||
await this.getXapi(object).addTag(object._xapiId, tag)
|
await this.getXapi(object).addTag(object._xapiId, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ add.params = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function remove ({tag, object}) {
|
export async function remove ({ tag, object }) {
|
||||||
await this.getXapi(object).removeTag(object._xapiId, tag)
|
await this.getXapi(object).removeTag(object._xapiId, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export async function cancel ({task}) {
|
export async function cancel ({ task }) {
|
||||||
await this.getXapi(task).call('task.cancel', task._xapiRef)
|
await this.getXapi(task).call('task.cancel', task._xapiRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ cancel.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function destroy ({task}) {
|
export async function destroy ({ task }) {
|
||||||
await this.getXapi(task).call('task.destroy', task._xapiRef)
|
await this.getXapi(task).call('task.destroy', task._xapiRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,9 +13,7 @@ getPermissionsForUser.params = {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function hasPermission ({ userId, objectId, permission }) {
|
export function hasPermission ({ userId, objectId, permission }) {
|
||||||
return this.hasPermissions(userId, [
|
return this.hasPermissions(userId, [[objectId, permission]])
|
||||||
[ objectId, permission ],
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPermission.permission = 'admin'
|
hasPermission.permission = 'admin'
|
||||||
@ -34,7 +32,7 @@ hasPermission.params = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export function wait ({duration, returnValue}) {
|
export function wait ({ duration, returnValue }) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve(returnValue)
|
resolve(returnValue)
|
||||||
@ -81,6 +79,6 @@ copyVm.params = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copyVm.resolve = {
|
copyVm.resolve = {
|
||||||
vm: [ 'vm', 'VM' ],
|
vm: ['vm', 'VM'],
|
||||||
sr: [ 'sr', 'SR' ],
|
sr: ['sr', 'SR'],
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ create.description = 'create a new authentication token'
|
|||||||
create.params = {
|
create.params = {
|
||||||
expiresIn: {
|
expiresIn: {
|
||||||
optional: true,
|
optional: true,
|
||||||
type: [ 'number', 'string' ],
|
type: ['number', 'string'],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,11 +21,11 @@ create.permission = '' // sign in
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// TODO: an user should be able to delete its own tokens.
|
// TODO: an user should be able to delete its own tokens.
|
||||||
async function delete_ ({token: id}) {
|
async function delete_ ({ token: id }) {
|
||||||
await this.deleteAuthenticationToken(id)
|
await this.deleteAuthenticationToken(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
delete_.description = 'delete an existing authentication token'
|
delete_.description = 'delete an existing authentication token'
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import {invalidParameters} from 'xo-common/api-errors'
|
import { invalidParameters } from 'xo-common/api-errors'
|
||||||
import { getUserPublicProperties, mapToArray } from '../utils'
|
import { getUserPublicProperties, mapToArray } from '../utils'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export async function create ({email, password, permission}) {
|
export async function create ({ email, password, permission }) {
|
||||||
return (await this.createUser({email, password, permission})).id
|
return (await this.createUser({ email, password, permission })).id
|
||||||
}
|
}
|
||||||
|
|
||||||
create.description = 'creates a new user'
|
create.description = 'creates a new user'
|
||||||
@ -20,7 +20,7 @@ create.params = {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// Deletes an existing user.
|
// Deletes an existing user.
|
||||||
async function delete_ ({id}) {
|
async function delete_ ({ id }) {
|
||||||
if (id === this.session.get('user_id')) {
|
if (id === this.session.get('user_id')) {
|
||||||
throw invalidParameters('a user cannot delete itself')
|
throw invalidParameters('a user cannot delete itself')
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ async function delete_ ({id}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delete is not a valid identifier.
|
// delete is not a valid identifier.
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
delete_.description = 'deletes an existing user'
|
delete_.description = 'deletes an existing user'
|
||||||
|
|
||||||
@ -57,17 +57,19 @@ getAll.permission = 'admin'
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function set ({id, email, password, permission, preferences}) {
|
export async function set ({ id, email, password, permission, preferences }) {
|
||||||
const isAdmin = this.user && this.user.permission === 'admin'
|
const isAdmin = this.user && this.user.permission === 'admin'
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
if (permission && id === this.session.get('user_id')) {
|
if (permission && id === this.session.get('user_id')) {
|
||||||
throw invalidParameters('a user cannot change its own permission')
|
throw invalidParameters('a user cannot change its own permission')
|
||||||
}
|
}
|
||||||
} else if (email || password || permission) {
|
} else if (email || password || permission) {
|
||||||
throw invalidParameters('this properties can only changed by an administrator')
|
throw invalidParameters(
|
||||||
|
'this properties can only changed by an administrator'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateUser(id, {email, password, permission, preferences})
|
await this.updateUser(id, { email, password, permission, preferences })
|
||||||
}
|
}
|
||||||
|
|
||||||
set.description = 'changes the properties of an existing user'
|
set.description = 'changes the properties of an existing user'
|
||||||
@ -84,16 +86,17 @@ set.params = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function changePassword ({oldPassword, newPassword}) {
|
export async function changePassword ({ oldPassword, newPassword }) {
|
||||||
const id = this.session.get('user_id')
|
const id = this.session.get('user_id')
|
||||||
await this.changeUserPassword(id, oldPassword, newPassword)
|
await this.changeUserPassword(id, oldPassword, newPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
changePassword.description = 'change password after checking old password (user function)'
|
changePassword.description =
|
||||||
|
'change password after checking old password (user function)'
|
||||||
|
|
||||||
changePassword.permission = ''
|
changePassword.permission = ''
|
||||||
|
|
||||||
changePassword.params = {
|
changePassword.params = {
|
||||||
oldPassword: {type: 'string'},
|
oldPassword: { type: 'string' },
|
||||||
newPassword: {type: 'string'},
|
newPassword: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// FIXME: too low level, should be removed.
|
// FIXME: too low level, should be removed.
|
||||||
|
|
||||||
async function delete_ ({vbd}) {
|
async function delete_ ({ vbd }) {
|
||||||
await this.getXapi(vbd).deleteVbd(vbd)
|
await this.getXapi(vbd).deleteVbd(vbd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ export { delete_ as delete }
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function disconnect ({vbd}) {
|
export async function disconnect ({ vbd }) {
|
||||||
const xapi = this.getXapi(vbd)
|
const xapi = this.getXapi(vbd)
|
||||||
await xapi.disconnectVbd(vbd._xapiRef)
|
await xapi.disconnectVbd(vbd._xapiRef)
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ disconnect.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function connect ({vbd}) {
|
export async function connect ({ vbd }) {
|
||||||
const xapi = this.getXapi(vbd)
|
const xapi = this.getXapi(vbd)
|
||||||
await xapi.connectVbd(vbd._xapiRef)
|
await xapi.connectVbd(vbd._xapiRef)
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ connect.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function set ({position, vbd}) {
|
export async function set ({ position, vbd }) {
|
||||||
if (position !== undefined) {
|
if (position !== undefined) {
|
||||||
const xapi = this.getXapi(vbd)
|
const xapi = this.getXapi(vbd)
|
||||||
await xapi.call('VBD.set_userdevice', vbd._xapiRef, String(position))
|
await xapi.call('VBD.set_userdevice', vbd._xapiRef, String(position))
|
||||||
@ -66,7 +66,7 @@ set.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function setBootable ({vbd, bootable}) {
|
export async function setBootable ({ vbd, bootable }) {
|
||||||
const xapi = this.getXapi(vbd)
|
const xapi = this.getXapi(vbd)
|
||||||
|
|
||||||
await xapi.call('VBD.set_bootable', vbd._xapiRef, bootable)
|
await xapi.call('VBD.set_bootable', vbd._xapiRef, bootable)
|
||||||
|
@ -7,10 +7,11 @@ import { parseSize } from '../utils'
|
|||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
|
|
||||||
export async function delete_ ({vdi}) {
|
export async function delete_ ({ vdi }) {
|
||||||
const resourceSet = reduce(
|
const resourceSet = reduce(
|
||||||
vdi.$VBDs,
|
vdi.$VBDs,
|
||||||
(resourceSet, vbd) => resourceSet || this.getObject(this.getObject(vbd, 'VBD').VM).resourceSet,
|
(resourceSet, vbd) =>
|
||||||
|
resourceSet || this.getObject(this.getObject(vbd, 'VBD').VM).resourceSet,
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ export { delete_ as delete }
|
|||||||
|
|
||||||
// FIXME: human readable strings should be handled.
|
// FIXME: human readable strings should be handled.
|
||||||
export async function set (params) {
|
export async function set (params) {
|
||||||
const {vdi} = params
|
const { vdi } = params
|
||||||
const xapi = this.getXapi(vdi)
|
const xapi = this.getXapi(vdi)
|
||||||
const ref = vdi._xapiRef
|
const ref = vdi._xapiRef
|
||||||
|
|
||||||
@ -52,18 +53,26 @@ export async function set (params) {
|
|||||||
|
|
||||||
const vbds = vdi.$VBDs
|
const vbds = vdi.$VBDs
|
||||||
if (
|
if (
|
||||||
(vbds.length === 1) &&
|
vbds.length === 1 &&
|
||||||
((resourceSetId = xapi.xo.getData(this.getObject(vbds[0], 'VBD').VM, 'resourceSet')) !== undefined)
|
(resourceSetId = xapi.xo.getData(
|
||||||
|
this.getObject(vbds[0], 'VBD').VM,
|
||||||
|
'resourceSet'
|
||||||
|
)) !== undefined
|
||||||
) {
|
) {
|
||||||
if (this.user.permission !== 'admin') {
|
if (this.user.permission !== 'admin') {
|
||||||
await this.checkResourceSetConstraints(resourceSetId, this.user.id)
|
await this.checkResourceSetConstraints(resourceSetId, this.user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.allocateLimitsInResourceSet({ disk: size - vdi.size }, resourceSetId)
|
await this.allocateLimitsInResourceSet(
|
||||||
} else if (!(
|
{ disk: size - vdi.size },
|
||||||
(this.user.permission === 'admin') ||
|
resourceSetId
|
||||||
(await this.hasPermissions(this.user.id, [ [ vdi.$SR, 'operate' ] ]))
|
)
|
||||||
)) {
|
} else if (
|
||||||
|
!(
|
||||||
|
this.user.permission === 'admin' ||
|
||||||
|
(await this.hasPermissions(this.user.id, [[vdi.$SR, 'operate']]))
|
||||||
|
)
|
||||||
|
) {
|
||||||
throw unauthorized()
|
throw unauthorized()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,14 +81,16 @@ export async function set (params) {
|
|||||||
|
|
||||||
// Other fields.
|
// Other fields.
|
||||||
const object = {
|
const object = {
|
||||||
'name_label': 'name_label',
|
name_label: 'name_label',
|
||||||
'name_description': 'name_description',
|
name_description: 'name_description',
|
||||||
}
|
}
|
||||||
for (const param in object) {
|
for (const param in object) {
|
||||||
const fields = object[param]
|
const fields = object[param]
|
||||||
if (!(param in params)) { continue }
|
if (!(param in params)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for (const field of (isArray(fields) ? fields : [fields])) {
|
for (const field of isArray(fields) ? fields : [fields]) {
|
||||||
await xapi.call(`VDI.set_${field}`, ref, `${params[param]}`)
|
await xapi.call(`VDI.set_${field}`, ref, `${params[param]}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +114,7 @@ set.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export async function migrate ({vdi, sr}) {
|
export async function migrate ({ vdi, sr }) {
|
||||||
const xapi = this.getXapi(vdi)
|
const xapi = this.getXapi(vdi)
|
||||||
|
|
||||||
await xapi.moveVdi(vdi._xapiRef, sr._xapiRef)
|
await xapi.moveVdi(vdi._xapiRef, sr._xapiRef)
|
||||||
|
@ -5,8 +5,8 @@ import { diffItems } from '../utils'
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
// TODO: move into vm and rename to removeInterface
|
// TODO: move into vm and rename to removeInterface
|
||||||
async function delete_ ({vif}) {
|
async function delete_ ({ vif }) {
|
||||||
this.allocIpAddresses(
|
;this.allocIpAddresses(
|
||||||
vif.id,
|
vif.id,
|
||||||
null,
|
null,
|
||||||
vif.allowedIpv4Addresses.concat(vif.allowedIpv6Addresses)
|
vif.allowedIpv4Addresses.concat(vif.allowedIpv6Addresses)
|
||||||
@ -14,7 +14,7 @@ async function delete_ ({vif}) {
|
|||||||
|
|
||||||
await this.getXapi(vif).deleteVif(vif._xapiId)
|
await this.getXapi(vif).deleteVif(vif._xapiId)
|
||||||
}
|
}
|
||||||
export {delete_ as delete}
|
export { delete_ as delete }
|
||||||
|
|
||||||
delete_.params = {
|
delete_.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' },
|
||||||
@ -27,7 +27,7 @@ delete_.resolve = {
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// TODO: move into vm and rename to disconnectInterface
|
// TODO: move into vm and rename to disconnectInterface
|
||||||
export async function disconnect ({vif}) {
|
export async function disconnect ({ vif }) {
|
||||||
// TODO: check if VIF is attached before
|
// TODO: check if VIF is attached before
|
||||||
await this.getXapi(vif).disconnectVif(vif._xapiId)
|
await this.getXapi(vif).disconnectVif(vif._xapiId)
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ disconnect.resolve = {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// TODO: move into vm and rename to connectInterface
|
// TODO: move into vm and rename to connectInterface
|
||||||
export async function connect ({vif}) {
|
export async function connect ({ vif }) {
|
||||||
// TODO: check if VIF is attached before
|
// TODO: check if VIF is attached before
|
||||||
await this.getXapi(vif).connectVif(vif._xapiId)
|
await this.getXapi(vif).connectVif(vif._xapiId)
|
||||||
}
|
}
|
||||||
@ -65,7 +65,9 @@ export async function set ({
|
|||||||
allowedIpv6Addresses,
|
allowedIpv6Addresses,
|
||||||
attached,
|
attached,
|
||||||
}) {
|
}) {
|
||||||
const oldIpAddresses = vif.allowedIpv4Addresses.concat(vif.allowedIpv6Addresses)
|
const oldIpAddresses = vif.allowedIpv4Addresses.concat(
|
||||||
|
vif.allowedIpv6Addresses
|
||||||
|
)
|
||||||
const newIpAddresses = []
|
const newIpAddresses = []
|
||||||
{
|
{
|
||||||
const { push } = newIpAddresses
|
const { push } = newIpAddresses
|
||||||
@ -96,15 +98,11 @@ export async function set ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ addAddresses, removeAddresses ] = diffItems(
|
const [addAddresses, removeAddresses] = diffItems(
|
||||||
newIpAddresses,
|
newIpAddresses,
|
||||||
oldIpAddresses
|
oldIpAddresses
|
||||||
)
|
)
|
||||||
await this.allocIpAddresses(
|
await this.allocIpAddresses(vif.id, addAddresses, removeAddresses)
|
||||||
vif.id,
|
|
||||||
addAddresses,
|
|
||||||
removeAddresses
|
|
||||||
)
|
|
||||||
|
|
||||||
return this.getXapi(vif).editVif(vif._xapiId, {
|
return this.getXapi(vif).editVif(vif._xapiId, {
|
||||||
ipv4Allowed: allowedIpv4Addresses,
|
ipv4Allowed: allowedIpv4Addresses,
|
||||||
|
@ -25,14 +25,13 @@ function checkPermissionOnSrs (vm, permission = 'operate') {
|
|||||||
return permissions.push([this.getObject(vdiId, 'VDI').$SR, permission])
|
return permissions.push([this.getObject(vdiId, 'VDI').$SR, permission])
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.hasPermissions(
|
return this.hasPermissions(this.session.get('user_id'), permissions).then(
|
||||||
this.session.get('user_id'),
|
success => {
|
||||||
permissions
|
if (!success) {
|
||||||
).then(success => {
|
throw unauthorized()
|
||||||
if (!success) {
|
}
|
||||||
throw unauthorized()
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
@ -359,7 +358,7 @@ async function delete_ ({
|
|||||||
// Update resource sets
|
// Update resource sets
|
||||||
const resourceSet = xapi.xo.getData(vm._xapiId, 'resourceSet')
|
const resourceSet = xapi.xo.getData(vm._xapiId, 'resourceSet')
|
||||||
if (resourceSet != null) {
|
if (resourceSet != null) {
|
||||||
this.setVmResourceSet(vm._xapiId, null)::ignoreErrors()
|
;this.setVmResourceSet(vm._xapiId, null)::ignoreErrors()
|
||||||
}
|
}
|
||||||
|
|
||||||
return xapi.deleteVm(vm._xapiId, deleteDisks, force)
|
return xapi.deleteVm(vm._xapiId, deleteDisks, force)
|
||||||
@ -1239,8 +1238,10 @@ export async function createInterface ({
|
|||||||
}) {
|
}) {
|
||||||
const { resourceSet } = vm
|
const { resourceSet } = vm
|
||||||
if (resourceSet != null) {
|
if (resourceSet != null) {
|
||||||
await this.checkResourceSetConstraints(resourceSet, this.user.id, [ network.id ])
|
await this.checkResourceSetConstraints(resourceSet, this.user.id, [
|
||||||
} else if (!(await this.hasPermissions(this.user.id, [ [ network.id, 'view' ] ]))) {
|
network.id,
|
||||||
|
])
|
||||||
|
} else if (!await this.hasPermissions(this.user.id, [[network.id, 'view']])) {
|
||||||
throw unauthorized()
|
throw unauthorized()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,15 +12,17 @@ clean.permission = 'admin'
|
|||||||
|
|
||||||
export async function exportConfig () {
|
export async function exportConfig () {
|
||||||
return {
|
return {
|
||||||
$getFrom: await this.registerHttpRequest((req, res) => {
|
$getFrom: await this.registerHttpRequest(
|
||||||
res.writeHead(200, 'OK', {
|
(req, res) => {
|
||||||
'content-disposition': 'attachment',
|
res.writeHead(200, 'OK', {
|
||||||
})
|
'content-disposition': 'attachment',
|
||||||
|
})
|
||||||
|
|
||||||
return this.exportConfig()
|
return this.exportConfig()
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
{ suffix: '/config.json' }),
|
{ suffix: '/config.json' }
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,7 @@
|
|||||||
import Model from './model'
|
import Model from './model'
|
||||||
import {BaseError} from 'make-error'
|
import { BaseError } from 'make-error'
|
||||||
import {EventEmitter} from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import {
|
import { isArray, isObject, map } from './utils'
|
||||||
isArray,
|
|
||||||
isObject,
|
|
||||||
map,
|
|
||||||
} from './utils'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -39,34 +35,34 @@ export default class Collection extends EventEmitter {
|
|||||||
models = [models]
|
models = [models]
|
||||||
}
|
}
|
||||||
|
|
||||||
const {Model} = this
|
const { Model } = this
|
||||||
map(models, model => {
|
map(
|
||||||
if (!(model instanceof Model)) {
|
models,
|
||||||
model = new Model(model)
|
model => {
|
||||||
}
|
if (!(model instanceof Model)) {
|
||||||
|
model = new Model(model)
|
||||||
|
}
|
||||||
|
|
||||||
const error = model.validate()
|
const error = model.validate()
|
||||||
if (error) {
|
if (error) {
|
||||||
// TODO: Better system inspired by Backbone.js
|
// TODO: Better system inspired by Backbone.js
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return model.properties
|
return model.properties
|
||||||
}, models)
|
},
|
||||||
|
models
|
||||||
|
)
|
||||||
|
|
||||||
models = await this._add(models, opts)
|
models = await this._add(models, opts)
|
||||||
this.emit('add', models)
|
this.emit('add', models)
|
||||||
|
|
||||||
return array
|
return array ? models : new this.Model(models[0])
|
||||||
? models
|
|
||||||
: new this.Model(models[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async first (properties) {
|
async first (properties) {
|
||||||
if (!isObject(properties)) {
|
if (!isObject(properties)) {
|
||||||
properties = (properties !== undefined)
|
properties = properties !== undefined ? { id: properties } : {}
|
||||||
? { id: properties }
|
|
||||||
: {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = await this._first(properties)
|
const model = await this._first(properties)
|
||||||
@ -75,9 +71,7 @@ export default class Collection extends EventEmitter {
|
|||||||
|
|
||||||
async get (properties) {
|
async get (properties) {
|
||||||
if (!isObject(properties)) {
|
if (!isObject(properties)) {
|
||||||
properties = (properties !== undefined)
|
properties = properties !== undefined ? { id: properties } : {}
|
||||||
? { id: properties }
|
|
||||||
: {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return /* await */ this._get(properties)
|
return /* await */ this._get(properties)
|
||||||
@ -100,37 +94,39 @@ export default class Collection extends EventEmitter {
|
|||||||
models = [models]
|
models = [models]
|
||||||
}
|
}
|
||||||
|
|
||||||
const {Model} = this
|
const { Model } = this
|
||||||
map(models, model => {
|
map(
|
||||||
if (!(model instanceof Model)) {
|
models,
|
||||||
// TODO: Problems, we may be mixing in some default
|
model => {
|
||||||
// properties which will overwrite existing ones.
|
if (!(model instanceof Model)) {
|
||||||
model = new Model(model)
|
// TODO: Problems, we may be mixing in some default
|
||||||
}
|
// properties which will overwrite existing ones.
|
||||||
|
model = new Model(model)
|
||||||
|
}
|
||||||
|
|
||||||
const id = model.get('id')
|
const id = model.get('id')
|
||||||
|
|
||||||
// Missing models should be added not updated.
|
// Missing models should be added not updated.
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
// FIXME: should not throw an exception but return a rejected promise.
|
// FIXME: should not throw an exception but return a rejected promise.
|
||||||
throw new Error('a model without an id cannot be updated')
|
throw new Error('a model without an id cannot be updated')
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = model.validate()
|
const error = model.validate()
|
||||||
if (error !== undefined) {
|
if (error !== undefined) {
|
||||||
// TODO: Better system inspired by Backbone.js.
|
// TODO: Better system inspired by Backbone.js.
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return model.properties
|
return model.properties
|
||||||
}, models)
|
},
|
||||||
|
models
|
||||||
|
)
|
||||||
|
|
||||||
models = await this._update(models)
|
models = await this._update(models)
|
||||||
this.emit('update', models)
|
this.emit('update', models)
|
||||||
|
|
||||||
return array
|
return array ? models : new this.Model(models[0])
|
||||||
? models
|
|
||||||
: new this.Model(models[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods to override in implementations.
|
// Methods to override in implementations.
|
||||||
@ -165,8 +161,6 @@ export default class Collection extends EventEmitter {
|
|||||||
async _first (properties) {
|
async _first (properties) {
|
||||||
const models = await this.get(properties)
|
const models = await this.get(properties)
|
||||||
|
|
||||||
return models.length
|
return models.length ? models[0] : null
|
||||||
? models[0]
|
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { createClient as createRedisClient } from 'redis'
|
import { createClient as createRedisClient } from 'redis'
|
||||||
import { difference, filter, forEach, isEmpty, keys as getKeys, map } from 'lodash'
|
import {
|
||||||
|
difference,
|
||||||
|
filter,
|
||||||
|
forEach,
|
||||||
|
isEmpty,
|
||||||
|
keys as getKeys,
|
||||||
|
map,
|
||||||
|
} from 'lodash'
|
||||||
import { ignoreErrors, promisifyAll } from 'promise-toolbox'
|
import { ignoreErrors, promisifyAll } from 'promise-toolbox'
|
||||||
import { v4 as generateUuid } from 'uuid'
|
import { v4 as generateUuid } from 'uuid'
|
||||||
|
|
||||||
@ -28,33 +35,33 @@ import { asyncMap } from '../utils'
|
|||||||
const VERSION = '20170905'
|
const VERSION = '20170905'
|
||||||
|
|
||||||
export default class Redis extends Collection {
|
export default class Redis extends Collection {
|
||||||
constructor ({
|
constructor ({ connection, indexes = [], prefix, uri }) {
|
||||||
connection,
|
|
||||||
indexes = [],
|
|
||||||
prefix,
|
|
||||||
uri,
|
|
||||||
}) {
|
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.indexes = indexes
|
this.indexes = indexes
|
||||||
this.prefix = prefix
|
this.prefix = prefix
|
||||||
const redis = this.redis = promisifyAll(connection || createRedisClient(uri))
|
const redis = (this.redis = promisifyAll(
|
||||||
|
connection || createRedisClient(uri)
|
||||||
|
))
|
||||||
|
|
||||||
const key = `${prefix}:version`
|
const key = `${prefix}:version`
|
||||||
redis.get(key).then(version => {
|
redis
|
||||||
if (version === VERSION) {
|
.get(key)
|
||||||
return
|
.then(version => {
|
||||||
}
|
if (version === VERSION) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let p = redis.set(`${prefix}:version`, VERSION)
|
let p = redis.set(`${prefix}:version`, VERSION)
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case undefined:
|
case undefined:
|
||||||
// - clean indexes
|
// - clean indexes
|
||||||
// - indexes are now case insensitive
|
// - indexes are now case insensitive
|
||||||
p = p.then(() => this.rebuildIndexes())
|
p = p.then(() => this.rebuildIndexes())
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
})::ignoreErrors()
|
})
|
||||||
|
::ignoreErrors()
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuildIndexes () {
|
rebuildIndexes () {
|
||||||
@ -66,113 +73,120 @@ export default class Redis extends Collection {
|
|||||||
|
|
||||||
const idsIndex = `${prefix}_ids`
|
const idsIndex = `${prefix}_ids`
|
||||||
return asyncMap(indexes, index =>
|
return asyncMap(indexes, index =>
|
||||||
redis.keys(`${prefix}_${index}:*`).then(keys =>
|
redis
|
||||||
keys.length !== 0 && redis.del(keys)
|
.keys(`${prefix}_${index}:*`)
|
||||||
|
.then(keys => keys.length !== 0 && redis.del(keys))
|
||||||
|
).then(() =>
|
||||||
|
asyncMap(redis.smembers(idsIndex), id =>
|
||||||
|
redis.hgetall(`${prefix}:${id}`).then(
|
||||||
|
values =>
|
||||||
|
values == null
|
||||||
|
? redis.srem(idsIndex, id) // entry no longer exists
|
||||||
|
: asyncMap(indexes, index => {
|
||||||
|
const value = values[index]
|
||||||
|
if (value !== undefined) {
|
||||||
|
return redis.sadd(
|
||||||
|
`${prefix}_${index}:${String(value).toLowerCase()}`,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
).then(() => asyncMap(redis.smembers(idsIndex), id =>
|
)
|
||||||
redis.hgetall(`${prefix}:${id}`).then(values =>
|
|
||||||
values == null
|
|
||||||
? redis.srem(idsIndex, id) // entry no longer exists
|
|
||||||
: asyncMap(indexes, index => {
|
|
||||||
const value = values[index]
|
|
||||||
if (value !== undefined) {
|
|
||||||
return redis.sadd(
|
|
||||||
`${prefix}_${index}:${String(value).toLowerCase()}`,
|
|
||||||
id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_extract (ids) {
|
_extract (ids) {
|
||||||
const prefix = this.prefix + ':'
|
const prefix = this.prefix + ':'
|
||||||
const {redis} = this
|
const { redis } = this
|
||||||
|
|
||||||
const models = []
|
const models = []
|
||||||
return Promise.all(map(ids, id => {
|
return Promise.all(
|
||||||
return redis.hgetall(prefix + id).then(model => {
|
map(ids, id => {
|
||||||
// If empty, consider it a no match.
|
return redis.hgetall(prefix + id).then(model => {
|
||||||
if (isEmpty(model)) {
|
// If empty, consider it a no match.
|
||||||
return
|
if (isEmpty(model)) {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Mix the identifier in.
|
// Mix the identifier in.
|
||||||
model.id = id
|
model.id = id
|
||||||
|
|
||||||
models.push(model)
|
models.push(model)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})).then(() => models)
|
).then(() => models)
|
||||||
}
|
}
|
||||||
|
|
||||||
_add (models, {replace = false} = {}) {
|
_add (models, { replace = false } = {}) {
|
||||||
// TODO: remove “replace” which is a temporary measure, implement
|
// TODO: remove “replace” which is a temporary measure, implement
|
||||||
// “set()” instead.
|
// “set()” instead.
|
||||||
|
|
||||||
const {indexes, prefix, redis} = this
|
const { indexes, prefix, redis } = this
|
||||||
|
|
||||||
return Promise.all(map(models, async model => {
|
return Promise.all(
|
||||||
// Generate a new identifier if necessary.
|
map(models, async model => {
|
||||||
if (model.id === undefined) {
|
// Generate a new identifier if necessary.
|
||||||
model.id = generateUuid()
|
if (model.id === undefined) {
|
||||||
}
|
model.id = generateUuid()
|
||||||
const { id } = model
|
}
|
||||||
|
const { id } = model
|
||||||
|
|
||||||
const newEntry = await redis.sadd(prefix + '_ids', id)
|
const newEntry = await redis.sadd(prefix + '_ids', id)
|
||||||
|
|
||||||
if (!newEntry) {
|
if (!newEntry) {
|
||||||
if (!replace) {
|
if (!replace) {
|
||||||
throw new ModelAlreadyExists(id)
|
throw new ModelAlreadyExists(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the previous values from indexes
|
||||||
|
if (indexes.length !== 0) {
|
||||||
|
const previous = await redis.hgetall(`${prefix}:${id}`)
|
||||||
|
await asyncMap(indexes, index => {
|
||||||
|
const value = previous[index]
|
||||||
|
if (value !== undefined) {
|
||||||
|
return redis.srem(
|
||||||
|
`${prefix}_${index}:${String(value).toLowerCase()}`,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the previous values from indexes
|
const params = []
|
||||||
if (indexes.length !== 0) {
|
forEach(model, (value, name) => {
|
||||||
const previous = await redis.hgetall(`${prefix}:${id}`)
|
// No need to store the identifier (already in the key).
|
||||||
await asyncMap(indexes, index => {
|
if (name === 'id') {
|
||||||
const value = previous[index]
|
return
|
||||||
if (value !== undefined) {
|
}
|
||||||
return redis.srem(`${prefix}_${index}:${String(value).toLowerCase()}`, id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = []
|
params.push(name, value)
|
||||||
forEach(model, (value, name) => {
|
})
|
||||||
// No need to store the identifier (already in the key).
|
|
||||||
if (name === 'id') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params.push(name, value)
|
const key = `${prefix}:${id}`
|
||||||
|
const promises = [redis.del(key), redis.hmset(key, ...params)]
|
||||||
|
|
||||||
|
// Update indexes.
|
||||||
|
forEach(indexes, index => {
|
||||||
|
const value = model[index]
|
||||||
|
if (value === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = prefix + '_' + index + ':' + String(value).toLowerCase()
|
||||||
|
promises.push(redis.sadd(key, id))
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(promises)
|
||||||
|
|
||||||
|
return model
|
||||||
})
|
})
|
||||||
|
)
|
||||||
const key = `${prefix}:${id}`
|
|
||||||
const promises = [
|
|
||||||
redis.del(key),
|
|
||||||
redis.hmset(key, ...params),
|
|
||||||
]
|
|
||||||
|
|
||||||
// Update indexes.
|
|
||||||
forEach(indexes, (index) => {
|
|
||||||
const value = model[index]
|
|
||||||
if (value === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = prefix + '_' + index + ':' + String(value).toLowerCase()
|
|
||||||
promises.push(redis.sadd(key, id))
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.all(promises)
|
|
||||||
|
|
||||||
return model
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_get (properties) {
|
_get (properties) {
|
||||||
const {prefix, redis} = this
|
const { prefix, redis } = this
|
||||||
|
|
||||||
if (isEmpty(properties)) {
|
if (isEmpty(properties)) {
|
||||||
return redis.smembers(prefix + '_ids').then(ids => this._extract(ids))
|
return redis.smembers(prefix + '_ids').then(ids => this._extract(ids))
|
||||||
@ -183,13 +197,11 @@ export default class Redis extends Collection {
|
|||||||
if (id !== undefined) {
|
if (id !== undefined) {
|
||||||
delete properties.id
|
delete properties.id
|
||||||
return this._extract([id]).then(models => {
|
return this._extract([id]).then(models => {
|
||||||
return (models.length && !isEmpty(properties))
|
return models.length && !isEmpty(properties) ? filter(models) : models
|
||||||
? filter(models)
|
|
||||||
: models
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const {indexes} = this
|
const { indexes } = this
|
||||||
|
|
||||||
// Check for non indexed fields.
|
// Check for non indexed fields.
|
||||||
const unfit = difference(getKeys(properties), indexes)
|
const unfit = difference(getKeys(properties), indexes)
|
||||||
@ -197,7 +209,10 @@ export default class Redis extends Collection {
|
|||||||
throw new Error('fields not indexed: ' + unfit.join())
|
throw new Error('fields not indexed: ' + unfit.join())
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = map(properties, (value, index) => `${prefix}_${index}:${String(value).toLowerCase()}`)
|
const keys = map(
|
||||||
|
properties,
|
||||||
|
(value, index) => `${prefix}_${index}:${String(value).toLowerCase()}`
|
||||||
|
)
|
||||||
return redis.sinter(...keys).then(ids => this._extract(ids))
|
return redis.sinter(...keys).then(ids => this._extract(ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,16 +228,24 @@ export default class Redis extends Collection {
|
|||||||
|
|
||||||
// update other indexes
|
// update other indexes
|
||||||
if (indexes.length !== 0) {
|
if (indexes.length !== 0) {
|
||||||
promise = Promise.all([ promise, asyncMap(ids, id =>
|
promise = Promise.all([
|
||||||
redis.hgetall(`${prefix}:${id}`).then(values =>
|
promise,
|
||||||
values != null && asyncMap(indexes, index => {
|
asyncMap(ids, id =>
|
||||||
const value = values[index]
|
redis.hgetall(`${prefix}:${id}`).then(
|
||||||
if (value !== undefined) {
|
values =>
|
||||||
return redis.srem(`${prefix}_${index}:${String(value).toLowerCase()}`, id)
|
values != null &&
|
||||||
}
|
asyncMap(indexes, index => {
|
||||||
})
|
const value = values[index]
|
||||||
)
|
if (value !== undefined) {
|
||||||
) ])
|
return redis.srem(
|
||||||
|
`${prefix}_${index}:${String(value).toLowerCase()}`,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise.then(() =>
|
return promise.then(() =>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {EventEmitter} from 'events'
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
import {createRawObject, noop} from './utils'
|
import { createRawObject, noop } from './utils'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ export default class Connection extends EventEmitter {
|
|||||||
|
|
||||||
// Gets the value for this key.
|
// Gets the value for this key.
|
||||||
get (key, defaultValue) {
|
get (key, defaultValue) {
|
||||||
const {_data: data} = this
|
const { _data: data } = this
|
||||||
|
|
||||||
if (key in data) {
|
if (key in data) {
|
||||||
return data[key]
|
return data[key]
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
|
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
|
||||||
|
|
||||||
import {
|
import { isArray, isFunction } from './utils'
|
||||||
isArray,
|
|
||||||
isFunction,
|
|
||||||
} from './utils'
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const {
|
const { defineProperties, getOwnPropertyDescriptor } = Object
|
||||||
defineProperties,
|
|
||||||
getOwnPropertyDescriptor,
|
|
||||||
} = Object
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -27,10 +21,12 @@ export const debounce = duration => (target, name, descriptor) => {
|
|||||||
const s = Symbol(`debounced ${name} data`)
|
const s = Symbol(`debounced ${name} data`)
|
||||||
|
|
||||||
function debounced () {
|
function debounced () {
|
||||||
const data = this[s] || (this[s] = {
|
const data =
|
||||||
lastCall: 0,
|
this[s] ||
|
||||||
wrapper: null,
|
(this[s] = {
|
||||||
})
|
lastCall: 0,
|
||||||
|
wrapper: null,
|
||||||
|
})
|
||||||
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (now > data.lastCall + duration) {
|
if (now > data.lastCall + duration) {
|
||||||
@ -39,12 +35,16 @@ export const debounce = duration => (target, name, descriptor) => {
|
|||||||
const result = fn.apply(this, arguments)
|
const result = fn.apply(this, arguments)
|
||||||
data.wrapper = () => result
|
data.wrapper = () => result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
data.wrapper = () => { throw error }
|
data.wrapper = () => {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data.wrapper()
|
return data.wrapper()
|
||||||
}
|
}
|
||||||
debounced.reset = obj => { delete obj[s] }
|
debounced.reset = obj => {
|
||||||
|
delete obj[s]
|
||||||
|
}
|
||||||
|
|
||||||
descriptor.value = debounced
|
descriptor.value = debounced
|
||||||
return descriptor
|
return descriptor
|
||||||
@ -52,21 +52,12 @@ export const debounce = duration => (target, name, descriptor) => {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
const _ownKeys = (
|
const _ownKeys =
|
||||||
(typeof Reflect !== 'undefined' && Reflect.ownKeys) ||
|
(typeof Reflect !== 'undefined' && Reflect.ownKeys) ||
|
||||||
(({
|
(({ getOwnPropertyNames: names, getOwnPropertySymbols: symbols }) =>
|
||||||
getOwnPropertyNames: names,
|
symbols ? obj => names(obj).concat(symbols(obj)) : names)(Object)
|
||||||
getOwnPropertySymbols: symbols,
|
|
||||||
}) => symbols
|
|
||||||
? obj => names(obj).concat(symbols(obj))
|
|
||||||
: names
|
|
||||||
)(Object)
|
|
||||||
)
|
|
||||||
|
|
||||||
const _isIgnoredProperty = name => (
|
const _isIgnoredProperty = name => name[0] === '_' || name === 'constructor'
|
||||||
name[0] === '_' ||
|
|
||||||
name === 'constructor'
|
|
||||||
)
|
|
||||||
|
|
||||||
const _IGNORED_STATIC_PROPERTIES = {
|
const _IGNORED_STATIC_PROPERTIES = {
|
||||||
__proto__: null,
|
__proto__: null,
|
||||||
@ -81,7 +72,7 @@ const _isIgnoredStaticProperty = name => _IGNORED_STATIC_PROPERTIES[name]
|
|||||||
|
|
||||||
export const mixin = MixIns => Class => {
|
export const mixin = MixIns => Class => {
|
||||||
if (!isArray(MixIns)) {
|
if (!isArray(MixIns)) {
|
||||||
MixIns = [ MixIns ]
|
MixIns = [MixIns]
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name } = Class
|
const { name } = Class
|
||||||
@ -103,9 +94,10 @@ export const mixin = MixIns => Class => {
|
|||||||
throw new Error(`${name}#${prop} is already defined`)
|
throw new Error(`${name}#${prop} is already defined`)
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
;(descriptors[prop] = getOwnPropertyDescriptor(
|
||||||
descriptors[prop] = getOwnPropertyDescriptor(MixIn, prop)
|
MixIn,
|
||||||
).enumerable = false // Object methods are enumerable but class methods are not.
|
prop
|
||||||
|
)).enumerable = false // Object methods are enumerable but class methods are not.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defineProperties(prototype, descriptors)
|
defineProperties(prototype, descriptors)
|
||||||
@ -143,16 +135,15 @@ export const mixin = MixIns => Class => {
|
|||||||
const descriptors = { __proto__: null }
|
const descriptors = { __proto__: null }
|
||||||
for (const prop of _ownKeys(Class)) {
|
for (const prop of _ownKeys(Class)) {
|
||||||
let descriptor
|
let descriptor
|
||||||
if (!(
|
if (
|
||||||
// Special properties are not defined...
|
!(
|
||||||
_isIgnoredStaticProperty(prop) &&
|
_isIgnoredStaticProperty(prop) &&
|
||||||
|
// if they already exist...
|
||||||
// if they already exist...
|
(descriptor = getOwnPropertyDescriptor(Decorator, prop)) &&
|
||||||
(descriptor = getOwnPropertyDescriptor(Decorator, prop)) &&
|
// and are not configurable.
|
||||||
|
!descriptor.configurable
|
||||||
// and are not configurable.
|
)
|
||||||
!descriptor.configurable
|
) {
|
||||||
)) {
|
|
||||||
descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
|
descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-env jest */
|
/* eslint-env jest */
|
||||||
|
|
||||||
import {debounce} from './decorators'
|
import { debounce } from './decorators'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
|
@ -27,28 +27,31 @@ export function init () {
|
|||||||
const buf = Buffer.alloc(TEN_MIB)
|
const buf = Buffer.alloc(TEN_MIB)
|
||||||
|
|
||||||
// https://github.com/natevw/fatfs/blob/master/structs.js
|
// https://github.com/natevw/fatfs/blob/master/structs.js
|
||||||
fat16.pack({
|
fat16.pack(
|
||||||
jmpBoot: Buffer.from('eb3c90', 'hex'),
|
{
|
||||||
OEMName: 'mkfs.fat',
|
jmpBoot: Buffer.from('eb3c90', 'hex'),
|
||||||
BytsPerSec: SECTOR_SIZE,
|
OEMName: 'mkfs.fat',
|
||||||
SecPerClus: 4,
|
BytsPerSec: SECTOR_SIZE,
|
||||||
ResvdSecCnt: 1,
|
SecPerClus: 4,
|
||||||
NumFATs: 2,
|
ResvdSecCnt: 1,
|
||||||
RootEntCnt: 512,
|
NumFATs: 2,
|
||||||
TotSec16: 20480,
|
RootEntCnt: 512,
|
||||||
Media: 248,
|
TotSec16: 20480,
|
||||||
FATSz16: 20,
|
Media: 248,
|
||||||
SecPerTrk: 32,
|
FATSz16: 20,
|
||||||
NumHeads: 64,
|
SecPerTrk: 32,
|
||||||
HiddSec: 0,
|
NumHeads: 64,
|
||||||
TotSec32: 0,
|
HiddSec: 0,
|
||||||
DrvNum: 128,
|
TotSec32: 0,
|
||||||
Reserved1: 0,
|
DrvNum: 128,
|
||||||
BootSig: 41,
|
Reserved1: 0,
|
||||||
VolID: 895111106,
|
BootSig: 41,
|
||||||
VolLab: 'NO NAME ',
|
VolID: 895111106,
|
||||||
FilSysType: 'FAT16 ',
|
VolLab: 'NO NAME ',
|
||||||
}, buf)
|
FilSysType: 'FAT16 ',
|
||||||
|
},
|
||||||
|
buf
|
||||||
|
)
|
||||||
|
|
||||||
// End of sector.
|
// End of sector.
|
||||||
buf[0x1fe] = 0x55
|
buf[0x1fe] = 0x55
|
||||||
|
@ -17,11 +17,7 @@ import { join as joinPath } from 'path'
|
|||||||
|
|
||||||
import JsonRpcPeer from 'json-rpc-peer'
|
import JsonRpcPeer from 'json-rpc-peer'
|
||||||
import { invalidCredentials } from 'xo-common/api-errors'
|
import { invalidCredentials } from 'xo-common/api-errors'
|
||||||
import {
|
import { ensureDir, readdir, readFile } from 'fs-extra'
|
||||||
ensureDir,
|
|
||||||
readdir,
|
|
||||||
readFile,
|
|
||||||
} from 'fs-extra'
|
|
||||||
|
|
||||||
import WebServer from 'http-server-plus'
|
import WebServer from 'http-server-plus'
|
||||||
import Xo from './xo'
|
import Xo from './xo'
|
||||||
@ -52,10 +48,7 @@ const warn = (...args) => {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const DEPRECATED_ENTRIES = [
|
const DEPRECATED_ENTRIES = ['users', 'servers']
|
||||||
'users',
|
|
||||||
'servers',
|
|
||||||
]
|
|
||||||
|
|
||||||
async function loadConfiguration () {
|
async function loadConfiguration () {
|
||||||
const config = await appConf.load('xo-server', {
|
const config = await appConf.load('xo-server', {
|
||||||
@ -85,13 +78,15 @@ function createExpressApp () {
|
|||||||
// Registers the cookie-parser and express-session middlewares,
|
// Registers the cookie-parser and express-session middlewares,
|
||||||
// necessary for connect-flash.
|
// necessary for connect-flash.
|
||||||
app.use(cookieParser())
|
app.use(cookieParser())
|
||||||
app.use(expressSession({
|
app.use(
|
||||||
resave: false,
|
expressSession({
|
||||||
saveUninitialized: false,
|
resave: false,
|
||||||
|
saveUninitialized: false,
|
||||||
|
|
||||||
// TODO: should be in the config file.
|
// TODO: should be in the config file.
|
||||||
secret: 'CLWguhRZAZIXZcbrMzHCYmefxgweItKnS',
|
secret: 'CLWguhRZAZIXZcbrMzHCYmefxgweItKnS',
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// Registers the connect-flash middleware, necessary for Passport to
|
// Registers the connect-flash middleware, necessary for Passport to
|
||||||
// display error messages.
|
// display error messages.
|
||||||
@ -112,7 +107,7 @@ async function setUpPassport (express, xo) {
|
|||||||
xo.registerPassportStrategy = strategy => {
|
xo.registerPassportStrategy = strategy => {
|
||||||
passport.use(strategy)
|
passport.use(strategy)
|
||||||
|
|
||||||
const {name} = strategy
|
const { name } = strategy
|
||||||
if (name !== 'local') {
|
if (name !== 'local') {
|
||||||
strategies[name] = strategy.label || name
|
strategies[name] = strategy.label || name
|
||||||
}
|
}
|
||||||
@ -123,10 +118,12 @@ async function setUpPassport (express, xo) {
|
|||||||
await readFile(joinPath(__dirname, '..', 'signin.pug'))
|
await readFile(joinPath(__dirname, '..', 'signin.pug'))
|
||||||
)
|
)
|
||||||
express.get('/signin', (req, res, next) => {
|
express.get('/signin', (req, res, next) => {
|
||||||
res.send(signInPage({
|
res.send(
|
||||||
error: req.flash('error')[0],
|
signInPage({
|
||||||
strategies,
|
error: req.flash('error')[0],
|
||||||
}))
|
strategies,
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
express.get('/signout', (req, res) => {
|
express.get('/signout', (req, res) => {
|
||||||
@ -154,7 +151,7 @@ async function setUpPassport (express, xo) {
|
|||||||
// browsers do not save cookies on redirect.
|
// browsers do not save cookies on redirect.
|
||||||
req.flash(
|
req.flash(
|
||||||
'token',
|
'token',
|
||||||
(await xo.createAuthenticationToken({userId: user.id})).id
|
(await xo.createAuthenticationToken({ userId: user.id })).id
|
||||||
)
|
)
|
||||||
|
|
||||||
// The session is only persistent for internal provider and if 'Remember me' box is checked
|
// The session is only persistent for internal provider and if 'Remember me' box is checked
|
||||||
@ -183,7 +180,9 @@ async function setUpPassport (express, xo) {
|
|||||||
next()
|
next()
|
||||||
} else if (req.cookies.token) {
|
} else if (req.cookies.token) {
|
||||||
next()
|
next()
|
||||||
} else if (/favicon|fontawesome|images|styles|\.(?:css|jpg|png)$/.test(url)) {
|
} else if (
|
||||||
|
/favicon|fontawesome|images|styles|\.(?:css|jpg|png)$/.test(url)
|
||||||
|
) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
req.flash('return-url', url)
|
req.flash('return-url', url)
|
||||||
@ -192,16 +191,16 @@ async function setUpPassport (express, xo) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Install the local strategy.
|
// Install the local strategy.
|
||||||
xo.registerPassportStrategy(new LocalStrategy(
|
xo.registerPassportStrategy(
|
||||||
async (username, password, done) => {
|
new LocalStrategy(async (username, password, done) => {
|
||||||
try {
|
try {
|
||||||
const user = await xo.authenticateUser({username, password})
|
const user = await xo.authenticateUser({ username, password })
|
||||||
done(null, user)
|
done(null, user)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
done(null, false, { message: error.message })
|
done(null, false, { message: error.message })
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
@ -274,40 +273,44 @@ async function registerPluginsInPath (path) {
|
|||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
await Promise.all(mapToArray(files, name => {
|
await Promise.all(
|
||||||
if (startsWith(name, PLUGIN_PREFIX)) {
|
mapToArray(files, name => {
|
||||||
return registerPluginWrapper.call(
|
if (startsWith(name, PLUGIN_PREFIX)) {
|
||||||
this,
|
return registerPluginWrapper.call(
|
||||||
`${path}/${name}`,
|
this,
|
||||||
name.slice(PLUGIN_PREFIX_LENGTH)
|
`${path}/${name}`,
|
||||||
)
|
name.slice(PLUGIN_PREFIX_LENGTH)
|
||||||
}
|
)
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function registerPlugins (xo) {
|
async function registerPlugins (xo) {
|
||||||
await Promise.all(mapToArray([
|
await Promise.all(
|
||||||
`${__dirname}/../node_modules/`,
|
mapToArray(
|
||||||
'/usr/local/lib/node_modules/',
|
[`${__dirname}/../node_modules/`, '/usr/local/lib/node_modules/'],
|
||||||
], xo::registerPluginsInPath))
|
xo::registerPluginsInPath
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
async function makeWebServerListen (webServer, {
|
async function makeWebServerListen (
|
||||||
certificate,
|
webServer,
|
||||||
|
{
|
||||||
|
certificate,
|
||||||
|
|
||||||
// The properties was called `certificate` before.
|
// The properties was called `certificate` before.
|
||||||
cert = certificate,
|
cert = certificate,
|
||||||
|
|
||||||
key,
|
key,
|
||||||
...opts
|
...opts
|
||||||
}) {
|
}
|
||||||
|
) {
|
||||||
if (cert && key) {
|
if (cert && key) {
|
||||||
[opts.cert, opts.key] = await Promise.all([
|
;[opts.cert, opts.key] = await Promise.all([readFile(cert), readFile(key)])
|
||||||
readFile(cert),
|
|
||||||
readFile(key),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const niceAddress = await webServer.listen(opts)
|
const niceAddress = await webServer.listen(opts)
|
||||||
@ -316,7 +319,7 @@ async function makeWebServerListen (webServer, {
|
|||||||
if (error.niceAddress) {
|
if (error.niceAddress) {
|
||||||
warn(`Web server could not listen on ${error.niceAddress}`)
|
warn(`Web server could not listen on ${error.niceAddress}`)
|
||||||
|
|
||||||
const {code} = error
|
const { code } = error
|
||||||
if (code === 'EACCES') {
|
if (code === 'EACCES') {
|
||||||
warn(' Access denied.')
|
warn(' Access denied.')
|
||||||
warn(' Ports < 1024 are often reserved to privileges users.')
|
warn(' Ports < 1024 are often reserved to privileges users.')
|
||||||
@ -332,9 +335,11 @@ async function makeWebServerListen (webServer, {
|
|||||||
async function createWebServer ({ listen, listenOptions }) {
|
async function createWebServer ({ listen, listenOptions }) {
|
||||||
const webServer = new WebServer()
|
const webServer = new WebServer()
|
||||||
|
|
||||||
await Promise.all(mapToArray(listen,
|
await Promise.all(
|
||||||
opts => makeWebServerListen(webServer, { ...listenOptions, ...opts })
|
mapToArray(listen, opts =>
|
||||||
))
|
makeWebServerListen(webServer, { ...listenOptions, ...opts })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return webServer
|
return webServer
|
||||||
}
|
}
|
||||||
@ -348,7 +353,7 @@ const setUpProxies = (express, opts, xo) => {
|
|||||||
|
|
||||||
const proxy = createProxyServer({
|
const proxy = createProxyServer({
|
||||||
ignorePath: true,
|
ignorePath: true,
|
||||||
}).on('error', (error) => console.error(error))
|
}).on('error', error => console.error(error))
|
||||||
|
|
||||||
// TODO: sort proxies by descending prefix length.
|
// TODO: sort proxies by descending prefix length.
|
||||||
|
|
||||||
@ -464,7 +469,9 @@ const setUpApi = (webServer, xo, verboseLogsOnErrors) => {
|
|||||||
}
|
}
|
||||||
webServer.on('upgrade', (req, socket, head) => {
|
webServer.on('upgrade', (req, socket, head) => {
|
||||||
if (req.url === '/api/') {
|
if (req.url === '/api/') {
|
||||||
webSocketServer.handleUpgrade(req, socket, head, ws => onConnection(ws, req))
|
webSocketServer.handleUpgrade(req, socket, head, ws =>
|
||||||
|
onConnection(ws, req)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -492,7 +499,7 @@ const setUpConsoleProxy = (webServer, xo) => {
|
|||||||
const { token } = parseCookies(req.headers.cookie)
|
const { token } = parseCookies(req.headers.cookie)
|
||||||
|
|
||||||
const user = await xo.authenticateUser({ token })
|
const user = await xo.authenticateUser({ token })
|
||||||
if (!await xo.hasPermissions(user.id, [ [ id, 'operate' ] ])) {
|
if (!await xo.hasPermissions(user.id, [[id, 'operate']])) {
|
||||||
throw invalidCredentials()
|
throw invalidCredentials()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,10 +525,7 @@ const setUpConsoleProxy = (webServer, xo) => {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const USAGE = (({
|
const USAGE = (({ name, version }) => `Usage: ${name} [--safe-mode]
|
||||||
name,
|
|
||||||
version,
|
|
||||||
}) => `Usage: ${name} [--safe-mode]
|
|
||||||
|
|
||||||
${name} v${version}`)(require('../package.json'))
|
${name} v${version}`)(require('../package.json'))
|
||||||
|
|
||||||
@ -545,7 +549,7 @@ export default async function main (args) {
|
|||||||
|
|
||||||
// Now the web server is listening, drop privileges.
|
// Now the web server is listening, drop privileges.
|
||||||
try {
|
try {
|
||||||
const {user, group} = config
|
const { user, group } = config
|
||||||
if (group) {
|
if (group) {
|
||||||
process.setgid(group)
|
process.setgid(group)
|
||||||
debug('Group changed to', group)
|
debug('Group changed to', group)
|
||||||
@ -576,10 +580,7 @@ export default async function main (args) {
|
|||||||
if (config.http.redirectToHttps) {
|
if (config.http.redirectToHttps) {
|
||||||
let port
|
let port
|
||||||
forEach(config.http.listen, listen => {
|
forEach(config.http.listen, listen => {
|
||||||
if (
|
if (listen.port && (listen.cert || listen.certificate)) {
|
||||||
listen.port &&
|
|
||||||
(listen.cert || listen.certificate)
|
|
||||||
) {
|
|
||||||
port = listen.port
|
port = listen.port
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -629,7 +630,7 @@ export default async function main (args) {
|
|||||||
//
|
//
|
||||||
// TODO: implements a timeout? (or maybe it is the services launcher
|
// TODO: implements a timeout? (or maybe it is the services launcher
|
||||||
// responsibility?)
|
// responsibility?)
|
||||||
forEach([ 'SIGINT', 'SIGTERM' ], signal => {
|
forEach(['SIGINT', 'SIGTERM'], signal => {
|
||||||
let alreadyCalled = false
|
let alreadyCalled = false
|
||||||
|
|
||||||
process.on(signal, () => {
|
process.on(signal, () => {
|
||||||
|
@ -2,20 +2,10 @@ import Bluebird from 'bluebird'
|
|||||||
import { BaseError } from 'make-error'
|
import { BaseError } from 'make-error'
|
||||||
import { createPredicate } from 'value-matcher'
|
import { createPredicate } from 'value-matcher'
|
||||||
import { timeout } from 'promise-toolbox'
|
import { timeout } from 'promise-toolbox'
|
||||||
import {
|
import { assign, filter, find, isEmpty, map, mapValues } from 'lodash'
|
||||||
assign,
|
|
||||||
filter,
|
|
||||||
find,
|
|
||||||
isEmpty,
|
|
||||||
map,
|
|
||||||
mapValues,
|
|
||||||
} from 'lodash'
|
|
||||||
|
|
||||||
import { crossProduct } from './math'
|
import { crossProduct } from './math'
|
||||||
import {
|
import { serializeError, thunkToArray } from './utils'
|
||||||
serializeError,
|
|
||||||
thunkToArray,
|
|
||||||
} from './utils'
|
|
||||||
|
|
||||||
export class JobExecutorError extends BaseError {}
|
export class JobExecutorError extends BaseError {}
|
||||||
export class UnsupportedJobType extends JobExecutorError {
|
export class UnsupportedJobType extends JobExecutorError {
|
||||||
@ -36,9 +26,9 @@ const paramsVectorActionsMap = {
|
|||||||
return mapValues(mapping, key => value[key])
|
return mapValues(mapping, key => value[key])
|
||||||
},
|
},
|
||||||
crossProduct ({ items }) {
|
crossProduct ({ items }) {
|
||||||
return thunkToArray(crossProduct(
|
return thunkToArray(
|
||||||
map(items, value => resolveParamsVector.call(this, value))
|
crossProduct(map(items, value => resolveParamsVector.call(this, value)))
|
||||||
))
|
)
|
||||||
},
|
},
|
||||||
fetchObjects ({ pattern }) {
|
fetchObjects ({ pattern }) {
|
||||||
const objects = filter(this.xo.getObjects(), createPredicate(pattern))
|
const objects = filter(this.xo.getObjects(), createPredicate(pattern))
|
||||||
@ -74,9 +64,11 @@ export default class JobExecutor {
|
|||||||
this.xo = xo
|
this.xo = xo
|
||||||
|
|
||||||
// The logger is not available until Xo has started.
|
// The logger is not available until Xo has started.
|
||||||
xo.on('start', () => xo.getLogger('jobs').then(logger => {
|
xo.on('start', () =>
|
||||||
this._logger = logger
|
xo.getLogger('jobs').then(logger => {
|
||||||
}))
|
this._logger = logger
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async exec (job) {
|
async exec (job) {
|
||||||
@ -130,51 +122,68 @@ export default class JobExecutor {
|
|||||||
timezone: schedule !== undefined ? schedule.timezone : undefined,
|
timezone: schedule !== undefined ? schedule.timezone : undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
await Bluebird.map(paramsFlatVector, params => {
|
await Bluebird.map(
|
||||||
const runCallId = this._logger.notice(`Starting ${job.method} call. (${job.id})`, {
|
paramsFlatVector,
|
||||||
event: 'jobCall.start',
|
params => {
|
||||||
runJobId,
|
const runCallId = this._logger.notice(
|
||||||
method: job.method,
|
`Starting ${job.method} call. (${job.id})`,
|
||||||
params,
|
{
|
||||||
})
|
event: 'jobCall.start',
|
||||||
|
|
||||||
const call = execStatus.calls[runCallId] = {
|
|
||||||
method: job.method,
|
|
||||||
params,
|
|
||||||
start: Date.now(),
|
|
||||||
}
|
|
||||||
let promise = this.xo.callApiMethod(connection, job.method, assign({}, params))
|
|
||||||
if (job.timeout) {
|
|
||||||
promise = promise::timeout(job.timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise.then(
|
|
||||||
value => {
|
|
||||||
this._logger.notice(`Call ${job.method} (${runCallId}) is a success. (${job.id})`, {
|
|
||||||
event: 'jobCall.end',
|
|
||||||
runJobId,
|
runJobId,
|
||||||
runCallId,
|
method: job.method,
|
||||||
returnedValue: value,
|
params,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
call.returnedValue = value
|
const call = (execStatus.calls[runCallId] = {
|
||||||
call.end = Date.now()
|
method: job.method,
|
||||||
},
|
params,
|
||||||
reason => {
|
start: Date.now(),
|
||||||
this._logger.notice(`Call ${job.method} (${runCallId}) has failed. (${job.id})`, {
|
})
|
||||||
event: 'jobCall.end',
|
let promise = this.xo.callApiMethod(
|
||||||
runJobId,
|
connection,
|
||||||
runCallId,
|
job.method,
|
||||||
error: serializeError(reason),
|
assign({}, params)
|
||||||
})
|
)
|
||||||
|
if (job.timeout) {
|
||||||
call.error = reason
|
promise = promise::timeout(job.timeout)
|
||||||
call.end = Date.now()
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}, {
|
return promise.then(
|
||||||
concurrency: 2,
|
value => {
|
||||||
})
|
this._logger.notice(
|
||||||
|
`Call ${job.method} (${runCallId}) is a success. (${job.id})`,
|
||||||
|
{
|
||||||
|
event: 'jobCall.end',
|
||||||
|
runJobId,
|
||||||
|
runCallId,
|
||||||
|
returnedValue: value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
call.returnedValue = value
|
||||||
|
call.end = Date.now()
|
||||||
|
},
|
||||||
|
reason => {
|
||||||
|
this._logger.notice(
|
||||||
|
`Call ${job.method} (${runCallId}) has failed. (${job.id})`,
|
||||||
|
{
|
||||||
|
event: 'jobCall.end',
|
||||||
|
runJobId,
|
||||||
|
runCallId,
|
||||||
|
error: serializeError(reason),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
call.error = reason
|
||||||
|
call.end = Date.now()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
concurrency: 2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
connection.close()
|
connection.close()
|
||||||
execStatus.end = Date.now()
|
execStatus.end = Date.now()
|
||||||
|
@ -4,97 +4,113 @@ import { forEach } from 'lodash'
|
|||||||
import { resolveParamsVector } from './job-executor'
|
import { resolveParamsVector } from './job-executor'
|
||||||
|
|
||||||
describe('resolveParamsVector', function () {
|
describe('resolveParamsVector', function () {
|
||||||
forEach({
|
forEach(
|
||||||
'cross product with three sets': [
|
{
|
||||||
// Expected result.
|
'cross product with three sets': [
|
||||||
[ { id: 3, value: 'foo', remote: 'local' },
|
// Expected result.
|
||||||
{ id: 7, value: 'foo', remote: 'local' },
|
[
|
||||||
{ id: 10, value: 'foo', remote: 'local' },
|
{ id: 3, value: 'foo', remote: 'local' },
|
||||||
{ id: 3, value: 'bar', remote: 'local' },
|
{ id: 7, value: 'foo', remote: 'local' },
|
||||||
{ id: 7, value: 'bar', remote: 'local' },
|
{ id: 10, value: 'foo', remote: 'local' },
|
||||||
{ id: 10, value: 'bar', remote: 'local' } ],
|
{ id: 3, value: 'bar', remote: 'local' },
|
||||||
// Entry.
|
{ id: 7, value: 'bar', remote: 'local' },
|
||||||
{
|
{ id: 10, value: 'bar', remote: 'local' },
|
||||||
type: 'crossProduct',
|
],
|
||||||
items: [{
|
// Entry.
|
||||||
type: 'set',
|
{
|
||||||
values: [ { id: 3 }, { id: 7 }, { id: 10 } ],
|
type: 'crossProduct',
|
||||||
}, {
|
items: [
|
||||||
type: 'set',
|
{
|
||||||
values: [ { value: 'foo' }, { value: 'bar' } ],
|
type: 'set',
|
||||||
}, {
|
values: [{ id: 3 }, { id: 7 }, { id: 10 }],
|
||||||
type: 'set',
|
},
|
||||||
values: [ { remote: 'local' } ],
|
{
|
||||||
}],
|
type: 'set',
|
||||||
},
|
values: [{ value: 'foo' }, { value: 'bar' }],
|
||||||
],
|
},
|
||||||
'cross product with `set` and `map`': [
|
{
|
||||||
// Expected result.
|
type: 'set',
|
||||||
[
|
values: [{ remote: 'local' }],
|
||||||
{ remote: 'local', id: 'vm:2' },
|
},
|
||||||
{ remote: 'smb', id: 'vm:2' },
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
'cross product with `set` and `map`': [
|
||||||
|
// Expected result.
|
||||||
|
[{ remote: 'local', id: 'vm:2' }, { remote: 'smb', id: 'vm:2' }],
|
||||||
|
|
||||||
// Entry.
|
// Entry.
|
||||||
{
|
{
|
||||||
type: 'crossProduct',
|
type: 'crossProduct',
|
||||||
items: [{
|
items: [
|
||||||
type: 'set',
|
{
|
||||||
values: [ { remote: 'local' }, { remote: 'smb' } ],
|
type: 'set',
|
||||||
}, {
|
values: [{ remote: 'local' }, { remote: 'smb' }],
|
||||||
type: 'map',
|
},
|
||||||
collection: {
|
{
|
||||||
type: 'fetchObjects',
|
type: 'map',
|
||||||
pattern: {
|
collection: {
|
||||||
$pool: { __or: [ 'pool:1', 'pool:8', 'pool:12' ] },
|
type: 'fetchObjects',
|
||||||
power_state: 'Running',
|
pattern: {
|
||||||
tags: [ 'foo' ],
|
$pool: { __or: ['pool:1', 'pool:8', 'pool:12'] },
|
||||||
type: 'VM',
|
power_state: 'Running',
|
||||||
|
tags: ['foo'],
|
||||||
|
type: 'VM',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
iteratee: {
|
||||||
|
type: 'extractProperties',
|
||||||
|
mapping: { id: 'id' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Context.
|
||||||
|
{
|
||||||
|
xo: {
|
||||||
|
getObjects: function () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'vm:1',
|
||||||
|
$pool: 'pool:1',
|
||||||
|
tags: [],
|
||||||
|
type: 'VM',
|
||||||
|
power_state: 'Halted',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'vm:2',
|
||||||
|
$pool: 'pool:1',
|
||||||
|
tags: ['foo'],
|
||||||
|
type: 'VM',
|
||||||
|
power_state: 'Running',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'host:1',
|
||||||
|
type: 'host',
|
||||||
|
power_state: 'Running',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'vm:3',
|
||||||
|
$pool: 'pool:8',
|
||||||
|
tags: ['foo'],
|
||||||
|
type: 'VM',
|
||||||
|
power_state: 'Halted',
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
iteratee: {
|
|
||||||
type: 'extractProperties',
|
|
||||||
mapping: { id: 'id' },
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
|
|
||||||
// Context.
|
|
||||||
{
|
|
||||||
xo: {
|
|
||||||
getObjects: function () {
|
|
||||||
return [{
|
|
||||||
id: 'vm:1',
|
|
||||||
$pool: 'pool:1',
|
|
||||||
tags: [],
|
|
||||||
type: 'VM',
|
|
||||||
power_state: 'Halted',
|
|
||||||
}, {
|
|
||||||
id: 'vm:2',
|
|
||||||
$pool: 'pool:1',
|
|
||||||
tags: [ 'foo' ],
|
|
||||||
type: 'VM',
|
|
||||||
power_state: 'Running',
|
|
||||||
}, {
|
|
||||||
id: 'host:1',
|
|
||||||
type: 'host',
|
|
||||||
power_state: 'Running',
|
|
||||||
}, {
|
|
||||||
id: 'vm:3',
|
|
||||||
$pool: 'pool:8',
|
|
||||||
tags: [ 'foo' ],
|
|
||||||
type: 'VM',
|
|
||||||
power_state: 'Halted',
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
}, ([ expectedResult, entry, context ], name) => {
|
([expectedResult, entry, context], name) => {
|
||||||
describe(`with ${name}`, () => {
|
describe(`with ${name}`, () => {
|
||||||
it('Resolves params vector', () => {
|
it('Resolves params vector', () => {
|
||||||
expect(resolveParamsVector.call(context, entry)).toEqual(expectedResult)
|
expect(resolveParamsVector.call(context, entry)).toEqual(
|
||||||
|
expectedResult
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
|
@ -8,26 +8,26 @@ import sublevel from 'level-sublevel'
|
|||||||
import util from 'util'
|
import util from 'util'
|
||||||
import { repair as repairDb } from 'level'
|
import { repair as repairDb } from 'level'
|
||||||
|
|
||||||
import {forEach} from './utils'
|
import { forEach } from './utils'
|
||||||
import globMatcher from './glob-matcher'
|
import globMatcher from './glob-matcher'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
async function printLogs (db, args) {
|
async function printLogs (db, args) {
|
||||||
let stream = highland(db.createReadStream({reverse: true}))
|
let stream = highland(db.createReadStream({ reverse: true }))
|
||||||
|
|
||||||
if (args.since) {
|
if (args.since) {
|
||||||
stream = stream.filter(({value}) => (value.time >= args.since))
|
stream = stream.filter(({ value }) => value.time >= args.since)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.until) {
|
if (args.until) {
|
||||||
stream = stream.filter(({value}) => (value.time <= args.until))
|
stream = stream.filter(({ value }) => value.time <= args.until)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = Object.keys(args.matchers)
|
const fields = Object.keys(args.matchers)
|
||||||
|
|
||||||
if (fields.length > 0) {
|
if (fields.length > 0) {
|
||||||
stream = stream.filter(({value}) => {
|
stream = stream.filter(({ value }) => {
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
const fieldValue = get(value, field)
|
const fieldValue = get(value, field)
|
||||||
if (fieldValue === undefined || !args.matchers[field](fieldValue)) {
|
if (fieldValue === undefined || !args.matchers[field](fieldValue)) {
|
||||||
@ -42,10 +42,9 @@ async function printLogs (db, args) {
|
|||||||
stream = stream.take(args.limit)
|
stream = stream.take(args.limit)
|
||||||
|
|
||||||
if (args.json) {
|
if (args.json) {
|
||||||
stream = highland(stream.pipe(ndjson.serialize()))
|
stream = highland(stream.pipe(ndjson.serialize())).each(value => {
|
||||||
.each(value => {
|
process.stdout.write(value)
|
||||||
process.stdout.write(value)
|
})
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
stream = stream.each(value => {
|
stream = stream.each(value => {
|
||||||
console.log(util.inspect(value, { depth: null }))
|
console.log(util.inspect(value, { depth: null }))
|
||||||
@ -126,7 +125,7 @@ function getArgs () {
|
|||||||
|
|
||||||
patterns[pattern]
|
patterns[pattern]
|
||||||
? patterns[field].push(pattern)
|
? patterns[field].push(pattern)
|
||||||
: patterns[field] = [ pattern ]
|
: (patterns[field] = [pattern])
|
||||||
} else if (!patterns[value]) {
|
} else if (!patterns[value]) {
|
||||||
patterns[value] = null
|
patterns[value] = null
|
||||||
}
|
}
|
||||||
@ -137,7 +136,7 @@ function getArgs () {
|
|||||||
|
|
||||||
for (const field in patterns) {
|
for (const field in patterns) {
|
||||||
const values = patterns[field]
|
const values = patterns[field]
|
||||||
args.matchers[field] = (values === null) ? trueFunction : globMatcher(values)
|
args.matchers[field] = values === null ? trueFunction : globMatcher(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning: minimist makes one array of values if the same option is used many times.
|
// Warning: minimist makes one array of values if the same option is used many times.
|
||||||
@ -147,7 +146,6 @@ function getArgs () {
|
|||||||
throw new Error(`error: too many values for ${arg} argument`)
|
throw new Error(`error: too many values for ${arg} argument`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
;['since', 'until'].forEach(arg => {
|
;['since', 'until'].forEach(arg => {
|
||||||
if (args[arg] !== undefined) {
|
if (args[arg] !== undefined) {
|
||||||
args[arg] = Date.parse(args[arg])
|
args[arg] = Date.parse(args[arg])
|
||||||
@ -158,7 +156,7 @@ function getArgs () {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isNaN(args.limit = +args.limit)) {
|
if (isNaN((args.limit = +args.limit))) {
|
||||||
throw new Error('error: limit is not a valid number')
|
throw new Error('error: limit is not a valid number')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,10 +191,9 @@ export default async function main () {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = sublevel(levelup(
|
const db = sublevel(
|
||||||
`${config.datadir}/leveldb`,
|
levelup(`${config.datadir}/leveldb`, { valueEncoding: 'json' })
|
||||||
{ valueEncoding: 'json' }
|
).sublevel('logs')
|
||||||
)).sublevel('logs')
|
|
||||||
|
|
||||||
return printLogs(db, args)
|
return printLogs(db, args)
|
||||||
}
|
}
|
||||||
|
@ -9,25 +9,29 @@ const parse = createParser({
|
|||||||
keyTransform: key => key.slice(5).toLowerCase(),
|
keyTransform: key => key.slice(5).toLowerCase(),
|
||||||
})
|
})
|
||||||
const makeFunction = command => (fields, ...args) =>
|
const makeFunction = command => (fields, ...args) =>
|
||||||
execa.stdout(command, [
|
execa
|
||||||
'--noheading',
|
.stdout(command, [
|
||||||
'--nosuffix',
|
'--noheading',
|
||||||
'--nameprefixes',
|
'--nosuffix',
|
||||||
'--unbuffered',
|
'--nameprefixes',
|
||||||
'--units',
|
'--unbuffered',
|
||||||
'b',
|
'--units',
|
||||||
'-o',
|
'b',
|
||||||
String(fields),
|
'-o',
|
||||||
...args,
|
String(fields),
|
||||||
]).then(stdout => map(
|
...args,
|
||||||
splitLines(stdout),
|
])
|
||||||
isArray(fields)
|
.then(stdout =>
|
||||||
? parse
|
map(
|
||||||
: line => {
|
splitLines(stdout),
|
||||||
const data = parse(line)
|
isArray(fields)
|
||||||
return data[fields]
|
? parse
|
||||||
}
|
: line => {
|
||||||
))
|
const data = parse(line)
|
||||||
|
return data[fields]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
export const lvs = makeFunction('lvs')
|
export const lvs = makeFunction('lvs')
|
||||||
export const pvs = makeFunction('pvs')
|
export const pvs = makeFunction('pvs')
|
||||||
|
@ -11,7 +11,7 @@ const _combine = (vectors, n, cb) => {
|
|||||||
const m = vector.length
|
const m = vector.length
|
||||||
if (n === 1) {
|
if (n === 1) {
|
||||||
for (let i = 0; i < m; ++i) {
|
for (let i = 0; i < m; ++i) {
|
||||||
cb([ vector[i] ]) // eslint-disable-line standard/no-callback-literal
|
cb([vector[i]]) // eslint-disable-line standard/no-callback-literal
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ const _combine = (vectors, n, cb) => {
|
|||||||
for (let i = 0; i < m; ++i) {
|
for (let i = 0; i < m; ++i) {
|
||||||
const value = vector[i]
|
const value = vector[i]
|
||||||
|
|
||||||
_combine(vectors, nLast, (vector) => {
|
_combine(vectors, nLast, vector => {
|
||||||
vector.push(value)
|
vector.push(value)
|
||||||
cb(vector)
|
cb(vector)
|
||||||
})
|
})
|
||||||
@ -41,8 +41,7 @@ export const mergeObjects = objects => assign({}, ...objects)
|
|||||||
//
|
//
|
||||||
// Ex: crossProduct([ [ { a: 2 }, { b: 3 } ], [ { c: 5 }, { d: 7 } ] ] )
|
// Ex: crossProduct([ [ { a: 2 }, { b: 3 } ], [ { c: 5 }, { d: 7 } ] ] )
|
||||||
// => [ { a: 2, c: 5 }, { b: 3, c: 5 }, { a: 2, d: 7 }, { b: 3, d: 7 } ]
|
// => [ { a: 2, c: 5 }, { b: 3, c: 5 }, { a: 2, d: 7 }, { b: 3, d: 7 } ]
|
||||||
export const crossProduct = (vectors, mergeFn = mergeObjects) => cb => (
|
export const crossProduct = (vectors, mergeFn = mergeObjects) => cb =>
|
||||||
combine(vectors)(vector => {
|
combine(vectors)(vector => {
|
||||||
cb(mergeFn(vector))
|
cb(mergeFn(vector))
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user