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 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 () {
|
||||||
|
Loading…
Reference in New Issue
Block a user