Three class: Plan, PerfomancePlan, DensityPlan.
This commit is contained in:
parent
651e4bb775
commit
abd89df365
@ -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 () {
|
||||
|
Loading…
Reference in New Issue
Block a user