committed by
Julien Fontanet
parent
ca695c38cd
commit
a8fad8193b
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user