Code refactoring - Watcher Decision Engine package
This patchset is there to change the code structure. The objective is to flatten the project file tree by merging 'api/' and 'framework/' into a single package. This also contains some tidy ups in package naming (like using only singular nouns). This should only affect file/folder names and their subsequent import paths wherever they were used. Change-Id: Ie903ba20ca5cf03b0b42efa60131d1b919b0c2c9
This commit is contained in:
23
watcher/decision_engine/strategy/README.md
Normal file
23
watcher/decision_engine/strategy/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
Basic Consolidation is based on Sercon :
|
||||
|
||||
Sercon: Server Consolidation Algorithm using Live Migration of Virtual Machines for Green Computing
|
||||
Virtualization technologies changed the way data centers of enterprises utilize their server resources.
|
||||
|
||||
Instead of using dedicated servers for each type of application, virtualization allows viewing resources as a pool of
|
||||
unified resources, thereby reducing complexity and easing manageability.
|
||||
|
||||
Server consolidation technique,which deals with reducing the number of servers used by consolidating applications, is one of the main
|
||||
applications of virtualization in data centers.
|
||||
The latter technique helps to use computing resources more effectively and has many benefits,such as reducing costs of power, cooling and, hence, contributes to the
|
||||
Green IT initiative.
|
||||
In a dynamic data center environment, where applications encapsulated as virtual machines
|
||||
are mapped to and released from the nodes frequently, reducing the number of server nodes used can be
|
||||
achieved by migrating applications without stopping their services, the technology known as live migration.
|
||||
|
||||
However, live migration is a costly operation; hence, how to perform periodic server consolidation operation
|
||||
in a migration-aware way is a challenging task.
|
||||
Sercon not only minimizes the overall number of used servers, but also minimizes the number of migrations.
|
||||
|
||||
Aziz Murtazaev and Sangyoon Oh
|
||||
Department of Computer and Information Engineering, Ajou University, Suwon, Korea
|
||||
0
watcher/decision_engine/strategy/__init__.py
Normal file
0
watcher/decision_engine/strategy/__init__.py
Normal file
83
watcher/decision_engine/strategy/base.py
Normal file
83
watcher/decision_engine/strategy/base.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import abc
|
||||
from oslo_log import log
|
||||
|
||||
import six
|
||||
from watcher.decision_engine.solution.default import DefaultSolution
|
||||
from watcher.decision_engine.strategy.level import StrategyLevel
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseStrategy(object):
|
||||
"""A Strategy is an algorithm implementation which is able to find a
|
||||
Solution for a given Goal.
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, description=None):
|
||||
self._name = name
|
||||
self.description = description
|
||||
# default strategy level
|
||||
self._strategy_level = StrategyLevel.conservative
|
||||
self._cluster_state_collector = None
|
||||
# the solution given by the strategy
|
||||
self._solution = DefaultSolution()
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute(self, model):
|
||||
"""Execute a strategy
|
||||
|
||||
:param model: The name of the strategy to execute (loaded dynamically)
|
||||
:type model: str
|
||||
:return: A computed solution (via a placement algorithm)
|
||||
:rtype: :class:`watcher.decision_engine.api.strategy.solution.Solution`
|
||||
"""
|
||||
|
||||
@property
|
||||
def solution(self):
|
||||
return self._solution
|
||||
|
||||
@solution.setter
|
||||
def solution(self, s):
|
||||
self._solution = s
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, n):
|
||||
self._name = n
|
||||
|
||||
@property
|
||||
def strategy_level(self):
|
||||
return self._strategy_level
|
||||
|
||||
@strategy_level.setter
|
||||
def strategy_level(self, s):
|
||||
self._strategy_level = s
|
||||
|
||||
@property
|
||||
def state_collector(self):
|
||||
return self._cluster_state_collector
|
||||
|
||||
@state_collector.setter
|
||||
def state_collector(self, s):
|
||||
self._cluster_state_collector = s
|
||||
489
watcher/decision_engine/strategy/basic_consolidation.py
Normal file
489
watcher/decision_engine/strategy/basic_consolidation.py
Normal file
@@ -0,0 +1,489 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.common.exception import ClusterEmpty
|
||||
from watcher.common.exception import ClusteStateNotDefined
|
||||
from watcher.decision_engine.strategy.base import BaseStrategy
|
||||
from watcher.decision_engine.strategy.level import StrategyLevel
|
||||
|
||||
from watcher.decision_engine.meta_action.hypervisor_state import \
|
||||
ChangeHypervisorState
|
||||
from watcher.decision_engine.meta_action.migrate import Migrate
|
||||
from watcher.decision_engine.meta_action.migrate import MigrationType
|
||||
from watcher.decision_engine.meta_action.power_state import ChangePowerState
|
||||
from watcher.decision_engine.meta_action.power_state import PowerState
|
||||
from watcher.decision_engine.model.hypervisor_state import HypervisorState
|
||||
from watcher.decision_engine.model.resource import ResourceType
|
||||
from watcher.decision_engine.model.vm_state import VMState
|
||||
from watcher.metrics_engine.cluster_history.ceilometer import \
|
||||
CeilometerClusterHistory
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class BasicConsolidation(BaseStrategy):
|
||||
|
||||
DEFAULT_NAME = "basic"
|
||||
DEFAULT_DESCRIPTION = "Basic offline consolidation"
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
||||
"""Basic offline Consolidation using live migration
|
||||
|
||||
The basic consolidation algorithm has several limitations.
|
||||
It has been developed only for tests.
|
||||
eg: The BasicConsolidation assumes that the virtual mahine and
|
||||
the compute node are on the same private network.
|
||||
|
||||
Good Strategy :
|
||||
The workloads of the VMs are changing over the time
|
||||
and often tend to migrate from one physical machine to another.
|
||||
Hence, the traditional and offline heuristics such as bin packing
|
||||
are not applicable for the placement VM in cloud computing.
|
||||
So, the decision Engine optimizer provide placement strategy considering
|
||||
not only the performance effects but also the workload characteristics of
|
||||
VMs and others metrics like the power consumption and
|
||||
the tenants constraints (SLAs).
|
||||
|
||||
The watcher optimizer use an online VM placement technique
|
||||
based on machine learning and meta-heuristics that must handle :
|
||||
- multi-objectives
|
||||
- Contradictory objectives
|
||||
- Adapt to changes dynamically
|
||||
- Fast convergence
|
||||
|
||||
:param name: the name of the strategy
|
||||
:param description: a description of the strategy
|
||||
"""
|
||||
super(BasicConsolidation, self).__init__(name, description)
|
||||
|
||||
# set default value for the number of released nodes
|
||||
self.number_of_released_nodes = 0
|
||||
# set default value for the number of migrations
|
||||
self.number_of_migrations = 0
|
||||
# set default value for number of allowed migration attempts
|
||||
self.migration_attempts = 0
|
||||
|
||||
# set default value for the efficiency
|
||||
self.efficiency = 100
|
||||
|
||||
self._ceilometer = None
|
||||
|
||||
# TODO(jed) improve threshold overbooking ?,...
|
||||
self.threshold_mem = 1
|
||||
self.threshold_disk = 1
|
||||
self.threshold_cores = 1
|
||||
|
||||
# TODO(jed) target efficiency
|
||||
self.target_efficiency = 60
|
||||
|
||||
# TODO(jed) weight
|
||||
self.weight_cpu = 1
|
||||
self.weight_mem = 1
|
||||
self.weight_disk = 1
|
||||
|
||||
# TODO(jed) bound migration attempts (80 %)
|
||||
self.bound_migration = 0.80
|
||||
|
||||
@property
|
||||
def ceilometer(self):
|
||||
if self._ceilometer is None:
|
||||
self._ceilometer = CeilometerClusterHistory()
|
||||
return self._ceilometer
|
||||
|
||||
@ceilometer.setter
|
||||
def ceilometer(self, c):
|
||||
self._ceilometer = c
|
||||
|
||||
def compute_attempts(self, size_cluster):
|
||||
"""Upper bound of the number of migration
|
||||
|
||||
:param size_cluster:
|
||||
"""
|
||||
self.migration_attempts = size_cluster * self.bound_migration
|
||||
|
||||
def check_migration(self, model,
|
||||
src_hypervisor,
|
||||
dest_hypervisor,
|
||||
vm_to_mig):
|
||||
'''check if the migration is possible
|
||||
|
||||
:param model: current state of the cluster
|
||||
:param src_hypervisor: the current of the virtual machine
|
||||
:param dest_hypervisor:the destination of the virtual machine
|
||||
:param vm_to_mig: the virtual machine
|
||||
:return: True if the there is enough place otherwise false
|
||||
'''
|
||||
if src_hypervisor == dest_hypervisor:
|
||||
return False
|
||||
|
||||
LOG.debug('Migrate VM {0} from {1} to {2} '.format(vm_to_mig,
|
||||
src_hypervisor,
|
||||
dest_hypervisor,
|
||||
))
|
||||
|
||||
total_cores = 0
|
||||
total_disk = 0
|
||||
total_mem = 0
|
||||
cap_cores = model.get_resource_from_id(ResourceType.cpu_cores)
|
||||
cap_disk = model.get_resource_from_id(ResourceType.disk)
|
||||
cap_mem = model.get_resource_from_id(ResourceType.memory)
|
||||
|
||||
for vm_id in model.get_mapping().get_node_vms(dest_hypervisor):
|
||||
vm = model.get_vm_from_id(vm_id)
|
||||
total_cores += cap_cores.get_capacity(vm)
|
||||
total_disk += cap_disk.get_capacity(vm)
|
||||
total_mem += cap_mem.get_capacity(vm)
|
||||
|
||||
# capacity requested by hypervisor
|
||||
total_cores += cap_cores.get_capacity(vm_to_mig)
|
||||
total_disk += cap_disk.get_capacity(vm_to_mig)
|
||||
total_mem += cap_mem.get_capacity(vm_to_mig)
|
||||
|
||||
return self.check_threshold(model,
|
||||
dest_hypervisor,
|
||||
total_cores,
|
||||
total_disk,
|
||||
total_mem)
|
||||
|
||||
def check_threshold(self, model,
|
||||
dest_hypervisor,
|
||||
total_cores,
|
||||
total_disk,
|
||||
total_mem):
|
||||
"""Check threshold
|
||||
|
||||
check the threshold value defined by the ratio of
|
||||
aggregated CPU capacity of VMS on one node to CPU capacity
|
||||
of this node must not exceed the threshold value.
|
||||
:param dest_hypervisor:
|
||||
:param total_cores
|
||||
:param total_disk
|
||||
:param total_mem
|
||||
:return: True if the threshold is not exceed
|
||||
"""
|
||||
cap_cores = model.get_resource_from_id(ResourceType.cpu_cores)
|
||||
cap_disk = model.get_resource_from_id(ResourceType.disk)
|
||||
cap_mem = model.get_resource_from_id(ResourceType.memory)
|
||||
# available
|
||||
cores_available = cap_cores.get_capacity(dest_hypervisor)
|
||||
disk_available = cap_disk.get_capacity(dest_hypervisor)
|
||||
mem_available = cap_mem.get_capacity(dest_hypervisor)
|
||||
|
||||
if cores_available >= total_cores * self.threshold_cores \
|
||||
and disk_available >= total_disk * self.threshold_disk \
|
||||
and mem_available >= total_mem * self.threshold_mem:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_allowed_migration_attempts(self):
|
||||
"""Allowed migration
|
||||
|
||||
Maximum allowed number of migrations this allows us to fix
|
||||
the upper bound of the number of migrations
|
||||
:return:
|
||||
"""
|
||||
return self.migration_attempts
|
||||
|
||||
def get_threshold_cores(self):
|
||||
return self.threshold_cores
|
||||
|
||||
def set_threshold_cores(self, threshold):
|
||||
self.threshold_cores = threshold
|
||||
|
||||
def get_number_of_released_nodes(self):
|
||||
return self.number_of_released_nodes
|
||||
|
||||
def get_number_of_migrations(self):
|
||||
return self.number_of_migrations
|
||||
|
||||
def calculate_weight(self, model, element, total_cores_used,
|
||||
total_disk_used, total_memory_used):
|
||||
"""Calculate weight of every
|
||||
|
||||
:param model:
|
||||
:param element:
|
||||
:param total_cores_used:
|
||||
:param total_disk_used:
|
||||
:param total_memory_used:
|
||||
:return:
|
||||
"""
|
||||
cpu_capacity = model.get_resource_from_id(
|
||||
ResourceType.cpu_cores).get_capacity(element)
|
||||
|
||||
disk_capacity = model.get_resource_from_id(
|
||||
ResourceType.disk).get_capacity(element)
|
||||
|
||||
memory_capacity = model.get_resource_from_id(
|
||||
ResourceType.memory).get_capacity(element)
|
||||
|
||||
score_cores = (1 - (float(cpu_capacity) - float(total_cores_used)) /
|
||||
float(cpu_capacity))
|
||||
|
||||
# It's possible that disk_capacity is 0, e.g. m1.nano.disk = 0
|
||||
if disk_capacity == 0:
|
||||
score_disk = 0
|
||||
else:
|
||||
score_disk = (1 - (float(disk_capacity) - float(total_disk_used)) /
|
||||
float(disk_capacity))
|
||||
|
||||
score_memory = (
|
||||
1 - (float(memory_capacity) - float(total_memory_used)) /
|
||||
float(memory_capacity))
|
||||
# todo(jed) take in account weight
|
||||
return (score_cores + score_disk + score_memory) / 3
|
||||
|
||||
def calculate_score_node(self, hypervisor, model):
|
||||
"""calculate the score that reprensent the utilization level
|
||||
|
||||
:param hypervisor:
|
||||
:param model:
|
||||
:return:
|
||||
"""
|
||||
cpu_avg_vm = self.ceilometer. \
|
||||
statistic_aggregation(resource_id=hypervisor.uuid,
|
||||
meter_name='compute.node.cpu.percent',
|
||||
period="7200",
|
||||
aggregate='avg'
|
||||
)
|
||||
if cpu_avg_vm is None:
|
||||
LOG.error(
|
||||
"No values returned for {0} compute.node.cpu.percent".format(
|
||||
hypervisor.uuid))
|
||||
cpu_avg_vm = 100
|
||||
|
||||
cpu_capacity = model.get_resource_from_id(
|
||||
ResourceType.cpu_cores).get_capacity(hypervisor)
|
||||
|
||||
total_cores_used = cpu_capacity * (cpu_avg_vm / 100)
|
||||
|
||||
return self.calculate_weight(model, hypervisor, total_cores_used,
|
||||
0,
|
||||
0)
|
||||
|
||||
def calculate_migration_efficiency(self):
|
||||
"""Calculate migration efficiency
|
||||
|
||||
:return: The efficiency tells us that every VM migration resulted
|
||||
in releasing on node
|
||||
"""
|
||||
if self.number_of_migrations > 0:
|
||||
return (float(self.number_of_released_nodes) / float(
|
||||
self.number_of_migrations)) * 100
|
||||
else:
|
||||
return 0
|
||||
|
||||
def calculate_score_vm(self, vm, model):
|
||||
"""Calculate Score of virtual machine
|
||||
|
||||
:param vm_id: the id of virtual machine
|
||||
:param model: the model
|
||||
:return: score
|
||||
"""
|
||||
if model is None:
|
||||
raise ClusteStateNotDefined()
|
||||
|
||||
vm = model.get_vm_from_id(vm.uuid)
|
||||
|
||||
vm_cpu_utilization = self.ceilometer. \
|
||||
statistic_aggregation(resource_id=vm.uuid,
|
||||
meter_name='cpu_util',
|
||||
period="7200",
|
||||
aggregate='avg'
|
||||
)
|
||||
if vm_cpu_utilization is None:
|
||||
LOG.error(
|
||||
"No values returned for {0} cpu_util".format(vm.uuid))
|
||||
vm_cpu_utilization = 100
|
||||
|
||||
cpu_capacity = model.get_resource_from_id(
|
||||
ResourceType.cpu_cores).get_capacity(vm)
|
||||
|
||||
total_cores_used = cpu_capacity * (vm_cpu_utilization / 100)
|
||||
|
||||
return self.calculate_weight(model, vm, total_cores_used,
|
||||
0,
|
||||
0)
|
||||
|
||||
return self.calculate_weight(model, vm, total_cores_used,
|
||||
0,
|
||||
0)
|
||||
|
||||
def print_utilization(self, model):
|
||||
if model is None:
|
||||
raise ClusteStateNotDefined()
|
||||
for node_id in model.get_all_hypervisors():
|
||||
LOG.debug("{0} utilization {1} % ".
|
||||
format(node_id,
|
||||
self.calculate_score_node(
|
||||
model.get_hypervisor_from_id(
|
||||
node_id),
|
||||
model)))
|
||||
|
||||
def execute(self, orign_model):
|
||||
LOG.debug("initialize Sercon Consolidation")
|
||||
|
||||
if orign_model is None:
|
||||
raise ClusteStateNotDefined()
|
||||
|
||||
# todo(jed) clone model
|
||||
current_model = orign_model
|
||||
|
||||
self.efficiency = 100
|
||||
unsuccessful_migration = 0
|
||||
|
||||
first = True
|
||||
size_cluster = len(current_model.get_all_hypervisors())
|
||||
if size_cluster == 0:
|
||||
raise ClusterEmpty()
|
||||
|
||||
self.compute_attempts(size_cluster)
|
||||
|
||||
for hypervisor_id in current_model.get_all_hypervisors():
|
||||
hypervisor = current_model.get_hypervisor_from_id(hypervisor_id)
|
||||
count = current_model.get_mapping(). \
|
||||
get_node_vms_from_id(hypervisor_id)
|
||||
if len(count) == 0:
|
||||
change_power = ChangePowerState(hypervisor)
|
||||
change_power.powerstate = PowerState.g1_S1
|
||||
change_power.level = StrategyLevel.conservative
|
||||
self.solution.add_change_request(change_power)
|
||||
if hypervisor.state == HypervisorState.ONLINE:
|
||||
h = ChangeHypervisorState(hypervisor)
|
||||
h.level = StrategyLevel.aggressive
|
||||
h.state = HypervisorState.OFFLINE
|
||||
self.solution.add_change_request(h)
|
||||
|
||||
while self.get_allowed_migration_attempts() >= unsuccessful_migration:
|
||||
if first is not True:
|
||||
self.efficiency = self.calculate_migration_efficiency()
|
||||
if self.efficiency < float(self.target_efficiency):
|
||||
break
|
||||
first = False
|
||||
score = []
|
||||
|
||||
''' calculate score of nodes based on load by VMs '''
|
||||
for hypervisor_id in current_model.get_all_hypervisors():
|
||||
hypervisor = current_model.get_hypervisor_from_id(
|
||||
hypervisor_id)
|
||||
count = current_model.get_mapping(). \
|
||||
get_node_vms_from_id(hypervisor_id)
|
||||
if len(count) > 0:
|
||||
result = self.calculate_score_node(hypervisor,
|
||||
current_model)
|
||||
else:
|
||||
''' the hypervisor has not VMs '''
|
||||
result = 0
|
||||
if len(count) > 0:
|
||||
score.append((hypervisor_id, result))
|
||||
|
||||
''' sort compute nodes by Score decreasing '''''
|
||||
s = sorted(score, reverse=True, key=lambda x: (x[1]))
|
||||
LOG.debug("Hypervisor(s) BFD {0}".format(s))
|
||||
|
||||
''' get Node to be released '''
|
||||
if len(score) == 0:
|
||||
LOG.warning(
|
||||
"The workloads of the compute nodes"
|
||||
" of the cluster is zero.")
|
||||
break
|
||||
|
||||
node_to_release = s[len(score) - 1][0]
|
||||
|
||||
''' get List of VMs from Node '''
|
||||
vms_to_mig = current_model.get_mapping().get_node_vms_from_id(
|
||||
node_to_release)
|
||||
|
||||
vm_score = []
|
||||
for vm_id in vms_to_mig:
|
||||
vm = current_model.get_vm_from_id(vm_id)
|
||||
if vm.state == VMState.ACTIVE.value:
|
||||
vm_score.append(
|
||||
(vm_id, self.calculate_score_vm(vm, current_model)))
|
||||
|
||||
''' sort VM's by Score '''
|
||||
v = sorted(vm_score, reverse=True, key=lambda x: (x[1]))
|
||||
LOG.debug("VM(s) BFD {0}".format(v))
|
||||
|
||||
m = 0
|
||||
tmp_vm_migration_schedule = []
|
||||
for vm in v:
|
||||
for j in range(0, len(s)):
|
||||
mig_vm = current_model.get_vm_from_id(vm[0])
|
||||
mig_src_hypervisor = current_model.get_hypervisor_from_id(
|
||||
node_to_release)
|
||||
mig_dst_hypervisor = current_model.get_hypervisor_from_id(
|
||||
s[j][0])
|
||||
|
||||
result = self.check_migration(current_model,
|
||||
mig_src_hypervisor,
|
||||
mig_dst_hypervisor, mig_vm)
|
||||
if result is True:
|
||||
|
||||
''' create migration VM '''
|
||||
if current_model.get_mapping(). \
|
||||
migrate_vm(mig_vm, mig_src_hypervisor,
|
||||
mig_dst_hypervisor):
|
||||
live_migrate = Migrate(mig_vm,
|
||||
mig_src_hypervisor,
|
||||
mig_dst_hypervisor)
|
||||
# live migration
|
||||
live_migrate.set_migration_type(
|
||||
MigrationType.pre_copy)
|
||||
live_migrate.level = StrategyLevel.conservative
|
||||
|
||||
tmp_vm_migration_schedule.append(live_migrate)
|
||||
|
||||
if len(current_model.get_mapping().get_node_vms(
|
||||
mig_src_hypervisor)) == 0:
|
||||
# TODO(jed) how to manage strategy level
|
||||
# from conservative to aggressive
|
||||
change_power = ChangePowerState(mig_src_hypervisor)
|
||||
change_power.powerstate = PowerState.g1_S1
|
||||
change_power.level = StrategyLevel.conservative
|
||||
tmp_vm_migration_schedule.append(change_power)
|
||||
|
||||
h = ChangeHypervisorState(mig_src_hypervisor)
|
||||
h.level = StrategyLevel.aggressive
|
||||
|
||||
h.state = HypervisorState.OFFLINE
|
||||
tmp_vm_migration_schedule.append(h)
|
||||
|
||||
self.number_of_released_nodes += 1
|
||||
|
||||
m += 1
|
||||
break
|
||||
if m > 0:
|
||||
self.number_of_migrations = self.number_of_migrations + m
|
||||
unsuccessful_migration = 0
|
||||
for a in tmp_vm_migration_schedule:
|
||||
self.solution.add_change_request(a)
|
||||
else:
|
||||
unsuccessful_migration += 1
|
||||
# self.print_utilization(current_model)
|
||||
infos = {
|
||||
"number_of_migrations": self.number_of_migrations,
|
||||
"number_of_nodes_released": self.number_of_released_nodes,
|
||||
"efficiency": self.efficiency
|
||||
}
|
||||
LOG.debug(infos)
|
||||
self.solution.model = current_model
|
||||
self.solution.efficiency = self.efficiency
|
||||
return self.solution
|
||||
28
watcher/decision_engine/strategy/context/base.py
Normal file
28
watcher/decision_engine/strategy/context/base.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import abc
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseStrategyContext(object):
|
||||
@abc.abstractmethod
|
||||
def execute_strategy(self, model):
|
||||
raise NotImplementedError(
|
||||
"Should have implemented this") # pragma:no cover
|
||||
48
watcher/decision_engine/strategy/context/default.py
Normal file
48
watcher/decision_engine/strategy/context/default.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.decision_engine.planner.default import DefaultPlanner
|
||||
from watcher.decision_engine.strategy.context.base import BaseStrategyContext
|
||||
from watcher.decision_engine.strategy.selector.default import StrategySelector
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class StrategyContext(BaseStrategyContext):
|
||||
def __init__(self, broker=None):
|
||||
LOG.debug("Initializing decision_engine Engine API ")
|
||||
self.strategies = {}
|
||||
self.selected_strategies = []
|
||||
self.broker = broker
|
||||
self.planner = DefaultPlanner()
|
||||
self.strategy_selector = StrategySelector()
|
||||
self.goal = None
|
||||
|
||||
def add_strategy(self, strategy):
|
||||
self.strategies[strategy.name] = strategy
|
||||
self.selected_strategy = strategy.name
|
||||
|
||||
def remove_strategy(self, strategy):
|
||||
pass
|
||||
|
||||
def set_goal(self, goal):
|
||||
self.goal = goal
|
||||
|
||||
def execute_strategy(self, model):
|
||||
# todo(jed) create thread + refactoring
|
||||
selected_strategy = self.strategy_selector.define_from_goal(self.goal)
|
||||
return selected_strategy.execute(model)
|
||||
38
watcher/decision_engine/strategy/dummy_strategy.py
Normal file
38
watcher/decision_engine/strategy/dummy_strategy.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.decision_engine.meta_action.nop import Nop
|
||||
from watcher.decision_engine.strategy.base import BaseStrategy
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DummyStrategy(BaseStrategy):
|
||||
|
||||
DEFAULT_NAME = "dummy"
|
||||
DEFAULT_DESCRIPTION = "Dummy Strategy"
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
||||
super(DummyStrategy, self).__init__(name, description)
|
||||
|
||||
def execute(self, model):
|
||||
n = Nop()
|
||||
self.solution.add_change_request(n)
|
||||
return self.solution
|
||||
27
watcher/decision_engine/strategy/level.py
Normal file
27
watcher/decision_engine/strategy/level.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class StrategyLevel(Enum):
|
||||
conservative = "conservative"
|
||||
balanced = "balanced"
|
||||
growth = "growth"
|
||||
aggressive = "aggressive"
|
||||
52
watcher/decision_engine/strategy/loader.py
Normal file
52
watcher/decision_engine/strategy/loader.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from oslo_log import log
|
||||
from stevedore import ExtensionManager
|
||||
from watcher.decision_engine.strategy.basic_consolidation import \
|
||||
BasicConsolidation
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class StrategyLoader(object):
|
||||
|
||||
default_strategy_cls = BasicConsolidation
|
||||
|
||||
def load_strategies(self):
|
||||
extension_manager = ExtensionManager(
|
||||
namespace='watcher_strategies',
|
||||
invoke_on_load=True,
|
||||
)
|
||||
return {ext.name: ext.plugin for ext in extension_manager.extensions}
|
||||
|
||||
def load(self, model):
|
||||
strategy = None
|
||||
try:
|
||||
available_strategies = self.load_strategies()
|
||||
strategy_cls = available_strategies.get(
|
||||
model, self.default_strategy_cls
|
||||
)
|
||||
strategy = strategy_cls()
|
||||
except Exception as exc:
|
||||
LOG.exception(exc)
|
||||
|
||||
return strategy
|
||||
28
watcher/decision_engine/strategy/selector/base.py
Normal file
28
watcher/decision_engine/strategy/selector/base.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import abc
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Selector(object):
|
||||
@abc.abstractmethod
|
||||
def define_from_goal(self, goal_name):
|
||||
raise NotImplementedError(
|
||||
"Should have implemented this") # pragma:no cover
|
||||
53
watcher/decision_engine/strategy/selector/default.py
Normal file
53
watcher/decision_engine/strategy/selector/default.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from watcher.decision_engine.strategy.loader import StrategyLoader
|
||||
from watcher.decision_engine.strategy.selector.base import Selector
|
||||
from watcher.objects.audit_template import Goal
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
goals = {
|
||||
'SERVERS_CONSOLIDATION': 'basic',
|
||||
'MINIMIZE_ENERGY_CONSUMPTION': 'basic',
|
||||
'BALANCE_LOAD': 'basic',
|
||||
'MINIMIZE_LICENSING_COST': 'basic',
|
||||
'PREPARE_PLANNED_OPERATION': 'basic'
|
||||
}
|
||||
WATCHER_GOALS_OPTS = [
|
||||
cfg.DictOpt('goals',
|
||||
default=goals, help='Goals used for the optimization ')
|
||||
]
|
||||
goals_opt_group = cfg.OptGroup(name='watcher_goals',
|
||||
title='Goals available for the optimization')
|
||||
CONF.register_group(goals_opt_group)
|
||||
CONF.register_opts(WATCHER_GOALS_OPTS, goals_opt_group)
|
||||
|
||||
|
||||
class StrategySelector(Selector):
|
||||
|
||||
def __init__(self):
|
||||
self.strategy_loader = StrategyLoader()
|
||||
|
||||
def define_from_goal(self, goal_name):
|
||||
if goal_name is None:
|
||||
goal_name = Goal.SERVERS_CONSOLIDATION
|
||||
|
||||
strategy_to_load = CONF.watcher_goals.goals[goal_name]
|
||||
return self.strategy_loader.load(strategy_to_load)
|
||||
28
watcher/decision_engine/strategy/state.py
Normal file
28
watcher/decision_engine/strategy/state.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class StrategyState(Enum):
|
||||
INIT = 1,
|
||||
READY = 2,
|
||||
RUNNING = 3,
|
||||
TERMINATED = 4,
|
||||
ERROR = 5
|
||||
Reference in New Issue
Block a user