summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conductor/conductor/solver/optimizer/constraints/hpa.py68
-rwxr-xr-xconductor/conductor/solver/request/parser.py42
-rw-r--r--conductor/conductor/solver/utils/constraint_engine_interface.py19
-rw-r--r--conductor/conductor/tests/unit/solver/__init__.py0
-rw-r--r--conductor/conductor/tests/unit/solver/candidate_list.json43
-rw-r--r--conductor/conductor/tests/unit/solver/hpa_constraints.json171
-rw-r--r--conductor/conductor/tests/unit/solver/test_hpa.py92
7 files changed, 420 insertions, 15 deletions
diff --git a/conductor/conductor/solver/optimizer/constraints/hpa.py b/conductor/conductor/solver/optimizer/constraints/hpa.py
new file mode 100644
index 0000000..a7e8d3c
--- /dev/null
+++ b/conductor/conductor/solver/optimizer/constraints/hpa.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 Intel Corporation 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.
+#
+# -------------------------------------------------------------------------
+#
+
+'''Solver class for constraint type hpa
+ Hardware Platform Awareness (HPA) constraint plugin'''
+
+# python imports
+
+from conductor.i18n import _LE, _LI
+# Conductor imports
+from conductor.solver.optimizer.constraints import constraint
+# Third-party library imports
+from oslo_log import log
+
+LOG = log.getLogger(__name__)
+
+
+class HPA(constraint.Constraint):
+ def __init__(self, _name, _type, _demand_list, _priority=0,
+ _properties=None):
+ constraint.Constraint.__init__(
+ self, _name, _type, _demand_list, _priority)
+ self.properties = _properties
+
+ def solve(self, _decision_path, _candidate_list, _request):
+ '''
+ Solver for HPA constraint type.
+ :param _decision_path: decision tree
+ :param _candidate_list: List of candidates
+ :param _request: solver request
+ :return: candidate_list with hpa features and flavor label mapping
+ '''
+ # call conductor engine with request parameters
+ cei = _request.cei
+ demand_name = _decision_path.current_demand.name
+ LOG.info(_LI("Solving constraint type '{}' for demand - [{}]").format(
+ self.constraint_type, demand_name))
+ vm_label_list = self.properties.get('evaluate')
+ for vm_demand in vm_label_list:
+ label_name = vm_demand['label']
+ features = vm_demand['features']
+ response = (cei.get_candidates_with_hpa(label_name,
+ _candidate_list,
+ features))
+ if response:
+ _candidate_list = response
+ else:
+ LOG.error(_LE("Flavor mapping for label name {} already"
+ "exists").format(label_name))
+
+ return _candidate_list
diff --git a/conductor/conductor/solver/request/parser.py b/conductor/conductor/solver/request/parser.py
index 1f966ec..d7f3cec 100755
--- a/conductor/conductor/solver/request/parser.py
+++ b/conductor/conductor/solver/request/parser.py
@@ -21,26 +21,26 @@
# import json
import operator
-
-from oslo_log import log
import random
from conductor.solver.optimizer.constraints \
import access_distance as access_dist
from conductor.solver.optimizer.constraints \
- import attribute as attribute_constraint
-from conductor.solver.optimizer.constraints \
import aic_distance as aic_dist
from conductor.solver.optimizer.constraints \
+ import attribute as attribute_constraint
+from conductor.solver.optimizer.constraints import hpa
+from conductor.solver.optimizer.constraints \
import inventory_group
from conductor.solver.optimizer.constraints \
import service as service_constraint
from conductor.solver.optimizer.constraints import zone
from conductor.solver.request import demand
+from conductor.solver.request import objective
from conductor.solver.request.functions import aic_version
from conductor.solver.request.functions import cost
from conductor.solver.request.functions import distance_between
-from conductor.solver.request import objective
+from oslo_log import log
# from conductor.solver.request.functions import distance_between
# from conductor.solver.request import objective
@@ -100,7 +100,8 @@ class Parser(object):
loc.loc_type = "coordinates"
loc.value = (float(location_info["latitude"]),
float(location_info["longitude"]))
- loc.country = location_info['country'] if 'country' in location_info else None
+ loc.country = location_info[
+ 'country'] if 'country' in location_info else None
self.locations[location_id] = loc
# get constraints
@@ -194,7 +195,8 @@ class Parser(object):
location = self.locations[location_id] if location_id else None
my_zone_constraint = zone.Zone(
constraint_id, constraint_type, constraint_demands,
- _qualifier=qualifier, _category=category, _location=location)
+ _qualifier=qualifier, _category=category,
+ _location=location)
self.constraints[my_zone_constraint.name] = my_zone_constraint
elif constraint_type == "attribute":
c_property = constraint_info.get("properties")
@@ -205,13 +207,21 @@ class Parser(object):
_properties=c_property)
self.constraints[my_attribute_constraint.name] = \
my_attribute_constraint
+ elif constraint_type == "hpa":
+ LOG.debug("Creating constraint - {}".format(constraint_type))
+ c_property = constraint_info.get("properties")
+ my_hpa_constraint = hpa.HPA(constraint_id,
+ constraint_type,
+ constraint_demands,
+ _properties=c_property)
+ self.constraints[my_hpa_constraint.name] = my_hpa_constraint
else:
LOG.error("unknown constraint type {}".format(constraint_type))
return
# get objective function
- if "objective" not in json_template["conductor_solver"]\
- or not json_template["conductor_solver"]["objective"]:
+ if "objective" not in json_template["conductor_solver"] \
+ or not json_template["conductor_solver"]["objective"]:
self.objective = objective.Objective()
else:
input_objective = json_template["conductor_solver"]["objective"]
@@ -302,6 +312,7 @@ class Parser(object):
_qualifier="same", _category="zone1")
constraint_list.append(same_zone)
'''
+
def reorder_constraint(self):
# added manual ranking to the constraint type for optimizing purpose the last 2 are costly interaction
for constraint_name, constraint in self.constraints.items():
@@ -311,17 +322,19 @@ class Parser(object):
constraint.rank = 2
elif constraint.constraint_type == "attribute":
constraint.rank = 3
- elif constraint.constraint_type == "inventory_group":
+ elif constraint.constraint_type == "hpa":
constraint.rank = 4
- elif constraint.constraint_type == "instance_fit":
+ elif constraint.constraint_type == "inventory_group":
constraint.rank = 5
- elif constraint.constraint_type == "region_fit":
+ elif constraint.constraint_type == "instance_fit":
constraint.rank = 6
- else:
+ elif constraint.constraint_type == "region_fit":
constraint.rank = 7
+ else:
+ constraint.rank = 8
def attr_sort(self, attrs=['rank']):
- #this helper for sorting the rank
+ # this helper for sorting the rank
return lambda k: [getattr(k, attr) for attr in attrs]
def sort_constraint_by_rank(self):
@@ -330,7 +343,6 @@ class Parser(object):
cl_list = cl.constraint_list
cl_list.sort(key=self.attr_sort(attrs=['rank']))
-
def assgin_constraints_to_demands(self):
# self.parse_dhv_template() # get data from DHV template
# self.get_data_from_aai_simulator() # get data from aai simulation
diff --git a/conductor/conductor/solver/utils/constraint_engine_interface.py b/conductor/conductor/solver/utils/constraint_engine_interface.py
index 99526f7..256e4bb 100644
--- a/conductor/conductor/solver/utils/constraint_engine_interface.py
+++ b/conductor/conductor/solver/utils/constraint_engine_interface.py
@@ -116,3 +116,22 @@ class ConstraintEngineInterface(object):
LOG.debug("get_candidates_by_attribute response: {}".format(response))
# response is a list of (candidate, cost) tuples
return response
+
+ def get_candidates_with_hpa(self, label_name, candidate_list, features):
+ '''
+ Returns the candidate_list with an addition of flavor_mapping for
+ matching cloud candidates with hpa constraints.
+ :param label_name: vm_label_name passed from the SO/Policy
+ :param candidate_list: list of candidates to process
+ :param features: hpa features for this vm_label_name
+ :return: candidate_list with hpa features and flavor mapping
+ '''
+ ctxt = {}
+ args = {"candidate_list": candidate_list,
+ "features": features,
+ "label_name": label_name}
+ response = self.client.call(ctxt=ctxt,
+ method="get_candidates_with_hpa",
+ args=args)
+ LOG.debug("get_candidates_with_hpa response: {}".format(response))
+ return response
diff --git a/conductor/conductor/tests/unit/solver/__init__.py b/conductor/conductor/tests/unit/solver/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/conductor/conductor/tests/unit/solver/__init__.py
diff --git a/conductor/conductor/tests/unit/solver/candidate_list.json b/conductor/conductor/tests/unit/solver/candidate_list.json
new file mode 100644
index 0000000..e29782c
--- /dev/null
+++ b/conductor/conductor/tests/unit/solver/candidate_list.json
@@ -0,0 +1,43 @@
+{
+ "candidate_list": [
+ {
+ "candidate_id": "1ac71fb8-ad43-4e16-9459-c3f372b8236d",
+ "candidate_type": "service",
+ "inventory_type": "service",
+ "inventory_provider": "aai",
+ "host_id": "vnf_123456",
+ "cost": "100",
+ "location_id": "DLLSTX55",
+ "location_type": "azure",
+ "latitude": "32.897480",
+ "longitude": "-97.040443",
+ "city": "Dallas",
+ "state": "TX",
+ "country": "USA",
+ "region": "US",
+ "complex_name": "dalls_one",
+ "cloud_owner": "att-aic",
+ "cloud_region_version": "1.1",
+ "physical_location_id": "DLLSTX55"
+ },
+ {
+ "candidate_id": "NYCNY55",
+ "candidate_type": "cloud",
+ "inventory_type": "cloud",
+ "inventory_provider": "aai",
+ "cost": "100",
+ "location_id": "NYCNY55",
+ "location_type": "azure",
+ "latitude": "40.7128",
+ "longitude": "-74.0060",
+ "city": "New York",
+ "state": "NY",
+ "country": "USA",
+ "region": "US",
+ "complex_name": "ny_one",
+ "cloud_owner": "att-aic",
+ "cloud_region_version": "1.1",
+ "physical_location_id": "NYCNY55"
+ }
+ ]
+} \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/solver/hpa_constraints.json b/conductor/conductor/tests/unit/solver/hpa_constraints.json
new file mode 100644
index 0000000..3954cd5
--- /dev/null
+++ b/conductor/conductor/tests/unit/solver/hpa_constraints.json
@@ -0,0 +1,171 @@
+{
+ "conductor_solver": {
+ "constraints": [
+ {
+ "hpa_constraint_vG": {
+ "demands": ["vG"],
+ "name": "hpa_constraint_vG",
+ "type": "hpa",
+ "properties": {
+ "evaluate": [
+ {
+ "features": [
+ {
+ "architecture": "generic",
+ "hpa-feature": "basicCapabilities",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numVirtualCpu",
+ "hpa-attribute-value": "4",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "virtualMemSize",
+ "hpa-attribute-value": "4",
+ "operator": "=",
+ "unit": "GB"
+ }
+ ],
+ "hpa-version": "v1"
+ },
+ {
+ "architecture": "generic",
+ "hpa-feature": "numa",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numaNodes",
+ "hpa-attribute-value": "2",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "numaCpu-0",
+ "hpa-attribute-value": "2",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "numaCpu-1",
+ "hpa-attribute-value": "4",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "numaMem-0",
+ "hpa-attribute-value": "2",
+ "operator": "=",
+ "unit": "GB"
+ },
+ {
+ "hpa-attribute-key": "numaMem-1",
+ "hpa-attribute-value": "4",
+ "operator": "=",
+ "unit": "GB"
+ }
+ ],
+ "hpa-version": "v1"
+ },
+ {
+ "architecture": "generic",
+ "hpa-feature": "cpuPinning",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "logicalCpuThreadPinningPolicy",
+ "hpa-attribute-value": "prefer",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "logicalCpuPinningPolicy",
+ "hpa-attribute-value": "dedicated",
+ "operator": "="
+ }
+ ],
+ "hpa-version": "v1"
+ }
+ ],
+ "label": "flavor_label_1"
+ },
+ {
+ "features": [
+ {
+ "architecture": "generic",
+ "hpa-feature": "basicCapabilities",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numVirtualCpu",
+ "hpa-attribute-value": "8",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "virtualMemSize",
+ "hpa-attribute-value": "16",
+ "operator": "=",
+ "unit": "GB"
+ }
+ ],
+ "hpa-version": "v1"
+ },
+ {
+ "architecture": "generic",
+ "hpa-feature": "numa",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numaNodes",
+ "hpa-attribute-value": "2",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "numaCpu-0",
+ "hpa-attribute-value": "2",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "numaCpu-1",
+ "hpa-attribute-value": "4",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "numaMem-0",
+ "hpa-attribute-value": "2",
+ "operator": "=",
+ "unit": "GB"
+ },
+ {
+ "hpa-attribute-key": "numaMem-1",
+ "hpa-attribute-value": "4",
+ "operator": "=",
+ "unit": "GB"
+ }
+ ],
+ "hpa-version": "v1"
+ },
+ {
+ "architecture": "generic",
+ "hpa-feature": "memoryPageSize",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "memoryPageSize",
+ "hpa-attribute-value": "2",
+ "operator": "=",
+ "unit": "GB"
+ }
+ ],
+ "hpa-version": "v1"
+ }
+ ],
+ "label": "flavor_label_2"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "constraint_vgmux_customer": {
+ "type": "distance_to_location",
+ "demands": ["vGMuxInfra"],
+ "properties": {
+ "distance": "< 100 km",
+ "location": "customer_loc"
+ }
+ }
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/solver/test_hpa.py b/conductor/conductor/tests/unit/solver/test_hpa.py
new file mode 100644
index 0000000..c9bbbbc
--- /dev/null
+++ b/conductor/conductor/tests/unit/solver/test_hpa.py
@@ -0,0 +1,92 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 Intel Corporation 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.
+#
+# -------------------------------------------------------------------------
+#
+
+
+import copy
+import unittest
+
+import mock
+import yaml
+from conductor.solver.optimizer.constraints import hpa
+from conductor.solver.utils import constraint_engine_interface as cei
+
+
+class TestHPA(unittest.TestCase):
+
+ def setUp(self):
+ req_json_file = './conductor/tests/unit/solver/candidate_list.json'
+ hpa_json_file = './conductor/tests/unit/solver/hpa_constraints.json'
+ hpa_json = yaml.safe_load(open(hpa_json_file).read())
+ req_json = yaml.safe_load(open(req_json_file).read())
+
+ (constraint_id, constraint_info) = \
+ hpa_json["conductor_solver"]["constraints"][0].items()[0]
+ c_property = constraint_info['properties']
+ constraint_type = constraint_info['properties']
+ constraint_demands = list()
+ parsed_demands = constraint_info['demands']
+ if isinstance(parsed_demands, list):
+ for d in parsed_demands:
+ constraint_demands.append(d)
+ self.hpa = hpa.HPA(constraint_id,
+ constraint_type,
+ constraint_demands,
+ _properties=c_property)
+
+ self.candidate_list = req_json['candidate_list']
+
+ def tearDown(self):
+ pass
+
+ @mock.patch.object(hpa.LOG, 'error')
+ @mock.patch.object(hpa.LOG, 'info')
+ @mock.patch.object(cei.LOG, 'debug')
+ def test_solve(self, debug_mock, info_mock, error_mock):
+
+ flavor_infos = [{"flavor_label_1": {"flavor-id": "vim-flavor-id1",
+ "flavor-name": "vim-flavor-1"}},
+ {"flavor_label_2": {"flavor-id": "vim-flavor-id2",
+ "flavor-name": "vim-flavor-2"}}]
+ self.maxDiff = None
+ hpa_candidate_list_1 = copy.deepcopy(self.candidate_list)
+ hpa_candidate_list_1[1]['flavor_map'] = {}
+ hpa_candidate_list_1[1]['flavor_map'].update(flavor_infos[0])
+ hpa_candidate_list_2 = copy.deepcopy(hpa_candidate_list_1)
+ hpa_candidate_list_2[1]['flavor_map'].update(flavor_infos[1])
+
+ mock_decision_path = mock.MagicMock()
+ mock_decision_path.current_demand.name = 'vG'
+ request_mock = mock.MagicMock()
+ client_mock = mock.MagicMock()
+ client_mock.call.return_value = None
+ request_mock.cei = cei.ConstraintEngineInterface(client_mock)
+
+ self.assertEqual(self.candidate_list,
+ self.hpa.solve(mock_decision_path,
+ self.candidate_list, request_mock))
+
+ client_mock.call.side_effect = [hpa_candidate_list_1,
+ hpa_candidate_list_2]
+ self.assertEqual(hpa_candidate_list_2,
+ self.hpa.solve(mock_decision_path,
+ self.candidate_list, request_mock))
+
+
+if __name__ == "__main__":
+ unittest.main()