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