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 PERFORMANCE_MODE = 0
const DENSITY_MODE = 1 const DENSITY_MODE = 1
const LOW_BEHAVIOR = 0
const NORMAL_BEHAVIOR = 1
const AGGRESSIVE_BEHAVIOR = 2
// Delay between each ressources evaluation in minutes. // Delay between each ressources evaluation in minutes.
// Must be less than MINUTES_OF_HISTORICAL_DATA. // Must be less than MINUTES_OF_HISTORICAL_DATA.
const EXECUTION_DELAY = 1 const EXECUTION_DELAY = 1
@ -40,6 +36,7 @@ const DEFAULT_CRITICAL_THRESHOLD_CPU = 90.0
// Memory threshold in MB. // Memory threshold in MB.
const DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE = 64.0 const DEFAULT_CRITICAL_THRESHOLD_MEMORY_FREE = 64.0
// Thresholds factors.
const HIGH_THRESHOLD_FACTOR = 0.85 const HIGH_THRESHOLD_FACTOR = 0.85
const LOW_THRESHOLD_FACTOR = 0.25 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: { pools: {
type: 'array', type: 'array',
description: 'list of pools id where to apply the policy', 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 makeJob = (cronPattern, fn) => {
const job = { const job = {
running: false, running: false,
@ -146,6 +129,23 @@ 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) { function computeAverage (values, nPoints = values.length) {
let sum = 0 let sum = 0
let tot = 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 { class Plan {
constructor (xo, { name, mode, behavior, poolIds, thresholds = {} }) { constructor (xo, name, poolIds, {
thresholds = {}
} = {}) {
this.xo = xo this.xo = xo
this._name = name // Useful ? this._name = name
this._mode = mode
this._behavior = behavior
this._poolIds = poolIds this._poolIds = poolIds
this._thresholds = { this._thresholds = {
cpu: { cpu: {
critical: thresholds.cpu || DEFAULT_CRITICAL_THRESHOLD_CPU critical: thresholds.cpu || DEFAULT_CRITICAL_THRESHOLD_CPU
@ -251,29 +238,24 @@ class Plan {
} }
} }
async execute () { execute () {
if (this._mode === PERFORMANCE_MODE) { throw new Error('Not implemented')
await this._executeInPerformanceMode()
} else {
await this._executeInDensityMode()
}
} }
// ================================================================= // ===================================================================
// Performance mode. // Get hosts to optimize.
// ================================================================= // ===================================================================
async _executeInPerformanceMode () { async _findHostsToOptimize () {
const hosts = this._getHosts() const hosts = this._getHosts()
const hostsStats = await this._getHostsStats(hosts, 'minutes') const hostsStats = await this._getHostsStats(hosts, 'minutes')
// 1. Check if a ressource's utilization exceeds threshold. // 1. Check if a ressource's utilization exceeds threshold.
const avgNow = computeRessourcesAverage(hosts, hostsStats, EXECUTION_DELAY) const avgNow = computeRessourcesAverage(hosts, hostsStats, EXECUTION_DELAY)
let exceededHosts = this._checkRessourcesThresholds(hosts, avgNow) const toOptimize = this._checkRessourcesThresholds(hosts, avgNow)
// No ressource's utilization problem. // No ressource's utilization problem.
if (exceededHosts.length === 0) { if (toOptimize.length === 0) {
debug('No ressource\'s utilization problem.')
return return
} }
@ -281,118 +263,15 @@ class Plan {
const avgBefore = computeRessourcesAverage(hosts, hostsStats, MINUTES_OF_HISTORICAL_DATA) const avgBefore = computeRessourcesAverage(hosts, hostsStats, MINUTES_OF_HISTORICAL_DATA)
const avgWithRatio = computeRessourcesAverageWithWeight(avgNow, avgBefore, 0.75) const avgWithRatio = computeRessourcesAverageWithWeight(avgNow, avgBefore, 0.75)
exceededHosts = this._checkRessourcesThresholds(exceededHosts, avgWithRatio) return {
toOptimize: this._checkRessourcesThresholds(toOptimize, avgWithRatio),
// No ressource's utilization problem. averages: avgWithRatio,
if (exceededHosts.length === 0) { hosts
debug('No ressource\'s utilization problem.')
return
} }
// 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) { _checkRessourcesThresholds () {
const vms = await this._getVms(exceededHost.id) throw new Error('Not implemented')
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.')
}
} }
// =================================================================== // ===================================================================
@ -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) { if (plans) {
for (const plan of plans) { for (const plan of plans) {
const mode = plan.mode.performance this._addPlan({
? PERFORMANCE_MODE name: plan.name,
: DENSITY_MODE mode: plan.mode.performance
? PERFORMANCE_MODE
const { behavior: planBehavior } = plan : DENSITY_MODE,
let behavior poolIds: plan.pools
})
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 })
} }
} }
@ -511,15 +520,19 @@ class LoadBalancerPlugin {
this._job.cron.stop() this._job.cron.stop()
} }
_addPlan (plan) { _addPlan ({ name, mode, poolIds }) {
const poolIds = plan.poolIds = uniq(plan.poolIds) poolIds = uniq(poolIds)
// Check already used pools. // 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}`) 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 () { _executePlans () {