Compare commits

..

1 Commits

Author SHA1 Message Date
Manon Mercier
45d021e6aa Replacing XenServer naming
I would like to replace "XenServer" by "XCP-ng/XenServer" wherever I find it in the XO doc.
This follows a internal discussion with Olivier and Yann.
2023-12-21 14:39:01 +01:00
18 changed files with 125 additions and 122 deletions

View File

@@ -123,7 +123,7 @@ const onProgress = makeOnProgress({
onTaskUpdate(taskLog) {},
})
Task.run({ properties: { name: 'my task' }, onProgress }, asyncFn)
Task.run({ data: { name: 'my task' }, onProgress }, asyncFn)
```
It can also be fed event logs directly:

View File

@@ -139,7 +139,7 @@ const onProgress = makeOnProgress({
onTaskUpdate(taskLog) {},
})
Task.run({ properties: { name: 'my task' }, onProgress }, asyncFn)
Task.run({ data: { name: 'my task' }, onProgress }, asyncFn)
```
It can also be fed event logs directly:

View File

@@ -6,16 +6,12 @@
### Enhancements
> Users must be able to say: “Nice enhancement, I'm eager to test it”
- [SR] show an icon on SR during VDI coalescing (with XCP-ng 8.3+) (PR [#7241](https://github.com/vatesfr/xen-orchestra/pull/7241))
- [VDI/Export] Expose NBD settings in the XO and REST APIs api (PR [#7251](https://github.com/vatesfr/xen-orchestra/pull/7251))
- [Menu/Proxies] Added a warning icon if unable to check proxies upgrade (PR [#7237](https://github.com/vatesfr/xen-orchestra/pull/7237))
### Bug fixes
- [Backup/Report] Missing report for Mirror Backup (PR [#7254](https://github.com/vatesfr/xen-orchestra/pull/7254))
> Users must be able to say: “I had this issue, happy to know it's fixed”
### Packages to release

View File

@@ -58,18 +58,18 @@ Please only use this if you have issues with [the default way to deploy XOA](ins
### Via a bash script
Alternatively, you can deploy it by connecting to your XenServer host and executing the following:
Alternatively, you can deploy it by connecting to your XCP-ng/XenServer host and executing the following:
```sh
bash -c "$(wget -qO- https://xoa.io/deploy)"
```
:::tip
This won't write or modify anything on your XenServer host: it will just import the XOA VM into your default storage repository.
This won't write or modify anything on your XCP-ng/XenServer host: it will just import the XOA VM into your default storage repository.
:::
:::warning
If you are using an old XenServer version, you may get a `curl` error:
If you are using an old XCP-ng/XenServer version, you may get a `curl` error:
```
curl: (35) error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version

View File

@@ -157,32 +157,33 @@ function extractFlags(args) {
const noop = Function.prototype
function parseValue(value) {
if (value.startsWith('json:')) {
return JSON.parse(value.slice(5))
}
if (value === 'true') {
return true
}
if (value === 'false') {
return false
}
return value
}
const PARAM_RE = /^([^=]+)=([^]*)$/
function parseParameters(args) {
if (args[0] === '--') {
return args.slice(1).map(parseValue)
}
const params = {}
forEach(args, function (arg) {
let matches
if (!(matches = arg.match(PARAM_RE))) {
throw new Error('invalid arg: ' + arg)
}
params[matches[1]] = parseValue(matches[2])
const name = matches[1]
let value = matches[2]
if (value.startsWith('json:')) {
value = JSON.parse(value.slice(5))
}
if (name === '@') {
params['@'] = value
return
}
if (value === 'true') {
value = true
} else if (value === 'false') {
value = false
}
params[name] = value
})
return params

View File

@@ -11,7 +11,7 @@ import { defer } from 'golike-defer'
import { format } from 'json-rpc-peer'
import { FAIL_ON_QUEUE } from 'limit-concurrency-decorator'
import { getStreamAsBuffer } from 'get-stream'
import { ignoreErrors, timeout } from 'promise-toolbox'
import { ignoreErrors } from 'promise-toolbox'
import { invalidParameters, noSuchObject, unauthorized } from 'xo-common/api-errors.js'
import { Ref } from 'xen-api'
@@ -1027,7 +1027,11 @@ start.resolve = {
// -------------------------------------------------------------------
export const stop = defer(async function ($defer, { vm, force, forceShutdownDelay, bypassBlockedOperation = force }) {
// TODO: implements timeout.
// - if !force → clean shutdown
// - if force is true → hard shutdown
// - if force is integer → clean shutdown and after force seconds, hard shutdown.
export const stop = defer(async function ($defer, { vm, force, bypassBlockedOperation = force }) {
const xapi = this.getXapi(vm)
if (bypassBlockedOperation) {
@@ -1049,14 +1053,13 @@ export const stop = defer(async function ($defer, { vm, force, forceShutdownDela
// Clean shutdown
try {
await timeout.call(xapi.shutdownVm(vm._xapiRef), forceShutdownDelay, () =>
xapi.shutdownVm(vm._xapiRef, { hard: true })
)
await xapi.shutdownVm(vm._xapiRef)
} catch (error) {
const { code } = error
if (code === 'VM_MISSING_PV_DRIVERS' || code === 'VM_LACKS_FEATURE_SHUTDOWN') {
throw invalidParameters('clean shutdown requires PV drivers')
}
throw error
}
})
@@ -1064,7 +1067,6 @@ export const stop = defer(async function ($defer, { vm, force, forceShutdownDela
stop.params = {
id: { type: 'string' },
force: { type: 'boolean', optional: true },
forceShutdownDelay: { type: 'number', default: 0 },
bypassBlockedOperation: { type: 'boolean', optional: true },
}
@@ -1398,7 +1400,7 @@ export async function importMultipleFromEsxi({
await asyncEach(
vms,
async vm => {
await Task.run({ properties: { name: `importing vm ${vm}` } }, async () => {
await Task.run({ data: { name: `importing vm ${vm}` } }, async () => {
try {
const vmUuid = await this.migrationfromEsxi({
host,

View File

@@ -190,8 +190,6 @@ export default class Redis extends Collection {
await Promise.all(promises)
model = this._unserialize(model) ?? model
model.id = id
return model
})
)

View File

@@ -29,7 +29,7 @@ export class PluginsMetadata extends Collection {
throw new Error('no such plugin metadata')
}
await this.update({
return /* await */ this.update({
...pluginMetadata,
...data,
})

View File

@@ -251,19 +251,9 @@ export default class Api {
constructor(app) {
this._logger = null
this._methods = { __proto__: null }
this._app = app
const defer =
const seq = async methods => {
for (const method of methods) {
await this.#callApiMethod(method[0], method[1])
}
}
seq.validate = ajv.compile({ type: 'array', minLength: 1, items: { type: ['array', 'string'] } })
const if =
this._methods = { __proto__: null, seq }
this.addApiMethods(methods)
app.hooks.on('start', async () => {
this._logger = await app.getLogger('api')
@@ -377,7 +367,8 @@ export default class Api {
}
async callApiMethod(connection, name, params = {}) {
if (!Object.hasOwn(this._methods, name)) {
const method = this._methods[name]
if (!method) {
throw new MethodNotFound(name)
}
@@ -392,12 +383,11 @@ export default class Api {
apiContext.permission = 'none'
}
return this.#apiContext.run(apiContext, () => this.#callApiMethod(name, params))
return this.#apiContext.run(apiContext, () => this.#callApiMethod(name, method, params))
}
async #callApiMethod(name, params) {
async #callApiMethod(name, method, params) {
const app = this._app
const method = this._methods[name]
const startTime = Date.now()
const { connection, user } = this.apiContext

View File

@@ -3,7 +3,11 @@ import { noSuchObject } from 'xo-common/api-errors.js'
import Collection from '../collection/redis.mjs'
import patch from '../patch.mjs'
class CloudConfigs extends Collection {}
class CloudConfigs extends Collection {
get(properties) {
return super.get(properties)
}
}
export default class {
constructor(app) {
@@ -31,7 +35,7 @@ export default class {
async updateCloudConfig({ id, name, template }) {
const cloudConfig = await this.getCloudConfig(id)
patch(cloudConfig, { name, template })
await this._db.update(cloudConfig)
return this._db.update(cloudConfig)
}
deleteCloudConfig(id) {

View File

@@ -19,31 +19,47 @@ const log = createLogger('xo:jobs')
// -----------------------------------------------------------------------------
class JobsDb extends Collection {
_serialize(job) {
Object.keys(job).forEach(key => {
const value = job[key]
if (typeof value !== 'string') {
job[key] = JSON.stringify(job[key])
const normalize = job => {
Object.keys(job).forEach(key => {
try {
const value = (job[key] = JSON.parse(job[key]))
// userId are always strings, even if the value is numeric, which might to
// them being parsed as numbers.
//
// The issue has been introduced by
// 48b2297bc151df582160be7c1bf1e8ee160320b8.
if (key === 'userId' && typeof value === 'number') {
job[key] = String(value)
}
})
} catch (_) {}
})
return job
}
const serialize = job => {
Object.keys(job).forEach(key => {
const value = job[key]
if (typeof value !== 'string') {
job[key] = JSON.stringify(job[key])
}
})
return job
}
class JobsDb extends Collection {
async create(job) {
return normalize(await this.add(serialize(job)))
}
_unserialize(job) {
Object.keys(job).forEach(key => {
try {
const value = (job[key] = JSON.parse(job[key]))
async save(job) {
await this.update(serialize(job))
}
// userId are always strings, even if the value is numeric, which might to
// them being parsed as numbers.
//
// The issue has been introduced by
// 48b2297bc151df582160be7c1bf1e8ee160320b8.
if (key === 'userId' && typeof value === 'number') {
job[key] = String(value)
}
} catch (_) {}
})
async get(properties) {
const jobs = await super.get(properties)
jobs.forEach(normalize)
return jobs
}
}
@@ -74,7 +90,7 @@ export default class Jobs {
app.addConfigManager(
'jobs',
() => jobsDb.get(),
jobs => jobsDb.update(jobs),
jobs => Promise.all(jobs.map(job => jobsDb.save(job))),
['users']
)
})
@@ -134,7 +150,7 @@ export default class Jobs {
}
createJob(job) {
return this._jobs.add(job)
return this._jobs.create(job)
}
async updateJob(job, merge = true) {
@@ -143,7 +159,7 @@ export default class Jobs {
job = await this.getJob(id)
patch(job, props)
}
await this._jobs.update(job)
return /* await */ this._jobs.save(job)
}
registerJobExecutor(type, executor) {
@@ -171,7 +187,7 @@ export default class Jobs {
const runJobId = logger.notice(`Starting execution of ${id}.`, {
data:
type === 'backup' || type === 'metadataBackup' || type === 'mirrorBackup'
type === 'backup' || type === 'metadataBackup'
? {
mode: job.mode,
reportWhen: job.settings['']?.reportWhen ?? 'failure',

View File

@@ -154,7 +154,7 @@ export default class MigrateVm {
}
#connectToEsxi(host, user, password, sslVerify) {
return Task.run({ properties: { name: `connecting to ${host}` } }, async () => {
return Task.run({ data: { name: `connecting to ${host}` } }, async () => {
const esxi = new Esxi(host, user, password, sslVerify)
await fromEvent(esxi, 'ready')
return esxi
@@ -174,7 +174,7 @@ export default class MigrateVm {
const app = this._app
const esxi = await this.#connectToEsxi(host, user, password, sslVerify)
const esxiVmMetadata = await Task.run({ properties: { name: `get metadata of ${vmId}` } }, async () => {
const esxiVmMetadata = await Task.run({ data: { name: `get metadata of ${vmId}` } }, async () => {
return esxi.getTransferableVmMetadata(vmId)
})
@@ -182,7 +182,7 @@ export default class MigrateVm {
const isRunning = powerState !== 'poweredOff'
const chainsByNodes = await Task.run(
{ properties: { name: `build disks and snapshots chains for ${vmId}` } },
{ data: { name: `build disks and snapshots chains for ${vmId}` } },
async () => {
return this.#buildDiskChainByNode(disks, snapshots)
}
@@ -191,7 +191,7 @@ export default class MigrateVm {
const sr = app.getXapiObject(srId)
const xapi = sr.$xapi
const vm = await Task.run({ properties: { name: 'creating MV on XCP side' } }, async () => {
const vm = await Task.run({ data: { name: 'creating MV on XCP side' } }, async () => {
// got data, ready to start creating
const vm = await xapi._getOrWaitObject(
await xapi.VM_create({
@@ -236,7 +236,7 @@ export default class MigrateVm {
const vhds = await Promise.all(
Object.keys(chainsByNodes).map(async (node, userdevice) =>
Task.run({ properties: { name: `Cold import of disks ${node}` } }, async () => {
Task.run({ data: { name: `Cold import of disks ${node}` } }, async () => {
const chainByNode = chainsByNodes[node]
const vdi = await xapi._getOrWaitObject(
await xapi.VDI_create({
@@ -289,11 +289,11 @@ export default class MigrateVm {
if (isRunning && stopSource) {
// it the vm was running, we stop it and transfer the data in the active disk
await Task.run({ properties: { name: 'powering down source VM' } }, () => esxi.powerOff(vmId))
await Task.run({ data: { name: 'powering down source VM' } }, () => esxi.powerOff(vmId))
await Promise.all(
Object.keys(chainsByNodes).map(async (node, userdevice) => {
await Task.run({ properties: { name: `Transfering deltas of ${userdevice}` } }, async () => {
await Task.run({ data: { name: `Transfering deltas of ${userdevice}` } }, async () => {
const chainByNode = chainsByNodes[node]
const disk = chainByNode[chainByNode.length - 1]
const { fileName, path, datastore, isFull } = disk
@@ -322,7 +322,7 @@ export default class MigrateVm {
)
}
await Task.run({ properties: { name: 'Finishing transfer' } }, async () => {
await Task.run({ data: { name: 'Finishing transfer' } }, async () => {
// remove the importing in label
await vm.set_name_label(esxiVmMetadata.name_label)

View File

@@ -47,7 +47,7 @@ export default class {
app.addConfigManager(
'remotes',
() => this._remotes.get(),
remotes => this._remotes.update(remotes)
remotes => Promise.all(remotes.map(remote => this._remotes.update(remote)))
)
})
app.hooks.on('start', async () => {

View File

@@ -7,16 +7,23 @@ import { noSuchObject } from 'xo-common/api-errors.js'
import Collection from '../collection/redis.mjs'
import patch from '../patch.mjs'
const normalize = schedule => {
const { enabled } = schedule
if (typeof enabled !== 'boolean') {
schedule.enabled = enabled === 'true'
}
if ('job' in schedule) {
schedule.jobId = schedule.job
delete schedule.job
}
return schedule
}
class Schedules extends Collection {
_unserialize(schedule) {
const { enabled } = schedule
if (typeof enabled !== 'boolean') {
schedule.enabled = enabled === 'true'
}
if ('job' in schedule) {
schedule.jobId = schedule.job
delete schedule.job
}
async get(properties) {
const schedules = await super.get(properties)
schedules.forEach(normalize)
return schedules
}
}
@@ -48,7 +55,7 @@ export default class Scheduling {
() => db.get(),
schedules =>
asyncMapSettled(schedules, async schedule => {
await db.update(schedule)
await db.update(normalize(schedule))
this._start(schedule.id)
}),
['jobs']

View File

@@ -39,7 +39,7 @@ export default class {
app.addConfigManager(
'groups',
() => groupsDb.get(),
groups => groupsDb.update(groups),
groups => Promise.all(groups.map(group => groupsDb.update(group))),
['users']
)
app.addConfigManager(
@@ -83,7 +83,10 @@ export default class {
properties.pw_hash = await hash(password)
}
return this._users.create(properties)
// TODO: use plain objects
const user = await this._users.create(properties)
return user
}
async deleteUser(id) {
@@ -333,8 +336,11 @@ export default class {
// -----------------------------------------------------------------
createGroup({ name, provider, providerGroupId }) {
return this._groups.create(name, provider, providerGroupId)
async createGroup({ name, provider, providerGroupId }) {
// TODO: use plain objects.
const group = await this._groups.create(name, provider, providerGroupId)
return group
}
async deleteGroup(id) {

View File

@@ -2703,8 +2703,6 @@ const messages = {
proxiesNeedUpgrade: 'Some proxies need to be upgraded.',
upgradeNeededForProxies: 'Some proxies need to be upgraded. Click here to get more information.',
xoProxyConcreteGuide: 'XO Proxy: a concrete guide',
someProxiesHaveErrors:
'{n, number} prox{n, plural, one {y} other {ies}} ha{n, plural, one {s} other {ve}} error{n, plural, one {} other {s}}',
// ----- Utils -----
secondsFormat: '{seconds, plural, one {# second} other {# seconds}}',

View File

@@ -512,14 +512,7 @@ subscribeHostMissingPatches.forceRefresh = host => {
const proxiesApplianceUpdaterState = {}
export const subscribeProxiesApplianceUpdaterState = (proxyId, cb) => {
if (proxiesApplianceUpdaterState[proxyId] === undefined) {
proxiesApplianceUpdaterState[proxyId] = createSubscription(async () => {
try {
return await getProxyApplianceUpdaterState(proxyId)
} catch (error) {
console.error(error)
return { state: 'error' }
}
})
proxiesApplianceUpdaterState[proxyId] = createSubscription(() => getProxyApplianceUpdaterState(proxyId))
}
return proxiesApplianceUpdaterState[proxyId](cb)
}

View File

@@ -32,7 +32,7 @@ import {
getXoaState,
isAdmin,
} from 'selectors'
import { countBy, every, forEach, identity, isEmpty, isEqual, map, pick, size, some } from 'lodash'
import { every, forEach, identity, isEmpty, isEqual, map, pick, size, some } from 'lodash'
import styles from './index.css'
@@ -111,10 +111,7 @@ export default class Menu extends Component {
() => this.state.proxyStates,
proxyStates => some(proxyStates, state => state.endsWith('-upgrade-needed'))
)
_getNProxiesErrors = createSelector(
() => this.state.proxyStates,
proxyStates => countBy(proxyStates).error
)
_checkPermissions = createSelector(
() => this.props.isAdmin,
() => this.props.permissions,
@@ -216,7 +213,6 @@ export default class Menu extends Component {
const noOperatablePools = this._getNoOperatablePools()
const noResourceSets = this._getNoResourceSets()
const noNotifications = this._getNoNotifications()
const nProxiesErrors = this._getNProxiesErrors()
const missingPatchesWarning = this._hasMissingPatches() ? (
<Tooltip content={_('homeMissingPatches')}>
@@ -472,10 +468,6 @@ export default class Menu extends Component {
]}
/>
</Tooltip>
) : nProxiesErrors > 0 ? (
<Tooltip content={_('someProxiesHaveErrors', { n: nProxiesErrors })}>
<span className='tag tag-pill tag-danger'>{nProxiesErrors}</span>
</Tooltip>
) : null,
],
},