summaryrefslogtreecommitdiffstats
path: root/conductor
diff options
context:
space:
mode:
Diffstat (limited to 'conductor')
-rw-r--r--conductor/conductor/common/config_loader.py38
-rw-r--r--conductor/conductor/common/music/api.py12
-rw-r--r--conductor/conductor/common/sms.py120
-rw-r--r--conductor/conductor/controller/translator.py38
-rw-r--r--conductor/conductor/data/plugins/inventory_provider/aai.py4
-rw-r--r--conductor/conductor/data/plugins/inventory_provider/hpa_utils.py39
-rw-r--r--conductor/conductor/data/service.py89
-rw-r--r--conductor/conductor/opts.py2
-rw-r--r--conductor/conductor/solver/optimizer/constraints/hpa.py10
-rwxr-xr-xconductor/conductor/solver/request/functions/hpa_score.py29
-rwxr-xr-xconductor/conductor/solver/request/objective.py14
-rwxr-xr-xconductor/conductor/solver/request/parser.py4
-rw-r--r--conductor/conductor/solver/service.py23
-rw-r--r--conductor/conductor/solver/utils/constraint_engine_interface.py6
-rw-r--r--conductor/conductor/tests/unit/controller/hpa_constraints.json221
-rw-r--r--conductor/conductor/tests/unit/controller/test_translator.py130
-rw-r--r--conductor/conductor/tests/unit/data/hpa_constraints.json46
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_flavors.json99
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_req_features.json519
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py75
-rw-r--r--conductor/conductor/tests/unit/data/test_service.py29
-rw-r--r--conductor/conductor/tests/unit/solver/hpa_constraints.json40
-rw-r--r--conductor/conductor/tests/unit/test_sms.py89
-rw-r--r--conductor/pom.xml4
-rw-r--r--conductor/requirements.txt1
-rw-r--r--conductor/test-requirements.txt1
26 files changed, 1365 insertions, 317 deletions
diff --git a/conductor/conductor/common/config_loader.py b/conductor/conductor/common/config_loader.py
new file mode 100644
index 0000000..60e05f1
--- /dev/null
+++ b/conductor/conductor/common/config_loader.py
@@ -0,0 +1,38 @@
+#
+# -------------------------------------------------------------------------
+# 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 json
+
+import yaml
+
+
+def load_config_file(config_file, child_name="dockerConfiguration"):
+ """
+ Load YAML/JSON configuration from a file
+ :param config_file: path to config file (.yaml or .json).
+ :param child_name: if present, return only that child node
+ :return: config (all or specific child node)
+ """
+ with open(config_file, 'r') as fid:
+ res = {}
+ if config_file.endswith(".yaml"):
+ res = yaml.load(fid)
+ elif config_file.endswith(".json") or config_file.endswith("json"):
+ res = json.load(fid)
+ return res.get(child_name, res) if child_name else res
diff --git a/conductor/conductor/common/music/api.py b/conductor/conductor/common/music/api.py
index b3bb5fc..2517b8c 100644
--- a/conductor/conductor/common/music/api.py
+++ b/conductor/conductor/common/music/api.py
@@ -18,16 +18,15 @@
#
"""Music Data Store API"""
-
+import base64
import copy
import logging
import time
-from oslo_config import cfg
-from oslo_log import log
-
from conductor.common import rest
from conductor.i18n import _LE, _LI # pylint: disable=W0212
+from oslo_config import cfg
+from oslo_log import log
LOG = log.getLogger(__name__)
@@ -135,6 +134,11 @@ class MusicAPI(object):
self.rest.session.headers['content-type'] = 'application/json'
self.rest.session.headers['X-patchVersion'] = MUSIC_version[2]
self.rest.session.headers['ns'] = CONF.music_api.aafns
+ # auth_str = 'Basic {}'.format(base64.encodestring(
+ # '{}:{}'.format(CONF.music_api.aafuser,
+ # CONF.music_api.aafpass)).strip())
+ # self.rest.session.headers['Authorization'] = auth_str
+
self.rest.session.headers['userId'] = CONF.music_api.aafuser
self.rest.session.headers['password'] = CONF.music_api.aafpass
diff --git a/conductor/conductor/common/sms.py b/conductor/conductor/common/sms.py
new file mode 100644
index 0000000..43b9522
--- /dev/null
+++ b/conductor/conductor/common/sms.py
@@ -0,0 +1,120 @@
+#
+# -------------------------------------------------------------------------
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+'''Secret Management Service Integration'''
+from conductor.common import config_loader
+from onapsmsclient import Client
+
+from oslo_config import cfg
+from oslo_log import log
+
+LOG = log.getLogger(__name__)
+
+CONF = cfg.CONF
+
+AAF_SMS_OPTS = [
+ cfg.StrOpt('aaf_sms_url',
+ default='https://aaf-sms.onap:10443',
+ help='Base URL for SMS, up to and not including '
+ 'the version, and without a trailing slash.'),
+ cfg.IntOpt('aaf_sms_timeout',
+ default=30,
+ help='Timeout for SMS API Call'),
+ cfg.StrOpt('aaf_ca_certs',
+ default='AAF_RootCA.cer',
+ help='Path to the cacert that will be used to verify '
+ 'If this is None, verify will be False and the server cert'
+ 'is not verified by the client.'),
+ cfg.StrOpt('secret_domain',
+ default='has',
+ help='Domain UUID - A unique UUID generated when the domain'
+ 'for HAS is created by administrator during deployment')
+]
+
+CONF.register_opts(AAF_SMS_OPTS, group='aaf_sms')
+config_spec = {
+ "preload_secrets": "../preload_secrets.yaml"
+}
+
+secret_cache = {}
+
+
+def preload_secrets():
+ """ This is intended to load the secrets required for testing Application
+ Actual deployment will have a preload script. Make sure the config is
+ in sync"""
+ preload_config = config_loader.load_config_file(
+ config_spec.get("preload_secrets"))
+ domain = preload_config.get("domain")
+ config = CONF.aaf_sms
+ sms_url = config.aaf_sms_url
+ timeout = config.aaf_sms_timeout
+ cacert = config.aaf_ca_certs
+ sms_client = Client(url=sms_url, timeout=timeout, cacert=cacert)
+ domain = sms_client.createDomain(domain)
+ config.secret_domain = domain # uuid
+ secrets = preload_config.get("secrets")
+ for secret in secrets:
+ sms_client.storeSecret(domain, secret.get('name'),
+ secret.get('values'))
+ LOG.debug("Preload secrets complete")
+
+
+def retrieve_secrets():
+ """Get all secrets under the domain name"""
+ secret_dict = dict()
+ config = CONF.aaf_sms
+ sms_url = config.aaf_sms_url
+ timeout = config.aaf_sms_timeout
+ cacert = config.aaf_ca_certs
+ domain = config.secret_domain
+ sms_client = Client(url=sms_url, timeout=timeout, cacert=cacert)
+ secrets = sms_client.getSecretNames(domain)
+ for secret in secrets:
+ values = sms_client.getSecret(domain, secret)
+ secret_dict[secret] = values
+ LOG.debug("Secret Dictionary Retrieval Success")
+ return secret_dict
+
+
+def delete_secrets():
+ """ This is intended to delete the secrets for a clean initialization for
+ testing Application. Actual deployment will have a preload script.
+ Make sure the config is in sync"""
+ config = CONF.aaf_sms
+ sms_url = config.aaf_sms_url
+ timeout = config.aaf_sms_timeout
+ cacert = config.aaf_ca_certs
+ domain = config.secret_domain
+ sms_client = Client(url=sms_url, timeout=timeout, cacert=cacert)
+ ret_val = sms_client.deleteDomain(domain)
+ LOG.debug("Clean up complete")
+ return ret_val
+
+
+if __name__ == "__main__":
+ # Initialize Secrets from SMS
+ preload_secrets()
+
+ # Retrieve Secrets from SMS and load to secret cache
+ # Use the secret_cache instead of config files
+ secret_cache = retrieve_secrets()
+
+ # Clean up Delete secrets and domain
+ delete_secrets()
diff --git a/conductor/conductor/controller/translator.py b/conductor/conductor/controller/translator.py
index 7cd90f4..4f0ed20 100644
--- a/conductor/conductor/controller/translator.py
+++ b/conductor/conductor/controller/translator.py
@@ -105,7 +105,7 @@ CONSTRAINTS = {
},
}
HPA_FEATURES = ['architecture', 'hpa-feature', 'hpa-feature-attributes',
- 'hpa-version', 'mandatory']
+ 'hpa-version', 'mandatory', 'directives']
HPA_OPTIONAL = ['score']
HPA_ATTRIBUTES = ['hpa-attribute-key', 'hpa-attribute-value', 'operator']
HPA_ATTRIBUTES_OPTIONAL = ['unit']
@@ -553,14 +553,18 @@ class Translator(object):
def validate_hpa_constraints(self, req_prop, value):
for para in value.get(req_prop):
# Make sure there is at least one
- # set of flavorLabel and flavorProperties
- if not para.get('flavorLabel') \
+ # set of id, type, directives and flavorProperties
+ if not para.get('id') \
+ or not para.get('type') \
+ or not para.get('directives') \
or not para.get('flavorProperties') \
- or para.get('flavorLabel') == '' \
+ or para.get('id') == '' \
+ or para.get('type') == '' \
+ or not isinstance(para.get('directives'), list) \
or para.get('flavorProperties') == '':
raise TranslatorException(
"HPA requirements need at least "
- "one set of flavorLabel and flavorProperties"
+ "one set of id, type, directives and flavorProperties"
)
for feature in para.get('flavorProperties'):
if type(feature) is not dict:
@@ -574,7 +578,7 @@ class Translator(object):
hpa_optional = set(feature.keys()).difference(HPA_FEATURES)
if hpa_optional and not hpa_optional.issubset(HPA_OPTIONAL):
raise TranslatorException(
- "Lack of compulsory elements inside HPA feature")
+ "Got unrecognized elements inside HPA feature")
if feature.get('mandatory') == 'False' and not feature.get(
'score'):
raise TranslatorException(
@@ -591,7 +595,7 @@ class Translator(object):
if bool(hpa_attr_mandatory):
raise TranslatorException(
"Lack of compulsory elements inside HPA "
- "feature atrributes")
+ "feature attributes")
# process optional hpa attribute parameter
hpa_attr_optional = set(attr.keys()).difference(
HPA_ATTRIBUTES)
@@ -794,6 +798,13 @@ class Translator(object):
elif product_op.keys() == ['aic_version']:
function = 'aic_version'
args = product_op.get('aic_version')
+ elif product_op.keys() == ['hpa_score']:
+ function = 'hpa_score'
+ args = product_op.get('hpa_score')
+ if not self.is_hpa_policy_exists(args):
+ raise TranslatorException(
+ "HPA Score Optimization must include a "
+ "HPA Policy constraint ")
elif product_op.keys() == ['sum']:
nested = True
nested_operands = product_op.get('sum')
@@ -844,6 +855,18 @@ class Translator(object):
)
return parsed
+ def is_hpa_policy_exists(self, demand_list):
+ # Check if a HPA constraint exist for the demands in the demand list.
+ constraints_copy = copy.deepcopy(self._constraints)
+ for demand in demand_list:
+ for name, constraint in constraints_copy.items():
+ constraint_type = constraint.get('type')
+ if constraint_type == 'hpa':
+ hpa_demands = constraint.get('demands')
+ if demand in hpa_demands:
+ return True
+ return False
+
def parse_reservations(self, reservations):
demands = self._demands
if type(reservations) is not dict:
@@ -880,7 +903,6 @@ class Translator(object):
"request_type": request_type,
"locations": self.parse_locations(self._locations),
"demands": self.parse_demands(self._demands),
- "objective": self.parse_optimization(self._optmization),
"constraints": self.parse_constraints(self._constraints),
"objective": self.parse_optimization(self._optmization),
"reservations": self.parse_reservations(self._reservations),
diff --git a/conductor/conductor/data/plugins/inventory_provider/aai.py b/conductor/conductor/data/plugins/inventory_provider/aai.py
index d23a483..8634b3f 100644
--- a/conductor/conductor/data/plugins/inventory_provider/aai.py
+++ b/conductor/conductor/data/plugins/inventory_provider/aai.py
@@ -1340,6 +1340,6 @@ class AAI(base.InventoryProviderBase):
def match_hpa(self, candidate, features):
"""Match HPA features requirement with the candidate flavors """
hpa_provider = hpa_utils.HpaMatchProvider(candidate, features)
- flavor_map = hpa_provider.match_flavor()
- return flavor_map
+ directives = hpa_provider.match_flavor()
+ return directives
diff --git a/conductor/conductor/data/plugins/inventory_provider/hpa_utils.py b/conductor/conductor/data/plugins/inventory_provider/hpa_utils.py
index 24f901b..2414e61 100644
--- a/conductor/conductor/data/plugins/inventory_provider/hpa_utils.py
+++ b/conductor/conductor/data/plugins/inventory_provider/hpa_utils.py
@@ -69,7 +69,7 @@ class HpaMatchProvider(object):
if hpa_list not in req_filter_list:
req_filter_list.append(hpa_list)
max_score = -1
- flavor_map = None
+ directives = None
for flavor in self.flavors_list:
flavor_filter_list = []
try:
@@ -84,15 +84,18 @@ class HpaMatchProvider(object):
flavor_filter_list.append(hpa_list)
# if flavor has the matching capability compare attributes
if self._is_cap_supported(flavor_filter_list, req_filter_list):
- match_found, score = self._compare_feature_attributes(flavor_cap_list)
+ match_found, score, req_directives = self._compare_feature_attributes(flavor_cap_list)
if match_found:
LOG.info(_LI("Matching Flavor found '{}' for request - {}").
format(flavor['flavor-name'], self.req_cap_list))
if score > max_score:
max_score = score
flavor_map = {"flavor-id": flavor['flavor-id'],
- "flavor-name": flavor['flavor-name']}
- return flavor_map
+ "flavor-name": flavor['flavor-name'],
+ "score": max_score}
+ directives = {"flavor_map": flavor_map,
+ "directives": req_directives}
+ return directives
def _is_cap_supported(self, flavor, cap):
@@ -211,7 +214,7 @@ class HpaMatchProvider(object):
for capability in CapabilityDataParser.get_item(flavor_cap_list,
'hpa-capability'):
flavor_feature, feature_attributes = capability.get_fields()
- # One feature will match this condition as we have pre-filtered
+ # Multiple features will match this condition as we have pre-filtered
if feature == flavor_feature:
return feature_attributes
@@ -220,24 +223,31 @@ class HpaMatchProvider(object):
# and compare each attribute
def _compare_feature_attributes(self, flavor_cap_list):
score = 0
+ directives = []
for capability in CapabilityDataParser.get_item(self.req_cap_list, None):
hpa_feature, req_cfa_list = capability.get_fields()
+ feature_directive = capability.get_directives()
+ if feature_directive:
+ feature_directive[:] = [d for d in feature_directive
+ if d.get("type") != ""]
+ for item in feature_directive:
+ directives.append(item)
flavor_cfa_list = self._get_flavor_cfa_list(hpa_feature, flavor_cap_list)
if flavor_cfa_list is not None:
for req_feature_attr in req_cfa_list:
req_attr_key = req_feature_attr['hpa-attribute-key']
- # filter to get the attribute being compared
+ # filter to get the attribute being compared
flavor_feature_attr = \
filter(lambda ele: ele['hpa-attribute-key'] == \
- req_attr_key, flavor_cfa_list)
- if not flavor_feature_attr:
- return False, 0
- if not self._compare_attribute(flavor_feature_attr[0],
- req_feature_attr):
- return False, 0
+ req_attr_key, flavor_cfa_list)
+ if not flavor_feature_attr and capability.item['mandatory'] == 'True':
+ return False, 0, None
+ if not self._compare_attribute(flavor_feature_attr[0], req_feature_attr) \
+ and capability.item['mandatory'] == 'True':
+ return False, 0, None
if flavor_cfa_list is not None and capability.item['mandatory'] == 'False':
score = score + int(capability.item['score'])
- return True, score
+ return True, score, directives
class CapabilityDataParser(object):
@@ -268,3 +278,6 @@ class CapabilityDataParser(object):
def get_feature(self):
return self.item.get('hpa-feature')
+
+ def get_directives(self):
+ return self.item.get('directives')
diff --git a/conductor/conductor/data/service.py b/conductor/conductor/data/service.py
index e9d597b..d6ea20f 100644
--- a/conductor/conductor/data/service.py
+++ b/conductor/conductor/data/service.py
@@ -52,13 +52,13 @@ DATA_OPTS = [
'mode. When set to False, data will flush any abandoned '
'messages at startup.'),
cfg.FloatOpt('existing_placement_cost',
- default=-8000.0,
- help='Default value is -8000, which is the diameter of the earth. '
- 'The distance cannot larger than this value'),
+ default=-8000.0,
+ help='Default value is -8000, which is the diameter of the earth. '
+ 'The distance cannot larger than this value'),
cfg.FloatOpt('cloud_candidate_cost',
- default=2.0),
+ default=2.0),
cfg.FloatOpt('service_candidate_cost',
- default=1.0),
+ default=1.0),
]
CONF.register_opts(DATA_OPTS, group='data')
@@ -223,7 +223,7 @@ class DataEndpoint(object):
discard_set.add(candidate.get("candidate_id"))
return discard_set
- #(TODO:Larry) merge this function with the "get_candidate_discard_set"
+ # (TODO:Larry) merge this function with the "get_candidate_discard_set"
def get_candidate_discard_set_by_cloud_region(self, value, candidate_list, value_attrib):
discard_set = set()
@@ -239,10 +239,8 @@ class DataEndpoint(object):
(candidate.get(value_attrib) not in service_requests):
discard_set.add(candidate.get("candidate_id"))
-
return discard_set
-
def get_inventory_group_candidates(self, ctx, arg):
candidate_list = arg["candidate_list"]
resolved_candidate = arg["resolved_candidate"]
@@ -442,18 +440,22 @@ class DataEndpoint(object):
'''
error = False
candidate_list = arg["candidate_list"]
- label_name = arg["label_name"]
+ id = arg["id"]
+ type = arg["type"]
+ directives = arg["directives"]
+ attr = directives[0].get("attributes")
+ label_name = attr[0].get("attribute_name")
flavorProperties = arg["flavorProperties"]
discard_set = set()
- for candidate in candidate_list:
+ for i in range(len(candidate_list)):
# perform this check only for cloud candidates
- if candidate["inventory_type"] != "cloud":
+ if candidate_list[i]["inventory_type"] != "cloud":
continue
# Check if flavor mapping for current label_name already
# exists. This is an invalid condition.
- if candidate.get("flavor_map") and candidate["flavor_map"].get(
- label_name):
+ if candidate_list[i].get("directives") and attr[0].get(
+ "attribute_value") != "":
LOG.error(_LE("Flavor mapping for label name {} already"
"exists").format(label_name))
continue
@@ -461,31 +463,44 @@ class DataEndpoint(object):
# RPC call to inventory provider for matching hpa capabilities
results = self.ip_ext_manager.map_method(
'match_hpa',
- candidate=candidate,
+ candidate=candidate_list[i],
features=flavorProperties
)
- if results and len(results) > 0:
- flavor_info = results[0]
+ flavor_name = None
+ if results and len(results) > 0 and results[0] is not None:
+ LOG.debug("Find results {} and results length {}".format(results, len(results)))
+ flavor_info = results[0].get("flavor_map")
+ req_directives = results[0].get("directives")
+ LOG.debug("Get directives {}".format(req_directives))
+
else:
flavor_info = None
LOG.info(
_LW("No flavor mapping returned by "
"inventory provider: {} for candidate: {}").format(
self.ip_ext_manager.names()[0],
- candidate.get("candidate_id")))
+ candidate_list[i].get("candidate_id")))
if not flavor_info:
- discard_set.add(candidate.get("candidate_id"))
+ discard_set.add(candidate_list[i].get("candidate_id"))
else:
if not flavor_info.get("flavor-name"):
- discard_set.add(candidate.get("candidate_id"))
+ discard_set.add(candidate_list[i].get("candidate_id"))
else:
- # Create flavor_map if not exist already
- if not candidate.get("flavor_map"):
- candidate["flavor_map"] = {}
+ if not candidate_list[i].get("flavor_map"):
+ candidate_list[i]["flavor_map"] = {}
# Create flavor mapping for label_name to flavor
flavor_name = flavor_info.get("flavor-name")
- candidate["flavor_map"][label_name] = flavor_name
+ candidate_list[i]["flavor_map"][label_name] = flavor_name
+ # Create directives if not exist already
+ if not candidate_list[i].get("all_directives"):
+ candidate_list[i]["all_directives"] = {}
+ candidate_list[i]["all_directives"]["directives"] = []
+ # Create flavor mapping and merge directives
+ self.merge_directives(candidate_list, i, id, type, directives, req_directives)
+ if not candidate_list[i].get("hpa_score"):
+ candidate_list[i]["hpa_score"] = 0
+ candidate_list[i]["hpa_score"] += flavor_info.get("score")
# return candidates not in discard set
candidate_list[:] = [c for c in candidate_list
@@ -496,6 +511,34 @@ class DataEndpoint(object):
self.ip_ext_manager.names()[0]))
return {'response': candidate_list, 'error': error}
+ def merge_directives(self, candidate_list, index, id, type, directives, feature_directives):
+ '''
+ Merge the flavor_directives with other diectives listed under hpa capabilities in the policy
+ :param candidate_list: all candidates
+ :param index: index number
+ :param id: vfc name
+ :param type: vfc type
+ :param directives: directives for each vfc
+ :param feature_directives: directives for hpa-features
+ :return:
+ '''
+ directive= {"id": id,
+ "type": type,
+ "directives": ""}
+ for ele in directives:
+ if "flavor_directives" in ele.get("type"):
+ flag = True
+ break
+ else:
+ flag = False
+ if not flag:
+ LOG.error("No flavor directives found in {}".format(id))
+ for item in feature_directives:
+ if item and item not in directives:
+ directives.append(item)
+ directive["directives"] = directives
+ candidate_list[index]["all_directives"]["directives"].append(directive)
+
def get_candidates_with_vim_capacity(self, ctx, arg):
'''
RPC for getting candidates with vim capacity
diff --git a/conductor/conductor/opts.py b/conductor/conductor/opts.py
index e2ace38..52624cf 100644
--- a/conductor/conductor/opts.py
+++ b/conductor/conductor/opts.py
@@ -22,6 +22,7 @@ import itertools
import conductor.api.app
import conductor.common.music.api
import conductor.common.music.messaging.component
+import conductor.common.sms
import conductor.conf.inventory_provider
import conductor.conf.service_controller
import conductor.conf.vim_controller
@@ -68,4 +69,5 @@ def list_opts():
('music_api', conductor.common.music.api.MUSIC_API_OPTS),
('solver', conductor.solver.service.SOLVER_OPTS),
('reservation', conductor.reservation.service.reservation_OPTS),
+ ('aaf_sms', conductor.common.sms.AAF_SMS_OPTS),
]
diff --git a/conductor/conductor/solver/optimizer/constraints/hpa.py b/conductor/conductor/solver/optimizer/constraints/hpa.py
index 98d95d9..106953b 100644
--- a/conductor/conductor/solver/optimizer/constraints/hpa.py
+++ b/conductor/conductor/solver/optimizer/constraints/hpa.py
@@ -54,15 +54,19 @@ class HPA(constraint.Constraint):
self.constraint_type, demand_name))
vm_label_list = self.properties.get('evaluate')
for vm_demand in vm_label_list:
- label_name = vm_demand['flavorLabel']
+ id = vm_demand['id']
+ type = vm_demand['type']
+ directives = vm_demand['directives']
flavorProperties = vm_demand['flavorProperties']
- response = (cei.get_candidates_with_hpa(label_name,
+ response = (cei.get_candidates_with_hpa(id,
+ type,
+ directives,
_candidate_list,
flavorProperties))
_candidate_list = response
if not response:
LOG.error(_LE("No matching candidates for HPA exists").format(
- label_name))
+ id))
break
# No need to continue.
diff --git a/conductor/conductor/solver/request/functions/hpa_score.py b/conductor/conductor/solver/request/functions/hpa_score.py
new file mode 100755
index 0000000..b09e4a7
--- /dev/null
+++ b/conductor/conductor/solver/request/functions/hpa_score.py
@@ -0,0 +1,29 @@
+#!/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.
+#
+# -------------------------------------------------------------------------
+#
+
+'''Objective function for hpa_score
+ Hardware Platform Awareness (HPA)'''
+
+
+class HPAScore(object):
+
+ def __init__(self, _type):
+ self.func_type = _type
+ self.score = 0
diff --git a/conductor/conductor/solver/request/objective.py b/conductor/conductor/solver/request/objective.py
index 0559056..d957581 100755
--- a/conductor/conductor/solver/request/objective.py
+++ b/conductor/conductor/solver/request/objective.py
@@ -109,6 +109,20 @@ class Operand(object):
for demand_name, candidate_info in _decision_path.decisions.items():
value += float(candidate_info['cost'])
+ elif self.function.func_type == "hpa_score":
+ # Currently only minimize objective goal is supported
+ # Higher the HPA score the better.
+ # Invert HPA Score if goal is minimize
+ invert = -1
+
+ #
+ # if self.function.goal == "max":
+ # invert = 1
+
+ for demand_name, candidate_info in _decision_path.decisions.items():
+ hpa_score = invert * float(candidate_info.get('hpa_score', 0))
+ value += hpa_score
+
if self.operation == "product":
value *= self.weight
diff --git a/conductor/conductor/solver/request/parser.py b/conductor/conductor/solver/request/parser.py
index 0def215..da031cc 100755
--- a/conductor/conductor/solver/request/parser.py
+++ b/conductor/conductor/solver/request/parser.py
@@ -41,6 +41,7 @@ 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.functions import hpa_score
from oslo_log import log
# from conductor.solver.request.functions import distance_between
@@ -263,6 +264,9 @@ class Parser(object):
func = cost.Cost("cost")
func.loc = operand_data["function_param"]
operand.function = func
+ elif operand_data["function"] == "hpa_score":
+ func = hpa_score.HPAScore("hpa_score")
+ operand.function = func
self.objective.operand_list.append(operand)
diff --git a/conductor/conductor/solver/service.py b/conductor/conductor/solver/service.py
index e539acd..56ec683 100644
--- a/conductor/conductor/solver/service.py
+++ b/conductor/conductor/solver/service.py
@@ -347,10 +347,10 @@ class SolverService(cotyledon.Service):
rec["candidate"]["host_id"] = resource.get("host_id")
if rec["candidate"]["inventory_type"] == "cloud":
- if resource.get("flavor_map"):
- rec["attributes"]["flavors"] = resource.get(
- "flavor_map")
-
+ if resource.get("all_directives") and resource.get("flavor_map"):
+ rec["attributes"]["directives"] = \
+ self.set_flavor_in_flavor_directives(
+ resource.get("flavor_map"), resource.get("all_directives"))
# TODO(snarayanan): Add total value to recommendations?
# msg = "--- total value of decision = {}"
# LOG.debug(msg.format(_best_path.total_value))
@@ -387,3 +387,18 @@ class SolverService(cotyledon.Service):
"""Reload"""
LOG.debug("%s" % self.__class__.__name__)
self._restart()
+
+ def set_flavor_in_flavor_directives(self, flavor_map, directives):
+ '''
+ Insert the flavor name inside the flavor_map into flavor_directives
+ :param flavor_map: flavor map get
+ :param directives: All the directives get from request
+ '''
+ flavor_label = flavor_map.keys()
+ for ele in directives.get("directives"):
+ for item in ele.get("directives"):
+ if "flavor_directives" in item.get("type"):
+ for attr in item.get("attributes"):
+ attr["attribute_value"] = flavor_map.get(attr["attribute_name"]) \
+ if attr.get("attribute_name") in flavor_label else ""
+ return directives
diff --git a/conductor/conductor/solver/utils/constraint_engine_interface.py b/conductor/conductor/solver/utils/constraint_engine_interface.py
index 43331aa..bbb782d 100644
--- a/conductor/conductor/solver/utils/constraint_engine_interface.py
+++ b/conductor/conductor/solver/utils/constraint_engine_interface.py
@@ -117,7 +117,7 @@ class ConstraintEngineInterface(object):
# response is a list of (candidate, cost) tuples
return response
- def get_candidates_with_hpa(self, label_name, candidate_list,
+ def get_candidates_with_hpa(self, id, type, directives, candidate_list,
flavorProperties):
'''
Returns the candidate_list with an addition of flavor_mapping for
@@ -130,7 +130,9 @@ class ConstraintEngineInterface(object):
ctxt = {}
args = {"candidate_list": candidate_list,
"flavorProperties": flavorProperties,
- "label_name": label_name}
+ "id": id,
+ "type": type,
+ "directives": directives}
response = self.client.call(ctxt=ctxt,
method="get_candidates_with_hpa",
args=args)
diff --git a/conductor/conductor/tests/unit/controller/hpa_constraints.json b/conductor/conductor/tests/unit/controller/hpa_constraints.json
new file mode 100644
index 0000000..a2543bc
--- /dev/null
+++ b/conductor/conductor/tests/unit/controller/hpa_constraints.json
@@ -0,0 +1,221 @@
+{
+ "HAS_Template": {
+ "constraints": {
+ "hpa_constraint_vG": {
+ "demands": [
+ "vG"
+ ],
+ "name": "hpa_constraint_vG",
+ "type": "hpa",
+ "properties": {
+ "evaluate": [
+ {
+ "flavorProperties": [
+ {
+ "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",
+ "directives": []
+ },
+ {
+ "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",
+ "directives": []
+ },
+ {
+ "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",
+ "directives": []
+ }
+ ],
+ "id": "vg_1",
+ "type": "vnfc",
+ "directives": [
+ {
+ "type": "flavor_directives",
+ "attributes": [
+ {
+ "attribute_name": "flavor_label_1",
+ "attribute_value": ""
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "flavorProperties": [
+ {
+ "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",
+ "directives": []
+ },
+ {
+ "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",
+ "directives": []
+ },
+ {
+ "architecture": "generic",
+ "hpa-feature": "memoryPageSize",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "memoryPageSize",
+ "hpa-attribute-value": "2",
+ "operator": "=",
+ "unit": "GB"
+ }
+ ],
+ "hpa-version": "v1",
+ "directives": []
+ }
+ ],
+ "id": "vg_2",
+ "type": "vnfc",
+ "directives": [
+ {
+ "type": "flavor_directives",
+ "attributes": [
+ {
+ "attribute_name": "flavor_label_2",
+ "attribute_value": ""
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "constraint_vgmux_customer": {
+ "type": "distance_to_location",
+ "demands": [
+ "vGMuxInfra"
+ ],
+ "properties": {
+ "distance": "< 100 km",
+ "location": "customer_loc"
+ }
+ },
+ "check_cloud_capacity": {
+ "type": "vim_fit",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "controller": "multicloud",
+ "request": {
+ "vCPU": 10,
+ "Memory": {
+ "quantity": "10",
+ "unit": "GB"
+ },
+ "Storage": {
+ "quantity": "100",
+ "unit": "GB"
+ }
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/controller/test_translator.py b/conductor/conductor/tests/unit/controller/test_translator.py
index ba0e3ec..c61e57e 100644
--- a/conductor/conductor/tests/unit/controller/test_translator.py
+++ b/conductor/conductor/tests/unit/controller/test_translator.py
@@ -232,12 +232,25 @@ class TestNoExceptionTranslator(unittest.TestCase):
],
"properties": {
"evaluate": [
- {'flavorLabel': 'xx',
+ {'id': 'vg_0',
+ 'type': 'vnfc',
+ 'directives': [
+ {
+ "type": "flavor_directives",
+ "attributes": [
+ {
+ "attribute_name": "label_0",
+ "attribute_value": ""
+ }
+ ]
+ }
+ ],
'flavorProperties': [{
'hpa-feature': 'BasicCapabilities',
'hpa-version': 'v1',
'architecture': 'generic',
'mandatory': 'False',
+ 'directives': [],
'score': '5',
'hpa-feature-attributes': [
{
@@ -264,6 +277,7 @@ class TestNoExceptionTranslator(unittest.TestCase):
{'architecture': 'generic',
'mandatory': 'False',
'score': '5',
+ 'directives': [],
'hpa-feature': 'BasicCapabilities',
'hpa-feature-attributes': [
{
@@ -279,7 +293,20 @@ class TestNoExceptionTranslator(unittest.TestCase):
}
],
'hpa-version': 'v1'}],
- 'flavorLabel': 'xx'}]},
+ 'id': 'vg_0',
+ 'type': 'vnfc',
+ 'directives': [
+ {
+ 'type': 'flavor_directives',
+ 'attributes': [
+ {
+ 'attribute_name': 'label_0',
+ 'attribute_value': ''
+ }
+ ]
+ }
+ ]
+ }]},
'type': 'hpa'
}
}
@@ -295,12 +322,25 @@ class TestNoExceptionTranslator(unittest.TestCase):
],
"properties": {
"evaluate": [
- {'flavorLabel': 'xx',
+ {'id': 'vg_0',
+ 'type': 'vnfc',
+ 'directives': [
+ {
+ 'type': 'flavor_directives',
+ 'attributes': [
+ {
+ 'attribute_name': 'label_0',
+ 'attribute_value': ''
+ }
+ ]
+ }
+ ],
'flavorProperties': [{
'hpa-feature': 'BasicCapabilities',
'hpa-version': 'v1',
'architecture': 'generic',
'mandatory': 'True',
+ 'directives': [],
'hpa-feature-attributes': [
{
'hpa-attribute-key': 'numVirtualCpu',
@@ -325,6 +365,7 @@ class TestNoExceptionTranslator(unittest.TestCase):
'flavorProperties': [
{'architecture': 'generic',
'mandatory': 'True',
+ 'directives': [],
'hpa-feature': 'BasicCapabilities',
'hpa-feature-attributes': [
{
@@ -340,7 +381,19 @@ class TestNoExceptionTranslator(unittest.TestCase):
}
],
'hpa-version': 'v1'}],
- 'flavorLabel': 'xx'}]},
+ 'id': 'vg_0',
+ 'type': 'vnfc',
+ 'directives': [
+ {
+ 'type': 'flavor_directives',
+ 'attributes': [
+ {
+ 'attribute_name': 'label_0',
+ 'attribute_value': ''
+ }
+ ]
+ }
+ ]}]},
'type': 'hpa'
}
}
@@ -356,7 +409,7 @@ class TestNoExceptionTranslator(unittest.TestCase):
"vG"
],
"properties": {
- "evaluate": [{'flavor': 'xx',
+ "evaluate": [{'id': 'xx',
'flavorProperties': []}]
}
}
@@ -369,7 +422,8 @@ class TestNoExceptionTranslator(unittest.TestCase):
],
"properties": {
"evaluate": [
- {'flavorLabel': 'xx',
+ {'id': 'xx',
+ 'type': 'xx',
'flavorProperties': [
{
'hpa-feature': '',
@@ -393,7 +447,9 @@ class TestNoExceptionTranslator(unittest.TestCase):
"properties": {
"evaluate": [
{
- "flavorLabel": "xx",
+ "id": "xx",
+ "type": 'xx',
+ "directives": [],
"flavorProperties": [
{
"hpa-feature": "BasicCapabilities",
@@ -422,7 +478,9 @@ class TestNoExceptionTranslator(unittest.TestCase):
"vG"
],
"properties": {
- "evaluate": [{'flavorLabel': 'xx',
+ "evaluate": [{'id': 'xx',
+ "type": 'xx',
+ "directives": [],
'flavorProperties': [{
'hpa-feature': '',
'architecture': '',
@@ -573,6 +631,62 @@ class TestNoExceptionTranslator(unittest.TestCase):
opt), expected_parse)
@patch('conductor.controller.translator.Translator.create_components')
+ def test_parse_optimization_multi_objective(self, mock_create):
+ hpa_json_file = './conductor/tests/unit/controller/hpa_constraints.json'
+ hpa_json = yaml.safe_load(open(hpa_json_file).read())
+ expected_parse = {'goal': 'min',
+ 'operands': [{'function': 'distance_between',
+ 'function_param': ['customer_loc',
+ 'vGMuxInfra'],
+ 'operation': 'product',
+ 'weight': 2.0},
+ {'function': 'distance_between',
+ 'function_param': ['customer_loc',
+ 'vG'],
+ 'operation': 'product',
+ 'weight': 4.0},
+ {'function': 'hpa_score',
+ 'function_param': ['vG'],
+ 'operation': 'product',
+ 'weight': 8.0},
+ ],
+ 'operation': 'sum'
+ }
+
+ opt = {'minimize': {
+ 'sum': [{'product': [2.0, {'distance_between': ['customer_loc', 'vGMuxInfra']}]},
+ {'product': [4.0, {'distance_between': ['customer_loc', 'vG']}]},
+ {'product': [8.0, {'hpa_score': ['vG']}]}
+ ]}}
+ self.Translator._demands = {'vG': '',
+ 'vGMuxInfra': '',
+ 'customer_loc': ''}
+ self.Translator._locations = {'vG': '',
+ 'vGMuxInfra': '',
+ 'customer_loc': ''}
+ self.Translator._constraints = hpa_json["HAS_Template"]["constraints"]
+ self.maxDiff = None
+ self.assertEquals(
+ self.Translator.parse_optimization(
+ opt), expected_parse)
+
+ # No HPA Policy test
+ non_hpa_dict = dict(hpa_json["HAS_Template"]["constraints"])
+ non_hpa_dict.pop("hpa_constraint_vG")
+ self.Translator._constraints = non_hpa_dict
+ self.maxDiff = None
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_optimization, opt)
+
+ # HPA Policy Exists but not for the demand in objective function
+ hpa_wrong_demand_dict = dict(hpa_json["HAS_Template"]["constraints"])
+ hpa_wrong_demand_dict["hpa_constraint_vG"]["demands"] = ["vGMuxInfra"]
+ self.Translator._constraints = hpa_wrong_demand_dict
+ self.maxDiff = None
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_optimization, opt)
+
+ @patch('conductor.controller.translator.Translator.create_components')
def test_parse_reservation(self, mock_create):
expected_resv = {'counter': 0, 'demands': {
'instance_vG': {'demands': {'vG': 'null'},
diff --git a/conductor/conductor/tests/unit/data/hpa_constraints.json b/conductor/conductor/tests/unit/data/hpa_constraints.json
index 829eaf3..0f42cc9 100644
--- a/conductor/conductor/tests/unit/data/hpa_constraints.json
+++ b/conductor/conductor/tests/unit/data/hpa_constraints.json
@@ -26,7 +26,8 @@
"unit": "GB"
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
},
{
"architecture": "generic",
@@ -60,7 +61,8 @@
"unit": "GB"
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
},
{
"architecture": "generic",
@@ -77,10 +79,23 @@
"operator": "="
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
}
],
- "flavorLabel": "flavor_label_1"
+ "id": "vg_1",
+ "type": "vnfc",
+ "directives": [
+ {
+ "type": "flavor_directives",
+ "attributes": [
+ {
+ "attribute_name": "flavor_label_1",
+ "attribute_value": ""
+ }
+ ]
+ }
+ ]
},
{
"flavorProperties": [
@@ -100,7 +115,8 @@
"unit": "GB"
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
},
{
"architecture": "generic",
@@ -134,7 +150,8 @@
"unit": "GB"
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
},
{
"architecture": "generic",
@@ -147,10 +164,23 @@
"unit": "GB"
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
}
],
- "flavorLabel": "flavor_label_2"
+ "id": "vg_2",
+ "type": "vnfc",
+ "directives": [
+ {
+ "type": "flavor_directives",
+ "attributes": [
+ {
+ "attribute_name": "flavor_label_2",
+ "attribute_value": ""
+ }
+ ]
+ }
+ ]
}
]
}
diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_flavors.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_flavors.json
index db5ea54..19cf8b9 100644
--- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_flavors.json
+++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_flavors.json
@@ -198,6 +198,105 @@
"resource-version": "1520943846264"
},
{
+ "flavor-id": "f5aa2b2e-3206-41b6-80d5-cf6t2b098c43",
+ "flavor-name": "flavor-ovsdpdk-cpu-pinning-sriov-NIC-Network-set",
+ "flavor-vcpus": 32,
+ "flavor-ram": 131072,
+ "flavor-disk": 2097152,
+ "flavor-ephemeral": 128,
+ "flavor-swap": "0",
+ "flavor-is-public": false,
+ "flavor-selflink": "pXtX",
+ "flavor-disabled": false,
+ "hpa-capabilities": {
+ "hpa-capability": [
+ {
+ "hpa-capability-id": "8d36a8fe-bfee-446a-bbcb-881ee66c8f78",
+ "hpa-feature": "ovsDpdk",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "resource-version": "1520943846328",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "dataProcessingAccelerationLibrary",
+ "hpa-attribute-value": "{\"value\":\"v18.02\"}",
+ "resource-version": "1520943846346"
+ }
+ ]
+ },
+ {
+ "hpa-capability-id": "c140c945-1532-4908-86c9-d7f71416f1dd",
+ "hpa-feature": "cpuPinning",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "resource-version": "1520943846297",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "logicalCpuPinningPolicy",
+ "hpa-attribute-value": "{\"value\":\"dedicated\"}",
+ "resource-version": "1520943846312"
+ },
+ {
+ "hpa-attribute-key": "logicalCpuThreadPinningPolicy",
+ "hpa-attribute-value": "{\"value\":\"prefer\"}",
+ "resource-version": "1520943846301"
+ }
+ ]
+ },
+ {
+ "hpa-capability-id": "4565615b-1077-4bb5-a340-c5be48db2aaa",
+ "hpa-feature": "basicCapabilities",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "resource-version": "1520943846269",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "virtualMemSize",
+ "hpa-attribute-value": "{\"value\":\"16\", \"unit\":\"GB\" }",
+ "resource-version": "1520943846282"
+ },
+ {
+ "hpa-attribute-key": "numVirtualCpu",
+ "hpa-attribute-value": "{\"value\":\"8\"}",
+ "resource-version": "1520943846272"
+ }
+ ]
+ },
+ {
+ "hpa-capability-id": "8fa22e64-41b4-471f-96ad-6c470868eo4c",
+ "hpa-feature": "sriovNICNetwork",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "resource-version": "1520943845443",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "pciCount",
+ "hpa-attribute-value": "{\"value\":\"1\"}",
+ "resource-version": "1520943845870"
+ },
+ {
+ "hpa-attribute-key": "pciVendorId",
+ "hpa-attribute-value": "{\"value\": \"8086\"}",
+ "resource-version": "1520943845764"
+ },
+ {
+ "hpa-attribute-key": "pciDeviceId",
+ "hpa-attribute-value": "{\"value\": \"1234\"}",
+ "resource-version": "1520943847729"
+ },
+ {
+ "hpa-attribute-key": "physicalNetwork",
+ "hpa-attribute-value": "{\"value\": \"physnet1\"}",
+ "resource-version": "1520943871129"
+ }
+ ]
+ }
+
+ ]
+ },
+ "resource-version": "1520943439264"
+ },
+ {
"flavor-id": "f5aa2b2e-3206-41b6-80d5-cf041b098c43",
"flavor-name": "flavor-cpu-pinning-ovsdpdk-instruction-set",
"flavor-vcpus": 32,
diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_req_features.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_req_features.json
index 4a5d37a..bfda6a7 100644
--- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_req_features.json
+++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/hpa_req_features.json
@@ -1,200 +1,327 @@
[
- [
- {
- "hpa-feature":"basicCapabilities",
- "hpa-version":"v1",
- "architecture":"generic",
- "mandatory":"True",
- "hpa-feature-attributes":[
+ [
+ {
+ "hpa-feature": "basicCapabilities",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "True",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numVirtualCpu",
+ "hpa-attribute-value": "8",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "virtualMemSize",
+ "hpa-attribute-value": "16384",
+ "operator": "=",
+ "unit": "MB"
+ }
+ ]
+ },
+ {
+ "hpa-feature": "cpuPinning",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "True",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "logicalCpuThreadPinningPolicy",
+ "hpa-attribute-value": "prefer",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "logicalCpuPinningPolicy",
+ "hpa-attribute-value": "dedicated",
+ "operator": "="
+ }
+ ]
+ },
+ {
+ "hpa-feature": "cpuTopology",
+ "hpa-version": "v1",
+ "mandatory": "True",
+ "architecture": "generic",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numCpuSockets",
+ "hpa-attribute-value": "2",
+ "operator": ">=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuSockets",
+ "hpa-attribute-value": "4",
+ "operator": "<=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuCores",
+ "hpa-attribute-value": "2",
+ "operator": ">=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuCores",
+ "hpa-attribute-value": "4",
+ "operator": "<=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuThreads",
+ "hpa-attribute-value": "4",
+ "operator": ">=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuThreads",
+ "hpa-attribute-value": "8",
+ "operator": "<=",
+ "unit": ""
+ }
+ ]
+ }
+ ],
+ [
+ {
+ "hpa-feature": "basicCapabilities",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "True",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numVirtualCpu",
+ "hpa-attribute-value": "8",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "virtualMemSize",
+ "hpa-attribute-value": "16384",
+ "operator": "=",
+ "unit": "MB"
+ }
+ ]
+ },
+ {
+ "hpa-feature": "ovsDpdk",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "False",
+ "score": "5",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "dataProcessingAccelerationLibrary",
+ "hpa-attribute-value": "v18.02",
+ "operator": "="
+ }
+ ]
+ },
+ {
+ "hpa-feature": "cpuPinning",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "False",
+ "score": "1",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "logicalCpuThreadPinningPolicy",
+ "hpa-attribute-value": "prefer",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "logicalCpuPinningPolicy",
+ "hpa-attribute-value": "dedicated",
+ "operator": "="
+ }
+ ]
+ },
+ {
+ "hpa-feature": "instructionSetExtensions",
+ "hpa-version": "v1",
+ "architecture": "Intel64",
+ "mandatory": "False",
+ "score": "5",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "instructionSetExtensions",
+ "hpa-attribute-value": [
+ "A11",
+ "B22"
+ ],
+ "operator": "ALL"
+ }
+ ]
+ },
+ {
+ "hpa-feature": "cpuTopology",
+ "hpa-version": "v1",
+ "mandatory": "True",
+ "architecture": "generic",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numCpuSockets",
+ "hpa-attribute-value": "2",
+ "operator": ">=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuSockets",
+ "hpa-attribute-value": "4",
+ "operator": "<=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuCores",
+ "hpa-attribute-value": "2",
+ "operator": ">=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuCores",
+ "hpa-attribute-value": "4",
+ "operator": "<=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuThreads",
+ "hpa-attribute-value": "4",
+ "operator": ">=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "numCpuThreads",
+ "hpa-attribute-value": "8",
+ "operator": "<=",
+ "unit": ""
+ }
+ ]
+ }
+ ],
+ [
+ {
+ "hpa-feature": "basicCapabilities",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "True",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numVirtualCpu",
+ "hpa-attribute-value": "8",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "virtualMemSize",
+ "hpa-attribute-value": "16",
+ "operator": "=",
+ "unit": "GB"
+ }
+ ]
+ },
+ {
+ "hpa-feature": "ovsDpdk",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "False",
+ "score": "5",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "dataProcessingAccelerationLibrary",
+ "hpa-attribute-value": "v18.02",
+ "operator": "="
+ }
+ ]
+ },
+ {
+ "hpa-feature": "cpuPinning",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "False",
+ "score": "1",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "logicalCpuThreadPinningPolicy",
+ "hpa-attribute-value": "prefer",
+ "operator": "="
+ },
+ {
+ "hpa-attribute-key": "logicalCpuPinningPolicy",
+ "hpa-attribute-value": "dedicated",
+ "operator": "="
+ }
+ ]
+ },
+ {
+ "hpa-feature": "instructionSetExtensions",
+ "hpa-version": "v1",
+ "architecture": "Intel64",
+ "mandatory": "False",
+ "score": "5",
+ "directives": [],
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "instructionSetExtensions",
+ "hpa-attribute-value": [
+ "A11",
+ "B22"
+ ],
+ "operator": "ALL"
+ }
+ ]
+ },
+ {
+ "hpa-feature": "sriovNICNetwork",
+ "hpa-version": "v1",
+ "mandatory": "False",
+ "architecture": "generic",
+ "directives": [
+ {
+ "type": "sriovNICNetwork_directives",
+ "attributes": [
{
- "hpa-attribute-key":"numVirtualCpu",
- "hpa-attribute-value":"8",
- "operator":"="
- },
- {
- "hpa-attribute-key":"virtualMemSize",
- "hpa-attribute-value":"16384",
- "operator":"=",
- "unit":"MB"
- }
- ]
- },
- {
- "hpa-feature":"cpuPinning",
- "hpa-version":"v1",
- "architecture":"generic",
- "mandatory":"True",
- "hpa-feature-attributes":[
- {
- "hpa-attribute-key":"logicalCpuThreadPinningPolicy",
- "hpa-attribute-value":"prefer",
- "operator":"="
- },
- {
- "hpa-attribute-key":"logicalCpuPinningPolicy",
- "hpa-attribute-value":"dedicated",
- "operator":"="
- }
- ]
- },
- {
- "hpa-feature":"cpuTopology",
- "hpa-version":"v1",
- "mandatory":"True",
- "architecture":"generic",
- "hpa-feature-attributes":[
- {
- "hpa-attribute-key":"numCpuSockets",
- "hpa-attribute-value":"2",
- "operator":">=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuSockets",
- "hpa-attribute-value":"4",
- "operator":"<=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuCores",
- "hpa-attribute-value":"2",
- "operator":">=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuCores",
- "hpa-attribute-value":"4",
- "operator":"<=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuThreads",
- "hpa-attribute-value":"4",
- "operator":">=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuThreads",
- "hpa-attribute-value":"8",
- "operator":"<=",
- "unit":""
- }
- ]
- }
- ],
- [
- {
- "hpa-feature":"basicCapabilities",
- "hpa-version":"v1",
- "architecture":"generic",
- "mandatory":"True",
- "hpa-feature-attributes":[
- {
- "hpa-attribute-key":"numVirtualCpu",
- "hpa-attribute-value":"8",
- "operator":"="
- },
- {
- "hpa-attribute-key":"virtualMemSize",
- "hpa-attribute-value":"16384",
- "operator":"=",
- "unit":"MB"
- }
- ]
- },
- {
- "hpa-feature":"ovsDpdk",
- "hpa-version":"v1",
- "architecture":"generic",
- "mandatory":"False",
- "score":"5",
- "hpa-feature-attributes":[
- {
- "hpa-attribute-key":"dataProcessingAccelerationLibrary",
- "hpa-attribute-value":"v18.02",
- "operator":"="
- }
- ]
- },
- {
- "hpa-feature":"cpuPinning",
- "hpa-version":"v1",
- "architecture":"generic",
- "mandatory":"False",
- "score":"1",
- "hpa-feature-attributes":[
- {
- "hpa-attribute-key":"logicalCpuThreadPinningPolicy",
- "hpa-attribute-value":"prefer",
- "operator":"="
- },
- {
- "hpa-attribute-key":"logicalCpuPinningPolicy",
- "hpa-attribute-value":"dedicated",
- "operator":"="
- }
- ]
- },
- {
- "hpa-feature":"instructionSetExtensions",
- "hpa-version":"v1",
- "architecture":"Intel64",
- "mandatory":"False",
- "score":"5",
- "hpa-feature-attributes":[
- {
- "hpa-attribute-key":"instructionSetExtensions",
- "hpa-attribute-value":[
- "A11",
- "B22"
- ],
- "operator":"ALL"
- }
- ]
- },
- {
- "hpa-feature":"cpuTopology",
- "hpa-version":"v1",
- "mandatory":"True",
- "architecture":"generic",
- "hpa-feature-attributes":[
- {
- "hpa-attribute-key":"numCpuSockets",
- "hpa-attribute-value":"2",
- "operator":">=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuSockets",
- "hpa-attribute-value":"4",
- "operator":"<=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuCores",
- "hpa-attribute-value":"2",
- "operator":">=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuCores",
- "hpa-attribute-value":"4",
- "operator":"<=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuThreads",
- "hpa-attribute-value":"4",
- "operator":">=",
- "unit":""
- },
- {
- "hpa-attribute-key":"numCpuThreads",
- "hpa-attribute-value":"8",
- "operator":"<=",
- "unit":""
+ "attribute_name": "A",
+ "attribute_value": "a"
}
- ]
- }
- ]
+ ]
+ }
+ ],
+ "score": "7",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "pciCount",
+ "hpa-attribute-value": "1",
+ "operator": "=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "pciVendorId",
+ "hpa-attribute-value": "8086",
+ "operator": "=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "pciDeviceId",
+ "hpa-attribute-value": "1234",
+ "operator": "=",
+ "unit": ""
+ },
+ {
+ "hpa-attribute-key": "physicalNetwork",
+ "hpa-attribute-value": "physnet1",
+ "operator": "=",
+ "unit": ""
+ }
+ ]
+ }
+ ]
] \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py
index e12a114..435e0a9 100644
--- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py
+++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py
@@ -28,7 +28,6 @@ from oslo_config import cfg
class TestAAI(unittest.TestCase):
def setUp(self):
-
CONF = cfg.CONF
CONF.register_opts(aai.AAI_OPTS, group='aai')
self.conf = CONF
@@ -38,20 +37,16 @@ class TestAAI(unittest.TestCase):
mock.patch.stopall()
def test_get_version_from_string(self):
-
self.assertEqual("2.5", self.aai_ep._get_version_from_string("AAI2.5"))
self.assertEqual("3.0", self.aai_ep._get_version_from_string("AAI3.0"))
def test_aai_versioned_path(self):
-
self.assertEqual('/{}/cloud-infrastructure/cloud-regions/?depth=0'.format(self.conf.aai.server_url_version),
self.aai_ep._aai_versioned_path("/cloud-infrastructure/cloud-regions/?depth=0"))
self.assertEqual('/{}/query?format=id'.format(self.conf.aai.server_url_version),
self.aai_ep._aai_versioned_path("/query?format=id"))
-
def test_resolve_clli_location(self):
-
req_json_file = './conductor/tests/unit/data/plugins/inventory_provider/_request_clli_location.json'
req_json = json.loads(open(req_json_file).read())
@@ -62,11 +57,10 @@ class TestAAI(unittest.TestCase):
self.mock_get_request = mock.patch.object(AAI, '_request', return_value=response)
self.mock_get_request.start()
- self.assertEqual({'country': u'USA', 'latitude': u'40.39596', 'longitude': u'-74.135342'} ,
- self.aai_ep.resolve_clli_location("clli_code"))
+ self.assertEqual({'country': u'USA', 'latitude': u'40.39596', 'longitude': u'-74.135342'},
+ self.aai_ep.resolve_clli_location("clli_code"))
def test_get_inventory_group_pair(self):
-
req_json_file = './conductor/tests/unit/data/plugins/inventory_provider/_request_inventory_group_pair.json'
req_json = json.loads(open(req_json_file).read())
@@ -77,11 +71,10 @@ class TestAAI(unittest.TestCase):
self.mock_get_request = mock.patch.object(AAI, '_request', return_value=response)
self.mock_get_request.start()
- self.assertEqual([[u'instance-1', u'instance-2']] ,
- self.aai_ep.get_inventory_group_pairs("service_description"))
+ self.assertEqual([[u'instance-1', u'instance-2']],
+ self.aai_ep.get_inventory_group_pairs("service_description"))
def test_resolve_host_location(self):
-
req_json_file = './conductor/tests/unit/data/plugins/inventory_provider/_request_host_name.json'
req_json = json.loads(open(req_json_file).read())
@@ -99,11 +92,10 @@ class TestAAI(unittest.TestCase):
self.mock_get_complex = mock.patch.object(AAI, '_get_complex', return_value=complex_json)
self.mock_get_complex.start()
- self.assertEqual({'country': u'USA', 'latitude': u'28.543251', 'longitude': u'-81.377112'} ,
+ self.assertEqual({'country': u'USA', 'latitude': u'28.543251', 'longitude': u'-81.377112'},
self.aai_ep.resolve_host_location("host_name"))
def test_resolve_demands(self):
-
self.assertEqual({}, self.aai_ep.resolve_demands(dict()))
demands_list_file = './conductor/tests/unit/data/plugins/inventory_provider/demand_list.json'
@@ -126,7 +118,8 @@ class TestAAI(unittest.TestCase):
req_response.ok = True
req_response.json.return_value = demand_service_response
- self.mock_first_level_service_call = mock.patch.object(AAI, 'first_level_service_call', return_value=generic_vnf_list)
+ self.mock_first_level_service_call = mock.patch.object(AAI, 'first_level_service_call',
+ return_value=generic_vnf_list)
self.mock_first_level_service_call.start()
self.mock_get_regions = mock.patch.object(AAI, '_get_regions', return_value=regions_response)
@@ -167,7 +160,6 @@ class TestAAI(unittest.TestCase):
self.aai_ep.resolve_demands(demands_list))
def test_get_complex(self):
-
complex_json_file = './conductor/tests/unit/data/plugins/inventory_provider/_request_get_complex.json'
complex_json = json.loads(open(complex_json_file).read())
@@ -179,12 +171,12 @@ class TestAAI(unittest.TestCase):
self.mock_get_request = mock.patch.object(AAI, '_request', return_value=response)
self.mock_get_request.start()
- self.assertEqual({u'city': u'Middletown', u'latitude': u'28.543251', u'longitude': u'-81.377112', u'country': u'USA', u'region': u'SE'} ,
- self.aai_ep._get_complex("/v10/complex/complex_id", "complex_id"))
-
+ self.assertEqual(
+ {u'city': u'Middletown', u'latitude': u'28.543251', u'longitude': u'-81.377112', u'country': u'USA',
+ u'region': u'SE'},
+ self.aai_ep._get_complex("/v10/complex/complex_id", "complex_id"))
def test_check_network_roles(self):
-
network_role_json_file = './conductor/tests/unit/data/plugins/inventory_provider/_request_network_role.json'
network_role_json = json.loads(open(network_role_json_file).read())
@@ -195,12 +187,10 @@ class TestAAI(unittest.TestCase):
self.mock_get_request = mock.patch.object(AAI, '_request', return_value=response)
self.mock_get_request.start()
- self.assertEqual(set(['test-cloud-value']) ,
- self.aai_ep.check_network_roles("network_role_id"))
-
+ self.assertEqual(set(['test-cloud-value']),
+ self.aai_ep.check_network_roles("network_role_id"))
def test_check_candidate_role(self):
-
candidate_role_json_file = './conductor/tests/unit/data/plugins/inventory_provider/_request_candidate_role.json'
candidate_role_json = json.loads(open(candidate_role_json_file).read())
@@ -223,7 +213,8 @@ class TestAAI(unittest.TestCase):
inventory_attributes['attr-1'] = 'attr-1-value1'
self.assertEqual(True,
- self.aai_ep.match_inventory_attributes(template_attributes, inventory_attributes, "candidate-id"))
+ self.aai_ep.match_inventory_attributes(template_attributes, inventory_attributes,
+ "candidate-id"))
template_attributes['attr-1'] = {
'not': ['attr-1-value2']
@@ -268,7 +259,6 @@ class TestAAI(unittest.TestCase):
self.aai_ep._refresh_cache())
def test_get_aai_rel_link(self):
-
relatonship_response_file = './conductor/tests/unit/data/plugins/inventory_provider/relationship_list.json'
relatonship_response = json.loads(open(relatonship_response_file).read())
related_to = "service-instance"
@@ -301,7 +291,7 @@ class TestAAI(unittest.TestCase):
def test_match_hpa(self):
flavor_json_file = \
- './conductor/tests/unit/data/plugins/inventory_provider/hpa_flavors.json'
+ './conductor/tests/unit/data/plugins/inventory_provider/hpa_flavors.json'
flavor_json = json.loads(open(flavor_json_file).read())
feature_json_file = \
'./conductor/tests/unit/data/plugins/inventory_provider/hpa_req_features.json'
@@ -310,15 +300,34 @@ class TestAAI(unittest.TestCase):
candidate_json = json.loads(open(candidate_json_file).read())
candidate_json['candidate_list'][1]['flavors'] = flavor_json
- flavor_map = {"flavor-id": "f5aa2b2e-3206-41b6-80d5-cf041b098c43",
- "flavor-name": "flavor-cpu-pinning-ovsdpdk-instruction-set"}
+ flavor_map = {
+ "directives": [],
+ "flavor_map": {"flavor-id": "f5aa2b2e-3206-41b6-80d5-cf041b098c43",
+ "flavor-name": "flavor-cpu-pinning-ovsdpdk-instruction-set",
+ "score": 0}}
self.assertEqual(flavor_map,
self.aai_ep.match_hpa(candidate_json['candidate_list'][1],
feature_json[0]))
- flavor_map = {"flavor-id": "f5aa2b2e-3206-41b6-80d5-cf041b098c43",
- "flavor-name": "flavor-cpu-ovsdpdk-instruction-set" }
+ flavor_map = {"flavor_map": {"flavor-id": "f5aa2b2e-3206-41b6-80d5-cf041b098c43",
+ "flavor-name": "flavor-cpu-ovsdpdk-instruction-set",
+ "score": 10},
+ "directives": []}
self.assertEqual(flavor_map,
- self.aai_ep.match_hpa(candidate_json['candidate_list'][1],
- feature_json[1]))
-
+ self.aai_ep.match_hpa(candidate_json['candidate_list'][1],
+ feature_json[1]))
+ flavor_map = {"flavor_map": {"flavor-id": "f5aa2b2e-3206-41b6-80d5-cf6t2b098c43",
+ "flavor-name": "flavor-ovsdpdk-cpu-pinning-sriov-NIC-Network-set",
+ "score": 13},
+ "directives": [{
+ "type": "sriovNICNetwork_directives",
+ "attributes": [
+ {
+ "attribute_name": "A",
+ "attribute_value": "a"
+ }
+ ]
+ }]}
+ self.assertEqual(flavor_map,
+ self.aai_ep.match_hpa(candidate_json['candidate_list'][1],
+ feature_json[2]))
diff --git a/conductor/conductor/tests/unit/data/test_service.py b/conductor/conductor/tests/unit/data/test_service.py
index 01c2ab3..7953f3f 100644
--- a/conductor/conductor/tests/unit/data/test_service.py
+++ b/conductor/conductor/tests/unit/data/test_service.py
@@ -238,23 +238,38 @@ class TestDataEndpoint(unittest.TestCase):
hpa_json["conductor_solver"]["constraints"][0].items()[0]
hpa_constraint = constraint_info['properties']
flavorProperties = hpa_constraint['evaluate'][0]['flavorProperties']
- label_name = hpa_constraint['evaluate'][0]['flavorLabel']
+ id = hpa_constraint['evaluate'][0]['id']
+ type = hpa_constraint['evaluate'][0]['type']
+ directives = hpa_constraint['evaluate'][0]['directives']
+ attr = directives[0].get("attributes")
+ label_name = attr[0].get("attribute_name")
ext_mock1.return_value = ['aai']
flavor_info = {"flavor-id": "vim-flavor-id1",
"flavor-name": "vim-flavor-name1"}
+ directive = [
+ {
+ "id": id,
+ "type": type,
+ "directives": directives
+ }
+ ]
hpa_mock.return_value = [flavor_info]
self.maxDiff = None
- args = generate_args(candidate_list, flavorProperties, label_name)
+ args = generate_args(candidate_list, flavorProperties, id, type, directives)
hpa_candidate_list = copy.deepcopy(candidate_list)
hpa_candidate_list[1]['flavor_map'] = {}
hpa_candidate_list[1]['flavor_map'][label_name] = "vim-flavor-name1"
- expected_response = {'response': hpa_candidate_list, 'error': False}
+ hpa_candidate_list[1]['all_directives'] = {}
+ hpa_candidate_list[1]['all_directives']['directives'] = directive
+ hpa_candidate_list1 = []
+ hpa_candidate_list1.append(hpa_candidate_list[0])
+ expected_response = {'response': hpa_candidate_list1, 'error': False}
self.assertEqual(expected_response,
self.data_ep.get_candidates_with_hpa(None, args))
hpa_candidate_list2 = list()
hpa_candidate_list2.append(copy.deepcopy(candidate_list[0]))
- args = generate_args(candidate_list, flavorProperties, label_name)
+ args = generate_args(candidate_list, flavorProperties, id, type, directives)
hpa_mock.return_value = []
expected_response = {'response': hpa_candidate_list2, 'error': False}
self.assertEqual(expected_response,
@@ -320,11 +335,13 @@ class TestDataEndpoint(unittest.TestCase):
args))
-def generate_args(candidate_list, flavorProperties, label_name):
+def generate_args(candidate_list, flavorProperties, vf_id, model_type, directives):
arg_candidate_list = copy.deepcopy(candidate_list)
args = {"candidate_list": arg_candidate_list,
"flavorProperties": flavorProperties,
- "label_name": label_name}
+ "id": vf_id,
+ "type": model_type,
+ "directives": directives}
return args
def ip_ext_sideeffect(*args, **kwargs):
diff --git a/conductor/conductor/tests/unit/solver/hpa_constraints.json b/conductor/conductor/tests/unit/solver/hpa_constraints.json
index 829eaf3..5f9c4c5 100644
--- a/conductor/conductor/tests/unit/solver/hpa_constraints.json
+++ b/conductor/conductor/tests/unit/solver/hpa_constraints.json
@@ -13,6 +13,7 @@
{
"architecture": "generic",
"hpa-feature": "basicCapabilities",
+ "directives": [],
"hpa-feature-attributes": [
{
"hpa-attribute-key": "numVirtualCpu",
@@ -31,6 +32,7 @@
{
"architecture": "generic",
"hpa-feature": "numa",
+ "directives": [],
"hpa-feature-attributes": [
{
"hpa-attribute-key": "numaNodes",
@@ -65,6 +67,7 @@
{
"architecture": "generic",
"hpa-feature": "cpuPinning",
+ "directives": [],
"hpa-feature-attributes": [
{
"hpa-attribute-key": "logicalCpuThreadPinningPolicy",
@@ -80,7 +83,19 @@
"hpa-version": "v1"
}
],
- "flavorLabel": "flavor_label_1"
+ "id": "vg_1",
+ "type": "vnfc",
+ "directives": [
+ {
+ "type": "flavor_directives",
+ "attributes": [
+ {
+ "attribute_name": "flavor_label_1",
+ "attribute_value": ""
+ }
+ ]
+ }
+ ]
},
{
"flavorProperties": [
@@ -100,7 +115,8 @@
"unit": "GB"
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
},
{
"architecture": "generic",
@@ -134,7 +150,8 @@
"unit": "GB"
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
},
{
"architecture": "generic",
@@ -147,10 +164,23 @@
"unit": "GB"
}
],
- "hpa-version": "v1"
+ "hpa-version": "v1",
+ "directives": []
}
],
- "flavorLabel": "flavor_label_2"
+ "id": "vg_2",
+ "type": "vnfc",
+ "directives": [
+ {
+ "type": "flavor_directives",
+ "attributes": [
+ {
+ "attribute_name": "flavor_label_2",
+ "attribute_value": ""
+ }
+ ]
+ }
+ ]
}
]
}
diff --git a/conductor/conductor/tests/unit/test_sms.py b/conductor/conductor/tests/unit/test_sms.py
new file mode 100644
index 0000000..b04111e
--- /dev/null
+++ b/conductor/conductor/tests/unit/test_sms.py
@@ -0,0 +1,89 @@
+#
+# -------------------------------------------------------------------------
+# 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 unittest
+from uuid import uuid4
+
+import requests_mock
+
+import conductor.common.sms as SMS
+from oslo_config import cfg
+
+
+class TestSMS(unittest.TestCase):
+
+ def setUp(self):
+ self.config = cfg.CONF.aaf_sms
+ self.base_domain_url = '{}/v1/sms/domain'
+ self.domain_url = '{}/v1/sms/domain/{}'
+ self.secret_url = self.domain_url + '/secret'
+
+ @requests_mock.mock()
+ def test_sms(self, mock_sms):
+ ''' NOTE: preload_secret generate the uuid for the domain
+ Create Domain API is called during the deployment using a
+ preload script. So the application oly knows the domain_uuid.
+ All sub-sequent SMS API calls needs the uuid.
+ For test purposes we need to do preload ourselves'''
+ sms_url = self.config.aaf_sms_url
+
+ # JSON Data responses
+ secretnames = {'secretnames': ['s1', 's2', 's3', 's4']}
+ secretvalues = {'values': {'Password': '', 'UserName': ''}}
+ expecect_secret_dict = dict()
+ for secret in secretnames['secretnames']:
+ expecect_secret_dict[secret] = secretvalues['values']
+
+ # Part 1 : Preload Secrets ONLY FOR TEST
+ # Mock requests for preload_secret
+ cd_url = self.base_domain_url.format(sms_url)
+ domain_uuid1 = str(uuid4())
+ s_url = self.secret_url.format(sms_url, domain_uuid1)
+ mock_sms.post(cd_url, status_code=200, json={'uuid': domain_uuid1})
+ mock_sms.post(s_url, status_code=200)
+ # Initialize Secrets from SMS
+ SMS.preload_secrets()
+
+ # Part 2: Retrieve Secret Test
+ # Mock requests for retrieve_secrets
+ # IMPORTANT: Read the config again as the preload_secrets has
+ # updated the config with uuid
+ domain_uuid2 = self.config.secret_domain
+ self.assertEqual(domain_uuid1, domain_uuid2)
+
+ d_url = self.domain_url.format(sms_url, domain_uuid2)
+ s_url = self.secret_url.format(sms_url, domain_uuid2)
+
+ # Retrieve Secrets from SMS and load to secret cache
+ # Use the secret_cache instead of config files
+ mock_sms.get(s_url, status_code=200, json=secretnames)
+ for secret in secretnames['secretnames']:
+ mock_sms.get('{}/{}'.format(s_url, secret),
+ status_code=200, json=secretvalues)
+ secret_cache = SMS.retrieve_secrets()
+ self.assertDictEqual(expecect_secret_dict, secret_cache,
+ 'Failed to retrieve secrets')
+
+ # Part 3: Clean up Delete secrets and domain
+ # Mock requests for delete_secrets
+ mock_sms.delete(d_url, status_code=200)
+ self.assertTrue(SMS.delete_secrets())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/conductor/pom.xml b/conductor/pom.xml
index 40501dc..fecf927 100644
--- a/conductor/pom.xml
+++ b/conductor/pom.xml
@@ -21,13 +21,13 @@
<parent>
<groupId>org.onap.optf.has</groupId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.2.1-SNAPSHOT</version>
<artifactId>optf-has</artifactId>
</parent>
<groupId>org.onap.optf.has</groupId>
<artifactId>optf-has-conductor</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.2.1-SNAPSHOT</version>
<name>optf-has-conductor</name>
<description>Homing Allocation Service/Conductor</description>
diff --git a/conductor/requirements.txt b/conductor/requirements.txt
index 9359e26..d09c960 100644
--- a/conductor/requirements.txt
+++ b/conductor/requirements.txt
@@ -23,3 +23,4 @@ requests[security]!=2.9.0,>=2.8.1 # Apache-2.0
six>=1.9.0 # MIT, also required by futurist
stevedore>=1.9.0 # Apache-2.0, also required by oslo.config
WebOb>=1.2.3 # MIT
+onapsmsclient>=0.0.3 \ No newline at end of file
diff --git a/conductor/test-requirements.txt b/conductor/test-requirements.txt
index c0e68d0..7466c9d 100644
--- a/conductor/test-requirements.txt
+++ b/conductor/test-requirements.txt
@@ -18,3 +18,4 @@ os-testr>=1.0.0 # Apache-2.0
tempest>=11.0.0 # Apache-2.0
pifpaf>=0.0.11
junitxml>=0.7
+requests-mock>=1.5.2