Skip to content

Commit

Permalink
Merge branch 'master' into poolNetwork/displayBondMode
Browse files Browse the repository at this point in the history
  • Loading branch information
MathieuRA authored Sep 27, 2024
2 parents e6c5b5c + eb39e14 commit 0802ee9
Show file tree
Hide file tree
Showing 16 changed files with 486 additions and 32 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
- `rest post` and `rest put` now accept `--input $file` to upload a file and display progress information
- [Backup] Detect invalid VDI exports that are incorrectly reported as successful by XAPI
- [Backup/HealthCheck] Improve error messages on health check timeout (PR [#8016](https://github.com/vatesfr/xen-orchestra/pull/8016))
- [Backup] Backup job sequences: configure lists of backup jobs to run in order one after the other (PRs [#7985](https://github.com/vatesfr/xen-orchestra/pull/7985), [#8014](https://github.com/vatesfr/xen-orchestra/pull/8014))
- [Pool/Network] Display the bond mode of a network [#7802](https://github.com/vatesfr/xen-orchestra/issues/7802) (PR [#8010](https://github.com/vatesfr/xen-orchestra/pull/8010))

### Bug fixes

> Users must be able to say: “I had this issue, happy to know it's fixed”
- [REST API] Fix broken _Rolling Pool Update_ pool action [Forum#82867](https://xcp-ng.org/forum/post/82867)

### Packages to release

> When modifying a package, add it here with its release type.
Expand Down
24 changes: 24 additions & 0 deletions packages/xo-server/src/api/schedule.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// FIXME so far, no acls for schedules

import { Task } from '@xen-orchestra/mixins/Tasks.mjs'

export async function getAll() {
return /* await */ this.getAllSchedules()
}
Expand Down Expand Up @@ -64,3 +66,25 @@ delete_.params = {
}

export { delete_ as delete }

export async function runSequence({ schedules }) {
const t = this.tasks.create({ type: 'xo:schedule:sequence', name: 'Schedule sequence' })
await t.run(async () => {
const nb = schedules.length
const signal = Task.abortSignal
for (let i = 0; i < nb; i++) {
signal.throwIfAborted()
Task.set('progress', Math.round((i * 100) / nb))
const idSchedule = schedules[i]
// we can't auto resolve array parameters, we have to resolve them by hand
const schedule = await this.getSchedule(idSchedule)
const job = await this.getJob(schedule.jobId)
await this.runJob(job, schedule)
}
})
}
runSequence.permission = 'admin'
runSequence.description = 'Run a sequence of schedules, one after the other'
runSequence.params = {
schedules: { type: 'array', items: { type: 'string' } },
}
20 changes: 16 additions & 4 deletions packages/xo-server/src/xo-mixins/api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -316,18 +316,30 @@ export default class Api {
throw new MethodNotFound(name)
}

const apiContext = { __proto__: null, connection }

let user
const userId = connection.get('user_id', undefined)
if (userId !== undefined) {
const user = await this._app.getUser(userId)
user = await this._app.getUser(userId)
}

return this.runWithApiContext(user, () => {
this.apiContext.connection = connection

return this.#callApiMethod(name, method, params)
})
}

async runWithApiContext(user, fn) {
const apiContext = { __proto__: null }

if (user !== undefined) {
apiContext.user = user
apiContext.permission = user.permission
} else {
apiContext.permission = 'none'
}

return this.#apiContext.run(apiContext, () => this.#callApiMethod(name, method, params))
return this.#apiContext.run(apiContext, fn)
}

async #callApiMethod(name, method, params) {
Expand Down
4 changes: 2 additions & 2 deletions packages/xo-server/src/xo-mixins/jobs/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export default class Jobs {
}

@decorateWith(defer)
async _runJob($defer, job, schedule, data_) {
async runJob($defer, job, schedule, data_) {
const logger = this._logger
const { id, type } = job

Expand Down Expand Up @@ -316,7 +316,7 @@ export default class Jobs {
const jobs = await Promise.all(idSequence.map(id => this.getJob(id)))

for (const job of jobs) {
await this._runJob(job, schedule, data)
await this.runJob(job, schedule, data)
}
}
}
5 changes: 2 additions & 3 deletions packages/xo-server/src/xo-mixins/rest-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,7 @@ export default class RestApi {
app.authenticateUser({ token: cookies.authenticationToken ?? cookies.token }, { ip }).then(
({ user }) => {
if (user.permission === 'admin') {
req.user = user
return next()
return app.runWithApiContext(user, next)
}

res.sendStatus(401)
Expand Down Expand Up @@ -658,7 +657,7 @@ export default class RestApi {
params.affinityHost = affinity
params.installRepository = install?.repository

const vm = await $xapi.createVm(template, params, undefined, req.user.id)
const vm = await $xapi.createVm(template, params, undefined, app.apiContext.user.id)
$defer.onFailure.call($xapi, 'VM_destroy', vm.$ref)

if (boot) {
Expand Down
5 changes: 5 additions & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ const messages = {
xcpNg: 'XCP-ng',
noFileSelected: 'No file selected',
nRetriesVmBackupFailures: 'Number of retries if VM backup fails',
sequence: 'Sequence',
sequences: 'Sequences',

// ----- Modals -----
alertOk: 'OK',
Expand Down Expand Up @@ -531,7 +533,9 @@ const messages = {
scheduleAdd: 'Add a schedule',
scheduleDelete: 'Delete',
scheduleRun: 'Run schedule',
scheduleSequence: 'Schedule sequence',
unnamedSchedule: 'Unnamed schedule',
unnamedJob: 'Unnamed Job',
deleteSelectedSchedules: 'Delete selected schedules',
noScheduledJobs: 'No scheduled jobs.',
legacySnapshotsLink: 'You can delete all your legacy backup snapshots.',
Expand Down Expand Up @@ -1671,6 +1675,7 @@ const messages = {
missingVm: 'Missing VM',
missingVmInJob: 'This VM does not belong to this job',
missingSchedule: 'Missing schedule',
unknownSchedule: 'Unknown schedule',
noDetachedBackups: 'No backups',
noDuplicatedMacAddresses: 'No duplicated MAC addresses',
reason: 'Reason',
Expand Down
82 changes: 69 additions & 13 deletions packages/xo-web/src/common/render-xo-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CopyToClipboard from 'react-copy-to-clipboard'
import PropTypes from 'prop-types'
import React from 'react'
import { get } from '@xen-orchestra/defined'
import { injectState, provideState } from 'reaclette'
import find from 'lodash/find.js'
import isEmpty from 'lodash/isEmpty.js'

Expand All @@ -13,7 +14,16 @@ import Link from './link'
import Tooltip from './tooltip'
import { addSubscriptions, connectStore, formatSize, NumericDate, ShortDate } from './utils'
import { createGetObject, createSelector } from './selectors'
import { isSrWritable, subscribeBackupNgJobs, subscribeProxies, subscribeRemotes, subscribeUsers } from './xo'
import {
isSrWritable,
subscribeBackupNgJobs,
subscribeMetadataBackupJobs,
subscribeProxies,
subscribeRemotes,
subscribeSchedules,
subscribeUsers,
subscribeMirrorBackupJobs,
} from './xo'

// ===================================================================

Expand Down Expand Up @@ -493,6 +503,62 @@ BackupJob.defaultProps = {

// ===================================================================

export const Schedule = decorate([
addSubscriptions(({ id }) => ({
schedule: cb => subscribeSchedules(schedules => cb(schedules.find(schedule => schedule.id === id))),
backupJobs: subscribeBackupNgJobs,
metadataBackupJobs: subscribeMetadataBackupJobs,
mirrorBackupJobs: subscribeMirrorBackupJobs,
})),
provideState({
initialState: () => ({}),
computed: {
job: (_, { backupJobs = [], metadataBackupJobs = [], mirrorBackupJobs = [], schedule }) =>
schedule && [...backupJobs, ...metadataBackupJobs, ...mirrorBackupJobs].find(job => job.id === schedule.jobId),
},
}),
injectState,
({ id, schedule, showJob, showState, state: { job } }) => {
if (schedule === undefined) {
return unknowItem(id, 'schedule')
}

const isEnabled = schedule.enabled
const scheduleName = schedule.name.trim()
const jobName = job?.name.trim()

return (
<span>
{showState && (
<span className={`mr-1 tag tag-${isEnabled ? 'success' : 'danger'}`}>
{isEnabled ? _('stateEnabled') : _('stateDisabled')}
</span>
)}
<span>{scheduleName === '' ? <em>{_('unnamedSchedule')}</em> : scheduleName}</span>
{showJob && (
<span>
{' '}
({job ? jobName === '' ? <em>{_('unnamedJob')}</em> : jobName : unknowItem(schedule.jobId, 'job')})
</span>
)}
</span>
)
},
])

Schedule.propTypes = {
id: PropTypes.string.isRequired,
showState: PropTypes.bool,
showJob: PropTypes.bool,
}

Schedule.defaultProps = {
showState: true,
showJob: true,
}

// ===================================================================

export const Vgpu = connectStore(() => ({
vgpuType: createGetObject((_, props) => props.vgpu.vgpuType),
}))(({ vgpu, vgpuType }) => (
Expand Down Expand Up @@ -684,18 +750,8 @@ const xoItemToRender = {

PCI: props => <Pci {...props} self />,

schedule: schedule => {
const isEnabled = schedule.enabled
const scheduleName = schedule.name.trim()
return (
<span>
<span className={`mr-1 tag tag-${isEnabled ? 'success' : 'danger'}`}>
{isEnabled ? _('stateEnabled') : _('stateDisabled')}
</span>
<span>{scheduleName === '' ? <em>{_('unnamedSchedule')}</em> : scheduleName}</span>
</span>
)
},
schedule: props => <Schedule {...props} />,

job: job => <spans>{job.name}</spans>,
}

Expand Down
1 change: 1 addition & 0 deletions packages/xo-web/src/common/select-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ class GenericSelect extends React.Component {
: undefined,
memoryFree: option.xoItem.type === 'host' || undefined,
showNetwork: true,
...(this.props.optionProps ?? {}),
})}
</span>
)
Expand Down
4 changes: 4 additions & 0 deletions packages/xo-web/src/icons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,10 @@
@extend .fa;
@extend .fa-eye;
}
&-sequence {
@extend .fa;
@extend .fa-list-ol;
}
&-new {
@extend .fa;
@extend .fa-plus;
Expand Down
33 changes: 27 additions & 6 deletions packages/xo-web/src/xo-app/backup/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ import Icon from 'icon'
import React from 'react'
import { injectState, provideState } from 'reaclette'
import { find, groupBy, keyBy } from 'lodash'
import { subscribeBackupNgJobs, subscribeMetadataBackupJobs, subscribeMirrorBackupJobs, subscribeSchedules } from 'xo'
import {
subscribeBackupNgJobs,
subscribeJobs,
subscribeMetadataBackupJobs,
subscribeMirrorBackupJobs,
subscribeSchedules,
} from 'xo'

import Metadata from './new/metadata'
import New from './new'
import NewMirrorBackup from './new/mirror'
import NewSequence from './new/sequence'

export default decorate([
addSubscriptions({
jobs: subscribeBackupNgJobs,
jobs: subscribeJobs,
backupJobs: subscribeBackupNgJobs,
metadataJobs: subscribeMetadataBackupJobs,
mirrorBackupJobs: subscribeMirrorBackupJobs,
schedulesByJob: cb =>
Expand All @@ -24,11 +32,20 @@ export default decorate([
}),
provideState({
computed: {
job: (_, { jobs, metadataJobs, mirrorBackupJobs, routeParams: { id } }) =>
defined(find(jobs, { id }), find(metadataJobs, { id }), find(mirrorBackupJobs, { id })),
job: (_, { backupJobs, jobs, metadataJobs, mirrorBackupJobs, routeParams: { id } }) =>
defined(
find(jobs, { id }),
find(backupJobs, { id }),
find(metadataJobs, { id }),
find(mirrorBackupJobs, { id })
),
schedules: (_, { schedulesByJob, routeParams: { id } }) => schedulesByJob && keyBy(schedulesByJob[id], 'id'),
loading: (_, props) =>
props.jobs === undefined || props.metadataJobs === undefined || props.schedulesByJob === undefined,
props.jobs === undefined ||
props.backupJobs === undefined ||
props.metadataJobs === undefined ||
props.mirrorBackupJobs === undefined ||
props.schedulesByJob === undefined,
},
}),
injectState,
Expand All @@ -43,7 +60,11 @@ export default decorate([
<New job={job} schedules={schedules} />
) : job.type === 'mirrorBackup' ? (
<NewMirrorBackup job={job} schedules={schedules} />
) : (
) : job.type === 'metadataBackup' ? (
<Metadata job={job} schedules={schedules} />
) : job.type === 'call' && job.method === 'schedule.runSequence' ? (
<NewSequence job={job} schedule={schedules[Object.keys(schedules)[0]]} />
) : (
'Unknown job type'
),
])
11 changes: 10 additions & 1 deletion packages/xo-web/src/xo-app/backup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import { subscribeBackupNgJobs, subscribeSchedules } from 'xo'
import Edit from './edit'
import FileRestore from './file-restore'
import Health from './health'
import NewVmBackup, { NewMetadataBackup, NewMirrorBackup } from './new'
import NewVmBackup, { NewMetadataBackup, NewMirrorBackup, NewSequence } from './new'
import Overview from './overview'
import Restore, { RestoreMetadata } from './restore'
import Sequences from './sequences'

import Page from '../page'

Expand Down Expand Up @@ -55,6 +56,9 @@ const HEADER = (
<NavLink to='/backup/overview'>
<Icon icon='menu-backup-overview' /> {_('backupOverviewPage')}
</NavLink>
<NavLink to='/backup/sequences'>
<Icon icon='menu-backup-sequence' /> {_('sequences')}
</NavLink>
<NavLink to='/backup/new'>
<Icon icon='menu-backup-new' /> {_('backupNewPage')}
</NavLink>
Expand Down Expand Up @@ -89,6 +93,9 @@ const ChooseBackupType = () => (
<p>
<ButtonLink to='backup/new/metadata'>
<Icon icon='database' /> {_('backupMetadata')}
</ButtonLink>{' '}
<ButtonLink to='backup/new/sequence'>
<Icon icon='menu-backup-sequence' /> {_('sequence')}
</ButtonLink>
</p>
</CardBlock>
Expand All @@ -104,7 +111,9 @@ export default routes('overview', {
'new/vms': NewVmBackup,
'new/mirror': NewMirrorBackup,
'new/metadata': NewMetadataBackup,
'new/sequence': NewSequence,
overview: Overview,
sequences: Sequences,
restore: Restore,
'restore/metadata': RestoreMetadata,
'file-restore': FileRestore,
Expand Down
1 change: 1 addition & 0 deletions packages/xo-web/src/xo-app/backup/new/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { canDeltaBackup, constructPattern, destructPattern, FormFeedback, FormGr

export NewMetadataBackup from './metadata'
export NewMirrorBackup from './mirror'
export NewSequence from './sequence'

// ===================================================================

Expand Down
Loading

0 comments on commit 0802ee9

Please sign in to comment.