summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xconductor.conf22
-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
-rw-r--r--docs/sections/offeredapis.rst470
-rw-r--r--docs/sections/swaggerdoc/oof-has-api.json341
-rw-r--r--pom.xml4
-rwxr-xr-xpreload_secrets.yaml21
-rwxr-xr-xrun-dockers.sh2
-rw-r--r--version.properties2
33 files changed, 1758 insertions, 786 deletions
diff --git a/conductor.conf b/conductor.conf
index a2a4765..b4e74e4 100755
--- a/conductor.conf
+++ b/conductor.conf
@@ -120,6 +120,28 @@ log_config_append = /usr/local/bin/log.conf
#fatal_deprecations = false
+[aaf_sms]
+
+#
+# From conductor
+#
+
+# Base URL for SMS, up to and not including the version, and without a trailing
+# slash. (string value)
+#aaf_sms_url = https://aaf-sms.onap:10443
+
+# Timeout for SMS API Call (integer value)
+#aaf_sms_timeout = 30
+
+# Path to the cacert that will be used to verify If this is None, verify will
+# be False and the server certis not verified by the client. (string value)
+#aaf_ca_certs = AAF_RootCA.cer
+
+# Domain UUID - A unique UUID generated when the domainfor HAS is created by
+# administrator during deployment (string value)
+#secret_domain = has
+
+
[aai]
#
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
diff --git a/docs/sections/offeredapis.rst b/docs/sections/offeredapis.rst
index 9ffb6fb..497f9a3 100644
--- a/docs/sections/offeredapis.rst
+++ b/docs/sections/offeredapis.rst
@@ -1,478 +1,18 @@
.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
Offered APIs
=============================================
-.. This work is licensed under a Creative Commons Attribution 4.0 International License.
-.. Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
-
-Homing API v1
-------------------
-
-*Updated: 28 Feb 2018*
-
This document describes the Homing API, provided by the Homing and Allocation service (Conductor).
-It is a work in progress and subject to frequent revision.
-
-General API Information
--------------------------
-
-Authenticated calls that target a known URI but that use an HTTP method
-the implementation does not support return a 405 Method Not Allowed
-status. In addition, the HTTP OPTIONS method is supported for each known
-URI. In both cases, the Allow response header indicates the supported
-HTTP methods. See the API Errors section for more information about the
-error response structure.
-
-API versions
-------------------
-
-List all Homing API versions
-----------------------------
-
-**GET** ``/``\ F
-
-**Normal response codes:** 200
-
-.. code:: json
-
- {
- "versions": [
- {
- "status": "EXPERIMENTAL",
- "id": "v1",
- "updated": "2016-11-01T00:00:00Z",
- "media-types": [
- {
- "base": "application/json",
- "type": "application/vnd.onap.homing-v1+json"
- }
- ],
- "links": [
- {
- "href": "http://has.ip/v1",
- "rel": "self"
- },
- {
- "href": "http://has.url/",
- "type": "text/html",
- "rel": "describedby"
- }
- ]
- }
- ]
- }
-
-This operation does not accept a request body.
-
-Plans
------
-
-Create a plan
--------------
-
-**POST** ``/v1/plans``
-
-- **Normal response codes:** 201
-- **Error response codes:** badRequest (400), unauthorized (401),
- internalServerError (500)
-
-Request an inventory plan for one or more related service demands.
-
-The request includes or references a declarative **template**,
-consisting of:
-
-- **Parameters** that can be referenced like macros
-- **Demands** for service made against inventory
-- **Locations** that are common to the overall plan
-- **Constraints** made against demands, resulting in a set of inventory
- candidates
-- **Optimizations** to further narrow down the remaining candidates
-The response contains an inventory **plan**, consisting of one or more
-sets of recommended pairings of demands with an inventory candidate's
-attributes and region.
-Request Parameters
-~~~~~~~~~~~~~~~~~~
-
-+--------------------+-------+------------+-------------------+
-| Parameter | Style | Type | Description |
-+====================+=======+============+===================+
-| ``name`` | plain | xsd:string | A name for the |
-| (Optional) | | | new plan. If a |
-| | | | name is not |
-| | | | provided, it |
-| | | | will be |
-| | | | auto-generated |
-| | | | based on the |
-| | | | homing |
-| | | | template. This |
-| | | | name must be |
-| | | | unique within |
-| | | | a given |
-| | | | Conductor |
-| | | | environment. |
-| | | | When deleting |
-| | | | a plan, its |
-| | | | name will not |
-| | | | become |
-| | | | available for |
-| | | | reuse until |
-| | | | the deletion |
-| | | | completes |
-| | | | successfully. |
-| | | | Must only |
-| | | | contain |
-| | | | letters, |
-| | | | numbers, |
-| | | | hypens, full |
-| | | | stops, |
-| | | | underscores, |
-| | | | and tildes |
-| | | | (RFC 3986, |
-| | | | Section 2.3). |
-| | | | This parameter |
-| | | | is immutable. |
-+--------------------+-------+------------+-------------------+
-| ``id`` | plain | csapi:UUID | The UUID of |
-| (Optional) | | | the plan. UUID |
-| | | | is assigned by |
-| | | | Conductor if |
-| | | | no id is |
-| | | | provided in |
-| | | | the request. |
-+--------------------+-------+------------+-------------------+
-| ``transaction_id`` | plain | csapi:UUID | The |
-| | | | transaction id |
-| | | | assigned by |
-| | | | MSO. The logs |
-| | | | should have |
-| | | | this |
-| | | | transaction id |
-| | | | for tracking |
-| | | | purposes. |
-+--------------------+-------+------------+-------------------+
-| ``files`` | plain | xsd:dict | Supplies the |
-| (Optional) | | | contents of |
-| | | | files |
-| | | | referenced in |
-| | | | the template. |
-| | | | Conductor |
-| | | | templates can |
-| | | | explicitly |
-| | | | reference |
-| | | | files by using |
-| | | | the |
-| | | | ``get_file`` |
-| | | | intrinsic |
-| | | | function. The |
-| | | | value is a |
-| | | | JSON object, |
-| | | | where each key |
-| | | | is a relative |
-| | | | or absolute |
-| | | | URI which |
-| | | | serves as the |
-| | | | name of a |
-| | | | file, and the |
-| | | | associated |
-| | | | value provides |
-| | | | the contents |
-| | | | of the file. |
-| | | | Additionally, |
-| | | | some template |
-| | | | authors encode |
-| | | | their user |
-| | | | data in a |
-| | | | local file. |
-| | | | The Homing |
-| | | | client (e.g., |
-| | | | a CLI) can |
-| | | | examine the |
-| | | | template for |
-| | | | the |
-| | | | ``get_file`` |
-| | | | intrinsic |
-| | | | function |
-| | | | (e.g., |
-| | | | ``{get_file: f |
-| | | | ile.yaml}``) |
-| | | | and add an |
-| | | | entry to the |
-| | | | ``files`` map |
-| | | | with the path |
-| | | | to the file as |
-| | | | the name and |
-| | | | the file |
-| | | | contents as |
-| | | | the value. Do |
-| | | | not use this |
-| | | | parameter to |
-| | | | provide the |
-| | | | content of the |
-| | | | template |
-| | | | located at the |
-| | | | ``template_url`` |
-| | | | address. |
-| | | | Instead, use |
-| | | | the |
-| | | | ``template`` |
-| | | | parameter to |
-| | | | supply the |
-| | | | template |
-| | | | content as |
-| | | | part of the |
-| | | | request. |
-+--------------------+-------+------------+-------------------+
-| ``template_url`` | plain | xsd:string | A URI to |
-| (Optional) | | | the location |
-| | | | containing the |
-| | | | template on |
-| | | | which to |
-| | | | perform the |
-| | | | operation. See |
-| | | | the |
-| | | | description of |
-| | | | the |
-| | | | ``template`` |
-| | | | parameter for |
-| | | | information |
-| | | | about the |
-| | | | expected |
-| | | | template |
-| | | | content |
-| | | | located at the |
-| | | | URI. This |
-| | | | parameter is |
-| | | | only required |
-| | | | when you omit |
-| | | | the |
-| | | | ``template`` |
-| | | | parameter. If |
-| | | | you specify |
-| | | | both |
-| | | | parameters, |
-| | | | this parameter |
-| | | | is ignored. |
-+--------------------+-------+------------+-------------------+
-| ``template`` | plain | xsd:string | The template |
-| | | or xsd:dict| on which to |
-| | | | perform the |
-| | | | operation. See |
-| | | | the Homing |
-| | | | Template |
-| | | | Guide |
-| | | | for complete |
-| | | | information on |
-| | | | the format. |
-| | | | This parameter |
-| | | | is either |
-| | | | provided as a |
-| | | | ``string`` or |
-| | | | ``dict`` in |
-| | | | the JSON |
-| | | | request body. |
-| | | | For ``string`` |
-| | | | content it may |
-| | | | be a JSON- or |
-| | | | YAML-formatted |
-| | | | Conductor |
-| | | | template. For |
-| | | | ``dict`` |
-| | | | content it |
-| | | | must be a |
-| | | | direct JSON |
-| | | | representation |
-| | | | of the |
-| | | | Conductor |
-| | | | template. This |
-| | | | parameter is |
-| | | | required only |
-| | | | when you omit |
-| | | | the |
-| | | | ``template_url`` |
-| | | | parameter. If |
-| | | | you specify |
-| | | | both |
-| | | | parameters, |
-| | | | this value |
-| | | | overrides the |
-| | | | ``template_url`` |
-| | | | parameter |
-| | | | value. |
-+--------------------+-------+------------+-------------------+
-| ``timeout`` | plain | xsd:number | The timeout |
-| (Optional) | | | for plan |
-| | | | creation in |
-| | | | minutes. |
-| | | | Default is 1. |
-+--------------------+-------+------------+-------------------+
-| ``limit`` | plain | xsd:number | The maximum |
-| (Optional) | | | number of |
-| | | | recommendations |
-| | | | to return. |
-| | | | Default is 1. |
-+--------------------+-------+------------+-------------------+
-
-**NOTE**: ``files``, ``template_url``, and ``timeout`` are not yet
-supported.
+To view API documentation in the interactive swagger UI download the following and
+paste into the swagger tool here: https://editor.swagger.io
-Response Parameters
-~~~~~~~~~~~~~~~~~~~
+:download:`oof-has-api.json <./swaggerdoc/oof-has-api.json>`
-+--------------------+----------+------------+--------------------+
-| Parameter | Style | Type | Description |
-+====================+==========+============+====================+
-| ``plan`` | plain | xsd:dict | The ``plan`` |
-| | | | object. |
-+--------------------+----------+------------+--------------------+
-| ``id`` | plain | csapi:UUID | The UUID of |
-| | | | the plan. |
-+--------------------+----------+------------+--------------------+
-| ``transaction_id`` | plain | csapi:UUID | The |
-| | | | transaction id |
-| | | | assigned by |
-| | | | the MSO. |
-+--------------------+----------+------------+--------------------+
-| ``name`` | plain | xsd:string | The plan name. |
-| | | | |
-+--------------------+----------+------------+--------------------+
-| ``status`` | plain | xsd:string | The plan |
-| | | | status. One of |
-| | | | ``template``, |
-| | | | ``translated``, |
-| | | | ``solving``, |
-| | | | ``solved``, or |
-| | | | ``error``. See |
-| | | | **Plan |
-| | | | Status** table |
-| | | | for |
-| | | | descriptions |
-| | | | of each value. |
-+--------------------+----------+------------+--------------------+
-| ``message`` | plain | xsd:string | Additional |
-| | | | context, if |
-| | | | any, around |
-| | | | the message |
-| | | | status. If the |
-| | | | status is |
-| | | | ``error``, |
-| | | | this may |
-| | | | include a |
-| | | | reason and |
-| | | | suggested |
-| | | | remediation, |
-| | | | if available. |
-+--------------------+----------+------------+--------------------+
-| ``links`` | plain | xsd:list | A list of URLs |
-| | | | for the plan. |
-| | | | Each URL is a |
-| | | | JSON object |
-| | | | with an |
-| | | | ``href`` key |
-| | | | indicating the |
-| | | | URL and a |
-| | | | ``rel`` key |
-| | | | indicating its |
-| | | | relationship |
-| | | | to the plan in |
-| | | | question. |
-| | | | There may be |
-| | | | multiple links |
-| | | | returned. The |
-| | | | ``self`` |
-| | | | relationship |
-| | | | identifies the |
-| | | | URL of the |
-| | | | plan itself. |
-+--------------------+----------+------------+--------------------+
-| ``recommendations``| plain | xsd:list | A list of one |
-| | | | or more |
-| | | | recommendationS. |
-| | | | A |
-| | | | recommendation |
-| | | | pairs each |
-| | | | requested |
-| | | | demand with an |
-| | | | inventory |
-| | | | provider, a |
-| | | | single |
-| | | | candidate, and |
-| | | | an opaque |
-| | | | dictionary of |
-| | | | attributes. |
-| | | | Refer to the |
-| | | | Demand |
-| | | | candidate |
-| | | | schema in the |
-| | | | Homing |
-| | | | Template |
-| | | | Guide |
-| | | | for further |
-| | | | details. (Note |
-| | | | that, when |
-| | | | ``inventory_type`` |
-| | | | is ``cloud`` |
-| | | | the |
-| | | | candidate's |
-| | | | ``candidate_id`` |
-| | | | field is |
-| | | | redundant and |
-| | | | thus omitted.) |
-+--------------------+----------+------------+--------------------+
-
-Plan Status
-~~~~~~~~~~~
-
-+----------------+-----------------+
-| Status | Description |
-+================+=================+
-| ``template`` | Plan request |
-| | and homing |
-| | template have |
-| | been received. |
-| | Awaiting |
-| | translation. |
-+----------------+-----------------+
-| ``translated`` | Homing |
-| | template has |
-| | been |
-| | translated, |
-| | and candidates |
-| | have been |
-| | obtained from |
-| | inventory |
-| | providers. |
-| | Awaiting |
-| | solving. |
-+----------------+-----------------+
-| ``solving`` | Search for a |
-| | solution is in |
-| | progress. This |
-| | may |
-| | incorporate |
-| | requests to |
-| | service |
-| | controllers |
-| | for additional |
-| | information. |
-+----------------+-----------------+
-| ``solved`` | Search is |
-| | complete. A |
-| | solution with |
-| | one or more |
-| | recommendations |
-| | was found. |
-+----------------+-----------------+
-| ``not found`` | Search is |
-| | complete. No |
-| | recommendations |
-| | were found. |
-+----------------+-----------------+
-| ``error`` | An error was |
-| | encountered. |
-+----------------+-----------------+
+.. swaggerv2doc:: ./swaggerdoc/oof-has-api.json
State Diagram
^^^^^^^^^^^^^
diff --git a/docs/sections/swaggerdoc/oof-has-api.json b/docs/sections/swaggerdoc/oof-has-api.json
new file mode 100644
index 0000000..dcb2aeb
--- /dev/null
+++ b/docs/sections/swaggerdoc/oof-has-api.json
@@ -0,0 +1,341 @@
+{
+ "swagger" : "2.0",
+ "info" : {
+ "description" : "This is the ONAP OOF HAS (Homing and Allocation Service) API",
+ "version" : "1.0.0",
+ "title" : "HAS API",
+ "contact" : {
+ "email" : "frank.sandoval@oamtechnologies.com"
+ },
+ "license" : {
+ "name" : "Apache 2.0",
+ "url" : "http://www.apache.org/licenses/LICENSE-2.0.html"
+ }
+ },
+ "securityDefinitions" : {
+ "basicAuth" : {
+ "type" : "basic",
+ "description" : "HTTP Basic Auth"
+ }
+ },
+ "security" : [ {
+ "basicAuth" : [ ]
+ } ],
+ "paths" : {
+ "/" : {
+ "get" : {
+ "summary" : "retrieve versions",
+ "operationId" : "retrieveVersions",
+ "description" : "retrieve supported versions of the API",
+ "security" : [ ],
+ "produces" : [ "application/json" ],
+ "responses" : {
+ "200" : {
+ "description" : "list of supported versions",
+ "schema" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/definitions/Versions"
+ }
+ }
+ },
+ "400" : {
+ "description" : "bad request",
+ "schema" : {
+ "$ref" : "#/definitions/Error"
+ }
+ },
+ "401" : {
+ "description" : "unauthorized request"
+ }
+ }
+ }
+ },
+ "/v1/plans" : {
+ "post" : {
+ "summary" : "create a plan",
+ "operationId" : "createPlan",
+ "description" : "creates a plan from one or more service demands",
+ "consumes" : [ "application/json" ],
+ "produces" : [ "application/json" ],
+ "parameters" : [ {
+ "in" : "body",
+ "name" : "demand",
+ "description" : "service demand",
+ "schema" : {
+ "$ref" : "#/definitions/Demand"
+ }
+ } ],
+ "responses" : {
+ "201" : {
+ "description" : "plan created",
+ "schema" : {
+ "items" : {
+ "$ref" : "#/definitions/Plan"
+ }
+ }
+ },
+ "400" : {
+ "description" : "bad request",
+ "schema" : {
+ "$ref" : "#/definitions/Error"
+ }
+ },
+ "401" : {
+ "description" : "unauthorized request"
+ }
+ }
+ }
+ },
+ "/v1/plans/{plan_id}" : {
+ "get" : {
+ "summary" : "retreive a plan",
+ "operationId" : "getPlan",
+ "description" : "retrieve a plan",
+ "produces" : [ "application/json" ],
+ "parameters" : [ {
+ "in" : "path",
+ "name" : "plan_id",
+ "description" : "UUID of plan identifier",
+ "required" : true,
+ "type" : "string",
+ "format" : "uuid"
+ } ],
+ "responses" : {
+ "200" : {
+ "description" : "retrieve a plan",
+ "schema" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/definitions/Plan"
+ }
+ }
+ },
+ "400" : {
+ "description" : "bad request",
+ "schema" : {
+ "$ref" : "#/definitions/Error"
+ }
+ },
+ "401" : {
+ "description" : "unauthorized request"
+ },
+ "500" : {
+ "description" : "Internal Server Error"
+ }
+ }
+ },
+ "delete" : {
+ "summary" : "delete a plan",
+ "operationId" : "deletePlan",
+ "description" : "delete a plan",
+ "produces" : [ "application/json" ],
+ "parameters" : [ {
+ "in" : "path",
+ "name" : "plan_id",
+ "description" : "UUID of plan identifier",
+ "required" : true,
+ "type" : "string",
+ "format" : "uuid"
+ } ],
+ "responses" : {
+ "204" : {
+ "description" : "deleted a plan"
+ },
+ "400" : {
+ "description" : "bad request",
+ "schema" : {
+ "$ref" : "#/definitions/Error"
+ }
+ },
+ "401" : {
+ "description" : "unauthorized request"
+ }
+ }
+ }
+ }
+ },
+ "definitions" : {
+ "Demand" : {
+ "type" : "object",
+ "required" : [ "transaction_id", "template" ],
+ "properties" : {
+ "name" : {
+ "type" : "string"
+ },
+ "id" : {
+ "type" : "string",
+ "format" : "uuid"
+ },
+ "transaction_id" : {
+ "type" : "string",
+ "format" : "uuid",
+ "example" : "d290f1ee-6c54-4b01-90e6-d701748f0851"
+ },
+ "template" : {
+ "type" : "string",
+ "externalDocs" : {
+ "description" : "See here for template format",
+ "url" : "http://onap.readthedocs.io/en/latest/submodules/optf/has.git/docs/sections/homingspecification.html"
+ }
+ }
+ }
+ },
+ "Plan" : {
+ "type" : "object",
+ "required" : [ "plan", "id", "transaction_id", "name", "status", "message", "links", "recommendations" ],
+ "properties" : {
+ "plan" : {
+ "type" : "string",
+ "example" : "JSON string describing plan",
+ "externalDocs" : {
+ "description" : "See here for plan format",
+ "url" : "http://onap.readthedocs.io/en/latest/submodules/optf/has.git/docs/sections/offeredapis.html"
+ }
+ },
+ "id" : {
+ "type" : "string",
+ "format" : "uuid",
+ "example" : "d290f1ee-6c54-4b01-90e6-d701748f0851"
+ },
+ "transaction_id" : {
+ "type" : "string",
+ "format" : "uuid",
+ "example" : "d290f1ee-6c54-4b01-90e6-d701748f0851"
+ },
+ "name" : {
+ "type" : "string",
+ "example" : "name of plan"
+ },
+ "status" : {
+ "type" : "string",
+ "enum" : [ "template", "translated", "solving", "solved", "not found", "error" ]
+ },
+ "message" : {
+ "type" : "string",
+ "example" : "Additional context, if any, around the message status"
+ },
+ "links" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/definitions/Link"
+ },
+ "externalDocs" : {
+ "description" : "See here for links description",
+ "url" : "http://onap.readthedocs.io/en/latest/submodules/optf/has.git/docs/sections/offeredapis.html"
+ }
+ },
+ "recommendations" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/definitions/Recommendation"
+ },
+ "externalDocs" : {
+ "description" : "Refer to the Demand candidate schema in the Homing Template Guide for further details",
+ "url" : "http://onap.readthedocs.io/en/latest/submodules/optf/has.git/docs/sections/homingspecification.html"
+ }
+ }
+ }
+ },
+ "Link" : {
+ "required" : [ "href", "rel" ],
+ "properties" : {
+ "href" : {
+ "type" : "string",
+ "example" : "http://localhost:8091/v1"
+ },
+ "rel" : {
+ "type" : "string",
+ "example" : "self"
+ }
+ }
+ },
+ "Recommendation" : {
+ "required" : [ "recommendation" ],
+ "properties" : {
+ "recommendation" : {
+ "type" : "string",
+ "description" : "JSON string, see description of Plan.recommendations",
+ "example" : "JSON string describing recommendation"
+ }
+ }
+ },
+ "MediaTypes" : {
+ "required" : [ "base", "type" ],
+ "properties" : {
+ "base" : {
+ "type" : "string",
+ "example" : "application/json"
+ },
+ "rel" : {
+ "type" : "string",
+ "example" : "application/vnd.onap.has-v1+json"
+ }
+ }
+ },
+ "Versions" : {
+ "required" : [ "name", "links", "media_types", "status", "updated" ],
+ "properties" : {
+ "id" : {
+ "type" : "string",
+ "example" : "v1"
+ },
+ "links" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/definitions/Link"
+ }
+ },
+ "media-types" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/definitions/MediaTypes"
+ }
+ },
+ "status" : {
+ "type" : "string",
+ "example" : "EXPERIMENTAL"
+ },
+ "updated" : {
+ "type" : "string",
+ "example" : "2016-11-01T00:00:00Z"
+ }
+ }
+ },
+ "Error" : {
+ "properties" : {
+ "title" : {
+ "type" : "string",
+ "example" : "Bad Request"
+ },
+ "explanation" : {
+ "type" : "string",
+ "example" : "pl an did not pass validation against callable"
+ },
+ "code" : {
+ "type" : "integer",
+ "example" : 400
+ },
+ "error" : {
+ "properties" : {
+ "message" : {
+ "type" : "string",
+ "example" : "message"
+ },
+ "traceback" : {
+ "type" : "string",
+ "example" : "traceback"
+ },
+ "errortype" : {
+ "type" : "string",
+ "example" : "type"
+ }
+ }
+ }
+ }
+ }
+ },
+ "schemes" : [ "https" ],
+ "host" : "virtserver.swaggerhub.com",
+ "basePath" : "/onap_oof/has/1.0.0"
+} \ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 0c249a3..e17b0d1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,14 +21,14 @@
<parent>
<groupId>org.onap.oparent</groupId>
<artifactId>oparent-python</artifactId>
- <version>1.1.1</version>
+ <version>1.2.0</version>
</parent>
<groupId>org.onap.optf.has</groupId>
<artifactId>optf-has</artifactId>
<name>optf-has</name>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.2.1-SNAPSHOT</version>
<description>Homing Allocation Service</description>
<modules>
diff --git a/preload_secrets.yaml b/preload_secrets.yaml
new file mode 100755
index 0000000..65a814a
--- /dev/null
+++ b/preload_secrets.yaml
@@ -0,0 +1,21 @@
+# DO NOT USE THIS IN PRODUCTION
+# USED ONLY FOR TESTING
+---
+domain: has
+secrets:
+- name: aai
+ values:
+ UserName: OOF
+ Password: OOF
+- name: conductor_api
+ values:
+ UserName: admin1
+ Password: plan.15
+- name: sdnc
+ values:
+ UserName: admin
+ Password: sdnc.15
+- name: music_api
+ values:
+ UserName: conductor
+ Password: c0nduct0r
diff --git a/run-dockers.sh b/run-dockers.sh
index 5efb080..e61ea3c 100755
--- a/run-dockers.sh
+++ b/run-dockers.sh
@@ -4,7 +4,7 @@
BUILD_ARGS="--no-cache"
ORG="onap"
-VERSION="1.1.1"
+VERSION="1.2.1"
PROJECT="optf-has"
DOCKER_REPOSITORY="nexus3.onap.org:10003"
IMAGE_NAME="${DOCKER_REPOSITORY}/${ORG}/${PROJECT}"
diff --git a/version.properties b/version.properties
index 2217466..eb859e6 100644
--- a/version.properties
+++ b/version.properties
@@ -19,7 +19,7 @@
major=1
minor=2
-patch=0
+patch=1
base_version=${major}.${minor}.${patch}