Three class: Plan, PerfomancePlan, DensityPlan.

This commit is contained in:
wescoeur 2016-03-03 14:47:56 +01:00
parent 651e4bb775
commit abd89df365

View File

@ -25,10 +25,6 @@ const debug = LOAD_BALANCER_DEBUG
const PERFORMANCE_MODE = 0
const DENSITY_MODE = 1
const LOW_BEHAVIOR = 0
const NORMAL_BEHAVIOR = 1
const AGGRESSIVE_BEHAVIOR = 2
// Delay between each ressources evaluation in minutes.
// Must be less than MINUTES_OF_HISTORICAL_DATA.
const EXECUTION_DELAY = 1
@ -40,6 +36,7 @@ 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
@ -79,22 +76,6 @@ export const configurationSchema = {
]
},
behavior: {
type: 'object',
properties: {
low: { type: 'boolean' },
normal: { type: 'boolean' },
aggressive: { type: 'boolean' }
},
oneOf: [
{ required: ['low'] },
{ required: ['normal'] },
{ required: ['aggressive'] }
]
},
pools: {
type: 'array',
description: 'list of pools id where to apply the policy',
@ -118,6 +99,8 @@ export const configurationSchema = {
// ===================================================================
// Create a job not enabled by default.
// A job is a cron task, a running and enabled state.
const makeJob = (cronPattern, fn) => {
const job = {
running: false,
@ -146,6 +129,23 @@ const makeJob = (cronPattern, fn) => {
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
@ -204,28 +204,15 @@ function setRealCpuAverageOfVms (vms, vmsAverages) {
}
}
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
}
// ===================================================================
class Plan {
constructor (xo, { name, mode, behavior, poolIds, thresholds = {} }) {
constructor (xo, name, poolIds, {
thresholds = {}
} = {}) {
this.xo = xo
this._name = name // Useful ?
this._mode = mode
this._behavior = behavior
this._name = name
this._poolIds = poolIds
this._thresholds = {
cpu: {
critical: thresholds.cpu || DEFAULT_CRITICAL_THRESHOLD_CPU
@ -251,29 +238,24 @@ class Plan {
}
}
async execute () {
if (this._mode === PERFORMANCE_MODE) {
await this._executeInPerformanceMode()
} else {
await this._executeInDensityMode()
}
execute () {
throw new Error('Not implemented')
}
// =================================================================
// Performance mode.
// =================================================================
// ===================================================================
// Get hosts to optimize.
// ===================================================================
async _executeInPerformanceMode () {
async _findHostsToOptimize () {
const hosts = this._getHosts()
const hostsStats = await this._getHostsStats(hosts, 'minutes')
// 1. Check if a ressource's utilization exceeds threshold.
const avgNow = computeRessourcesAverage(hosts, hostsStats, EXECUTION_DELAY)
let exceededHosts = this._checkRessourcesThresholds(hosts, avgNow)
const toOptimize = this._checkRessourcesThresholds(hosts, avgNow)
// No ressource's utilization problem.
if (exceededHosts.length === 0) {
debug('No ressource\'s utilization problem.')
if (toOptimize.length === 0) {
return
}
@ -281,118 +263,15 @@ class Plan {
const avgBefore = computeRessourcesAverage(hosts, hostsStats, MINUTES_OF_HISTORICAL_DATA)
const avgWithRatio = computeRessourcesAverageWithWeight(avgNow, avgBefore, 0.75)
exceededHosts = this._checkRessourcesThresholds(exceededHosts, avgWithRatio)
// No ressource's utilization problem.
if (exceededHosts.length === 0) {
debug('No ressource\'s utilization problem.')
return
return {
toOptimize: this._checkRessourcesThresholds(toOptimize, avgWithRatio),
averages: avgWithRatio,
hosts
}
// 3. Search the worst exceeded host by priority.
const toOptimize = searchObject(exceededHosts, (a, b) => {
a = avgWithRatio[a.id]
b = avgWithRatio[b.id]
return (b.cpu - a.cpu) || (a.memoryFree - b.memoryFree)
})
// 4. Search bests combinations for the worst host.
await this._optimize(
toOptimize,
filter(hosts, host => host.id !== toOptimize.id),
avgWithRatio
)
}
async _optimize (exceededHost, hosts, hostsAverages) {
const vms = await this._getVms(exceededHost.id)
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)
// Sort vms by cpu usage. (higher to lower)
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)
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})`)
// promises.push(
// xapiSrc.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
// )
}
return
}
// =================================================================
// Density mode.
// =================================================================
async _executeInDensityMode () {
throw new Error('not yet implemented')
}
// =================================================================
// Check ressources.
// =================================================================
_checkRessourcesThresholds (objects, averages, mode = PERFORMANCE_MODE) {
if (mode === PERFORMANCE_MODE) {
return filter(objects, object => {
const objectAverages = averages[object.id]
return (
objectAverages.cpu >= this._thresholds.cpu.high ||
objectAverages.memoryFree <= this._thresholds.memoryFree.high
)
})
} else if (mode === DENSITY_MODE) {
return filter(objects, object => {
const objectAverages = averages[object.id]
return (
objectAverages.cpu < this._thresholds.cpu.low ||
objectAverages.memoryFree > this._thresholds.memoryFree.low
)
})
} else {
throw new Error('Unsupported load balancing mode.')
}
_checkRessourcesThresholds () {
throw new Error('Not implemented')
}
// ===================================================================
@ -451,6 +330,145 @@ class Plan {
}
}
// ===================================================================
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 {
averages,
hosts,
toOptimize
} = await this._findHostsToOptimize()
if (toOptimize.length === 0) {
return
}
const exceededHost = searchObject(toOptimize, (a, b) => {
a = averages[a.id]
b = averages[b.id]
return (b.cpu - a.cpu) || (a.memoryFree - b.memoryFree)
})
// 3. Search bests combinations for the worst host.
await this._optimize({
exceededHost,
hosts: filter(hosts, host => host.id !== exceededHost.id),
hostsAverages: averages
})
}
async _optimize ({ exceededHost, hosts, hostsAverages }) {
const vms = await this._getVms(exceededHost.id)
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)
// Sort vms by cpu usage. (higher to lower)
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)
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})`)
// promises.push(
// xapiSrc.migrateVm(vm._xapiId, this.xo.getXapi(destination), destination._xapiId)
// )
}
await Promise.all(promises)
return
}
}
// ===================================================================
class DensityPlan 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.low ||
objectAverages.memoryFree > this._thresholds.memoryFree.low
)
})
}
async execute () {
throw new Error('Not implemented')
const hosts = this._getHosts()
const hostsStats = await this._getHostsStats(hosts, 'minutes')
// 1. Check if a ressource's utilization is under lower threshold.
const avgNow = computeRessourcesAverage(hosts, hostsStats, EXECUTION_DELAY)
let exceededHosts = this._checkRessourcesThresholds(hosts, avgNow, DENSITY_MODE)
// No ressource's utilization problem.
if (exceededHosts.length === 0) {
debug('No optimization found.')
return
}
// TODO
}
}
// ===================================================================
// ===================================================================
@ -479,22 +497,13 @@ class LoadBalancerPlugin {
if (plans) {
for (const plan of plans) {
const mode = plan.mode.performance
? PERFORMANCE_MODE
: DENSITY_MODE
const { behavior: planBehavior } = plan
let behavior
if (planBehavior.low) {
behavior = LOW_BEHAVIOR
} else if (planBehavior.normal) {
behavior = NORMAL_BEHAVIOR
} else {
behavior = AGGRESSIVE_BEHAVIOR
}
this._addPlan({ name: plan.name, mode, behavior, poolIds: plan.pools })
this._addPlan({
name: plan.name,
mode: plan.mode.performance
? PERFORMANCE_MODE
: DENSITY_MODE,
poolIds: plan.pools
})
}
}
@ -511,15 +520,19 @@ class LoadBalancerPlugin {
this._job.cron.stop()
}
_addPlan (plan) {
const poolIds = plan.poolIds = uniq(plan.poolIds)
_addPlan ({ name, mode, poolIds }) {
poolIds = uniq(poolIds)
// Check already used pools.
if (intersection(poolIds, this._poolIds) > 0) {
if (intersection(poolIds, this._poolIds).length > 0) {
throw new Error(`Pool(s) already included in an other plan: ${poolIds}`)
}
this._plans.push(new Plan(this.xo, plan))
this._poolIds = this._poolIds.concat(poolIds)
this._plans.push(mode === PERFORMANCE_MODE
? new PerformancePlan(this.xo, name, poolIds)
: new DensityPlan(this.xo, name, poolIds)
)
}
_executePlans () {