parent
c9991655cf
commit
b0a152612e
@ -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(' ')}`,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -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:',
|
||||||
|
@ -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;
|
||||||
|
@ -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.',
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user