Compare commits

..

1 Commits

Author SHA1 Message Date
Florent Beauchamp
698086f4b2 fix(backups): clean snapshot correctly on failure 2024-02-21 14:38:53 +00:00
8 changed files with 34 additions and 82 deletions

View File

@@ -2,6 +2,7 @@ import assert from 'node:assert'
import groupBy from 'lodash/groupBy.js'
import ignoreErrors from 'promise-toolbox/ignoreErrors'
import { asyncMap } from '@xen-orchestra/async-map'
import { createLogger } from '@xen-orchestra/log'
import { decorateMethodsWith } from '@vates/decorate-with'
import { defer } from 'golike-defer'
import { formatDateTime } from '@xen-orchestra/xapi'
@@ -10,6 +11,8 @@ import { getOldEntries } from '../../_getOldEntries.mjs'
import { Task } from '../../Task.mjs'
import { Abstract } from './_Abstract.mjs'
const { info, warn } = createLogger('xo:backups:AbstractXapi')
export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
constructor({
config,
@@ -171,6 +174,20 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
}
}
// this will delete current snapshot in case of failure
// to ensure any retry will start with a clean state, especially in the case of rolling snapshots
#removeCurrentSnapshotOnFailure() {
if (this._mustDoSnapshot() && this._exportedVm !== undefined) {
info('will delete snapshot on failure', { vm: this._vm, snapshot: this._exportedVm })
assert.notStrictEqual(
this._vm.$ref,
this._exportedVm.$ref,
'there should have a snapshot, but vm and snapshot have the same ref'
)
return this._xapi.VM_destroy(this._exportedVm.$ref)
}
}
async _fetchJobSnapshots() {
const jobId = this._jobId
const vmRef = this._vm.$ref
@@ -271,11 +288,17 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
await this._exportedVm.update_blocked_operations({ pool_migrate, migrate_send })
}
}
} catch (error) {
try {
await this.#removeCurrentSnapshotOnFailure()
} catch (removeSnapshotError) {
warn('fail removing current snapshot', { error: removeSnapshotError })
}
throw error
} finally {
if (startAfter) {
ignoreErrors.call(vm.$callAsync('start', false, false))
}
await this._fetchJobSnapshots()
await this._removeUnusedSnapshots()
}

View File

