parent
c9991655cf
commit
b0a152612e
@ -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(' ')}`,
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
@ -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:',
|
||||
|
@ -369,6 +369,12 @@
|
||||
@extend .xo-status-running;
|
||||
}
|
||||
|
||||
&-skipped {
|
||||
@extend .fa;
|
||||
@extend .fa-circle;
|
||||
@extend .xo-status-suspended;
|
||||
}
|
||||
|
||||
&-halted {
|
||||
@extend .fa;
|
||||
@extend .fa-circle;
|
||||
|
@ -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.',
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user