valet/valet/engine/optimizer/ostro/constraint_solver.py

428 lines
18 KiB
Python

#
# Copyright 2014-2017 AT&T Intellectual Property
#
# 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 valet.engine.optimizer.app_manager.app_topology_base import LEVELS
from valet.engine.optimizer.app_manager.app_topology_base import VGroup
from valet.engine.optimizer.app_manager.app_topology_base import VM
from valet.engine.optimizer.ostro.openstack_filters \
import AggregateInstanceExtraSpecsFilter
from valet.engine.optimizer.ostro.openstack_filters \
import AvailabilityZoneFilter
from valet.engine.optimizer.ostro.openstack_filters import CoreFilter
from valet.engine.optimizer.ostro.openstack_filters import DiskFilter
from valet.engine.optimizer.ostro.openstack_filters import RamFilter
class ConstraintSolver(object):
"""ConstraintSolver."""
def __init__(self, _logger):
"""Initialization."""
"""Instantiate filters to help enforce constraints."""
self.logger = _logger
self.openstack_AZ = AvailabilityZoneFilter(self.logger)
self.openstack_AIES = AggregateInstanceExtraSpecsFilter(self.logger)
self.openstack_R = RamFilter(self.logger)
self.openstack_C = CoreFilter(self.logger)
self.openstack_D = DiskFilter(self.logger)
self.status = "success"
def compute_candidate_list(self, _level, _n, _node_placements,
_avail_resources, _avail_logical_groups):
"""Compute candidate list for the given VGroup or VM."""
candidate_list = []
"""When replanning."""
if _n.node.host is not None and len(_n.node.host) > 0:
for hk in _n.node.host:
for ark, ar in _avail_resources.iteritems():
if hk == ark:
candidate_list.append(ar)
else:
for _, r in _avail_resources.iteritems():
candidate_list.append(r)
if len(candidate_list) == 0:
self.status = "no candidate for node = " + _n.node.name
self.logger.warn(self.status)
return candidate_list
else:
self.logger.debug("ConstraintSolver: num of candidates = " +
str(len(candidate_list)))
"""Availability zone constraint."""
if isinstance(_n.node, VGroup) or isinstance(_n.node, VM):
if (isinstance(_n.node, VM) and _n.node.availability_zone
is not None) or (isinstance(_n.node, VGroup) and
len(_n.node.availability_zone_list) > 0):
self._constrain_availability_zone(_level, _n, candidate_list)
if len(candidate_list) == 0:
self.status = "violate availability zone constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
"""Host aggregate constraint."""
if isinstance(_n.node, VGroup) or isinstance(_n.node, VM):
if len(_n.node.extra_specs_list) > 0:
self._constrain_host_aggregates(_level, _n, candidate_list)
if len(candidate_list) == 0:
self.status = "violate host aggregate constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
"""CPU capacity constraint."""
if isinstance(_n.node, VGroup) or isinstance(_n.node, VM):
self._constrain_cpu_capacity(_level, _n, candidate_list)
if len(candidate_list) == 0:
self.status = "violate cpu capacity constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
"""Memory capacity constraint."""
if isinstance(_n.node, VGroup) or isinstance(_n.node, VM):
self._constrain_mem_capacity(_level, _n, candidate_list)
if len(candidate_list) == 0:
self.status = "violate memory capacity constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
"""Local disk capacity constraint."""
if isinstance(_n.node, VGroup) or isinstance(_n.node, VM):
self._constrain_local_disk_capacity(_level, _n, candidate_list)
if len(candidate_list) == 0:
self.status = "violate local disk capacity constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
""" diversity constraint """
if len(_n.node.diversity_groups) > 0:
for _, diversity_id in _n.node.diversity_groups.iteritems():
if diversity_id.split(":")[0] == _level:
if diversity_id in _avail_logical_groups.keys():
self._constrain_diversity_with_others(_level,
diversity_id,
candidate_list)
if len(candidate_list) == 0:
break
if len(candidate_list) == 0:
self.status = "violate diversity constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
else:
self._constrain_diversity(_level, _n, _node_placements,
candidate_list)
if len(candidate_list) == 0:
self.status = "violate diversity constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
"""Exclusivity constraint."""
exclusivities = self.get_exclusivities(_n.node.exclusivity_groups,
_level)
if len(exclusivities) > 1:
self.status = "violate exclusivity constraint (more than one " \
"exclusivity) for node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return []
else:
if len(exclusivities) == 1:
exclusivity_id = exclusivities[exclusivities.keys()[0]]
if exclusivity_id.split(":")[0] == _level:
self._constrain_exclusivity(_level, exclusivity_id,
candidate_list)
if len(candidate_list) == 0:
self.status = "violate exclusivity constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
else:
self._constrain_non_exclusivity(_level, candidate_list)
if len(candidate_list) == 0:
self.status = "violate non-exclusivity constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
"""Affinity constraint."""
affinity_id = _n.get_affinity_id() # level:name, except name == "any"
if affinity_id is not None:
if affinity_id.split(":")[0] == _level:
if affinity_id in _avail_logical_groups.keys():
self._constrain_affinity(_level, affinity_id,
candidate_list)
if len(candidate_list) == 0:
self.status = "violate affinity constraint for " \
"node = " + _n.node.name
self.logger.error("ConstraintSolver: " + self.status)
return candidate_list
return candidate_list
"""
Constraint modules.
"""
def _constrain_affinity(self, _level, _affinity_id, _candidate_list):
conflict_list = []
for r in _candidate_list:
if self.exist_group(_level, _affinity_id, "AFF", r) is False:
if r not in conflict_list:
conflict_list.append(r)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def _constrain_diversity_with_others(self, _level, _diversity_id,
_candidate_list):
conflict_list = []
for r in _candidate_list:
if self.exist_group(_level, _diversity_id, "DIV", r) is True:
if r not in conflict_list:
conflict_list.append(r)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def exist_group(self, _level, _id, _group_type, _candidate):
"""Check if group esists."""
"""Return True if there exists a group within the candidate's
membership list that matches the provided id and group type.
"""
match = False
memberships = _candidate.get_memberships(_level)
for lgk, lgr in memberships.iteritems():
if lgr.group_type == _group_type and lgk == _id:
match = True
break
return match
def _constrain_diversity(self, _level, _n, _node_placements,
_candidate_list):
conflict_list = []
for r in _candidate_list:
if self.conflict_diversity(_level, _n, _node_placements, r):
if r not in conflict_list:
conflict_list.append(r)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def conflict_diversity(self, _level, _n, _node_placements, _candidate):
"""Return True if the candidate has a placement conflict."""
conflict = False
for v in _node_placements.keys():
diversity_level = _n.get_common_diversity(v.diversity_groups)
if diversity_level != "ANY" and \
LEVELS.index(diversity_level) >= \
LEVELS.index(_level):
if diversity_level == "host":
if _candidate.cluster_name == \
_node_placements[v].cluster_name and \
_candidate.rack_name == \
_node_placements[v].rack_name and \
_candidate.host_name == \
_node_placements[v].host_name:
conflict = True
break
elif diversity_level == "rack":
if _candidate.cluster_name == \
_node_placements[v].cluster_name and \
_candidate.rack_name == _node_placements[v].rack_name:
conflict = True
break
elif diversity_level == "cluster":
if _candidate.cluster_name == \
_node_placements[v].cluster_name:
conflict = True
break
return conflict
def _constrain_non_exclusivity(self, _level, _candidate_list):
conflict_list = []
for r in _candidate_list:
if self.conflict_exclusivity(_level, r) is True:
if r not in conflict_list:
conflict_list.append(r)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def conflict_exclusivity(self, _level, _candidate):
"""Check for an exculsivity conflict."""
"""Check if the candidate contains an exclusivity group within its
list of memberships."""
conflict = False
memberships = _candidate.get_memberships(_level)
for mk in memberships.keys():
if memberships[mk].group_type == "EX" and \
mk.split(":")[0] == _level:
conflict = True
return conflict
def get_exclusivities(self, _exclusivity_groups, _level):
"""Return a list of filtered exclusivities."""
"""Extract and return only those exclusivities that exist at the
specified level.
"""
exclusivities = {}
for exk, level in _exclusivity_groups.iteritems():
if level.split(":")[0] == _level:
exclusivities[exk] = level
return exclusivities
def _constrain_exclusivity(self, _level, _exclusivity_id, _candidate_list):
candidate_list = self._get_exclusive_candidates(
_level, _exclusivity_id, _candidate_list)
if len(candidate_list) == 0:
candidate_list = self._get_hibernated_candidates(_level,
_candidate_list)
_candidate_list[:] = [x for x in _candidate_list
if x in candidate_list]
else:
_candidate_list[:] = [x for x in _candidate_list
if x in candidate_list]
def _get_exclusive_candidates(self, _level, _exclusivity_id,
_candidate_list):
candidate_list = []
for r in _candidate_list:
if self.exist_group(_level, _exclusivity_id, "EX", r):
if r not in candidate_list:
candidate_list.append(r)
return candidate_list
def _get_hibernated_candidates(self, _level, _candidate_list):
candidate_list = []
for r in _candidate_list:
if self.check_hibernated(_level, r) is True:
if r not in candidate_list:
candidate_list.append(r)
return candidate_list
def check_hibernated(self, _level, _candidate):
"""Check if the candidate is hibernated.
Return True if the candidate has no placed VMs at the specified
level.
"""
match = False
num_of_placed_vms = _candidate.get_num_of_placed_vms(_level)
if num_of_placed_vms == 0:
match = True
return match
def _constrain_host_aggregates(self, _level, _n, _candidate_list):
conflict_list = []
for r in _candidate_list:
if self.check_host_aggregates(_level, r, _n.node) is False:
if r not in conflict_list:
conflict_list.append(r)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def check_host_aggregates(self, _level, _candidate, _v):
"""Check if candidate passes aggregate instance extra specs.
Return true if the candidate passes the aggregate instance extra specs
zone filter.
"""
return self.openstack_AIES.host_passes(_level, _candidate, _v)
def _constrain_availability_zone(self, _level, _n, _candidate_list):
conflict_list = []
for r in _candidate_list:
if self.check_availability_zone(_level, r, _n.node) is False:
if r not in conflict_list:
conflict_list.append(r)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def check_availability_zone(self, _level, _candidate, _v):
"""Check if the candidate passes the availability zone filter."""
return self.openstack_AZ.host_passes(_level, _candidate, _v)
def _constrain_cpu_capacity(self, _level, _n, _candidate_list):
conflict_list = []
for ch in _candidate_list:
if self.check_cpu_capacity(_level, _n.node, ch) is False:
conflict_list.append(ch)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def check_cpu_capacity(self, _level, _v, _candidate):
"""Check if the candidate passes the core filter."""
return self.openstack_C.host_passes(_level, _candidate, _v)
def _constrain_mem_capacity(self, _level, _n, _candidate_list):
conflict_list = []
for ch in _candidate_list:
if self.check_mem_capacity(_level, _n.node, ch) is False:
conflict_list.append(ch)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def check_mem_capacity(self, _level, _v, _candidate):
"""Check if the candidate passes the RAM filter."""
return self.openstack_R.host_passes(_level, _candidate, _v)
def _constrain_local_disk_capacity(self, _level, _n, _candidate_list):
conflict_list = []
for ch in _candidate_list:
if self.check_local_disk_capacity(_level, _n.node, ch) is False:
conflict_list.append(ch)
_candidate_list[:] = [
c for c in _candidate_list if c not in conflict_list]
def check_local_disk_capacity(self, _level, _v, _candidate):
"""Check if the candidate passes the disk filter."""
return self.openstack_D.host_passes(_level, _candidate, _v)