@@ -23,7 +23,6 @@
- [Remotes] Correctly clear error when the remote is tested with success
- [Import/VMWare] Fix importing last snapshot (PR [#7370](https://github.com/vatesfr/xen-orchestra/pull/7370))
- [Host/Reboot] Fix false positive warning when restarting an host after updates (PR [#7366](https://github.com/vatesfr/xen-orchestra/pull/7366))
- [New/SR] Fix create button never appearing for `smb iso` SR [#7355](https://github.com/vatesfr/xen-orchestra/issues/7355), [Forum#8417](https://xcp-ng.org/forum/topic/8417) (PR [#7405](https://github.com/vatesfr/xen-orchestra/pull/7405))
### Packages to release

View File

@@ -425,32 +425,6 @@ It works even if the VM is running, because we'll automatically export a snapsho
In the VM "Snapshots" tab, you can also export a snapshot like you export a VM.
## VM migration
### Simple VM Migration (VM.pool_migrate)
In simple migration, the VM's active state is transferred from host A to host B while its disks remains in its original location. This feature is only possible when the VM's disks are on a shared SR by both hosts and if the VM is running.
#### Use Case
- Migrate a VM within the same pool from host A to host B without moving the VM's VDIs.
### VM Migration with Storage Motion (VM.migrate_send)
VM migration with storage motion allows you to migrate a VM from one host to another when the VM's disks are not on a shared SR between the two hosts or if a specific network is chosen for the migration. VDIs will be migrated to the destination SR if one is provided.
#### Use Cases
- Migrate a VM to another pool.
- Migrate a VM within the same pool from host A to host B by selecting a network for the migration.
- Migrate a VM within the same pool from host A to host B by moving the VM's VDIs to another storage.
### Expected Behavior
- Migrating a VM that has VDIs on a shared SR from host A to host B must trigger a "Simple VM Migration".
- Migrating a VM that has VDIs on a shared SR from host A to host B using a particular network must trigger a "VM Migration with Storage Motion" without moving its VDIs.
- Migrating a VM from host A to host B with a destination SR must trigger a "VM Migration with Storage Motion" and move VDIs to the destination SR, regardless of where the VDIs were stored.
## Hosts management
Outside updates (see next section), you can also do host management via Xen Orchestra. Basic operations are supported, like reboot, shutdown and so on.

View File

@@ -1,27 +0,0 @@
export async function scan({ host }) {
await this.getXapi(host).call('PUSB.scan', host._xapiRef)
}
scan.params = {
host: { type: 'string' },
}
scan.resolve = {
host: ['host', 'host', 'operate'],
}
export async function set({ pusb, enabled }) {
const xapi = this.getXapi(pusb)
if (enabled !== undefined && enabled !== pusb.passthroughEnabled) {
await xapi.call('PUSB.set_passthrough_enabled', pusb._xapiRef, enabled)
}
}
set.params = {
id: { type: 'string' },
enabled: { type: 'boolean', optional: true },
}
set.resolve = {
pusb: ['id', 'PUSB', 'administrate'],
}

View File

@@ -889,17 +889,6 @@ const TRANSFORMS = {
vm: link(obj, 'VM'),
}
},
pusb(obj) {
return {
type: 'PUSB',
description: obj.description,
host: link(obj, 'host'),
passthroughEnabled: obj.passthrough_enabled,
usbGroup: link(obj, 'USB_group'),
}
},
}
// ===================================================================

View File

@@ -54,9 +54,13 @@ export default class IsoDevice extends Component {
() => this.props.vm.$pool,
() => this.props.vm.$container,
(vmPool, vmContainer) => sr => {
const vmRunning = vmContainer !== vmPool
const sameHost = vmContainer === sr.$container
const samePool = vmPool === sr.$pool
return (
vmPool === sr.$pool &&
(sr.shared || vmContainer === sr.$container) &&
samePool &&
(vmRunning ? sr.shared || sameHost : true) &&
(sr.SR_type === 'iso' || (sr.SR_type === 'udev' && sr.size))
)
}

View File

@@ -3,6 +3,7 @@ import _ from 'intl'
import Copiable from 'copiable'
import decorate from 'apply-decorators'
import Icon from 'icon'
import map from 'lodash/map'
import React from 'react'
import store from 'store'
import HomeTags from 'home-tags'
@@ -23,21 +24,10 @@ export default decorate([
provideState({
computed: {
areHostsVersionsEqual: ({ areHostsVersionsEqualByPool }, { host }) => areHostsVersionsEqualByPool[host.$pool],
inMemoryVms: (_, { vms }) => {
const result = []
for (const key of Object.keys(vms)) {
const vm = vms[key]
const { power_state } = vm
if (power_state === 'Running' || power_state === 'Paused') {
result.push(vm)
}
}
return result
},
},
}),
injectState,
({ statsOverview, host, nVms, vmController, state: { areHostsVersionsEqual, inMemoryVms } }) => {
({ statsOverview, host, nVms, vmController, vms, state: { areHostsVersionsEqual } }) => {
const pool = getObject(store.getState(), host.$pool)
const vmsFilter = encodeURIComponent(new CM.Property('$container', new CM.String(host.id)).toString())
return (
@@ -130,7 +120,7 @@ export default decorate([
tooltip={`${host.productBrand} (${formatSize(vmController.memory.size)})`}
value={vmController.memory.size}
/>
{inMemoryVms.map(vm => (
{map(vms, vm => (
<UsageElement
tooltip={`${vm.name_label} (${formatSize(vm.memory.size)})`}
key={vm.id}

View File

@@ -394,7 +394,7 @@ export default class New extends Component {
hbaDevices: undefined,
iqns: undefined,
paths: undefined,
summary: includes(['ext', 'lvm', 'local', 'smb', 'smbiso', 'hba', 'zfs'], type),
summary: includes(['ext', 'lvm', 'local', 'smb', 'hba', 'zfs'], type),
type,
usage: undefined,
})