Skip to content

Commit

Permalink
feat(xo-web/backup): long-term retention settings (#8141)
Browse files Browse the repository at this point in the history
  • Loading branch information
pdonias authored and stephane-m-dev committed Nov 28, 2024
1 parent c419351 commit 2ce6b05
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Add 404 page (PR [#8145](https://github.com/vatesfr/xen-orchestra/pull/8145))
- [backups] Handle VTPM content on incremental backup/replication/restore, including differential restore (PR [#8139](https://github.com/vatesfr/xen-orchestra/pull/8139))
- [Host/Advanced] Allow bypassing blocked migration in maintenance mode (PR [#8149](https://github.com/vatesfr/xen-orchestra/pull/8149))
- [Backup] Long-term retention (GFS) (PRs [#7999](https://github.com/vatesfr/xen-orchestra/pull/7999) [#8141](https://github.com/vatesfr/xen-orchestra/pull/8141))
- [VM/Advanced] Add ability to block/unblock migration (PR [#8129](https://github.com/vatesfr/xen-orchestra/pull/8129))

### Bug fixes
Expand Down
8 changes: 8 additions & 0 deletions packages/xo-server/src/api/backup-ng.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const SCHEMA_SETTINGS = {
minimum: 0,
optional: true,
},
longTermRetention: {
type: 'object',
optional: true,
},
maxExportRate: {
type: 'number',
minimum: 0,
Expand All @@ -40,6 +44,10 @@ const SCHEMA_SETTINGS = {
type: 'boolean',
optional: true,
},
timezone: {
type: 'string',
optional: true,
},
},
additionalProperties: true,
},
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 @@ -623,6 +623,11 @@ const messages = {
cbtDestroySnapshotDataDisabledInformation:
'Snapshot data can be purged only when NBD is enabled and rolling snapshot is not used',
shorterBackupReports: 'Shorter backup reports',
longTermRetention: 'Long-term retention of backups',
numberOfDailyBackupsKept: 'Number of daily backups kept',
numberOfWeeklyBackupsKept: 'Number of weekly backups kept',
numberOfMonthlyBackupsKept: 'Number of monthly backups kept',
numberOfYearlyBackupsKept: 'Number of yearly backups kept',

// ------ New Remote -----
newRemote: 'New file system remote',
Expand Down
87 changes: 86 additions & 1 deletion packages/xo-web/src/xo-app/backup/new/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ export NewSequence from './sequence'
// ===================================================================

const DEFAULT_RETENTION = 1
const DEFAULT_TIMEZONE = moment.tz.guess()
const DEFAULT_SCHEDULE = {
copyRetention: DEFAULT_RETENTION,
exportRetention: DEFAULT_RETENTION,
snapshotRetention: DEFAULT_RETENTION,
cron: '0 0 * * *',
timezone: moment.tz.guess(),
timezone: DEFAULT_TIMEZONE,
}
const RETENTION_LIMIT = 50

Expand Down Expand Up @@ -273,10 +274,20 @@ const New = decorate([
}
}

if (settings[''] === undefined) {
settings[''] = { __proto__: null }
}

if (!state.backupMode && !state.deltaMode) {
delete settings[''].longTermRetention
}

if (settings['']?.maxExportRate <= 0) {
settings[''].maxExportRate = undefined
}

settings[''].timezone = DEFAULT_TIMEZONE

await createBackupNgJob({
name: state.name,
mode: state.isDelta ? 'delta' : 'full',
Expand Down Expand Up @@ -351,10 +362,20 @@ const New = decorate([
snapshotMode: state.snapshotMode,
}).toObject()

if (normalizedSettings[''] === undefined) {
normalizedSettings[''] = { __proto__: null }
}

if (!state.backupMode && !state.deltaMode) {
delete normalizedSettings[''].longTermRetention
}

if (normalizedSettings['']?.maxExportRate <= 0) {
normalizedSettings[''].maxExportRate = undefined
}

normalizedSettings[''].timezone = DEFAULT_TIMEZONE

await editBackupNgJob({
id: props.job.id,
name: state.name,
Expand Down Expand Up @@ -599,6 +620,18 @@ const New = decorate([
reportRecipients: (reportRecipients.splice(key, 1), reportRecipients),
})
},
setLongTermRetention({ setGlobalSettings }, retention, granularity) {
const { propSettings, settings = propSettings } = this.state
const longTermRetention = settings.getIn(['', 'longTermRetention']) ?? {}

if (retention > 0) {
longTermRetention[granularity] = { retention, settings: {} } // settings will be used for advanced configuration in the future
} else {
delete longTermRetention[granularity]
}

setGlobalSettings({ longTermRetention: isEmpty(longTermRetention) ? undefined : longTermRetention })
},
setReportWhen:
({ setGlobalSettings }, { value }) =>
() => {
Expand Down Expand Up @@ -679,6 +712,10 @@ const New = decorate([
inputNRetriesVmBackupFailures: generateId,
inputBackupReportTplId: generateId,
inputTimeoutId: generateId,
inputLongTermRetentionDaily: generateId,
inputLongTermRetentionWeekly: generateId,
inputLongTermRetentionMonthly: generateId,
inputLongTermRetentionYearly: generateId,

// In order to keep the user preference, the offline backup is kept in the DB
// and it's considered active only when the full mode is enabled
Expand Down Expand Up @@ -789,6 +826,7 @@ const New = decorate([
checkpointSnapshot,
concurrency,
fullInterval,
longTermRetention = {},
maxExportRate,
nbdConcurrency = 1,
nRetriesVmBackupFailures = 0,
Expand Down Expand Up @@ -1244,6 +1282,53 @@ const New = decorate([
</CardBlock>
</Card>
<Schedules />
{(state.backupMode || state.deltaMode) && (
<Card>
<CardHeader>{_('longTermRetention')}</CardHeader>
<CardBlock>
<FormGroup>
<label htmlFor={state.inputLongTermRetentionDaily}>
<strong>{_('numberOfDailyBackupsKept')}</strong>
</label>
<Number
id={state.inputLongTermRetentionDaily}
onChange={value => effects.setLongTermRetention(value, 'daily')}
value={longTermRetention.daily?.retention}
/>
</FormGroup>
<FormGroup>
<label htmlFor={state.inputLongTermRetentionWeekly}>
<strong>{_('numberOfWeeklyBackupsKept')}</strong>
</label>
<Number
id={state.inputLongTermRetentionWeekly}
onChange={value => effects.setLongTermRetention(value, 'weekly')}
value={longTermRetention.weekly?.retention}
/>
</FormGroup>
<FormGroup>
<label htmlFor={state.inputLongTermRetentionMonthly}>
<strong>{_('numberOfMonthlyBackupsKept')}</strong>
</label>
<Number
id={state.inputLongTermRetentionMonthly}
onChange={value => effects.setLongTermRetention(value, 'monthly')}
value={longTermRetention.monthly?.retention}
/>
</FormGroup>
<FormGroup>
<label htmlFor={state.inputLongTermRetentionYearly}>
<strong>{_('numberOfYearlyBackupsKept')}</strong>
</label>
<Number
id={state.inputLongTermRetentionYearly}
onChange={value => effects.setLongTermRetention(value, 'yearly')}
value={longTermRetention.yearly?.retention}
/>
</FormGroup>
</CardBlock>
</Card>
)}
</Col>
</Row>
<Row>
Expand Down

0 comments on commit 2ce6b05

Please sign in to comment.