feat(pack,patch): XS 7.1 mechanism (#527)

See vatesfr/xo-web#2058
This commit is contained in:
Pierre Donias
2017-04-06 19:05:43 +02:00
committed by Julien Fontanet
parent ca695c38cd
commit a8fad8193b
4 changed files with 149 additions and 39 deletions

View File

@@ -1,6 +1,5 @@
import {
includes,
pickBy
startsWith
} from 'lodash'
import {
@@ -9,6 +8,7 @@ import {
forEach,
isArray,
isEmpty,
mapFilter,
mapToArray,
parseXml
} from './utils'
@@ -18,6 +18,9 @@ import {
isVmRunning,
parseDateTime
} from './xapi'
import {
useUpdateSystem
} from './xapi/utils'
// ===================================================================
@@ -99,6 +102,32 @@ const TRANSFORMS = {
} = obj
const isRunning = isHostRunning(obj)
const { software_version } = obj
let supplementalPacks, patches
if (useUpdateSystem(obj)) {
supplementalPacks = []
patches = []
forEach(obj.$updates, update => {
const formattedUpdate = {
name: update.name_label,
description: update.name_description,
author: update.key.split('-')[3],
version: update.version,
guidance: update.after_apply_guidance,
hosts: link(update, 'hosts'),
vdi: link(update, 'vdi'),
size: update.installation_size
}
if (startsWith(update.name_label, 'XS')) {
patches.push(formattedUpdate)
} else {
supplementalPacks.push(formattedUpdate)
}
})
}
return {
// Deprecated
@@ -139,13 +168,25 @@ const TRANSFORMS = {
total: 0
}
})(),
patches: link(obj, 'patches'),
patches: patches || link(obj, 'patches'),
powerOnMode: obj.power_on_mode,
power_state: metrics
? (isRunning ? 'Running' : 'Halted')
: 'Unknown',
startTime: toTimestamp(otherConfig.boot_time),
supplementalPacks: pickBy(obj.software_version, (value, key) => includes(key, ':')),
supplementalPacks: supplementalPacks ||
mapFilter(software_version, (value, key) => {
let author, name
if (([ author, name ] = key.split(':')).length === 2) {
const [ description, version ] = value.split(', ')
return {
name,
description,
author,
version: version.split(' ')[1]
}
}
}),
agentStartTime: toTimestamp(otherConfig.agent_start_time),
tags: obj.tags,
version: obj.software_version.product_version,

View File

@@ -1107,37 +1107,13 @@ export default class Xapi extends XapiBase {
return loop()
}
async _createSuppPackVdi (stream, sr) {
const vdi = await this.createVdi(stream.length, {
sr: sr.$ref,
name_label: '[XO] Supplemental pack ISO',
name_description: 'small temporary VDI to store a supplemental pack ISO'
})
await this.importVdiContent(vdi.$id, stream, { format: VDI_FORMAT_RAW })
return vdi
}
@deferrable
async installSupplementalPack ($defer, stream, { hostId }) {
if (!stream.length) {
throw new Error('stream must have a length')
}
let sr = this.pool.$default_SR
if (!sr || sr.physical_size - sr.physical_utilisation < stream.length) {
sr = find(
mapToArray(this.getObject(hostId, 'host').$PBDs, '$SR'),
sr => sr && sr.content_type === 'user' && sr.physical_size - sr.physical_utilisation >= stream.length
)
if (!sr) {
throw new Error('no SR available to store installation file')
}
}
const vdi = await this._createSuppPackVdi(stream, sr)
const vdi = await this.createTemporaryVdiOnHost(stream, hostId, '[XO] Supplemental pack ISO', 'small temporary VDI to store a supplemental pack ISO')
$defer(() => this._deleteVdi(vdi))
await this.call('host.call_plugin', this.getObject(hostId).$ref, 'install-supp-pack', 'install', { vdi: vdi.uuid })
@@ -1162,7 +1138,7 @@ export default class Xapi extends XapiBase {
// Shared SR available: create only 1 VDI for all the installations
if (sr) {
const vdi = await this._createSuppPackVdi(stream, sr)
const vdi = await this._createTemporaryVdiOnSr(stream, sr, '[XO] Supplemental pack ISO', 'small temporary VDI to store a supplemental pack ISO')
$defer(() => this._deleteVdi(vdi))
// Install pack sequentially to prevent concurrent access to the unique VDI
@@ -1188,7 +1164,7 @@ export default class Xapi extends XapiBase {
throw new Error('no SR available to store installation file')
}
const vdi = await this._createSuppPackVdi(pt, sr)
const vdi = await this._createTemporaryVdiOnSr(pt, sr, '[XO] Supplemental pack ISO', 'small temporary VDI to store a supplemental pack ISO')
$defer(() => this._deleteVdi(vdi))
await this.call('host.call_plugin', host.$ref, 'install-supp-pack', 'install', { vdi: vdi.uuid })
@@ -2141,5 +2117,36 @@ export default class Xapi extends XapiBase {
await this._createVbd(vm, vdi)
}
@deferrable.onFailure
async _createTemporaryVdiOnSr ($onFailure, stream, sr, name_label, name_description) {
const vdi = await this.createVdi(stream.length, {
sr: sr.$ref,
name_label,
name_description
})
$onFailure(() => this._deleteVdi(vdi))
await this.importVdiContent(vdi.$id, stream, { format: VDI_FORMAT_RAW })
return vdi
}
async createTemporaryVdiOnHost (stream, hostId, name_label, name_description) {
let sr = this.pool.$default_SR
if (!sr || sr.physical_size - sr.physical_utilisation < stream.length) {
sr = find(
mapToArray(this.getObject(hostId).$PBDs, '$SR'),
sr => sr && sr.content_type === 'user' && sr.physical_size - sr.physical_utilisation >= stream.length
)
if (!sr) {
throw new Error('no SR available to store installation file')
}
}
return this._createTemporaryVdiOnSr(stream, sr, name_label, name_description)
}
// =================================================================
}

View File

@@ -1,3 +1,4 @@
import deferrable from 'golike-defer'
import filter from 'lodash/filter'
import includes from 'lodash/includes'
import some from 'lodash/some'
@@ -18,7 +19,8 @@ import {
import {
debug,
put
put,
useUpdateSystem
} from '../utils'
export default {
@@ -115,10 +117,16 @@ export default {
_getInstalledPoolPatchesOnHost (host) {
const installed = createRawObject()
// platform_version < 2.1.1
forEach(host.$patches, hostPatch => {
installed[hostPatch.$pool_patch.uuid] = true
})
// platform_version >= 2.1.1
forEach(host.$updates, update => {
installed[update.uuid] = true // TODO: ignore packs
})
return installed
},
@@ -196,6 +204,7 @@ export default {
// -----------------------------------------------------------------
// platform_version < 2.1.1 ----------------------------------------
async uploadPoolPatch (stream, patchName = 'unknown') {
const taskRef = await this._createTask('Patch upload', patchName)
@@ -245,21 +254,67 @@ export default {
return this.uploadPoolPatch(stream, patchInfo.name)
},
// patform_version >= 2.1.1 ----------------------------------------
_installPatch: deferrable(async function ($defer, stream, { hostId }) {
if (!stream.length) {
throw new Error('stream must have a length')
}
const vdi = await this.createTemporaryVdiOnHost(stream, hostId, '[XO] Patch ISO', 'small temporary VDI to store a patch ISO')
$defer(() => this._deleteVdi(vdi))
const updateRef = await this.call('pool_update.introduce', vdi.$ref)
// TODO: check update status
// await this.call('pool_update.precheck', updateRef, host.$ref)
// - ok_livepatch_complete An applicable live patch exists for every required component
// - ok_livepatch_incomplete An applicable live patch exists but it is not sufficient
// - ok There is no applicable live patch
await this.call('pool_update.apply', updateRef, this.getObject(hostId).$ref)
}),
async _downloadPatchAndInstall (uuid, hostId) {
debug('downloading patch %s', uuid)
const patchInfo = (await this._getXenUpdates()).patches[uuid]
if (!patchInfo) {
throw new Error('no such patch ' + uuid)
}
let stream = await httpRequest(patchInfo.url, { agent: httpProxy })
stream = await new Promise((resolve, reject) => {
stream.pipe(unzip.Parse()).on('entry', entry => {
entry.length = entry.size
resolve(entry)
}).on('error', reject)
})
return this._installPatch(stream, { hostId })
},
// -----------------------------------------------------------------
async _installPoolPatchOnHost (patchUuid, host) {
debug('installing patch %s', patchUuid)
const [ patch ] = await Promise.all([ this._getOrUploadPoolPatch(patchUuid), this._ejectToolsIsos(host.$ref) ])
await this.call('pool_patch.apply', patch.$ref, host.$ref)
},
_installPatchUpdateOnHost (patchUuid, host) {
return Promise.all([ this._downloadPatchAndInstall(patchUuid, host.$id), this._ejectToolsIsos(host.$ref) ])
},
async _checkSoftwareVersionAndInstallPatch (patchUuid, hostId) {
const host = this.getObject(hostId)
return useUpdateSystem(host)
? this._installPatchUpdateOnHost(patchUuid, host)
: this._installPoolPatchOnHost(patchUuid, host)
},
async installPoolPatchOnHost (patchUuid, hostId) {
return /* await */ this._installPoolPatchOnHost(
patchUuid,
this.getObject(hostId)
)
debug('installing patch %s', patchUuid)
return this._checkSoftwareVersionAndInstallPatch(patchUuid, hostId)
},
// -----------------------------------------------------------------
@@ -285,7 +340,7 @@ export default {
}
}
await this._installPoolPatchOnHost(patch.uuid, host)
await this._checkSoftwareVersionAndInstallPatch(patch.uuid, host)
},
async installAllPoolPatchesOnHost (hostId) {
@@ -316,6 +371,7 @@ export default {
async installAllPoolPatchesOnAllHosts () {
await this.installAllPoolPatchesOnHost(this.pool.master)
// TODO: use pool_update.pool_apply for platform_version ^2.1.1
await Promise.all(mapToArray(
filter(this.objects.all, { $type: 'host' }),
host => this.installAllPoolPatchesOnHost(host.$id)

View File

@@ -5,6 +5,7 @@ import isEqual from 'lodash/isEqual'
import isPlainObject from 'lodash/isPlainObject'
import pickBy from 'lodash/pickBy'
import { utcFormat, utcParse } from 'd3-time-format'
import { satisfies as versionSatisfies } from 'semver'
import httpRequest from '../http-request'
import {
@@ -384,3 +385,8 @@ export const put = (stream, {
return makeRequest().readAll()
}
export const useUpdateSystem = host => {
// Match Xen Center's condition: https://github.com/xenserver/xenadmin/blob/f3a64fc54bbff239ca6f285406d9034f57537d64/XenModel/Utils/Helpers.cs#L420
return versionSatisfies(host.software_version.platform_version, '^2.1.1')
}