Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backup/overview): ability to migrate a backup to a backup NG #2801

Merged
merged 4 commits into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/xo-server/src/api/backup-ng.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ createJob.params = {
},
}

export function migrateLegacyJob ({ id }) {
return this.migrateLegacyBackupJob(id)
}
migrateLegacyJob.permission = 'admin'
migrateLegacyJob.params = {
id: {
type: 'string',
},
}

export function deleteJob ({ id }) {
return this.deleteBackupNgJob(id)
}
Expand Down
18 changes: 15 additions & 3 deletions packages/xo-server/src/xo-mixins/backups-ng/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { basename, dirname } from 'path'
import { isEmpty, last, mapValues, noop, values } from 'lodash'
import { timeout as pTimeout } from 'promise-toolbox'

import { type Executor, type Job } from '../jobs'
import { type CallJob, type Executor, type Job } from '../jobs'
import { type Schedule } from '../scheduling'

import type RemoteHandler from '../../remote-handlers/abstract'
Expand All @@ -31,6 +31,8 @@ import {
readVhdMetadata,
} from '../../vhd-merge'

import { translateLegacyJob } from './migration'

type Mode = 'full' | 'delta'

type Settings = {|
Expand Down Expand Up @@ -305,8 +307,10 @@ export default class BackupNg {
getAllSchedules: () => Promise<Schedule[]>,
getRemoteHandler: (id: string) => Promise<RemoteHandler>,
getXapi: (id: string) => Xapi,
getJob: (id: string, 'backup') => Promise<BackupJob>,
updateJob: ($Shape<BackupJob>) => Promise<BackupJob>,
getJob: ((id: string, 'backup') => Promise<BackupJob>) &
((id: string, 'call') => Promise<CallJob>),
updateJob: (($Shape<BackupJob>, ?boolean) => Promise<BackupJob>) &
(($Shape<CallJob>, ?boolean) => Promise<CallJob>),
removeJob: (id: string) => Promise<void>,
worker: $Dict<any>,
}
Expand Down Expand Up @@ -540,6 +544,14 @@ export default class BackupNg {
return backupsByVmByRemote
}

async migrateLegacyBackupJob (jobId: string) {
const [job, schedules] = await Promise.all([
this._app.getJob(jobId, 'call'),
this._app.getAllSchedules(),
])
await this._app.updateJob(translateLegacyJob(job, schedules), false)
}

// High:
// - [ ] clones of replicated VMs should not be garbage collected
// - if storing uuids in source VM, how to detect them if the source is
Expand Down
54 changes: 23 additions & 31 deletions packages/xo-server/src/xo-mixins/backups-ng/migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const methods = {
}),
}

