Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c7c9f9156 |
38
@xen-orchestra/fs/src/_parseGlob.js
Normal file
38
@xen-orchestra/fs/src/_parseGlob.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import escapeRegExp from 'lodash/escapeRegExp'
|
||||
|
||||
const compileFragment = pattern =>
|
||||
new RegExp(
|
||||
`^${pattern
|
||||
.split('*')
|
||||
.map(escapeRegExp)
|
||||
.join('[^]*')}$`
|
||||
)
|
||||
|
||||
export function parseGlob(pattern) {
|
||||
const parts = []
|
||||
while (pattern.length !== 0) {
|
||||
const i = pattern.indexOf('*')
|
||||
if (i === -1) {
|
||||
parts.push(pattern)
|
||||
break
|
||||
}
|
||||
|
||||
let fragmentStart = pattern.lastIndexOf('/', i)
|
||||
if (fragmentStart === -1) {
|
||||
fragmentStart = 0
|
||||
} else {
|
||||
parts.push(pattern.slice(0, fragmentStart))
|
||||
++fragmentStart
|
||||
}
|
||||
|
||||
let fragmentEnd = pattern.indexOf('/', i)
|
||||
if (fragmentEnd === -1) {
|
||||
fragmentEnd = pattern.length
|
||||
}
|
||||
|
||||
parts.push(compileFragment(pattern.slice(fragmentStart, fragmentEnd)))
|
||||
|
||||
pattern = pattern.slice(fragmentEnd + 1)
|
||||
}
|
||||
return parts
|
||||
}
|
||||
12
@xen-orchestra/fs/src/_parseGlob.spec.js
Normal file
12
@xen-orchestra/fs/src/_parseGlob.spec.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import { parseGlob } from './_parseGlob'
|
||||
|
||||
describe('parseGlob', () => {
|
||||
it.each([['foo/*/bar*baz/qux', ['foo', /^[^]*$/, /^bar[^]*baz$/, 'qux']]])(
|
||||
'parse %j correctly',
|
||||
(pattern, result) => {
|
||||
expect(parseGlob(pattern)).toEqual(result)
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -14,6 +14,7 @@ import { type Readable, type Writable } from 'stream'
|
||||
|
||||
import normalizePath from './_normalizePath'
|
||||
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
|
||||
import { parseGlob } from './_parseGlob'
|
||||
|
||||
const { dirname } = path.posix
|
||||
|
||||
@@ -258,6 +259,12 @@ export default class RemoteHandlerAbstract {
|
||||
)
|
||||
}
|
||||
|
||||
// basic glob support, only `*` is supported
|
||||
async glob(pattern) {
|
||||
const parts = parseGlob(pattern)
|
||||
// TODO
|
||||
}
|
||||
|
||||
async list(
|
||||
dir: string,
|
||||
{
|
||||
|
||||
@@ -4,16 +4,12 @@
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [Support] Ability to check the XOA on the user interface [#4513](https://github.com/vatesfr/xen-orchestra/issues/4513) (PR [#4574](https://github.com/vatesfr/xen-orchestra/pull/4574))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [VM/new-vm] Fix template selection on creating new VM for resource sets [#4565](https://github.com/vatesfr/xen-orchestra/issues/4565) (PR [#4568](https://github.com/vatesfr/xen-orchestra/pull/4568))
|
||||
- [VM] Clearer invalid cores per socket error [#4120](https://github.com/vatesfr/xen-orchestra/issues/4120) (PR [#4187](https://github.com/vatesfr/xen-orchestra/pull/4187))
|
||||
|
||||
### Released packages
|
||||
|
||||
- xo-web v5.50.3
|
||||
- xo-server v5.51.0
|
||||
- xo-web v5.51.0
|
||||
|
||||
|
||||
## **5.39.0** (2019-09-30)
|
||||
|
||||
@@ -7,14 +7,11 @@
|
||||
|
||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||
|
||||
[Support] Ability to check the XOA on the user interface [#4513](https://github.com/vatesfr/xen-orchestra/issues/4513) (PR [#4574](https://github.com/vatesfr/xen-orchestra/pull/4574))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||
|
||||
- [VM/new-vm] Fix template selection on creating new VM for resource sets [#4565](https://github.com/vatesfr/xen-orchestra/issues/4565) (PR [#4568](https://github.com/vatesfr/xen-orchestra/pull/4568))
|
||||
- [VM] Clearer invalid cores per socket error [#4120](https://github.com/vatesfr/xen-orchestra/issues/4120) (PR [#4187](https://github.com/vatesfr/xen-orchestra/pull/4187))
|
||||
|
||||
### Released packages
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@xen-orchestra/log": "^0.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"node-openssl-cert": "^0.0.97",
|
||||
"promise-toolbox": "^0.14.0",
|
||||
"promise-toolbox": "^0.13.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"private": true
|
||||
|
||||
@@ -5,7 +5,7 @@ import uuidv4 from 'uuid/v4'
|
||||
import { access, constants, readFile, writeFile } from 'fs'
|
||||
import { EventEmitter } from 'events'
|
||||
import { filter, find, forOwn, map, omitBy, sample } from 'lodash'
|
||||
import { fromCallback, promisify } from 'promise-toolbox'
|
||||
import { fromCallback, fromEvent } from 'promise-toolbox'
|
||||
import { join } from 'path'
|
||||
|
||||
import { OvsdbClient } from './ovsdb-client'
|
||||
@@ -47,8 +47,15 @@ export const configurationSchema = {
|
||||
|
||||
// =============================================================================
|
||||
|
||||
const fileWrite = promisify(writeFile)
|
||||
const fileRead = promisify(readFile)
|
||||
async function fileWrite(path, data) {
|
||||
await fromCallback(writeFile, path, data)
|
||||
}
|
||||
|
||||
async function fileRead(path) {
|
||||
const result = await fromCallback(readFile, path)
|
||||
return result
|
||||
}
|
||||
|
||||
async function fileExists(path) {
|
||||
try {
|
||||
await fromCallback(access, path, constants.F_OK)
|
||||
@@ -67,8 +74,8 @@ async function fileExists(path) {
|
||||
|
||||
// 2019-09-03
|
||||
// Compatibility code, to be removed in 1 year.
|
||||
const updateNetworkOtherConfig = network =>
|
||||
Promise.all(
|
||||
function updateNetworkOtherConfig(network) {
|
||||
return Promise.all(
|
||||
map(
|
||||
{
|
||||
'cross-pool-network-uuid': 'cross_pool_network_uuid',
|
||||
@@ -94,107 +101,14 @@ const updateNetworkOtherConfig = network =>
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!'
|
||||
const createPassword = () =>
|
||||
Array.from({ length: 16 }, _ => sample(CHARS)).join('')
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
async function generateCertificatesAndKey(dataDir) {
|
||||
const openssl = new NodeOpenssl()
|
||||
const rsaKeyOptions = {
|
||||
rsa_keygen_bits: 4096,
|
||||
format: 'PKCS8',
|
||||
}
|
||||
const subject = {
|
||||
countryName: 'XX',
|
||||
localityName: 'Default City',
|
||||
organizationName: 'Default Company LTD',
|
||||
}
|
||||
const csrOptions = {
|
||||
hash: 'sha256',
|
||||
startdate: new Date('1984-02-04 00:00:00'),
|
||||
enddate: new Date('2143-06-04 04:16:23'),
|
||||
subject: subject,
|
||||
}
|
||||
const caCsrOptions = {
|
||||
hash: 'sha256',
|
||||
days: NB_DAYS,
|
||||
subject: subject,
|
||||
}
|
||||
|
||||
let operation
|
||||
try {
|
||||
// CA Cert
|
||||
operation = 'Generating CA private key'
|
||||
const caKey = await fromCallback.call(
|
||||
openssl,
|
||||
'generateRSAPrivateKey',
|
||||
rsaKeyOptions
|
||||
)
|
||||
|
||||
operation = 'Generating CA certificate'
|
||||
const caCsr = await fromCallback.call(
|
||||
openssl,
|
||||
'generateCSR',
|
||||
caCsrOptions,
|
||||
caKey,
|
||||
null
|
||||
)
|
||||
|
||||
operation = 'Signing CA certificate'
|
||||
const caCrt = await fromCallback.call(
|
||||
openssl,
|
||||
'selfSignCSR',
|
||||
caCsr,
|
||||
caCsrOptions,
|
||||
caKey,
|
||||
null
|
||||
)
|
||||
await fileWrite(join(dataDir, CA_CERT), caCrt)
|
||||
|
||||
// Cert
|
||||
operation = 'Generating private key'
|
||||
const key = await fromCallback.call(
|
||||
openssl,
|
||||
'generateRSAPrivateKey',
|
||||
rsaKeyOptions
|
||||
)
|
||||
await fileWrite(join(dataDir, CLIENT_KEY), key)
|
||||
|
||||
operation = 'Generating certificate'
|
||||
const csr = await fromCallback.call(
|
||||
openssl,
|
||||
'generateCSR',
|
||||
csrOptions,
|
||||
key,
|
||||
null
|
||||
)
|
||||
|
||||
operation = 'Signing certificate'
|
||||
const crt = await fromCallback.call(
|
||||
openssl,
|
||||
'CASignCSR',
|
||||
csr,
|
||||
caCsrOptions,
|
||||
false,
|
||||
caCrt,
|
||||
caKey,
|
||||
null
|
||||
)
|
||||
await fileWrite(join(dataDir, CLIENT_CERT), crt)
|
||||
} catch (error) {
|
||||
log.error('Error while generating certificates and keys', {
|
||||
operation,
|
||||
error,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
|
||||
log.debug('All certificates have been successfully written')
|
||||
function createPassword() {
|
||||
const chars =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!'
|
||||
return Array.from({ length: 16 }, _ => sample(chars)).join('')
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -263,7 +177,7 @@ class SDNController extends EventEmitter {
|
||||
)
|
||||
|
||||
log.debug(`No default self-signed certificates exists, creating them`)
|
||||
await generateCertificatesAndKey(certDirectory)
|
||||
await this._generateCertificatesAndKey(certDirectory)
|
||||
}
|
||||
}
|
||||
// TODO: verify certificates and create new certificates if needed
|
||||
@@ -317,11 +231,7 @@ class SDNController extends EventEmitter {
|
||||
|
||||
// Expose method to create cross-pool private network
|
||||
const createCrossPoolPrivateNetwork = params =>
|
||||
this._createCrossPoolPrivateNetwork({
|
||||
encrypted: false,
|
||||
mtu: 0,
|
||||
...params,
|
||||
})
|
||||
this._createCrossPoolPrivateNetwork({ encrypted: false, mtu: 0, ...params })
|
||||
|
||||
createCrossPoolPrivateNetwork.description =
|
||||
'Creates a cross-pool private network on selected pools'
|
||||
@@ -1520,6 +1430,119 @@ class SDNController extends EventEmitter {
|
||||
this._ovsdbClients.push(client)
|
||||
return client
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async _generateCertificatesAndKey(dataDir) {
|
||||
const openssl = new NodeOpenssl()
|
||||
|
||||
const rsakeyoptions = {
|
||||
rsa_keygen_bits: 4096,
|
||||
format: 'PKCS8',
|
||||
}
|
||||
const subject = {
|
||||
countryName: 'XX',
|
||||
localityName: 'Default City',
|
||||
organizationName: 'Default Company LTD',
|
||||
}
|
||||
const csroptions = {
|
||||
hash: 'sha256',
|
||||
startdate: new Date('1984-02-04 00:00:00'),
|
||||
enddate: new Date('2143-06-04 04:16:23'),
|
||||
subject: subject,
|
||||
}
|
||||
const cacsroptions = {
|
||||
hash: 'sha256',
|
||||
days: NB_DAYS,
|
||||
subject: subject,
|
||||
}
|
||||
|
||||
// In all the following callbacks, `error` is:
|
||||
// - either an error object if there was an error
|
||||
// - or a boolean set to `false` if no error occurred
|
||||
openssl.generateRSAPrivateKey(rsakeyoptions, (error, cakey, cmd) => {
|
||||
if (error !== false) {
|
||||
log.error('Error while generating CA private key', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
openssl.generateCSR(cacsroptions, cakey, null, (error, csr, cmd) => {
|
||||
if (error !== false) {
|
||||
log.error('Error while generating CA certificate', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
openssl.selfSignCSR(
|
||||
csr,
|
||||
cacsroptions,
|
||||
cakey,
|
||||
null,
|
||||
async (error, cacrt, cmd) => {
|
||||
if (error !== false) {
|
||||
log.error('Error while signing CA certificate', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await fileWrite(join(dataDir, CA_CERT), cacrt)
|
||||
openssl.generateRSAPrivateKey(
|
||||
rsakeyoptions,
|
||||
async (error, key, cmd) => {
|
||||
if (error !== false) {
|
||||
log.error('Error while generating private key', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await fileWrite(join(dataDir, CLIENT_KEY), key)
|
||||
openssl.generateCSR(
|
||||
csroptions,
|
||||
key,
|
||||
null,
|
||||
(error, csr, cmd) => {
|
||||
if (error !== false) {
|
||||
log.error('Error while generating certificate', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
openssl.CASignCSR(
|
||||
csr,
|
||||
cacsroptions,
|
||||
false,
|
||||
cacrt,
|
||||
cakey,
|
||||
null,
|
||||
async (error, crt, cmd) => {
|
||||
if (error !== false) {
|
||||
log.error('Error while signing certificate', {
|
||||
error,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await fileWrite(join(dataDir, CLIENT_CERT), crt)
|
||||
this.emit('certWritten')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
await fromEvent(this, 'certWritten', {})
|
||||
log.debug('All certificates have been successfully written')
|
||||
}
|
||||
}
|
||||
|
||||
export default opts => new SDNController(opts)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export const getDefaultName = () => `xo-server-test ${new Date().toISOString()}`
|
||||
|
||||
export const getDefaultSchedule = () => ({
|
||||
name: getDefaultName(),
|
||||
cron: '0 * * * * *',
|
||||
})
|
||||
@@ -2,11 +2,15 @@
|
||||
import defer from 'golike-defer'
|
||||
import Xo from 'xo-lib'
|
||||
import XoCollection from 'xo-collection'
|
||||
import { defaultsDeep, find, forOwn, pick } from 'lodash'
|
||||
import { find, forOwn } from 'lodash'
|
||||
import { fromEvent } from 'promise-toolbox'
|
||||
|
||||
import config from './_config'
|
||||
import { getDefaultName } from './_defaultValues'
|
||||
|
||||
const getDefaultCredentials = () => {
|
||||
const { email, password } = config.xoConnection
|
||||
return { email, password }
|
||||
}
|
||||
|
||||
class XoConnection extends Xo {
|
||||
constructor(opts) {
|
||||
@@ -68,10 +72,7 @@ class XoConnection extends Xo {
|
||||
}
|
||||
|
||||
@defer
|
||||
async connect(
|
||||
$defer,
|
||||
credentials = pick(config.xoConnection, 'email', 'password')
|
||||
) {
|
||||
async connect($defer, credentials = getDefaultCredentials()) {
|
||||
await this.open()
|
||||
$defer.onFailure(() => this.close())
|
||||
|
||||
@@ -110,26 +111,9 @@ class XoConnection extends Xo {
|
||||
}
|
||||
|
||||
async createTempBackupNgJob(params) {
|
||||
// mutate and inject default values
|
||||
defaultsDeep(params, {
|
||||
mode: 'full',
|
||||
name: getDefaultName(),
|
||||
settings: {
|
||||
'': {
|
||||
// it must be enabled because the XAPI might be not able to coalesce VDIs
|
||||
// as fast as the tests run
|
||||
//
|
||||
// see https://xen-orchestra.com/docs/backup_troubleshooting.html#vdi-chain-protection
|
||||
bypassVdiChainsCheck: true,
|
||||
|
||||
// it must be 'never' to avoid race conditions with the plugin `backup-reports`
|
||||
reportWhen: 'never',
|
||||
},
|
||||
},
|
||||
})
|
||||
const id = await this.call('backupNg.createJob', params)
|
||||
this._tempResourceDisposers.push('backupNg.deleteJob', { id })
|
||||
return this.call('backupNg.getJob', { id })
|
||||
const job = await this.call('backupNg.createJob', params)
|
||||
this._tempResourceDisposers.push('backupNg.deleteJob', { id: job.id })
|
||||
return job
|
||||
}
|
||||
|
||||
async createTempNetwork(params) {
|
||||
@@ -144,7 +128,7 @@ class XoConnection extends Xo {
|
||||
|
||||
async createTempVm(params) {
|
||||
const id = await this.call('vm.create', {
|
||||
name_label: getDefaultName(),
|
||||
name_label: 'XO Test',
|
||||
template: config.templates.templateWithoutDisks,
|
||||
...params,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,61 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`backupNg .createJob() : creates a new backup job with schedules 1`] = `
|
||||
Object {
|
||||
"id": Any<String>,
|
||||
"mode": "full",
|
||||
"name": "default-backupNg",
|
||||
"settings": Any<Object>,
|
||||
"type": "backup",
|
||||
"userId": Any<String>,
|
||||
"vms": Any<Object>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg .createJob() : creates a new backup job with schedules 2`] = `
|
||||
Object {
|
||||
"cron": "0 * * * * *",
|
||||
"enabled": false,
|
||||
"id": Any<String>,
|
||||
"jobId": Any<String>,
|
||||
"name": "scheduleTest",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg .createJob() : creates a new backup job without schedules 1`] = `
|
||||
Object {
|
||||
"id": Any<String>,
|
||||
"mode": "full",
|
||||
"name": "default-backupNg",
|
||||
"settings": Object {
|
||||
"": Object {
|
||||
"reportWhen": "never",
|
||||
},
|
||||
},
|
||||
"type": "backup",
|
||||
"userId": Any<String>,
|
||||
"vms": Any<Object>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg .runJob() : fails trying to run a backup job with a VM without disks 1`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"mode": "full",
|
||||
"reportWhen": "never",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"jobId": Any<String>,
|
||||
"jobName": "default-backupNg",
|
||||
"message": "backup",
|
||||
"scheduleId": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "skipped",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg .runJob() : fails trying to run a backup job with a VM without disks 2`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
@@ -37,6 +92,23 @@ Array [
|
||||
exports[`backupNg .runJob() : fails trying to run a backup job without schedule 1`] = `[JsonRpcError: invalid parameters]`;
|
||||
|
||||
exports[`backupNg .runJob() : fails trying to run backup job without retentions 1`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"mode": "full",
|
||||
"reportWhen": "never",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"jobId": Any<String>,
|
||||
"jobName": "default-backupNg",
|
||||
"message": "backup",
|
||||
"scheduleId": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "failure",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg .runJob() : fails trying to run backup job without retentions 2`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
@@ -56,6 +128,22 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 1`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"mode": "delta",
|
||||
"reportWhen": "never",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"jobId": Any<String>,
|
||||
"message": "backup",
|
||||
"scheduleId": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 2`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
@@ -69,7 +157,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 2`] = `
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 3`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
@@ -80,7 +168,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 3`] = `
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 4`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
@@ -95,19 +183,6 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 4`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 5`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
@@ -122,6 +197,19 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 6`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 7`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
@@ -136,19 +224,6 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 7`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 8`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
@@ -164,13 +239,12 @@ Object {
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 9`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
"type": "VM",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
@@ -178,10 +252,15 @@ Object {
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 10`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"mode": "delta",
|
||||
"reportWhen": "never",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": "snapshot",
|
||||
"result": Any<String>,
|
||||
"jobId": Any<String>,
|
||||
"message": "backup",
|
||||
"scheduleId": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
@@ -191,8 +270,7 @@ exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retent
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
"isFull": false,
|
||||
"type": "remote",
|
||||
"type": "VM",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
@@ -206,29 +284,14 @@ exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retent
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"message": "snapshot",
|
||||
"result": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 13`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 14`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
@@ -243,6 +306,19 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 14`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 15`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
@@ -257,6 +333,21 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 16`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
"isFull": false,
|
||||
"type": "remote",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 17`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
@@ -269,7 +360,36 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 17`] = `
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 18`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 19`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"mode": "delta",
|
||||
"reportWhen": "never",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"jobId": Any<String>,
|
||||
"message": "backup",
|
||||
"scheduleId": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 20`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
@@ -283,7 +403,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 18`] = `
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 21`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
@@ -294,47 +414,6 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 19`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
"isFull": true,
|
||||
"type": "remote",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 20`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 21`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 22`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
@@ -376,7 +455,65 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 25`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
"isFull": true,
|
||||
"type": "remote",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 26`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a delta backup with 2 remotes, 2 as retention and 2 as fullInterval 27`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"message": Any<String>,
|
||||
"result": Object {
|
||||
"size": Any<Number>,
|
||||
},
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a rolling snapshot with 2 as retention & revert to an old state 1`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"mode": "full",
|
||||
"reportWhen": "never",
|
||||
},
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
"jobId": Any<String>,
|
||||
"jobName": "default-backupNg",
|
||||
"message": "backup",
|
||||
"scheduleId": Any<String>,
|
||||
"start": Any<Number>,
|
||||
"status": "success",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a rolling snapshot with 2 as retention & revert to an old state 2`] = `
|
||||
Object {
|
||||
"end": Any<Number>,
|
||||
"id": Any<String>,
|
||||
@@ -387,7 +524,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`backupNg execute three times a rolling snapshot with 2 as retention & revert to an old state 2`] = `
|
||||
exports[`backupNg execute three times a rolling snapshot with 2 as retention & revert to an old state 3`] = `
|
||||
Object {
|
||||
"data": Object {
|
||||
"id": Any<String>,
|
||||
|
||||
@@ -6,44 +6,20 @@ import { noSuchObject } from 'xo-common/api-errors'
|
||||
import config from '../_config'
|
||||
import randomId from '../_randomId'
|
||||
import xo from '../_xoConnection'
|
||||
import { getDefaultName, getDefaultSchedule } from '../_defaultValues'
|
||||
|
||||
const validateBackupJob = (jobInput, jobOutput, createdSchedule) => {
|
||||
const expectedObj = {
|
||||
id: expect.any(String),
|
||||
mode: jobInput.mode,
|
||||
name: jobInput.name,
|
||||
type: 'backup',
|
||||
settings: {
|
||||
'': jobInput.settings[''],
|
||||
},
|
||||
userId: xo._user.id,
|
||||
vms: jobInput.vms,
|
||||
}
|
||||
|
||||
const schedules = jobInput.schedules
|
||||
if (schedules !== undefined) {
|
||||
const scheduleTmpId = Object.keys(schedules)[0]
|
||||
expect(createdSchedule).toEqual({
|
||||
...schedules[scheduleTmpId],
|
||||
enabled: false,
|
||||
id: expect.any(String),
|
||||
jobId: jobOutput.id,
|
||||
})
|
||||
|
||||
expectedObj.settings[createdSchedule.id] = jobInput.settings[scheduleTmpId]
|
||||
}
|
||||
|
||||
expect(jobOutput).toEqual(expectedObj)
|
||||
const DEFAULT_SCHEDULE = {
|
||||
name: 'scheduleTest',
|
||||
cron: '0 * * * * *',
|
||||
}
|
||||
|
||||
const validateRootTask = (log, expected) =>
|
||||
expect(log).toEqual({
|
||||
const validateRootTask = (log, props) =>
|
||||
expect(log).toMatchSnapshot({
|
||||
end: expect.any(Number),
|
||||
id: expect.any(String),
|
||||
message: 'backup',
|
||||
jobId: expect.any(String),
|
||||
scheduleId: expect.any(String),
|
||||
start: expect.any(Number),
|
||||
...expected,
|
||||
...props,
|
||||
})
|
||||
|
||||
const validateVmTask = (task, vmId, props) => {
|
||||
@@ -90,55 +66,88 @@ const validateOperationTask = (task, props) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Note: `bypassVdiChainsCheck` must be enabled because the XAPI might be not
|
||||
// able to coalesce VDIs as fast as the tests run.
|
||||
//
|
||||
// See https://xen-orchestra.com/docs/backup_troubleshooting.html#vdi-chain-protection
|
||||
describe('backupNg', () => {
|
||||
let defaultBackupNg
|
||||
|
||||
beforeAll(() => {
|
||||
defaultBackupNg = {
|
||||
name: 'default-backupNg',
|
||||
mode: 'full',
|
||||
vms: {
|
||||
id: config.vms.default,
|
||||
},
|
||||
settings: {
|
||||
'': {
|
||||
reportWhen: 'never',
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('.createJob() :', () => {
|
||||
it('creates a new backup job without schedules', async () => {
|
||||
const jobInput = {
|
||||
mode: 'full',
|
||||
vms: {
|
||||
id: config.vms.default,
|
||||
},
|
||||
}
|
||||
const jobOutput = await xo.createTempBackupNgJob(jobInput)
|
||||
validateBackupJob(jobInput, jobOutput)
|
||||
const backupNg = await xo.createTempBackupNgJob(defaultBackupNg)
|
||||
expect(backupNg).toMatchSnapshot({
|
||||
id: expect.any(String),
|
||||
userId: expect.any(String),
|
||||
vms: expect.any(Object),
|
||||
})
|
||||
expect(backupNg.vms).toEqual(defaultBackupNg.vms)
|
||||
expect(backupNg.userId).toBe(xo._user.id)
|
||||
})
|
||||
|
||||
it('creates a new backup job with schedules', async () => {
|
||||
const scheduleTempId = randomId()
|
||||
const jobInput = {
|
||||
mode: 'full',
|
||||
const { id: jobId } = await xo.createTempBackupNgJob({
|
||||
...defaultBackupNg,
|
||||
schedules: {
|
||||
[scheduleTempId]: getDefaultSchedule(),
|
||||
[scheduleTempId]: DEFAULT_SCHEDULE,
|
||||
},
|
||||
settings: {
|
||||
...defaultBackupNg.settings,
|
||||
[scheduleTempId]: { snapshotRetention: 1 },
|
||||
},
|
||||
vms: {
|
||||
id: config.vms.default,
|
||||
},
|
||||
}
|
||||
const jobOutput = await xo.createTempBackupNgJob(jobInput)
|
||||
validateBackupJob(
|
||||
jobInput,
|
||||
jobOutput,
|
||||
await xo.getSchedule({ jobId: jobOutput.id })
|
||||
)
|
||||
})
|
||||
|
||||
const backupNgJob = await xo.call('backupNg.getJob', { id: jobId })
|
||||
|
||||
expect(backupNgJob).toMatchSnapshot({
|
||||
id: expect.any(String),
|
||||
userId: expect.any(String),
|
||||
settings: expect.any(Object),
|
||||
vms: expect.any(Object),
|
||||
})
|
||||
expect(backupNgJob.vms).toEqual(defaultBackupNg.vms)
|
||||
expect(backupNgJob.userId).toBe(xo._user.id)
|
||||
|
||||
expect(Object.keys(backupNgJob.settings).length).toBe(2)
|
||||
const schedule = await xo.getSchedule({ jobId })
|
||||
expect(typeof schedule).toBe('object')
|
||||
expect(backupNgJob.settings[schedule.id]).toEqual({
|
||||
snapshotRetention: 1,
|
||||
})
|
||||
|
||||
expect(schedule).toMatchSnapshot({
|
||||
id: expect.any(String),
|
||||
jobId: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.delete() :', () => {
|
||||
it('deletes a backup job', async () => {
|
||||
const scheduleTempId = randomId()
|
||||
const jobId = await xo.call('backupNg.createJob', {
|
||||
mode: 'full',
|
||||
name: getDefaultName(),
|
||||
vms: {
|
||||
id: config.vms.default,
|
||||
},
|
||||
const { id: jobId } = await xo.call('backupNg.createJob', {
|
||||
...defaultBackupNg,
|
||||
schedules: {
|
||||
[scheduleTempId]: getDefaultSchedule(),
|
||||
[scheduleTempId]: DEFAULT_SCHEDULE,
|
||||
},
|
||||
settings: {
|
||||
...defaultBackupNg.settings,
|
||||
[scheduleTempId]: { snapshotRetention: 1 },
|
||||
},
|
||||
})
|
||||
@@ -164,19 +173,16 @@ describe('backupNg', () => {
|
||||
|
||||
describe('.runJob() :', () => {
|
||||
it('fails trying to run a backup job without schedule', async () => {
|
||||
const { id } = await xo.createTempBackupNgJob({
|
||||
vms: {
|
||||
id: config.vms.default,
|
||||
},
|
||||
})
|
||||
const { id } = await xo.createTempBackupNgJob(defaultBackupNg)
|
||||
await expect(xo.call('backupNg.runJob', { id })).rejects.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('fails trying to run a backup job with no matching VMs', async () => {
|
||||
const scheduleTempId = randomId()
|
||||
const { id: jobId } = await xo.createTempBackupNgJob({
|
||||
...defaultBackupNg,
|
||||
schedules: {
|
||||
[scheduleTempId]: getDefaultSchedule(),
|
||||
[scheduleTempId]: DEFAULT_SCHEDULE,
|
||||
},
|
||||
settings: {
|
||||
[scheduleTempId]: { snapshotRetention: 1 },
|
||||
@@ -199,8 +205,9 @@ describe('backupNg', () => {
|
||||
jest.setTimeout(7e3)
|
||||
const scheduleTempId = randomId()
|
||||
const { id: jobId } = await xo.createTempBackupNgJob({
|
||||
...defaultBackupNg,
|
||||
schedules: {
|
||||
[scheduleTempId]: getDefaultSchedule(),
|
||||
[scheduleTempId]: DEFAULT_SCHEDULE,
|
||||
},
|
||||
settings: {
|
||||
[scheduleTempId]: { snapshotRetention: 1 },
|
||||
@@ -224,23 +231,25 @@ describe('backupNg', () => {
|
||||
jest.setTimeout(8e3)
|
||||
await xo.createTempServer(config.servers.default)
|
||||
const { id: vmIdWithoutDisks } = await xo.createTempVm({
|
||||
name_label: 'XO Test Without Disks',
|
||||
name_description: 'Creating a vm without disks',
|
||||
template: config.templates.templateWithoutDisks,
|
||||
})
|
||||
|
||||
const scheduleTempId = randomId()
|
||||
const jobInput = {
|
||||
const { id: jobId } = await xo.createTempBackupNgJob({
|
||||
...defaultBackupNg,
|
||||
schedules: {
|
||||
[scheduleTempId]: getDefaultSchedule(),
|
||||
[scheduleTempId]: DEFAULT_SCHEDULE,
|
||||
},
|
||||
settings: {
|
||||
...defaultBackupNg.settings,
|
||||
[scheduleTempId]: { snapshotRetention: 1 },
|
||||
},
|
||||
vms: {
|
||||
id: vmIdWithoutDisks,
|
||||
},
|
||||
}
|
||||
const { id: jobId } = await xo.createTempBackupNgJob(jobInput)
|
||||
})
|
||||
|
||||
const schedule = await xo.getSchedule({ jobId })
|
||||
expect(typeof schedule).toBe('object')
|
||||
@@ -255,16 +264,12 @@ describe('backupNg', () => {
|
||||
jobId,
|
||||
scheduleId: schedule.id,
|
||||
})
|
||||
|
||||
validateRootTask(log, {
|
||||
data: {
|
||||
mode: jobInput.mode,
|
||||
reportWhen: jobInput.settings[''].reportWhen,
|
||||
},
|
||||
jobId,
|
||||
jobName: jobInput.name,
|
||||
scheduleId: schedule.id,
|
||||
status: 'skipped',
|
||||
expect(log).toMatchSnapshot({
|
||||
end: expect.any(Number),
|
||||
id: expect.any(String),
|
||||
jobId: expect.any(String),
|
||||
scheduleId: expect.any(String),
|
||||
start: expect.any(Number),
|
||||
})
|
||||
|
||||
expect(vmTask).toMatchSnapshot({
|
||||
@@ -288,24 +293,22 @@ describe('backupNg', () => {
|
||||
const scheduleTempId = randomId()
|
||||
await xo.createTempServer(config.servers.default)
|
||||
const { id: remoteId } = await xo.createTempRemote(config.remotes.default)
|
||||
const jobInput = {
|
||||
const { id: jobId } = await xo.createTempBackupNgJob({
|
||||
...defaultBackupNg,
|
||||
remotes: {
|
||||
id: remoteId,
|
||||
},
|
||||
schedules: {
|
||||
[scheduleTempId]: getDefaultSchedule(),
|
||||
[scheduleTempId]: DEFAULT_SCHEDULE,
|
||||
},
|
||||
settings: {
|
||||
...defaultBackupNg.settings,
|
||||
[scheduleTempId]: {},
|
||||
},
|
||||
srs: {
|
||||
id: config.srs.default,
|
||||
},
|
||||
vms: {
|
||||
id: config.vms.default,
|
||||
},
|
||||
}
|
||||
const { id: jobId } = await xo.createTempBackupNgJob(jobInput)
|
||||
})
|
||||
|
||||
const schedule = await xo.getSchedule({ jobId })
|
||||
expect(typeof schedule).toBe('object')
|
||||
@@ -321,15 +324,12 @@ describe('backupNg', () => {
|
||||
scheduleId: schedule.id,
|
||||
})
|
||||
|
||||
validateRootTask(log, {
|
||||
data: {
|
||||
mode: jobInput.mode,
|
||||
reportWhen: jobInput.settings[''].reportWhen,
|
||||
},
|
||||
jobId,
|
||||
jobName: jobInput.name,
|
||||
scheduleId: schedule.id,
|
||||
status: 'failure',
|
||||
expect(log).toMatchSnapshot({
|
||||
end: expect.any(Number),
|
||||
id: expect.any(String),
|
||||
jobId: expect.any(String),
|
||||
scheduleId: expect.any(String),
|
||||
start: expect.any(Number),
|
||||
})
|
||||
|
||||
expect(task).toMatchSnapshot({
|
||||
@@ -352,6 +352,7 @@ describe('backupNg', () => {
|
||||
jest.setTimeout(6e4)
|
||||
await xo.createTempServer(config.servers.default)
|
||||
let vm = await xo.createTempVm({
|
||||
name_label: 'XO Test Temp',
|
||||
name_description: 'Creating a temporary vm',
|
||||
template: config.templates.default,
|
||||
VDIs: [
|
||||
@@ -364,18 +365,22 @@ describe('backupNg', () => {
|
||||
})
|
||||
|
||||
const scheduleTempId = randomId()
|
||||
const jobInput = {
|
||||
const { id: jobId } = await xo.createTempBackupNgJob({
|
||||
...defaultBackupNg,
|
||||
vms: {
|
||||
id: vm.id,
|
||||
},
|
||||
schedules: {
|
||||
[scheduleTempId]: getDefaultSchedule(),
|
||||
[scheduleTempId]: DEFAULT_SCHEDULE,
|
||||
},
|
||||
settings: {
|
||||
'': {
|
||||
bypassVdiChainsCheck: true,
|
||||
reportWhen: 'never',
|
||||
},
|
||||
[scheduleTempId]: { snapshotRetention: 2 },
|
||||
},
|
||||
}
|
||||
const { id: jobId } = await xo.createTempBackupNgJob(jobInput)
|
||||
})
|
||||
|
||||
const schedule = await xo.getSchedule({ jobId })
|
||||
expect(typeof schedule).toBe('object')
|
||||
@@ -415,15 +420,12 @@ describe('backupNg', () => {
|
||||
scheduleId: schedule.id,
|
||||
})
|
||||
|
||||
validateRootTask(log, {
|
||||
data: {
|
||||
mode: jobInput.mode,
|
||||
reportWhen: jobInput.settings[''].reportWhen,
|
||||
},
|
||||
jobId,
|
||||
jobName: jobInput.name,
|
||||
scheduleId: schedule.id,
|
||||
status: 'success',
|
||||
expect(log).toMatchSnapshot({
|
||||
end: expect.any(Number),
|
||||
id: expect.any(String),
|
||||
jobId: expect.any(String),
|
||||
scheduleId: expect.any(String),
|
||||
start: expect.any(Number),
|
||||
})
|
||||
|
||||
const subTaskSnapshot = subTasks.find(
|
||||
@@ -468,7 +470,7 @@ describe('backupNg', () => {
|
||||
const exportRetention = 2
|
||||
const fullInterval = 2
|
||||
const scheduleTempId = randomId()
|
||||
const jobInput = {
|
||||
const { id: jobId } = await xo.createTempBackupNgJob({
|
||||
mode: 'delta',
|
||||
remotes: {
|
||||
id: {
|
||||
@@ -476,11 +478,13 @@ describe('backupNg', () => {
|
||||
},
|
||||
},
|
||||
schedules: {
|
||||
[scheduleTempId]: getDefaultSchedule(),
|
||||
[scheduleTempId]: DEFAULT_SCHEDULE,
|
||||
},
|
||||
settings: {
|
||||
'': {
|
||||
bypassVdiChainsCheck: true,
|
||||
fullInterval,
|
||||
reportWhen: 'never',
|
||||
},
|
||||
[remoteId1]: { deleteFirst: true },
|
||||
[scheduleTempId]: { exportRetention },
|
||||
@@ -488,8 +492,7 @@ describe('backupNg', () => {
|
||||
vms: {
|
||||
id: vmToBackup,
|
||||
},
|
||||
}
|
||||
const { id: jobId } = await xo.createTempBackupNgJob(jobInput)
|
||||
})
|
||||
|
||||
const schedule = await xo.getSchedule({ jobId })
|
||||
expect(typeof schedule).toBe('object')
|
||||
@@ -512,12 +515,10 @@ describe('backupNg', () => {
|
||||
backupLogs.forEach(({ tasks = [], ...log }, key) => {
|
||||
validateRootTask(log, {
|
||||
data: {
|
||||
mode: jobInput.mode,
|
||||
reportWhen: jobInput.settings[''].reportWhen,
|
||||
mode: 'delta',
|
||||
reportWhen: 'never',
|
||||
},
|
||||
jobId,
|
||||
jobName: jobInput.name,
|
||||
scheduleId: schedule.id,
|
||||
message: 'backup',
|
||||
status: 'success',
|
||||
})
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { safeDateFormat } from '../utils'
|
||||
|
||||
export function createJob({ schedules, ...job }) {
|
||||
job.userId = this.user.id
|
||||
return this.createBackupNgJob(job, schedules).then(({ id }) => id)
|
||||
return this.createBackupNgJob(job, schedules)
|
||||
}
|
||||
|
||||
createJob.permission = 'admin'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "xo-web",
|
||||
"version": "5.50.3",
|
||||
"version": "5.50.2",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
|
||||
@@ -2142,7 +2142,7 @@ export default {
|
||||
vmChooseCoresPerSocket: undefined,
|
||||
|
||||
// Original text: '{nSockets, number} socket{nSockets, plural, one {} other {s}} with {nCores, number} core{nCores, plural, one {} other {s}} per socket'
|
||||
vmSocketsWithCoresPerSocket: undefined,
|
||||
vmCoresPerSocket: undefined,
|
||||
|
||||
// Original text: 'Incorrect cores per socket value'
|
||||
vmCoresPerSocketIncorrectValue: undefined,
|
||||
|
||||
@@ -2185,7 +2185,7 @@ export default {
|
||||
vmChooseCoresPerSocket: 'Comportement par défaut',
|
||||
|
||||
// Original text: "{nSockets, number} socket{nSockets, plural, one {} other {s}} with {nCores, number} core{nCores, plural, one {} other {s}} per socket"
|
||||
vmSocketsWithCoresPerSocket:
|
||||
vmCoresPerSocket:
|
||||
'{nSockets, number} socket{nSockets, plural, one {} other {s}} avec {nCores, number} cœur{nCores, plural, one {} other {s}} par socket',
|
||||
|
||||
// Original text: "Incorrect cores per socket value"
|
||||
|
||||
@@ -2660,7 +2660,7 @@ export default {
|
||||
vmChooseCoresPerSocket: 'Varsayılan davranış',
|
||||
|
||||
// Original text: "{nSockets, number} socket{nSockets, plural, one {} other {s}} with {nCores, number} core{nCores, plural, one {} other {s}} per socket"
|
||||
vmSocketsWithCoresPerSocket:
|
||||
vmCoresPerSocket:
|
||||
'{nSockets, number} soket ve her sokette {nCores, number} çekirdek',
|
||||
|
||||
// Original text: "None"
|
||||
|
||||
@@ -50,7 +50,6 @@ const messages = {
|
||||
backupJobs: 'Backup jobs',
|
||||
iscsiSessions:
|
||||
'({ nSessions, number }) iSCSI session{nSessions, plural, one {} other {s}}',
|
||||
requiresAdminPermissions: 'Requires admin permissions',
|
||||
|
||||
// ----- Modals -----
|
||||
alertOk: 'OK',
|
||||
@@ -100,7 +99,6 @@ const messages = {
|
||||
updatePage: 'Updates',
|
||||
licensesPage: 'Licenses',
|
||||
notificationsPage: 'Notifications',
|
||||
supportPage: 'Support',
|
||||
settingsPage: 'Settings',
|
||||
settingsServersPage: 'Servers',
|
||||
settingsUsersPage: 'Users',
|
||||
@@ -155,9 +153,6 @@ const messages = {
|
||||
// ----- Support -----
|
||||
noSupport: 'No support',
|
||||
freeUpgrade: 'Free upgrade!',
|
||||
checkXoa: 'Check XOA',
|
||||
xoaCheck: 'XOA check',
|
||||
checkXoaCommunity: 'XOA check is available in XOA.',
|
||||
|
||||
// ----- Sign out -----
|
||||
signOut: 'Sign out',
|
||||
@@ -1159,16 +1154,12 @@ const messages = {
|
||||
vmCpuLimitsLabel: 'CPU limits',
|
||||
vmCpuTopology: 'Topology',
|
||||
vmChooseCoresPerSocket: 'Default behavior',
|
||||
vmSocketsWithCoresPerSocket:
|
||||
vmCoresPerSocket:
|
||||
'{nSockets, number} socket{nSockets, plural, one {} other {s}} with {nCores, number} core{nCores, plural, one {} other {s}} per socket',
|
||||
vmCoresPerSocketNone: 'None',
|
||||
vmCoresPerSocket:
|
||||
'{nCores, number} core{nCores, plural, one {} other {s}} per socket',
|
||||
vmCoresPerSocketNotDivisor: "Not a divisor of the VM's max CPUs",
|
||||
vmCoresPerSocketExceedsCoresLimit:
|
||||
'The selected value exceeds the cores limit ({maxCores, number})',
|
||||
vmCoresPerSocketExceedsSocketsLimit:
|
||||
'The selected value exceeds the sockets limit ({maxSockets, number})',
|
||||
vmCoresPerSocketIncorrectValue: 'Incorrect cores per socket value',
|
||||
vmCoresPerSocketIncorrectValueSolution:
|
||||
'Please change the selected value to fix it.',
|
||||
vmHaDisabled: 'Disabled',
|
||||
vmMemoryLimitsLabel: 'Memory limits (min/max)',
|
||||
vmVgpu: 'vGPU',
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
import _ from 'intl'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { omit } from 'lodash'
|
||||
|
||||
import decorate from './apply-decorators'
|
||||
import Icon from './icon'
|
||||
import Tooltip from './tooltip'
|
||||
import { Select } from './form'
|
||||
|
||||
const PROP_TYPES = {
|
||||
maxCores: PropTypes.number,
|
||||
maxVcpus: PropTypes.number,
|
||||
value: PropTypes.number,
|
||||
}
|
||||
|
||||
const SELECT_STYLE = {
|
||||
display: 'inline-block',
|
||||
fontSize: '1rem',
|
||||
width: '20em',
|
||||
}
|
||||
|
||||
const LINE_ITEM_STYLE = {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
}
|
||||
|
||||
// https://github.com/xcp-ng/xenadmin/blob/0160cd0119fae3b871eef656c23e2b76fcc04cb5/XenModel/XenAPI-Extensions/VM.cs#L62
|
||||
const MAX_VM_SOCKETS = 16
|
||||
|
||||
// This algorithm was inspired from: https://github.com/xcp-ng/xenadmin/blob/master/XenAdmin/Controls/ComboBoxes/CPUTopologyComboBox.cs#L116
|
||||
const SelectCoresPerSocket = decorate([
|
||||
provideState({
|
||||
computed: {
|
||||
isValidValue: (state, { maxVcpus, value }) =>
|
||||
value == null ||
|
||||
(maxVcpus % value === 0 &&
|
||||
!state.valueExceedsCoresLimit &&
|
||||
!state.valueExceedsSocketsLimit),
|
||||
valueExceedsCoresLimit: (state, { maxCores, value }) => value > maxCores,
|
||||
valueExceedsSocketsLimit: (state, { maxCores, maxVcpus, value }) =>
|
||||
maxVcpus / value > MAX_VM_SOCKETS,
|
||||
options: ({ isValidValue }, { maxCores, maxVcpus, value }) => {
|
||||
const options = []
|
||||
|
||||
if (maxCores === undefined || maxVcpus === undefined) {
|
||||
return options
|
||||
}
|
||||
|
||||
const minCores = maxVcpus / MAX_VM_SOCKETS
|
||||
|
||||
// cores per socket must be a divisor of the max vCPUs and must not exceed the cores and sockets limit
|
||||
// e.g: with maxCores = 4, maxSockets = 16 and maxVCPUS = 6
|
||||
// 2 cores per socket is a valid value and 4 cores per socket isn't a valid value
|
||||
for (
|
||||
let coresPerSocket = maxCores;
|
||||
coresPerSocket >= minCores;
|
||||
coresPerSocket--
|
||||
) {
|
||||
if (maxVcpus % coresPerSocket === 0) {
|
||||
options.push({
|
||||
label: _('vmSocketsWithCoresPerSocket', {
|
||||
nSockets: maxVcpus / coresPerSocket,
|
||||
nCores: coresPerSocket,
|
||||
}),
|
||||
value: coresPerSocket,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidValue) {
|
||||
options.push({
|
||||
label: _('vmCoresPerSocket', {
|
||||
nCores: value,
|
||||
}),
|
||||
value,
|
||||
})
|
||||
}
|
||||
return options
|
||||
},
|
||||
selectProps: (_, props) => omit(props, Object.keys(PROP_TYPES)),
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
({ maxCores, state, value }) => (
|
||||
<div style={LINE_ITEM_STYLE}>
|
||||
<span style={SELECT_STYLE}>
|
||||
<Select
|
||||
options={state.options}
|
||||
placeholder={_('vmChooseCoresPerSocket')}
|
||||
simpleValue
|
||||
value={value}
|
||||
{...state.selectProps}
|
||||
/>
|
||||
</span>
|
||||
|
||||
{!state.isValidValue && (
|
||||
<Tooltip
|
||||
content={
|
||||
state.valueExceedsCoresLimit
|
||||
? _('vmCoresPerSocketExceedsCoresLimit', { maxCores })
|
||||
: state.valueExceedsSocketsLimit
|
||||
? _('vmCoresPerSocketExceedsSocketsLimit', {
|
||||
maxSockets: MAX_VM_SOCKETS,
|
||||
})
|
||||
: _('vmCoresPerSocketNotDivisor')
|
||||
}
|
||||
>
|
||||
<Icon icon='error' size='lg' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
])
|
||||
|
||||
SelectCoresPerSocket.propTypes = PROP_TYPES
|
||||
|
||||
export { SelectCoresPerSocket as default }
|
||||
@@ -449,6 +449,26 @@ export const isXosanPack = ({ name }) => name.startsWith('XOSAN')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const getCoresPerSocketPossibilities = (maxCoresPerSocket, vCPUs) => {
|
||||
// According to : https://www.citrix.com/blogs/2014/03/11/citrix-xenserver-setting-more-than-one-vcpu-per-vm-to-improve-application-performance-and-server-consolidation-e-g-for-cad3-d-graphical-applications/
|
||||
const maxVCPUs = 16
|
||||
|
||||
const options = []
|
||||
if (maxCoresPerSocket !== undefined && vCPUs !== '') {
|
||||
const ratio = vCPUs / maxVCPUs
|
||||
|
||||
for (
|
||||
let coresPerSocket = maxCoresPerSocket;
|
||||
coresPerSocket >= ratio;
|
||||
coresPerSocket--
|
||||
) {
|
||||
if (vCPUs % coresPerSocket === 0) options.push(coresPerSocket)
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// Generates a random human-readable string of length `length`
|
||||
// Useful to generate random default names intended for the UI user
|
||||
export const generateReadableRandomString = (() => {
|
||||
|
||||
@@ -2918,7 +2918,3 @@ export const getLicense = (productId, boundObjectId) =>
|
||||
|
||||
export const unlockXosan = (licenseId, srId) =>
|
||||
_call('xosan.unlock', { licenseId, sr: srId })
|
||||
|
||||
// Support --------------------------------------------------------------------
|
||||
|
||||
export const checkXoa = () => _call('xoa.check')
|
||||
|
||||
@@ -830,10 +830,6 @@
|
||||
@extend .fa;
|
||||
@extend .fa-bell;
|
||||
}
|
||||
&-menu-support {
|
||||
@extend .fa;
|
||||
@extend .fa-support;
|
||||
}
|
||||
&-menu-settings {
|
||||
@extend .fa;
|
||||
@extend .fa-cog;
|
||||
|
||||
@@ -159,8 +159,8 @@ export default decorate([
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
installedTemplates: (_, { namespace, templates }) =>
|
||||
filter(templates, ['other.xo:resource:namespace', namespace]),
|
||||
installedTemplates: (_, { id, templates }) =>
|
||||
filter(templates, ['other.xo:resource:xva:id', id]),
|
||||
isTemplateInstalledOnAllPools: ({ installedTemplates }, { pools }) =>
|
||||
installedTemplates.length > 0 &&
|
||||
pools.every(
|
||||
|
||||
@@ -281,11 +281,6 @@ export default class Menu extends Component {
|
||||
label: 'notificationsPage',
|
||||
extra: <NotificationTag />,
|
||||
},
|
||||
isAdmin && {
|
||||
to: 'xoa/support',
|
||||
icon: 'menu-support',
|
||||
label: 'supportPage',
|
||||
},
|
||||
],
|
||||
},
|
||||
isAdmin && {
|
||||
|
||||
@@ -11,7 +11,6 @@ import Page from '../page'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import SelectBootFirmware from 'select-boot-firmware'
|
||||
import SelectCoresPerSocket from 'select-cores-per-socket'
|
||||
import store from 'store'
|
||||
import Tags from 'tags'
|
||||
import Tooltip from 'tooltip'
|
||||
@@ -81,6 +80,7 @@ import {
|
||||
addSubscriptions,
|
||||
connectStore,
|
||||
formatSize,
|
||||
getCoresPerSocketPossibilities,
|
||||
generateReadableRandomString,
|
||||
resolveIds,
|
||||
} from 'utils'
|
||||
@@ -347,7 +347,6 @@ export default class NewVm extends BaseComponent {
|
||||
this._replaceState(
|
||||
{
|
||||
bootAfterCreate: true,
|
||||
coresPerSocket: undefined,
|
||||
CPUs: '',
|
||||
cpuCap: '',
|
||||
cpuWeight: '',
|
||||
@@ -501,8 +500,7 @@ export default class NewVm extends BaseComponent {
|
||||
VIFs: _VIFs,
|
||||
resourceSet: resourceSet && resourceSet.id,
|
||||
// vm.set parameters
|
||||
coresPerSocket:
|
||||
state.coresPerSocket === null ? undefined : state.coresPerSocket,
|
||||
coresPerSocket: state.coresPerSocket,
|
||||
CPUs: state.CPUs,
|
||||
cpuWeight: state.cpuWeight === '' ? null : state.cpuWeight,
|
||||
cpuCap: state.cpuCap === '' ? null : state.cpuCap,
|
||||
@@ -763,6 +761,17 @@ export default class NewVm extends BaseComponent {
|
||||
pool => vgpuType => pool !== undefined && pool.id === vgpuType.$pool
|
||||
)
|
||||
|
||||
_getCoresPerSocketPossibilities = createSelector(
|
||||
() => {
|
||||
const { pool } = this.props
|
||||
if (pool !== undefined) {
|
||||
return pool.cpus.cores
|
||||
}
|
||||
},
|
||||
() => this.state.state.CPUs,
|
||||
getCoresPerSocketPossibilities
|
||||
)
|
||||
|
||||
_isCoreOs = createSelector(
|
||||
() => this.props.template,
|
||||
template => template && template.name_label === 'CoreOS'
|
||||
@@ -1070,17 +1079,7 @@ export default class NewVm extends BaseComponent {
|
||||
_renderPerformances = () => {
|
||||
const { coresPerSocket, CPUs, memoryDynamicMax } = this.state.state
|
||||
const { template } = this.props
|
||||
const { pool } = this.props
|
||||
const memoryThreshold = get(() => template.memory.static[0])
|
||||
const selectCoresPerSocket = (
|
||||
<SelectCoresPerSocket
|
||||
disabled={pool === undefined}
|
||||
maxCores={get(() => pool.cpus.cores)}
|
||||
maxVcpus={get(() => template.CPUs.max)}
|
||||
onChange={this._linkState('coresPerSocket')}
|
||||
value={coresPerSocket}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Section
|
||||
@@ -1115,13 +1114,29 @@ export default class NewVm extends BaseComponent {
|
||||
)}
|
||||
</Item>
|
||||
<Item label={_('vmCpuTopology')}>
|
||||
{pool !== undefined ? (
|
||||
selectCoresPerSocket
|
||||
) : (
|
||||
<Tooltip content={_('requiresAdminPermissions')}>
|
||||
{selectCoresPerSocket}
|
||||
</Tooltip>
|
||||
)}
|
||||
<select
|
||||
className='form-control'
|
||||
onChange={this._linkState('coresPerSocket')}
|
||||
value={coresPerSocket}
|
||||
>
|
||||
{_('vmChooseCoresPerSocket', message => (
|
||||
<option value=''>{message}</option>
|
||||
))}
|
||||
{map(this._getCoresPerSocketPossibilities(), coresPerSocket =>
|
||||
_(
|
||||
'vmCoresPerSocket',
|
||||
{
|
||||
nSockets: CPUs / coresPerSocket,
|
||||
nCores: coresPerSocket,
|
||||
},
|
||||
message => (
|
||||
<option key={coresPerSocket} value={coresPerSocket}>
|
||||
{message}
|
||||
</option>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</select>
|
||||
</Item>
|
||||
</SectionContent>
|
||||
</Section>
|
||||
|
||||
@@ -9,7 +9,6 @@ import Link from 'link'
|
||||
import React from 'react'
|
||||
import renderXoItem from 'render-xo-item'
|
||||
import SelectBootFirmware from 'select-boot-firmware'
|
||||
import SelectCoresPerSocket from 'select-cores-per-socket'
|
||||
import TabButton from 'tab-button'
|
||||
import Tooltip from 'tooltip'
|
||||
import { error } from 'notification'
|
||||
@@ -34,6 +33,7 @@ import {
|
||||
addSubscriptions,
|
||||
connectStore,
|
||||
formatSize,
|
||||
getCoresPerSocketPossibilities,
|
||||
getVirtualizationModeLabel,
|
||||
noop,
|
||||
osFamily,
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
every,
|
||||
filter,
|
||||
find,
|
||||
includes,
|
||||
isEmpty,
|
||||
keyBy,
|
||||
map,
|
||||
@@ -248,30 +249,80 @@ class Vgpus extends Component {
|
||||
}
|
||||
|
||||
class CoresPerSocket extends Component {
|
||||
_onChange = coresPerSocket => editVm(this.props.vm, { coresPerSocket })
|
||||
_getCoresPerSocketPossibilities = createSelector(
|
||||
() => {
|
||||
const { container } = this.props
|
||||
if (container != null) {
|
||||
return container.cpus.cores
|
||||
}
|
||||
},
|
||||
() => this.props.vm.CPUs.number,
|
||||
getCoresPerSocketPossibilities
|
||||
)
|
||||
|
||||
_selectedValueIsNotInOptions = createSelector(
|
||||
() => this.props.vm.coresPerSocket,
|
||||
this._getCoresPerSocketPossibilities,
|
||||
(selectedCoresPerSocket, options) =>
|
||||
selectedCoresPerSocket !== undefined &&
|
||||
!includes(options, selectedCoresPerSocket)
|
||||
)
|
||||
|
||||
_onChange = event =>
|
||||
editVm(this.props.vm, { coresPerSocket: getEventValue(event) || null })
|
||||
|
||||
render() {
|
||||
const { container, vm } = this.props
|
||||
const { coresPerSocket, CPUs: cpus } = vm
|
||||
const selectedCoresPerSocket = vm.coresPerSocket
|
||||
const options = this._getCoresPerSocketPossibilities()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form className='form-inline'>
|
||||
{container != null ? (
|
||||
<SelectCoresPerSocket
|
||||
maxCores={container.cpus.cores}
|
||||
maxVcpus={cpus.max}
|
||||
onChange={this._onChange}
|
||||
value={coresPerSocket}
|
||||
/>
|
||||
) : coresPerSocket !== undefined ? (
|
||||
_('vmSocketsWithCoresPerSocket', {
|
||||
nSockets: cpus.max / coresPerSocket,
|
||||
nCores: coresPerSocket,
|
||||
<span>
|
||||
<select
|
||||
className='form-control'
|
||||
onChange={this._onChange}
|
||||
value={selectedCoresPerSocket || ''}
|
||||
>
|
||||
{_({ key: 'none' }, 'vmChooseCoresPerSocket', message => (
|
||||
<option value=''>{message}</option>
|
||||
))}
|
||||
{this._selectedValueIsNotInOptions() &&
|
||||
_(
|
||||
{ key: 'incorrect' },
|
||||
'vmCoresPerSocketIncorrectValue',
|
||||
message => (
|
||||
<option value={selectedCoresPerSocket}> {message}</option>
|
||||
)
|
||||
)}
|
||||
{map(options, coresPerSocket =>
|
||||
_(
|
||||
{ key: coresPerSocket },
|
||||
'vmCoresPerSocket',
|
||||
{
|
||||
nSockets: vm.CPUs.number / coresPerSocket,
|
||||
nCores: coresPerSocket,
|
||||
},
|
||||
message => <option value={coresPerSocket}>{message}</option>
|
||||
)
|
||||
)}
|
||||
</select>{' '}
|
||||
{this._selectedValueIsNotInOptions() && (
|
||||
<Tooltip content={_('vmCoresPerSocketIncorrectValueSolution')}>
|
||||
<Icon icon='error' size='lg' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
) : selectedCoresPerSocket != null ? (
|
||||
_('vmCoresPerSocket', {
|
||||
nSockets: vm.CPUs.number / selectedCoresPerSocket,
|
||||
nCores: selectedCoresPerSocket,
|
||||
})
|
||||
) : (
|
||||
_('vmCoresPerSocketNone')
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -349,12 +400,14 @@ const Acls = decorate([
|
||||
computed: {
|
||||
rawAcls: (_, { acls, vm }) => filter(acls, { object: vm }),
|
||||
resolvedAcls: ({ rawAcls }, { users, groups }) => {
|
||||
if (users === undefined || groups === undefined) {
|
||||
if (users === undefined && groups === undefined) {
|
||||
return []
|
||||
}
|
||||
return rawAcls.map(({ subject, ...acl }) => ({
|
||||
...acl,
|
||||
subject: defined(users[subject], groups[subject]),
|
||||
subject:
|
||||
(users !== undefined && users[subject]) ||
|
||||
(groups !== undefined && groups[subject]),
|
||||
}))
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,10 +7,9 @@ import { Container, Row, Col } from 'grid'
|
||||
import { isAdmin } from 'selectors'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
|
||||
import Update from './update'
|
||||
import Licenses from './licenses'
|
||||
import Notifications, { NotificationTag } from './notifications'
|
||||
import Support from './support'
|
||||
import Update from './update'
|
||||
|
||||
const Header = ({ isAdmin }) => (
|
||||
<Container>
|
||||
@@ -36,11 +35,6 @@ const Header = ({ isAdmin }) => (
|
||||
<Icon icon='menu-notification' /> {_('notificationsPage')}{' '}
|
||||
<NotificationTag />
|
||||
</NavLink>
|
||||
{isAdmin && (
|
||||
<NavLink to='/xoa/support'>
|
||||
<Icon icon='menu-support' /> {_('supportPage')}
|
||||
</NavLink>
|
||||
)}
|
||||
</NavTabs>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -48,10 +42,9 @@ const Header = ({ isAdmin }) => (
|
||||
)
|
||||
|
||||
const Xoa = routes('xoa', {
|
||||
update: Update,
|
||||
licenses: Licenses,
|
||||
notifications: Notifications,
|
||||
support: Support,
|
||||
update: Update,
|
||||
})(
|
||||
connectStore({
|
||||
isAdmin,
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import AnsiUp from 'ansi_up'
|
||||
import decorate from 'apply-decorators'
|
||||
import React from 'react'
|
||||
import { adminOnly, getXoaPlan } from 'utils'
|
||||
import { Card, CardBlock, CardHeader } from 'card'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { checkXoa } from 'xo'
|
||||
|
||||
const ansiUp = new AnsiUp()
|
||||
const COMMUNITY = getXoaPlan() === 'Community'
|
||||
|
||||
const Support = decorate([
|
||||
adminOnly,
|
||||
provideState({
|
||||
initialState: () => ({ stdoutCheckXoa: '' }),
|
||||
effects: {
|
||||
initialize: async () => ({
|
||||
stdoutCheckXoa: COMMUNITY ? '' : await checkXoa(),
|
||||
}),
|
||||
checkXoa: async () => ({ stdoutCheckXoa: await checkXoa() }),
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
({ effects, state: { stdoutCheckXoa } }) => (
|
||||
<Container>
|
||||
<Row>
|
||||
<Col mediumSize={6}>
|
||||
<Card>
|
||||
<CardHeader>{_('xoaCheck')}</CardHeader>
|
||||
{COMMUNITY ? (
|
||||
<CardBlock>
|
||||
<span className='text-info'>{_('checkXoaCommunity')}</span>
|
||||
</CardBlock>
|
||||
) : (
|
||||
<CardBlock>
|
||||
<ActionButton
|
||||
btnStyle='success'
|
||||
handler={effects.checkXoa}
|
||||
icon='diagnosis'
|
||||
>
|
||||
{_('checkXoa')}
|
||||
</ActionButton>
|
||||
<hr />
|
||||
<pre
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: ansiUp.ansi_to_html(stdoutCheckXoa),
|
||||
}}
|
||||
/>
|
||||
</CardBlock>
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
),
|
||||
])
|
||||
|
||||
export default Support
|
||||
@@ -11103,13 +11103,6 @@ promise-toolbox@^0.13.0:
|
||||
dependencies:
|
||||
make-error "^1.3.2"
|
||||
|
||||
promise-toolbox@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/promise-toolbox/-/promise-toolbox-0.14.0.tgz#b2f8bd90fce6709b290b58fc06d89280375a98b4"
|
||||
integrity sha512-VV5lXK4lXaPB9oBO50ope1qd0AKN8N3nK14jYvV9/qFmfZW2Px/bJjPZBniGjXcIJf6J5Y/coNgJtPHDyiUV/g==
|
||||
dependencies:
|
||||
make-error "^1.3.2"
|
||||
|
||||
promise-toolbox@^0.8.0:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/promise-toolbox/-/promise-toolbox-0.8.3.tgz#b757232a21d246d8702df50da6784932dd0f5348"
|
||||
|
||||
Reference in New Issue
Block a user