From b0a152612eabd6faa475170f2bcbae97b24a06f5 Mon Sep 17 00:00:00 2001 From: badrAZ Date: Wed, 14 Feb 2018 11:22:30 +0100 Subject: [PATCH] feat(backup): new "skipped" state (#2612) Fixes #2591 --- .../xo-server-backup-reports/src/index.js | 70 ++++++++++++++----- packages/xo-web/src/common/intl/messages.js | 1 + packages/xo-web/src/icons.scss | 6 ++ .../xo-web/src/xo-app/backup/new/index.js | 3 +- packages/xo-web/src/xo-app/logs/index.js | 44 +++++++++--- 5 files changed, 94 insertions(+), 30 deletions(-) diff --git a/packages/xo-server-backup-reports/src/index.js b/packages/xo-server-backup-reports/src/index.js index 827ba5991..c37019885 100644 --- a/packages/xo-server-backup-reports/src/index.js +++ b/packages/xo-server-backup-reports/src/index.js @@ -34,6 +34,7 @@ export const configurationSchema = { // =================================================================== const ICON_FAILURE = '🚨' +const ICON_SKIPPED = '⏩' const ICON_SUCCESS = '✔' const DATE_FORMAT = 'dddd, MMMM Do YYYY, h:mm:ss a' @@ -65,6 +66,13 @@ const logError = e => { console.error('backup report error:', e) } +const UNHEALTHY_VDI_CHAIN_ERROR = 'unhealthy VDI chain' +const NO_SUCH_OBJECT_ERROR = 'no such object' + +const isSkippedError = error => + error.message === UNHEALTHY_VDI_CHAIN_ERROR || + error.message === NO_SUCH_OBJECT_ERROR + class BackupReportsXoPlugin { constructor (xo) { this._xo = xo @@ -117,15 +125,16 @@ class BackupReportsXoPlugin { return } - const reportOnFailure = - reportWhen === 'fail' || reportWhen === 'failure' // xo-web < 5 // xo-web >= 5 + const reportOnFailure = reportWhen === 'fail' || reportWhen === 'failure' // xo-web < 5 // xo-web >= 5 let globalMergeSize = 0 let globalTransferSize = 0 let nFailures = 0 + let nSkipped = 0 const failedBackupsText = [] const nagiosText = [] + const skippedBackupsText = [] const successfulBackupText = [] const formatDate = createDateFormater(status.timezone) @@ -151,15 +160,27 @@ class BackupReportsXoPlugin { const { error } = call if (error !== undefined) { - ++nFailures - const { message } = error - failedBackupsText.push(...text, `- **Error**: ${message}`, '') + if (isSkippedError(error)) { + ++nSkipped + skippedBackupsText.push(...text, `- **Reason**: ${message}`, '') - nagiosText.push( - `[ ${vm !== undefined ? vm.name_label : 'undefined'} : ${message} ]` - ) + nagiosText.push( + `[(Skipped) ${ + vm !== undefined ? vm.name_label : 'undefined' + } : ${message} ]` + ) + } else { + ++nFailures + failedBackupsText.push(...text, `- **Error**: ${message}`, '') + + nagiosText.push( + `[(Failed) ${ + vm !== undefined ? vm.name_label : 'undefined' + } : ${message} ]` + ) + } } else if (!reportOnFailure) { const { returnedValue } = call if (returnedValue != null) { @@ -190,7 +211,7 @@ class BackupReportsXoPlugin { } }) - const globalSuccess = nFailures === 0 + const globalSuccess = nFailures === 0 && nSkipped === 0 if (reportOnFailure && globalSuccess) { return } @@ -198,10 +219,13 @@ class BackupReportsXoPlugin { const { end, start } = status const { tag } = oneCall.params const duration = end - start - const nSuccesses = nCalls - nFailures + const nSuccesses = nCalls - nFailures - nSkipped + const globalStatus = globalSuccess + ? `Success` + : nFailures !== 0 ? `Failure` : `Skipped` let markdown = [ - `## Global status: ${globalSuccess ? `Success` : `Failure`}`, + `## Global status: ${globalStatus}`, '', `- **Type**: ${formatMethod(method)}`, `- **Start time**: ${formatDate(start)}`, @@ -227,6 +251,16 @@ class BackupReportsXoPlugin { ) } + if (nSkipped !== 0) { + markdown.push( + '---', + '', + `## ${nSkipped} Skipped`, + '', + ...skippedBackupsText + ) + } + if (nSuccesses !== 0 && !reportOnFailure) { markdown.push( '---', @@ -246,10 +280,10 @@ class BackupReportsXoPlugin { xo.sendEmail !== undefined && xo.sendEmail({ to: this._mailsReceivers, - subject: `[Xen Orchestra] ${ - globalSuccess ? 'Success' : 'Failure' - } − Backup report for ${tag} ${ - globalSuccess ? ICON_SUCCESS : ICON_FAILURE + subject: `[Xen Orchestra] ${globalStatus} − Backup report for ${tag} ${ + globalSuccess + ? ICON_SUCCESS + : nFailures !== 0 ? ICON_FAILURE : ICON_SKIPPED }`, markdown, }), @@ -267,9 +301,9 @@ class BackupReportsXoPlugin { status: globalSuccess ? 0 : 2, message: globalSuccess ? `[Xen Orchestra] [Success] Backup report for ${tag}` - : `[Xen Orchestra] [Failure] Backup report for ${tag} - VMs : ${nagiosText.join( - ' ' - )}`, + : `[Xen Orchestra] [${ + nFailures !== 0 ? 'Failure' : 'Skipped' + }] Backup report for ${tag} - VMs : ${nagiosText.join(' ')}`, }), ]) } diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index 8880ba7c3..9e788944a 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -247,6 +247,7 @@ const messages = { backupEditNotFoundMessage: 'Missing required info for edition', successfulJobCall: 'Successful', failedJobCall: 'Failed', + jobCallSkipped: 'Skipped', jobCallInProgess: 'In progress', jobTransferredDataSize: 'Transfer size:', jobTransferredDataSpeed: 'Transfer speed:', diff --git a/packages/xo-web/src/icons.scss b/packages/xo-web/src/icons.scss index 53b2b29c3..ae3b1c84a 100644 --- a/packages/xo-web/src/icons.scss +++ b/packages/xo-web/src/icons.scss @@ -369,6 +369,12 @@ @extend .xo-status-running; } + &-skipped { + @extend .fa; + @extend .fa-circle; + @extend .xo-status-suspended; + } + &-halted { @extend .fa; @extend .fa-circle; diff --git a/packages/xo-web/src/xo-app/backup/new/index.js b/packages/xo-web/src/xo-app/backup/new/index.js index a055e5c97..c75bf2d55 100644 --- a/packages/xo-web/src/xo-app/backup/new/index.js +++ b/packages/xo-web/src/xo-app/backup/new/index.js @@ -130,7 +130,8 @@ const COMMON_SCHEMA = { }, _reportWhen: { default: 'failure', - enum: ['never', 'always', 'failure'], // FIXME: can't translate + enum: ['never', 'always', 'failure'], + enumNames: ['never', 'always', 'failure or skipped'], // FIXME: can't translate title: _('editBackupReportTitle'), description: [ 'When to send reports.', diff --git a/packages/xo-web/src/xo-app/logs/index.js b/packages/xo-web/src/xo-app/logs/index.js index a7ed5b935..437ad7030 100644 --- a/packages/xo-web/src/xo-app/logs/index.js +++ b/packages/xo-web/src/xo-app/logs/index.js @@ -59,7 +59,9 @@ class JobReturn extends Component { const JobCallStateInfos = ({ end, error }) => { const [icon, tooltip] = error !== undefined - ? ['halted', 'failedJobCall'] + ? isSkippedError(error) + ? ['skipped', 'jobCallSkipped'] + : ['halted', 'failedJobCall'] : end !== undefined ? ['running', 'successfulJobCall'] : ['busy', 'jobCallInProgess'] @@ -102,23 +104,30 @@ const JobDataInfos = ({ ) const CALL_FILTER_OPTIONS = [ - { label: 'successfulJobCall', value: 'success' }, + { label: 'allJobCalls', value: 'all' }, { label: 'failedJobCall', value: 'error' }, { label: 'jobCallInProgess', value: 'running' }, - { label: 'allJobCalls', value: 'all' }, + { label: 'jobCallSkipped', value: 'skipped' }, + { label: 'successfulJobCall', value: 'success' }, ] const PREDICATES = { all: () => true, - error: call => call.error !== undefined, + skipped: call => call.error !== undefined && isSkippedError(call.error), + error: call => call.error !== undefined && !isSkippedError(call.error), running: call => call.end === undefined && call.error === undefined, success: call => call.end !== undefined && call.error === undefined, } const UNHEALTHY_VDI_CHAIN_ERROR = 'unhealthy VDI chain' +const NO_SUCH_OBJECT_ERROR = 'no such object' const UNHEALTHY_VDI_CHAIN_LINK = 'https://xen-orchestra.com/docs/backup_troubleshooting.html#vdi-chain-protection' +const isSkippedError = error => + error.message === UNHEALTHY_VDI_CHAIN_ERROR || + error.message === NO_SUCH_OBJECT_ERROR + class Log extends BaseComponent { state = { filter: 'all', @@ -225,8 +234,14 @@ class Log extends BaseComponent { ) : ( - - {' '} + + {' '} {error.message !== undefined ? ( {error.message} ) : ( @@ -310,10 +325,12 @@ const LOG_COLUMNS = [ {log.status === 'finished' && ( {_('jobFinished')} @@ -361,6 +378,7 @@ export default class LogList extends Component { this.filters = { onError: 'hasErrors?', successful: 'status:finished !hasErrors?', + jobCallSkipped: '!hasErrors? callSkipped?', } } @@ -409,7 +427,11 @@ export default class LogList extends Component { if (data.error) { call.error = data.error - entry.hasErrors = true + if (isSkippedError(data.error)) { + entry.callSkipped = true + } else { + entry.hasErrors = true + } entry.meta = 'error' } else { call.returnedValue = data.returnedValue