Make code modular.
This commit is contained in:
parent
3ddb4d2b23
commit
06f60b7d92
207
packages/xo-server-load-balancer/src/density-plan.js
Normal file
207
packages/xo-server-load-balancer/src/density-plan.js
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import filter from 'lodash.filter'
|
||||||
|
import clone from 'lodash.clonedeep'
|
||||||
|
import { default as mapToArray } from 'lodash.map'
|
||||||
|
|
||||||
|
import Plan from './plan'
|
||||||
|
import {
|
||||||
|
debug
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
export default class DensityPlan extends Plan {
|
||||||
|
constructor (xo, name, poolIds, options) {
|
||||||
|
super(xo, name, poolIds, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkRessourcesThresholds (objects, averages) {
|
||||||
|
return filter(objects, object =>
|
||||||
|
averages[object.id].memoryFree > this._thresholds.memoryFree.low
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute () {
|
||||||
|
const results = await this._findHostsToOptimize()
|
||||||
|
|
||||||
|
if (!results) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
hosts,
|
||||||
|
toOptimize
|
||||||
|
} = results
|
||||||
|
|
||||||
|
let {
|
||||||
|
averages: hostsAverages
|
||||||
|
} = results
|
||||||
|
|
||||||
|
const pools = await this._getPlanPools()
|
||||||
|
let optimizationsCount = 0
|
||||||
|
|
||||||
|
for (const hostToOptimize of toOptimize) {
|
||||||
|
const {
|
||||||
|
id: hostId,
|
||||||
|
$poolId: poolId
|
||||||
|
} = hostToOptimize
|
||||||
|
|
||||||
|
const {
|
||||||
|
master: masterId
|
||||||
|
} = pools[poolId]
|
||||||
|
|
||||||
|
// Avoid master optimization.
|
||||||
|
if (masterId === hostId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let poolMaster // Pool master.
|
||||||
|
const poolHosts = [] // Without master.
|
||||||
|
const masters = [] // Without the master of this loop.
|
||||||
|
const otherHosts = []
|
||||||
|
|
||||||
|
for (const dest of hosts) {
|
||||||
|
const {
|
||||||
|
id: destId,
|
||||||
|
$poolId: destPoolId
|
||||||
|
} = dest
|
||||||
|
|
||||||
|
// Destination host != Host to optimize!
|
||||||
|
if (destId === hostId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destPoolId === poolId) {
|
||||||
|
if (destId === masterId) {
|
||||||
|
poolMaster = dest
|
||||||
|
} else {
|
||||||
|
poolHosts.push(dest)
|
||||||
|
}
|
||||||
|
} else if (destId === pools[destPoolId].master) {
|
||||||
|
masters.push(dest)
|
||||||
|
} else {
|
||||||
|
otherHosts.push(dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const simulResults = await this._simulate({
|
||||||
|
host: hostToOptimize,
|
||||||
|
destinations: [
|
||||||
|
[ poolMaster ],
|
||||||
|
poolHosts,
|
||||||
|
masters,
|
||||||
|
otherHosts
|
||||||
|
],
|
||||||
|
hostsAverages: clone(hostsAverages)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (simulResults) {
|
||||||
|
// Update stats.
|
||||||
|
hostsAverages = simulResults.hostsAverages
|
||||||
|
|
||||||
|
// Migrate.
|
||||||
|
await this._migrate(simulResults.moves)
|
||||||
|
optimizationsCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`Density mode: ${optimizationsCount} optimizations.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async _simulate ({ host, destinations, hostsAverages }) {
|
||||||
|
const { id: hostId } = host
|
||||||
|
|
||||||
|
debug(`Try to optimize Host (${hostId}).`)
|
||||||
|
|
||||||
|
const vms = await this._getVms(hostId)
|
||||||
|
const vmsAverages = await this._getVmsAverages(vms, host)
|
||||||
|
|
||||||
|
// Sort vms by amount of memory. (+ -> -)
|
||||||
|
vms.sort((a, b) =>
|
||||||
|
vmsAverages[b.id].memory - vmsAverages[a.id].memory
|
||||||
|
)
|
||||||
|
|
||||||
|
const simulResults = {
|
||||||
|
hostsAverages,
|
||||||
|
moves: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find a destination for each VM.
|
||||||
|
for (const vm of vms) {
|
||||||
|
let move
|
||||||
|
|
||||||
|
// Simulate the VM move on a destinations set.
|
||||||
|
for (const subDestinations of destinations) {
|
||||||
|
move = this._testMigration({
|
||||||
|
vm,
|
||||||
|
destinations: subDestinations,
|
||||||
|
hostsAverages,
|
||||||
|
vmsAverages
|
||||||
|
})
|
||||||
|
|
||||||
|
// Destination found.
|
||||||
|
if (move) {
|
||||||
|
simulResults.moves.push(move)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unable to move a VM.
|
||||||
|
if (!move) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done.
|
||||||
|
return simulResults
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if a VM migration on a destination (of a destinations set) is possible.
|
||||||
|
_testMigration ({ vm, destinations, hostsAverages, vmsAverages }) {
|
||||||
|
const {
|
||||||
|
_thresholds: {
|
||||||
|
critical: criticalThreshold
|
||||||
|
}
|
||||||
|
} = this
|
||||||
|
|
||||||
|
// Sort the destinations by available memory. (- -> +)
|
||||||
|
destinations.sort((a, b) =>
|
||||||
|
hostsAverages[a.id].memoryFree - hostsAverages[b.id].memoryFree
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const destination of destinations) {
|
||||||
|
const destinationAverages = hostsAverages[destination.id]
|
||||||
|
const vmAverages = vmsAverages[vm.id]
|
||||||
|
|
||||||
|
// Unable to move the VM.
|
||||||
|
if (
|
||||||
|
destinationAverages.cpu + vmAverages.cpu >= criticalThreshold ||
|
||||||
|
destinationAverages.memoryFree - vmAverages.memory <= criticalThreshold
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
destinationAverages.cpu += vmAverages.cpu
|
||||||
|
destinationAverages.memoryFree -= vmAverages.memory
|
||||||
|
|
||||||
|
// Available movement.
|
||||||
|
return {
|
||||||
|
vm,
|
||||||
|
destination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _migrate (moves) {
|
||||||
|
await Promise.all(
|
||||||
|
mapToArray(moves, move => {
|
||||||
|
const {
|
||||||
|
vm,
|
||||||
|
destination
|
||||||
|
} = move
|
||||||
|
const xapiSrc = this.xo.getXapi(destination)
|
||||||
|
debug(`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${vm.$container}).`)
|
||||||
|
// xapiSrc.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,50 +1,24 @@
|
|||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
|
|
||||||
import clone from 'lodash.clonedeep'
|
|
||||||
import differenceBy from 'lodash.differenceby'
|
|
||||||
import eventToPromise from 'event-to-promise'
|
import eventToPromise from 'event-to-promise'
|
||||||
import filter from 'lodash.filter'
|
|
||||||
import includes from 'lodash.includes'
|
|
||||||
import intersection from 'lodash.intersection'
|
import intersection from 'lodash.intersection'
|
||||||
import uniq from 'lodash.uniq'
|
import uniq from 'lodash.uniq'
|
||||||
|
|
||||||
import { CronJob } from 'cron'
|
import { CronJob } from 'cron'
|
||||||
import { default as mapToArray } from 'lodash.map'
|
import { default as mapToArray } from 'lodash.map'
|
||||||
|
|
||||||
|
import DensityPlan from './density-plan'
|
||||||
|
import PerformancePlan from './performance-plan'
|
||||||
|
import {
|
||||||
|
EXECUTION_DELAY,
|
||||||
|
debug
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
class Emitter extends EventEmitter {}
|
class Emitter extends EventEmitter {}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const noop = () => {}
|
|
||||||
|
|
||||||
const LOAD_BALANCER_DEBUG = 1
|
|
||||||
const debug = LOAD_BALANCER_DEBUG
|
|
||||||
? str => console.log(`[load-balancer]${str}`)
|
|
||||||
: noop
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
const PERFORMANCE_MODE = 0
|
const PERFORMANCE_MODE = 0
|
||||||
const DENSITY_MODE = 1
|
const DENSITY_MODE = 1
|
||||||
|
|
||||||
// Delay between each ressources evaluation in minutes.
|
|
||||||
// Must be less than MINUTES_OF_HISTORICAL_DATA.
|
|
||||||
const EXECUTION_DELAY = 1
|
|
||||||
const MINUTES_OF_HISTORICAL_DATA = 30
|
|
||||||
|
|
||||||
// CPU threshold in percent.
|
|
||||||
const DEFAULT_CRITICAL_THRESHOLD_CPU = 90.0
|
|
||||||
|
|
||||||
// Memory threshold in MB.
|
|
||||||
const DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE = 64.0
|
|
||||||
|
|
||||||
// Thresholds factors.
|
|
||||||
const HIGH_THRESHOLD_FACTOR = 0.85
|
|
||||||
const LOW_THRESHOLD_FACTOR = 0.25
|
|
||||||
|
|
||||||
const HIGH_THRESHOLD_MEMORY_FREE_FACTOR = 1.25
|
|
||||||
const LOW_THRESHOLD_MEMORY_FREE_FACTOR = 20.0
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export const configurationSchema = {
|
export const configurationSchema = {
|
||||||
@ -145,556 +119,6 @@ const makeJob = (cronPattern, fn) => {
|
|||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare a list of objects and give the best.
|
|
||||||
function searchObject (objects, fun) {
|
|
||||||
let object = objects[0]
|
|
||||||
|
|
||||||
for (let i = 1; i < objects.length; i++) {
|
|
||||||
if (fun(object, objects[i]) > 0) {
|
|
||||||
object = objects[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// Averages.
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
function computeAverage (values, nPoints = values.length) {
|
|
||||||
let sum = 0
|
|
||||||
let tot = 0
|
|
||||||
|
|
||||||
const { length } = values
|
|
||||||
|
|
||||||
for (let i = length - nPoints; i < length; i++) {
|
|
||||||
const value = values[i]
|
|
||||||
|
|
||||||
sum += value || 0
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
tot += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum / tot
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeRessourcesAverage (objects, objectsStats, nPoints) {
|
|
||||||
const averages = {}
|
|
||||||
|
|
||||||
for (const object of objects) {
|
|
||||||
const { id } = object
|
|
||||||
const { stats } = objectsStats[id]
|
|
||||||
|
|
||||||
averages[id] = {
|
|
||||||
cpu: computeAverage(
|
|
||||||
mapToArray(stats.cpus, cpu => computeAverage(cpu, nPoints))
|
|
||||||
),
|
|
||||||
nCpus: stats.cpus.length,
|
|
||||||
memoryFree: computeAverage(stats.memoryFree, nPoints),
|
|
||||||
memory: computeAverage(stats.memory, nPoints)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return averages
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeRessourcesAverageWithWeight (averages1, averages2, ratio) {
|
|
||||||
const averages = {}
|
|
||||||
|
|
||||||
for (const id in averages1) {
|
|
||||||
const objectAverages = averages[id] = {}
|
|
||||||
|
|
||||||
for (const averageName in averages1[id]) {
|
|
||||||
objectAverages[averageName] = averages1[id][averageName] * ratio + averages2[id][averageName] * (1 - ratio)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return averages
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRealCpuAverageOfVms (vms, vmsAverages, nCpus) {
|
|
||||||
for (const vm of vms) {
|
|
||||||
const averages = vmsAverages[vm.id]
|
|
||||||
averages.cpu *= averages.nCpus / nCpus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
const numberOrDefault = (value, def) => (value >= 0) ? value : def
|
|
||||||
|
|
||||||
class Plan {
|
|
||||||
constructor (xo, name, poolIds, {
|
|
||||||
excludedHosts,
|
|
||||||
thresholds
|
|
||||||
} = {}) {
|
|
||||||
this.xo = xo
|
|
||||||
this._name = name
|
|
||||||
this._poolIds = poolIds
|
|
||||||
this._excludedHosts = excludedHosts
|
|
||||||
this._thresholds = {
|
|
||||||
cpu: {
|
|
||||||
critical: numberOrDefault(thresholds.cpu, DEFAULT_CRITICAL_THRESHOLD_CPU)
|
|
||||||
},
|
|
||||||
memoryFree: {
|
|
||||||
critical: numberOrDefault(thresholds.memoryFree, DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE) * 1024
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in this._thresholds) {
|
|
||||||
const attr = this._thresholds[key]
|
|
||||||
const { critical } = attr
|
|
||||||
|
|
||||||
if (key === 'memoryFree') {
|
|
||||||
attr.high = critical * HIGH_THRESHOLD_MEMORY_FREE_FACTOR
|
|
||||||
attr.low = critical * LOW_THRESHOLD_MEMORY_FREE_FACTOR
|
|
||||||
} else {
|
|
||||||
attr.high = critical * HIGH_THRESHOLD_FACTOR
|
|
||||||
attr.low = critical * LOW_THRESHOLD_FACTOR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
execute () {
|
|
||||||
throw new Error('Not implemented')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// Get hosts to optimize.
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
async _findHostsToOptimize () {
|
|
||||||
const hosts = this._getHosts()
|
|
||||||
const hostsStats = await this._getHostsStats(hosts, 'minutes')
|
|
||||||
|
|
||||||
// Check if a ressource's utilization exceeds threshold.
|
|
||||||
const avgNow = computeRessourcesAverage(hosts, hostsStats, EXECUTION_DELAY)
|
|
||||||
let toOptimize = this._checkRessourcesThresholds(hosts, avgNow)
|
|
||||||
|
|
||||||
// No ressource's utilization problem.
|
|
||||||
if (toOptimize.length === 0) {
|
|
||||||
debug('No hosts to optimize.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check in the last 30 min interval with ratio.
|
|
||||||
const avgBefore = computeRessourcesAverage(hosts, hostsStats, MINUTES_OF_HISTORICAL_DATA)
|
|
||||||
const avgWithRatio = computeRessourcesAverageWithWeight(avgNow, avgBefore, 0.75)
|
|
||||||
|
|
||||||
toOptimize = this._checkRessourcesThresholds(toOptimize, avgWithRatio)
|
|
||||||
|
|
||||||
// No ressource's utilization problem.
|
|
||||||
if (toOptimize.length === 0) {
|
|
||||||
debug('No hosts to optimize.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
toOptimize,
|
|
||||||
averages: avgWithRatio,
|
|
||||||
hosts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkRessourcesThresholds () {
|
|
||||||
throw new Error('Not implemented')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// Get objects.
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
_getPlanPools () {
|
|
||||||
const pools = {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (const poolId of this._poolIds) {
|
|
||||||
pools[poolId] = this.xo.getObject(poolId)
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pools
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute hosts for each pool. They can change over time.
|
|
||||||
_getHosts () {
|
|
||||||
return differenceBy(
|
|
||||||
filter(this.xo.getObjects(), object =>
|
|
||||||
object.type === 'host' && includes(this._poolIds, object.$poolId)
|
|
||||||
),
|
|
||||||
this._excludedHosts,
|
|
||||||
val => val.id || val
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async _getVms (hostId) {
|
|
||||||
return filter(this.xo.getObjects(), object =>
|
|
||||||
object.type === 'VM' &&
|
|
||||||
object.power_state === 'Running' &&
|
|
||||||
object.$container === hostId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// Get stats.
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
async _getHostsStats (hosts, granularity) {
|
|
||||||
const hostsStats = {}
|
|
||||||
|
|
||||||
await Promise.all(mapToArray(hosts, host =>
|
|
||||||
this.xo.getXapiHostStats(host, granularity).then(hostStats => {
|
|
||||||
hostsStats[host.id] = {
|
|
||||||
nPoints: hostStats.stats.cpus[0].length,
|
|
||||||
stats: hostStats.stats,
|
|
||||||
averages: {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
))
|
|
||||||
|
|
||||||
return hostsStats
|
|
||||||
}
|
|
||||||
|
|
||||||
async _getVmsStats (vms, granularity) {
|
|
||||||
const vmsStats = {}
|
|
||||||
|
|
||||||
await Promise.all(mapToArray(vms, vm =>
|
|
||||||
this.xo.getXapiVmStats(vm, granularity).then(vmStats => {
|
|
||||||
vmsStats[vm.id] = {
|
|
||||||
nPoints: vmStats.stats.cpus[0].length,
|
|
||||||
stats: vmStats.stats,
|
|
||||||
averages: {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
))
|
|
||||||
|
|
||||||
return vmsStats
|
|
||||||
}
|
|
||||||
|
|
||||||
async _getVmsAverages (vms, host) {
|
|
||||||
const vmsStats = await this._getVmsStats(vms, 'minutes')
|
|
||||||
const vmsAverages = computeRessourcesAverageWithWeight(
|
|
||||||
computeRessourcesAverage(vms, vmsStats, EXECUTION_DELAY),
|
|
||||||
computeRessourcesAverage(vms, vmsStats, MINUTES_OF_HISTORICAL_DATA),
|
|
||||||
0.75
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compute real CPU usage. Virtuals cpus to reals cpus.
|
|
||||||
setRealCpuAverageOfVms(vms, vmsAverages, host.CPUs.cpu_count)
|
|
||||||
|
|
||||||
return vmsAverages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
class PerformancePlan extends Plan {
|
|
||||||
constructor (xo, name, poolIds, options) {
|
|
||||||
super(xo, name, poolIds, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkRessourcesThresholds (objects, averages) {
|
|
||||||
return filter(objects, object => {
|
|
||||||
const objectAverages = averages[object.id]
|
|
||||||
|
|
||||||
return (
|
|
||||||
objectAverages.cpu >= this._thresholds.cpu.high ||
|
|
||||||
objectAverages.memoryFree <= this._thresholds.memoryFree.high
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute () {
|
|
||||||
const results = await this._findHostsToOptimize()
|
|
||||||
|
|
||||||
if (!results) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
averages,
|
|
||||||
toOptimize
|
|
||||||
} = results
|
|
||||||
let { hosts } = results
|
|
||||||
|
|
||||||
toOptimize.sort((a, b) => {
|
|
||||||
a = averages[a.id]
|
|
||||||
b = averages[b.id]
|
|
||||||
|
|
||||||
return (b.cpu - a.cpu) || (a.memoryFree - b.memoryFree)
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const exceededHost of toOptimize) {
|
|
||||||
const { id } = exceededHost
|
|
||||||
|
|
||||||
debug(`Try to optimize Host (${exceededHost.id}).`)
|
|
||||||
hosts = filter(hosts, host => host.id !== id)
|
|
||||||
|
|
||||||
// Search bests combinations for the worst host.
|
|
||||||
await this._optimize({
|
|
||||||
exceededHost,
|
|
||||||
hosts,
|
|
||||||
hostsAverages: averages
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _optimize ({ exceededHost, hosts, hostsAverages }) {
|
|
||||||
const vms = await this._getVms(exceededHost.id)
|
|
||||||
const vmsAverages = await this._getVmsAverages(vms, exceededHost)
|
|
||||||
|
|
||||||
// Sort vms by cpu usage. (lower to higher)
|
|
||||||
vms.sort((a, b) =>
|
|
||||||
vmsAverages[b.id].cpu - vmsAverages[a.id].cpu
|
|
||||||
)
|
|
||||||
|
|
||||||
const exceededAverages = hostsAverages[exceededHost.id]
|
|
||||||
const promises = []
|
|
||||||
|
|
||||||
const xapiSrc = this.xo.getXapi(exceededHost)
|
|
||||||
let optimizationsCount = 0
|
|
||||||
|
|
||||||
for (const vm of vms) {
|
|
||||||
// Search host with lower cpu usage.
|
|
||||||
const destination = searchObject(hosts, (a, b) =>
|
|
||||||
hostsAverages[b.id].cpu - hostsAverages[a.id].cpu
|
|
||||||
)
|
|
||||||
const destinationAverages = hostsAverages[destination.id]
|
|
||||||
const vmAverages = vmsAverages[vm.id]
|
|
||||||
|
|
||||||
// Unable to move the vm.
|
|
||||||
if (
|
|
||||||
exceededAverages.cpu - vmAverages.cpu < destinationAverages.cpu + vmAverages.cpu ||
|
|
||||||
destinationAverages.memoryFree > vmAverages.memory
|
|
||||||
) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
exceededAverages.cpu -= vmAverages.cpu
|
|
||||||
destinationAverages.cpu += vmAverages.cpu
|
|
||||||
|
|
||||||
exceededAverages.memoryFree += vmAverages.memory
|
|
||||||
destinationAverages.memoryFree -= vmAverages.memory
|
|
||||||
|
|
||||||
debug(`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${exceededHost.id}).`)
|
|
||||||
optimizationsCount++
|
|
||||||
|
|
||||||
// promises.push(
|
|
||||||
// xapiSrc.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
|
|
||||||
// )
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises)
|
|
||||||
debug(`Performance mode: ${optimizationsCount} optimizations for Host (${exceededHost.id}).`)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
class DensityPlan extends Plan {
|
|
||||||
constructor (xo, name, poolIds, options) {
|
|
||||||
super(xo, name, poolIds, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkRessourcesThresholds (objects, averages) {
|
|
||||||
return filter(objects, object =>
|
|
||||||
averages[object.id].memoryFree > this._thresholds.memoryFree.low
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute () {
|
|
||||||
const results = await this._findHostsToOptimize()
|
|
||||||
|
|
||||||
if (!results) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
hosts,
|
|
||||||
toOptimize
|
|
||||||
} = results
|
|
||||||
|
|
||||||
let {
|
|
||||||
averages: hostsAverages
|
|
||||||
} = results
|
|
||||||
|
|
||||||
const pools = await this._getPlanPools()
|
|
||||||
let optimizationsCount = 0
|
|
||||||
|
|
||||||
for (const hostToOptimize of toOptimize) {
|
|
||||||
const {
|
|
||||||
id: hostId,
|
|
||||||
$poolId: poolId
|
|
||||||
} = hostToOptimize
|
|
||||||
|
|
||||||
const {
|
|
||||||
master: masterId
|
|
||||||
} = pools[poolId]
|
|
||||||
|
|
||||||
// Avoid master optimization.
|
|
||||||
if (masterId === hostId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
let poolMaster // Pool master.
|
|
||||||
const poolHosts = [] // Without master.
|
|
||||||
const masters = [] // Without the master of this loop.
|
|
||||||
const otherHosts = []
|
|
||||||
|
|
||||||
for (const dest of hosts) {
|
|
||||||
const {
|
|
||||||
id: destId,
|
|
||||||
$poolId: destPoolId
|
|
||||||
} = dest
|
|
||||||
|
|
||||||
// Destination host != Host to optimize!
|
|
||||||
if (destId === hostId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destPoolId === poolId) {
|
|
||||||
if (destId === masterId) {
|
|
||||||
poolMaster = dest
|
|
||||||
} else {
|
|
||||||
poolHosts.push(dest)
|
|
||||||
}
|
|
||||||
} else if (destId === pools[destPoolId].master) {
|
|
||||||
masters.push(dest)
|
|
||||||
} else {
|
|
||||||
otherHosts.push(dest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const simulResults = await this._simulate({
|
|
||||||
host: hostToOptimize,
|
|
||||||
destinations: [
|
|
||||||
[ poolMaster ],
|
|
||||||
poolHosts,
|
|
||||||
masters,
|
|
||||||
otherHosts
|
|
||||||
],
|
|
||||||
hostsAverages: clone(hostsAverages)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (simulResults) {
|
|
||||||
// Update stats.
|
|
||||||
hostsAverages = simulResults.hostsAverages
|
|
||||||
|
|
||||||
// Migrate.
|
|
||||||
await this._migrate(simulResults.moves)
|
|
||||||
optimizationsCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(`Density mode: ${optimizationsCount} optimizations.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
async _simulate ({ host, destinations, hostsAverages }) {
|
|
||||||
const { id: hostId } = host
|
|
||||||
|
|
||||||
debug(`Try to optimize Host (${hostId}).`)
|
|
||||||
|
|
||||||
const vms = await this._getVms(hostId)
|
|
||||||
const vmsAverages = await this._getVmsAverages(vms, host)
|
|
||||||
|
|
||||||
// Sort vms by amount of memory. (+ -> -)
|
|
||||||
vms.sort((a, b) =>
|
|
||||||
vmsAverages[b.id].memory - vmsAverages[a.id].memory
|
|
||||||
)
|
|
||||||
|
|
||||||
const simulResults = {
|
|
||||||
hostsAverages,
|
|
||||||
moves: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find a destination for each VM.
|
|
||||||
for (const vm of vms) {
|
|
||||||
let move
|
|
||||||
|
|
||||||
// Simulate the VM move on a destinations set.
|
|
||||||
for (const subDestinations of destinations) {
|
|
||||||
move = this._testMigration({
|
|
||||||
vm,
|
|
||||||
destinations: subDestinations,
|
|
||||||
hostsAverages,
|
|
||||||
vmsAverages
|
|
||||||
})
|
|
||||||
|
|
||||||
// Destination found.
|
|
||||||
if (move) {
|
|
||||||
simulResults.moves.push(move)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unable to move a VM.
|
|
||||||
if (!move) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done.
|
|
||||||
return simulResults
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if a VM migration on a destination (of a destinations set) is possible.
|
|
||||||
_testMigration ({ vm, destinations, hostsAverages, vmsAverages }) {
|
|
||||||
const {
|
|
||||||
_thresholds: {
|
|
||||||
critical: criticalThreshold
|
|
||||||
}
|
|
||||||
} = this
|
|
||||||
|
|
||||||
// Sort the destinations by available memory. (- -> +)
|
|
||||||
destinations.sort((a, b) =>
|
|
||||||
hostsAverages[a.id].memoryFree - hostsAverages[b.id].memoryFree
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const destination of destinations) {
|
|
||||||
const destinationAverages = hostsAverages[destination.id]
|
|
||||||
const vmAverages = vmsAverages[vm.id]
|
|
||||||
|
|
||||||
// Unable to move the VM.
|
|
||||||
if (
|
|
||||||
destinationAverages.cpu + vmAverages.cpu >= criticalThreshold ||
|
|
||||||
destinationAverages.memoryFree - vmAverages.memory <= criticalThreshold
|
|
||||||
) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
destinationAverages.cpu += vmAverages.cpu
|
|
||||||
destinationAverages.memoryFree -= vmAverages.memory
|
|
||||||
|
|
||||||
// Available movement.
|
|
||||||
return {
|
|
||||||
vm,
|
|
||||||
destination
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _migrate (moves) {
|
|
||||||
await Promise.all(
|
|
||||||
mapToArray(moves, move => {
|
|
||||||
const {
|
|
||||||
vm,
|
|
||||||
destination
|
|
||||||
} = move
|
|
||||||
const xapiSrc = this.xo.getXapi(destination)
|
|
||||||
debug(`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${vm.$container}).`)
|
|
||||||
// xapiSrc.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
|
112
packages/xo-server-load-balancer/src/performance-plan.js
Normal file
112
packages/xo-server-load-balancer/src/performance-plan.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import filter from 'lodash.filter'
|
||||||
|
|
||||||
|
import Plan from './plan'
|
||||||
|
import {
|
||||||
|
debug,
|
||||||
|
searchObject
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
export default class PerformancePlan extends Plan {
|
||||||
|
constructor (xo, name, poolIds, options) {
|
||||||
|
super(xo, name, poolIds, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkRessourcesThresholds (objects, averages) {
|
||||||
|
return filter(objects, object => {
|
||||||
|
const objectAverages = averages[object.id]
|
||||||
|
|
||||||
|
return (
|
||||||
|
objectAverages.cpu >= this._thresholds.cpu.high ||
|
||||||
|
objectAverages.memoryFree <= this._thresholds.memoryFree.high
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute () {
|
||||||
|
const results = await this._findHostsToOptimize()
|
||||||
|
|
||||||
|
if (!results) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
averages,
|
||||||
|
toOptimize
|
||||||
|
} = results
|
||||||
|
let { hosts } = results
|
||||||
|
|
||||||
|
toOptimize.sort((a, b) => {
|
||||||
|
a = averages[a.id]
|
||||||
|
b = averages[b.id]
|
||||||
|
|
||||||
|
return (b.cpu - a.cpu) || (a.memoryFree - b.memoryFree)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const exceededHost of toOptimize) {
|
||||||
|
const { id } = exceededHost
|
||||||
|
|
||||||
|
debug(`Try to optimize Host (${exceededHost.id}).`)
|
||||||
|
hosts = filter(hosts, host => host.id !== id)
|
||||||
|
|
||||||
|
// Search bests combinations for the worst host.
|
||||||
|
await this._optimize({
|
||||||
|
exceededHost,
|
||||||
|
hosts,
|
||||||
|
hostsAverages: averages
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _optimize ({ exceededHost, hosts, hostsAverages }) {
|
||||||
|
const vms = await this._getVms(exceededHost.id)
|
||||||
|
const vmsAverages = await this._getVmsAverages(vms, exceededHost)
|
||||||
|
|
||||||
|
// Sort vms by cpu usage. (lower to higher)
|
||||||
|
vms.sort((a, b) =>
|
||||||
|
vmsAverages[b.id].cpu - vmsAverages[a.id].cpu
|
||||||
|
)
|
||||||
|
|
||||||
|
const exceededAverages = hostsAverages[exceededHost.id]
|
||||||
|
const promises = []
|
||||||
|
|
||||||
|
const xapiSrc = this.xo.getXapi(exceededHost)
|
||||||
|
let optimizationsCount = 0
|
||||||
|
|
||||||
|
for (const vm of vms) {
|
||||||
|
// Search host with lower cpu usage.
|
||||||
|
const destination = searchObject(hosts, (a, b) =>
|
||||||
|
hostsAverages[b.id].cpu - hostsAverages[a.id].cpu
|
||||||
|
)
|
||||||
|
const destinationAverages = hostsAverages[destination.id]
|
||||||
|
const vmAverages = vmsAverages[vm.id]
|
||||||
|
|
||||||
|
// Unable to move the vm.
|
||||||
|
if (
|
||||||
|
exceededAverages.cpu - vmAverages.cpu < destinationAverages.cpu + vmAverages.cpu ||
|
||||||
|
destinationAverages.memoryFree > vmAverages.memory
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
exceededAverages.cpu -= vmAverages.cpu
|
||||||
|
destinationAverages.cpu += vmAverages.cpu
|
||||||
|
|
||||||
|
exceededAverages.memoryFree += vmAverages.memory
|
||||||
|
destinationAverages.memoryFree -= vmAverages.memory
|
||||||
|
|
||||||
|
debug(`Migrate VM (${vm.id}) to Host (${destination.id}) from Host (${exceededHost.id}).`)
|
||||||
|
optimizationsCount++
|
||||||
|
|
||||||
|
// promises.push(
|
||||||
|
// xapiSrc.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises)
|
||||||
|
debug(`Performance mode: ${optimizationsCount} optimizations for Host (${exceededHost.id}).`)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
257
packages/xo-server-load-balancer/src/plan.js
Normal file
257
packages/xo-server-load-balancer/src/plan.js
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
import differenceBy from 'lodash.differenceby'
|
||||||
|
import filter from 'lodash.filter'
|
||||||
|
import includes from 'lodash.includes'
|
||||||
|
import { default as mapToArray } from 'lodash.map'
|
||||||
|
|
||||||
|
import {
|
||||||
|
EXECUTION_DELAY,
|
||||||
|
debug
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
const MINUTES_OF_HISTORICAL_DATA = 30
|
||||||
|
|
||||||
|
// CPU threshold in percent.
|
||||||
|
const DEFAULT_CRITICAL_THRESHOLD_CPU = 90.0
|
||||||
|
|
||||||
|
// Memory threshold in MB.
|
||||||
|
const DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE = 64.0
|
||||||
|
|
||||||
|
// Thresholds factors.
|
||||||
|
const HIGH_THRESHOLD_FACTOR = 0.85
|
||||||
|
const LOW_THRESHOLD_FACTOR = 0.25
|
||||||
|
|
||||||
|
const HIGH_THRESHOLD_MEMORY_FREE_FACTOR = 1.25
|
||||||
|
const LOW_THRESHOLD_MEMORY_FREE_FACTOR = 20.0
|
||||||
|
|
||||||
|
const numberOrDefault = (value, def) => (value >= 0) ? value : def
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Averages.
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
function computeAverage (values, nPoints = values.length) {
|
||||||
|
let sum = 0
|
||||||
|
let tot = 0
|
||||||
|
|
||||||
|
const { length } = values
|
||||||
|
|
||||||
|
for (let i = length - nPoints; i < length; i++) {
|
||||||
|
const value = values[i]
|
||||||
|
|
||||||
|
sum += value || 0
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
tot += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum / tot
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeRessourcesAverage (objects, objectsStats, nPoints) {
|
||||||
|
const averages = {}
|
||||||
|
|
||||||
|
for (const object of objects) {
|
||||||
|
const { id } = object
|
||||||
|
const { stats } = objectsStats[id]
|
||||||
|
|
||||||
|
averages[id] = {
|
||||||
|
cpu: computeAverage(
|
||||||
|
mapToArray(stats.cpus, cpu => computeAverage(cpu, nPoints))
|
||||||
|
),
|
||||||
|
nCpus: stats.cpus.length,
|
||||||
|
memoryFree: computeAverage(stats.memoryFree, nPoints),
|
||||||
|
memory: computeAverage(stats.memory, nPoints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return averages
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeRessourcesAverageWithWeight (averages1, averages2, ratio) {
|
||||||
|
const averages = {}
|
||||||
|
|
||||||
|
for (const id in averages1) {
|
||||||
|
const objectAverages = averages[id] = {}
|
||||||
|
|
||||||
|
for (const averageName in averages1[id]) {
|
||||||
|
objectAverages[averageName] = averages1[id][averageName] * ratio + averages2[id][averageName] * (1 - ratio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return averages
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRealCpuAverageOfVms (vms, vmsAverages, nCpus) {
|
||||||
|
for (const vm of vms) {
|
||||||
|
const averages = vmsAverages[vm.id]
|
||||||
|
averages.cpu *= averages.nCpus / nCpus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
export default class Plan {
|
||||||
|
constructor (xo, name, poolIds, {
|
||||||
|
excludedHosts,
|
||||||
|
thresholds
|
||||||
|
} = {}) {
|
||||||
|
this.xo = xo
|
||||||
|
this._name = name
|
||||||
|
this._poolIds = poolIds
|
||||||
|
this._excludedHosts = excludedHosts
|
||||||
|
this._thresholds = {
|
||||||
|
cpu: {
|
||||||
|
critical: numberOrDefault(thresholds.cpu, DEFAULT_CRITICAL_THRESHOLD_CPU)
|
||||||
|
},
|
||||||
|
memoryFree: {
|
||||||
|
critical: numberOrDefault(thresholds.memoryFree, DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE) * 1024
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in this._thresholds) {
|
||||||
|
const attr = this._thresholds[key]
|
||||||
|
const { critical } = attr
|
||||||
|
|
||||||
|
if (key === 'memoryFree') {
|
||||||
|
attr.high = critical * HIGH_THRESHOLD_MEMORY_FREE_FACTOR
|
||||||
|
attr.low = critical * LOW_THRESHOLD_MEMORY_FREE_FACTOR
|
||||||
|
} else {
|
||||||
|
attr.high = critical * HIGH_THRESHOLD_FACTOR
|
||||||
|
attr.low = critical * LOW_THRESHOLD_FACTOR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execute () {
|
||||||
|
throw new Error('Not implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Get hosts to optimize.
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
async _findHostsToOptimize () {
|
||||||
|
const hosts = this._getHosts()
|
||||||
|
const hostsStats = await this._getHostsStats(hosts, 'minutes')
|
||||||
|
|
||||||
|
// Check if a ressource's utilization exceeds threshold.
|
||||||
|
const avgNow = computeRessourcesAverage(hosts, hostsStats, EXECUTION_DELAY)
|
||||||
|
let toOptimize = this._checkRessourcesThresholds(hosts, avgNow)
|
||||||
|
|
||||||
|
// No ressource's utilization problem.
|
||||||
|
if (toOptimize.length === 0) {
|
||||||
|
debug('No hosts to optimize.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check in the last 30 min interval with ratio.
|
||||||
|
const avgBefore = computeRessourcesAverage(hosts, hostsStats, MINUTES_OF_HISTORICAL_DATA)
|
||||||
|
const avgWithRatio = computeRessourcesAverageWithWeight(avgNow, avgBefore, 0.75)
|
||||||
|
|
||||||
|
toOptimize = this._checkRessourcesThresholds(toOptimize, avgWithRatio)
|
||||||
|
|
||||||
|
// No ressource's utilization problem.
|
||||||
|
if (toOptimize.length === 0) {
|
||||||
|
debug('No hosts to optimize.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toOptimize,
|
||||||
|
averages: avgWithRatio,
|
||||||
|
hosts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkRessourcesThresholds () {
|
||||||
|
throw new Error('Not implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Get objects.
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
_getPlanPools () {
|
||||||
|
const pools = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const poolId of this._poolIds) {
|
||||||
|
pools[poolId] = this.xo.getObject(poolId)
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pools
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute hosts for each pool. They can change over time.
|
||||||
|
_getHosts () {
|
||||||
|
return differenceBy(
|
||||||
|
filter(this.xo.getObjects(), object =>
|
||||||
|
object.type === 'host' && includes(this._poolIds, object.$poolId)
|
||||||
|
),
|
||||||
|
this._excludedHosts,
|
||||||
|
val => val.id || val
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getVms (hostId) {
|
||||||
|
return filter(this.xo.getObjects(), object =>
|
||||||
|
object.type === 'VM' &&
|
||||||
|
object.power_state === 'Running' &&
|
||||||
|
object.$container === hostId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Get stats.
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
async _getHostsStats (hosts, granularity) {
|
||||||
|
const hostsStats = {}
|
||||||
|
|
||||||
|
await Promise.all(mapToArray(hosts, host =>
|
||||||
|
this.xo.getXapiHostStats(host, granularity).then(hostStats => {
|
||||||
|
hostsStats[host.id] = {
|
||||||
|
nPoints: hostStats.stats.cpus[0].length,
|
||||||
|
stats: hostStats.stats,
|
||||||
|
averages: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
))
|
||||||
|
|
||||||
|
return hostsStats
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getVmsStats (vms, granularity) {
|
||||||
|
const vmsStats = {}
|
||||||
|
|
||||||
|
await Promise.all(mapToArray(vms, vm =>
|
||||||
|
this.xo.getXapiVmStats(vm, granularity).then(vmStats => {
|
||||||
|
vmsStats[vm.id] = {
|
||||||
|
nPoints: vmStats.stats.cpus[0].length,
|
||||||
|
stats: vmStats.stats,
|
||||||
|
averages: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
))
|
||||||
|
|
||||||
|
return vmsStats
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getVmsAverages (vms, host) {
|
||||||
|
const vmsStats = await this._getVmsStats(vms, 'minutes')
|
||||||
|
const vmsAverages = computeRessourcesAverageWithWeight(
|
||||||
|
computeRessourcesAverage(vms, vmsStats, EXECUTION_DELAY),
|
||||||
|
computeRessourcesAverage(vms, vmsStats, MINUTES_OF_HISTORICAL_DATA),
|
||||||
|
0.75
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compute real CPU usage. Virtuals cpus to reals cpus.
|
||||||
|
setRealCpuAverageOfVms(vms, vmsAverages, host.CPUs.cpu_count)
|
||||||
|
|
||||||
|
return vmsAverages
|
||||||
|
}
|
||||||
|
}
|
26
packages/xo-server-load-balancer/src/utils.js
Normal file
26
packages/xo-server-load-balancer/src/utils.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const noop = () => {}
|
||||||
|
|
||||||
|
const LOAD_BALANCER_DEBUG = 1
|
||||||
|
|
||||||
|
// Delay between each ressources evaluation in minutes.
|
||||||
|
// Must be less than MINUTES_OF_HISTORICAL_DATA.
|
||||||
|
export const EXECUTION_DELAY = 1
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
export const debug = LOAD_BALANCER_DEBUG
|
||||||
|
? str => console.log(`[load-balancer]${str}`)
|
||||||
|
: noop
|
||||||
|
|
||||||
|
// Compare a list of objects and give the best.
|
||||||
|
export function searchObject (objects, fun) {
|
||||||
|
let object = objects[0]
|
||||||
|
|
||||||
|
for (let i = 1; i < objects.length; i++) {
|
||||||
|
if (fun(object, objects[i]) > 0) {
|
||||||
|
object = objects[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user