feat(backup): new "skipped" state (#2612)

Fixes #2591
This commit is contained in:
badrAZ 2018-02-14 11:22:30 +01:00 committed by Julien Fontanet
parent c9991655cf
commit b0a152612e
5 changed files with 94 additions and 30 deletions

View File

@ -34,6 +34,7 @@ export const configurationSchema = {
// =================================================================== // ===================================================================
const ICON_FAILURE = '🚨' const ICON_FAILURE = '🚨'
const ICON_SKIPPED = '⏩'
const ICON_SUCCESS = '✔' const ICON_SUCCESS = '✔'
const DATE_FORMAT = 'dddd, MMMM Do YYYY, h:mm:ss a' const DATE_FORMAT = 'dddd, MMMM Do YYYY, h:mm:ss a'
@ -65,6 +66,13 @@ const logError = e => {
console.error('backup report error:', 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 { class BackupReportsXoPlugin {
constructor (xo) { constructor (xo) {
this._xo = xo this._xo = xo
@ -117,15 +125,16 @@ class BackupReportsXoPlugin {
return return
} }
const reportOnFailure = const reportOnFailure = reportWhen === 'fail' || reportWhen === 'failure' // xo-web < 5 // xo-web >= 5
reportWhen === 'fail' || reportWhen === 'failure' // xo-web < 5 // xo-web >= 5
let globalMergeSize = 0 let globalMergeSize = 0
let globalTransferSize = 0 let globalTransferSize = 0
let nFailures = 0 let nFailures = 0
let nSkipped = 0
const failedBackupsText = [] const failedBackupsText = []
const nagiosText = [] const nagiosText = []
const skippedBackupsText = []
const successfulBackupText = [] const successfulBackupText = []
const formatDate = createDateFormater(status.timezone) const formatDate = createDateFormater(status.timezone)
@ -151,15 +160,27 @@ class BackupReportsXoPlugin {
const { error } = call const { error } = call
if (error !== undefined) { if (error !== undefined) {
++nFailures
const { message } = error const { message } = error
failedBackupsText.push(...text, `- **Error**: ${message}`, '') if (isSkippedError(error)) {
++nSkipped
skippedBackupsText.push(...text, `- **Reason**: ${message}`, '')
nagiosText.push( nagiosText.push(
`[ ${vm !== undefined ? vm.name_label : 'undefined'} : ${message} ]` `[(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) { } else if (!reportOnFailure) {
const { returnedValue } = call const { returnedValue } = call
if (returnedValue != null) { if (returnedValue != null) {
@ -190,7 +211,7 @@ class BackupReportsXoPlugin {
} }
}) })
const globalSuccess = nFailures === 0 const globalSuccess = nFailures === 0 && nSkipped === 0
if (reportOnFailure && globalSuccess) { if (reportOnFailure && globalSuccess) {
return return
} }
@ -198,10 +219,13 @@ class BackupReportsXoPlugin {
const { end, start } = status const { end, start } = status
const { tag } = oneCall.params const { tag } = oneCall.params
const duration = end - start const duration = end - start
const nSuccesses = nCalls - nFailures const nSuccesses = nCalls - nFailures - nSkipped
const globalStatus = globalSuccess
? `Success`
: nFailures !== 0 ? `Failure` : `Skipped`
let markdown = [ let markdown = [
`## Global status: ${globalSuccess ? `Success` : `Failure`}`, `## Global status: ${globalStatus}`,
'', '',
`- **Type**: ${formatMethod(method)}`, `- **Type**: ${formatMethod(method)}`,
`- **Start time**: ${formatDate(start)}`, `- **Start time**: ${formatDate(start)}`,
@ -227,6 +251,16 @@ class BackupReportsXoPlugin {
) )
} }
if (nSkipped !== 0) {
markdown.push(
'---',
'',
`## ${nSkipped} Skipped`,
'',
...skippedBackupsText
)
}
if (nSuccesses !== 0 && !reportOnFailure) { if (nSuccesses !== 0 && !reportOnFailure) {
markdown.push( markdown.push(
'---', '---',
@ -246,10 +280,10 @@ class BackupReportsXoPlugin {
xo.sendEmail !== undefined && xo.sendEmail !== undefined &&
xo.sendEmail({ xo.sendEmail({
to: this._mailsReceivers, to: this._mailsReceivers,
subject: `[Xen Orchestra] ${ subject: `[Xen Orchestra] ${globalStatus} Backup report for ${tag} ${
globalSuccess ? 'Success' : 'Failure' globalSuccess
} Backup report for ${tag} ${ ? ICON_SUCCESS
globalSuccess ? ICON_SUCCESS : ICON_FAILURE : nFailures !== 0 ? ICON_FAILURE : ICON_SKIPPED
}`, }`,
markdown, markdown,
}), }),
@ -267,9 +301,9 @@ class BackupReportsXoPlugin {
status: globalSuccess ? 0 : 2, status: globalSuccess ? 0 : 2,
message: globalSuccess message: globalSuccess
? `[Xen Orchestra] [Success] Backup report for ${tag}` ? `[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(' ')}`,
}), }),
]) ])
} }

View File

@ -247,6 +247,7 @@ const messages = {
backupEditNotFoundMessage: 'Missing required info for edition', backupEditNotFoundMessage: 'Missing required info for edition',
successfulJobCall: 'Successful', successfulJobCall: 'Successful',
failedJobCall: 'Failed', failedJobCall: 'Failed',
jobCallSkipped: 'Skipped',
jobCallInProgess: 'In progress', jobCallInProgess: 'In progress',
jobTransferredDataSize: 'Transfer size:', jobTransferredDataSize: 'Transfer size:',
jobTransferredDataSpeed: 'Transfer speed:', jobTransferredDataSpeed: 'Transfer speed:',

View File

@ -369,6 +369,12 @@
@extend .xo-status-running; @extend .xo-status-running;
} }
&-skipped {
@extend .fa;
@extend .fa-circle;
@extend .xo-status-suspended;
}
&-halted { &-halted {
@extend .fa; @extend .fa;
@extend .fa-circle; @extend .fa-circle;

View File

@ -130,7 +130,8 @@ const COMMON_SCHEMA = {
}, },
_reportWhen: { _reportWhen: {
default: 'failure', 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'), title: _('editBackupReportTitle'),
description: [ description: [
'When to send reports.', 'When to send reports.',

View File

@ -59,7 +59,9 @@ class JobReturn extends Component {
const JobCallStateInfos = ({ end, error }) => { const JobCallStateInfos = ({ end, error }) => {
const [icon, tooltip] = const [icon, tooltip] =
error !== undefined error !== undefined
? ['halted', 'failedJobCall'] ? isSkippedError(error)
? ['skipped', 'jobCallSkipped']
: ['halted', 'failedJobCall']
: end !== undefined : end !== undefined
? ['running', 'successfulJobCall'] ? ['running', 'successfulJobCall']
: ['busy', 'jobCallInProgess'] : ['busy', 'jobCallInProgess']
@ -102,23 +104,30 @@ const JobDataInfos = ({
) )
const CALL_FILTER_OPTIONS = [ const CALL_FILTER_OPTIONS = [
{ label: 'successfulJobCall', value: 'success' }, { label: 'allJobCalls', value: 'all' },
{ label: 'failedJobCall', value: 'error' }, { label: 'failedJobCall', value: 'error' },
{ label: 'jobCallInProgess', value: 'running' }, { label: 'jobCallInProgess', value: 'running' },
{ label: 'allJobCalls', value: 'all' }, { label: 'jobCallSkipped', value: 'skipped' },
{ label: 'successfulJobCall', value: 'success' },
] ]
const PREDICATES = { const PREDICATES = {
all: () => true, 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, running: call => call.end === undefined && call.error === undefined,
success: call => call.end !== undefined && call.error === undefined, success: call => call.end !== undefined && call.error === undefined,
} }
const UNHEALTHY_VDI_CHAIN_ERROR = 'unhealthy VDI chain' const UNHEALTHY_VDI_CHAIN_ERROR = 'unhealthy VDI chain'
const NO_SUCH_OBJECT_ERROR = 'no such object'
const UNHEALTHY_VDI_CHAIN_LINK = const UNHEALTHY_VDI_CHAIN_LINK =
'https://xen-orchestra.com/docs/backup_troubleshooting.html#vdi-chain-protection' '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 { class Log extends BaseComponent {
state = { state = {
filter: 'all', filter: 'all',
@ -225,8 +234,14 @@ class Log extends BaseComponent {
</a> </a>
</Tooltip> </Tooltip>
) : ( ) : (
<span className='text-danger'> <span
<Icon icon='error' />{' '} className={
isSkippedError(error) ? 'text-info' : 'text-danger'
}
>
<Icon
icon={isSkippedError(error) ? 'alarm' : 'error'}
/>{' '}
{error.message !== undefined ? ( {error.message !== undefined ? (
<strong>{error.message}</strong> <strong>{error.message}</strong>
) : ( ) : (
@ -310,10 +325,12 @@ const LOG_COLUMNS = [
<span> <span>
{log.status === 'finished' && ( {log.status === 'finished' && (
<span <span
className={classnames('tag', { className={classnames(
'tag-success': !log.hasErrors, 'tag',
'tag-danger': log.hasErrors, log.hasErrors
})} ? 'tag-danger'
: log.callSkipped ? 'tag-info' : 'tag-success'
)}
> >
{_('jobFinished')} {_('jobFinished')}
</span> </span>
@ -361,6 +378,7 @@ export default class LogList extends Component {
this.filters = { this.filters = {
onError: 'hasErrors?', onError: 'hasErrors?',
successful: 'status:finished !hasErrors?', successful: 'status:finished !hasErrors?',
jobCallSkipped: '!hasErrors? callSkipped?',
} }
} }
@ -409,7 +427,11 @@ export default class LogList extends Component {
if (data.error) { if (data.error) {
call.error = data.error call.error = data.error
entry.hasErrors = true if (isSkippedError(data.error)) {
entry.callSkipped = true
} else {
entry.hasErrors = true
}
entry.meta = 'error' entry.meta = 'error'
} else { } else {
call.returnedValue = data.returnedValue call.returnedValue = data.returnedValue