const parseParamsVector = vector => {
const parseParamsVector = (vector: any) => {
assert.strictEqual(vector.type, 'crossProduct')
const { items } = vector
assert.strictEqual(items.length, 2)
Expand Down Expand Up @@ -120,34 +120,26 @@ const parseParamsVector = vector => {
return { ...params, vms }
}

export const translateOldJobs = async (app: any): Promise<Array<BackupJob>> => {
const backupJobs: Array<BackupJob> = []
const [jobs, schedules] = await Promise.all([
app.getAllJobs('call'),
app.getAllSchedules(),
])
jobs.forEach(job => {
try {
const { id } = job
let method, schedule
if (
job.type === 'call' &&
(method = methods[job.method]) !== undefined &&
(schedule = schedules.find(_ => _.jobId === id)) !== undefined
) {
const params = parseParamsVector(job.paramsVector)
backupJobs.push({
id,
name: params.tag || job.name,
type: 'backup',
userId: job.userId,
// $FlowFixMe `method` is initialized but Flow fails to see this
...method(job, params, schedule),
})
}
} catch (error) {
console.warn('translateOldJobs', job, error)
}
})
return backupJobs
export const translateLegacyJob = (
job: CallJob,
schedules: Schedule[]
): BackupJob => {
const { id } = job
let method, schedule
if (
job.type !== 'call' ||
(method = methods[job.method]) === undefined ||
(schedule = schedules.find(_ => _.jobId === id)) === undefined
) {
throw new Error(`cannot convert job ${job.id}`)
}
const params = parseParamsVector(job.paramsVector)
return {
id,
name: params.tag || job.name,
type: 'backup',
userId: job.userId,
// $FlowFixMe `method` is initialized but Flow fails to see this
...method(job, params, schedule),
}
}
25 changes: 14 additions & 11 deletions packages/xo-server/src/xo-mixins/jobs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,40 @@ export type Job = {
id: string,
name: string,
type: string,
userId: string
userId: string,
}

type ParamsVector =
| {|
items: Array<Object>,
type: 'crossProduct'
type: 'crossProduct',
|}
| {|
mapping: Object,
type: 'extractProperties',
value: Object
value: Object,
|}
| {|
pattern: Pattern,
type: 'fetchObjects'
type: 'fetchObjects',
|}
| {|
collection: Object,
iteratee: Function,
paramName?: string,
type: 'map'
type: 'map',
|}
| {|
type: 'set',
values: any
values: any,
|}

export type CallJob = {|
...$Exact<Job>,
method: string,
paramsVector: ParamsVector,
timeout?: number,
type: 'call'
type: 'call',
|}

export type Executor = ({|
Expand All @@ -64,7 +64,7 @@ export type Executor = ({|
logger: Logger,
runJobId: string,
schedule?: Schedule,
session: Object
session: Object,
|}) => Promise<any>

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -180,9 +180,12 @@ export default class Jobs {
return this._jobs.create(job)
}

async updateJob ({ id, ...props }: $Shape<Job>) {
const job = await this.getJob(id)
patch(job, props)
async updateJob (job: $Shape<Job>, merge: boolean = true) {
if (merge) {
const { id, ...props } = job
job = await this.getJob(id)
patch(job, props)
}
return /* await */ this._jobs.save(job)
}

Expand Down
3 changes: 3 additions & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ const messages = {
noMatchingVms: 'There are no matching VMs!',
allMatchingVms: '{icon} See the matching VMs ({nMatchingVms, number})',
backupOwner: 'Backup owner',
migrateBackupSchedule: 'Migrate to backup NG',
migrateBackupScheduleMessage:
'This will migrate this backup to a backup NG. This operation is not reversible. Do you want to continue?',

// ------ New backup -----
newBackupSelection: 'Select your backup type:',
Expand Down
6 changes: 6 additions & 0 deletions packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,12 @@ export const deleteBackupSchedule = async schedule => {
subscribeJobs.forceRefresh()
}

export const migrateBackupSchedule = id =>
confirm({
title: _('migrateBackupSchedule'),
body: _('migrateBackupScheduleMessage'),
}).then(() => _call('backupNg.migrateLegacyJob', { id: resolveId(id) }))

export const deleteSchedule = schedule =>
_call('schedule.delete', { id: resolveId(schedule) })::tap(
subscribeSchedules.forceRefresh
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 @@ -285,6 +285,10 @@
@extend .fa;
@extend .fa-cogs;
}
&-migrate-job {
@extend .fa;
@extend .fa-share;
}

// VM
&-vm {
Expand Down
17 changes: 11 additions & 6 deletions packages/xo-web/src/xo-app/backup-ng/new/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ import { FormGroup, getRandomId, Input, Ul, Li } from './utils'

const normaliseTagValues = values => resolveIds(values).map(value => [value])

const constructPattern = values => ({
id: {
__or: resolveIds(values),
},
})
const constructPattern = values =>
values.length === 1
? {
id: resolveId(values[0]),
}
: {
id: {
__or: resolveIds(values),
},
}

const destructPattern = pattern => pattern.id.__or
const destructPattern = pattern => pattern.id.__or || [pattern.id]

const destructVmsPattern = pattern =>
pattern.id === undefined
Expand Down
7 changes: 7 additions & 0 deletions packages/xo-web/src/xo-app/backup/overview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
deleteBackupSchedule,
disableSchedule,
enableSchedule,
migrateBackupSchedule,
runJob,
subscribeJobs,
subscribeSchedules,
Expand Down Expand Up @@ -115,6 +116,12 @@ const JOB_COLUMNS = [
handler={runJob}
handlerParam={schedule.jobId}
/>
<ActionRowButton
icon='migrate-job'
btnStyle='danger'
handler={migrateBackupSchedule}
handlerParam={schedule.jobId}
/>
</ButtonGroup>
</fieldset>
),
Expand Down