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_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(' ')}`,
}),
])
}

View File

@ -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:',

View File

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

View File

@ -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.',

View File

@ -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 {
</a>
</Tooltip>
) : (
<span className='text-danger'>
<Icon icon='error' />{' '}
<span
className={
isSkippedError(error) ? 'text-info' : 'text-danger'
}
>
<Icon
icon={isSkippedError(error) ? 'alarm' : 'error'}
/>{' '}
{error.message !== undefined ? (
<strong>{error.message}</strong>
) : (
@ -310,10 +325,12 @@ const LOG_COLUMNS = [
<span>
{log.status === 'finished' && (
<span
className={classnames('tag', {
'tag-success': !log.hasErrors,
'tag-danger': log.hasErrors,
})}
className={classnames(
'tag',
log.hasErrors
? 'tag-danger'
: log.callSkipped ? 'tag-info' : 'tag-success'
)}
>
{_('jobFinished')}
</span>
@ -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