From edf98746a52408386efab26143778198b0efd3c5 Mon Sep 17 00:00:00 2001 From: dhebeha Date: Sat, 5 Sep 2020 20:16:48 +0530 Subject: Add support to process NSI selection request Issue-ID: OPTFRA-802 Signed-off-by: dhebeha Signed-off-by: krishnaa96 Change-Id: I85d951061abc697714425bd223b89102d4f2ede9 --- osdf/adapters/conductor/api_builder.py | 19 +++--- osdf/adapters/conductor/conductor.py | 12 ++-- .../conductor/templates/conductor_interface.json | 2 +- osdf/adapters/conductor/translation.py | 75 ++++++++++++++-------- 4 files changed, 67 insertions(+), 41 deletions(-) (limited to 'osdf/adapters') diff --git a/osdf/adapters/conductor/api_builder.py b/osdf/adapters/conductor/api_builder.py index daf8e54..f3b0798 100644 --- a/osdf/adapters/conductor/api_builder.py +++ b/osdf/adapters/conductor/api_builder.py @@ -20,14 +20,14 @@ from jinja2 import Template import json -from osdf.adapters.policy.utils import group_policies_gen import osdf.adapters.conductor.translation as tr +from osdf.adapters.policy.utils import group_policies_gen from osdf.utils.programming_utils import list_flatten def _build_parameters(group_policies, service_info, request_parameters): - """ - Function prepares parameters section for has request + """Function prepares parameters section for has request + :param group_policies: filtered policies :param service_info: service info :param request_parameters: request parameters @@ -50,14 +50,15 @@ def _build_parameters(group_policies, service_info, request_parameters): def conductor_api_builder(req_info, demands, request_parameters, service_info, - location_enabled, flat_policies: list, local_config, + template_fields, flat_policies: list, local_config, template="osdf/adapters/conductor/templates/conductor_interface.json"): """Build an OSDF southbound API call for HAS-Conductor/Placement optimization + :param req_info: parameter data received from a client :param demands: list of demands :param request_parameters: request parameters :param service_info: service info object - :param location_enabled: boolean to check location to be sent in the request + :param template_fields: Fields that has to be passed to the template to render :param flat_policies: policy data received from the policy platform (flat policies) :param template: template to generate southbound API call to conductor :param local_config: local configuration file with pointers for @@ -73,6 +74,7 @@ def conductor_api_builder(req_info, demands, request_parameters, service_info, demand_list = tr.gen_demands(demands, gp['onap.policies.optimization.resource.VnfPolicy']) attribute_policy_list = tr.gen_attribute_policy( demand_name_list, gp['onap.policies.optimization.resource.AttributePolicy']) + distance_to_location_policy_list = tr.gen_distance_to_location_policy( demand_name_list, gp['onap.policies.optimization.resource.DistancePolicy']) inventory_policy_list = tr.gen_inventory_group_policy( @@ -95,8 +97,8 @@ def conductor_api_builder(req_info, demands, request_parameters, service_info, gp['onap.policies.optimization.resource.' 'ThresholdPolicy']) aggregation_policy_list = tr.gen_aggregation_policy(demand_name_list, - gp['onap.policies.optimization.resource.' - 'AggregationPolicy']) + gp['onap.policies.optimization.resource.' + 'AggregationPolicy']) req_params_dict = _build_parameters(gp, service_info, request_parameters) conductor_policies = [attribute_policy_list, distance_to_location_policy_list, inventory_policy_list, resource_instance_policy_list, @@ -114,7 +116,8 @@ def conductor_api_builder(req_info, demands, request_parameters, service_info, timeout=req_info['timeout'], limit=req_info['numSolutions'], request_params=req_params_dict, - location_enabled=location_enabled, + location_enabled=template_fields.get('location_enabled'), + version=template_fields.get('version'), json=json) json_payload = json.dumps(json.loads(rendered_req)) # need this because template's JSON is ugly! return json_payload diff --git a/osdf/adapters/conductor/conductor.py b/osdf/adapters/conductor/conductor.py index 155d4d5..6749c2c 100644 --- a/osdf/adapters/conductor/conductor.py +++ b/osdf/adapters/conductor/conductor.py @@ -18,17 +18,16 @@ # import json - from requests import RequestException import time from osdf.adapters.conductor.api_builder import conductor_api_builder from osdf.logging.osdf_logging import debug_log -from osdf.utils.interfaces import RestClient from osdf.operation.exceptions import BusinessException +from osdf.utils.interfaces import RestClient -def request(req_info, demands, request_parameters, service_info, location_enabled, +def request(req_info, demands, request_parameters, service_info, template_fields, osdf_config, flat_policies): config = osdf_config.deployment local_config = osdf_config.core @@ -53,7 +52,7 @@ def request(req_info, demands, request_parameters, service_info, location_enable rc = RestClient(userid=uid, passwd=passwd, method="GET", log_func=debug_log.debug, headers=headers) conductor_req_json_str = conductor_api_builder(req_info, demands, request_parameters, - service_info, location_enabled, flat_policies, + service_info, template_fields, flat_policies, local_config) conductor_req_json = json.loads(conductor_req_json_str) @@ -74,7 +73,7 @@ def request(req_info, demands, request_parameters, service_info, location_enable if resp["plans"][0].get("status") in ["done", "not found"]: return resp - new_url = resp['plans'][0]['links'][0][0]['href'] # TODO: check why a list of lists + new_url = resp['plans'][0]['links'][0][0]['href'] # TODO(krishna): check why a list of lists if total_time >= max_timeout: raise BusinessException("Conductor could not provide a solution within {} seconds," @@ -95,6 +94,7 @@ def request(req_info, demands, request_parameters, service_info, location_enable def initial_request_to_conductor(rc, conductor_url, conductor_req_json): """First steps in the request-redirect chain in making a call to Conductor + :param rc: REST client object for calling conductor :param conductor_url: conductor's base URL to submit a placement request :param conductor_req_json: request json object to send to Conductor @@ -112,7 +112,7 @@ def initial_request_to_conductor(rc, conductor_url, conductor_req_json): debug_log.debug("Attempting to read the plan from " "the conductor provided url {}".format(plan_url)) raw_resp = rc.request(raw_response=True, - url=plan_url) # TODO: check why a list of lists for links + url=plan_url) resp = raw_resp.json() if resp["plans"][0]["status"] in ["error"]: diff --git a/osdf/adapters/conductor/templates/conductor_interface.json b/osdf/adapters/conductor/templates/conductor_interface.json index d4a9a0e..e8d47b3 100755 --- a/osdf/adapters/conductor/templates/conductor_interface.json +++ b/osdf/adapters/conductor/templates/conductor_interface.json @@ -4,7 +4,7 @@ "timeout": {{ timeout }}, "num_solution": "{{ limit }}", "template": { - "homing_template_version": "2017-10-10", + "homing_template_version": "{{ version }}", "parameters": { {% set comma=joiner(",") %} {% for key, value in request_params.items() %} {{ comma() }} diff --git a/osdf/adapters/conductor/translation.py b/osdf/adapters/conductor/translation.py index 4b012f5..238428a 100644 --- a/osdf/adapters/conductor/translation.py +++ b/osdf/adapters/conductor/translation.py @@ -19,8 +19,8 @@ import copy import json import re - import yaml + from osdf.utils.programming_utils import dot_notation policy_config_mapping = yaml.safe_load(open('config/has_config.yaml')).get('policy_config_mapping') @@ -41,8 +41,8 @@ CONSTRAINT_TYPE_MAP = {"onap.policies.optimization.resource.AttributePolicy": "a def get_opt_query_data(request_parameters, policies): - """ - Fetch service and order specific details from the requestParameters field of a request. + """Fetch service and order specific details from the requestParameters field of a request. + :param request_parameters: A list of request parameters :param policies: A set of policies :return: A dictionary with service and order-specific attributes. @@ -60,10 +60,20 @@ def get_opt_query_data(request_parameters, policies): def gen_optimization_policy(vnf_list, optimization_policy): """Generate optimization policy details to pass to Conductor + :param vnf_list: List of vnf's to used in placement request :param optimization_policy: optimization objective policy information provided in the incoming request :return: List of optimization objective policies in a format required by Conductor """ + if len(optimization_policy) == 1: + policy = optimization_policy[0] + policy_content = policy[list(policy.keys())[0]] + if policy_content['type_version'] == '2.0.0': + properties = policy_content['properties'] + objective = {'goal': properties['goal'], + 'operation_function': properties['operation_function']} + return [objective] + optimization_policy_list = [] for policy in optimization_policy: content = policy[list(policy.keys())[0]]['properties'] @@ -71,7 +81,7 @@ def gen_optimization_policy(vnf_list, optimization_policy): parameters = ["cloud_version", "hpa_score"] for attr in content['objectiveParameter']['parameterAttributes']: - parameter = attr['parameter'] if attr['parameter'] in parameters else attr['parameter']+"_between" + parameter = attr['parameter'] if attr['parameter'] in parameters else attr['parameter'] + "_between" default, vnfs = get_matching_vnfs(attr['resources'], vnf_list) for vnf in vnfs: value = [vnf] if attr['parameter'] in parameters else [attr['customerLocationInfo'], vnf] @@ -80,13 +90,14 @@ def gen_optimization_policy(vnf_list, optimization_policy): }) optimization_policy_list.append({ - content['objective']: {content['objectiveParameter']['operator']: parameter_list } + content['objective']: {content['objectiveParameter']['operator']: parameter_list} }) return optimization_policy_list def get_matching_vnfs(resources, vnf_list, match_type="intersection"): """Get a list of matching VNFs from the list of resources + :param resources: :param vnf_list: List of vnfs to used in placement request :param match_type: "intersection" or "all" or "any" (any => send all_vnfs if there is any intersection) @@ -106,6 +117,7 @@ def get_matching_vnfs(resources, vnf_list, match_type="intersection"): def gen_policy_instance(vnf_list, resource_policy, match_type="intersection", rtype=None): """Generate a list of policies + :param vnf_list: List of vnf's to used in placement request :param resource_policy: policy for this specific resource :param match_type: How to match the vnf_names with the vnf_list (intersection or "any") @@ -129,13 +141,14 @@ def gen_policy_instance(vnf_list, resource_policy, match_type="intersection", rt if default: for d in demands: resource_repeated = True \ - if {pc['properties']['identity']: {'type': CONSTRAINT_TYPE_MAP.get(pc['type']), 'demands': d}} \ - in resource_policy_list else False + if {pc['properties']['identity']: {'type': CONSTRAINT_TYPE_MAP.get(pc['type']), + 'demands': d}} in resource_policy_list else False if resource_repeated: continue else: resource_policy_list.append( - {pc['properties']['identity']: {'type': CONSTRAINT_TYPE_MAP.get(pc['type']), 'demands': d }}) + {pc['properties']['identity']: {'type': CONSTRAINT_TYPE_MAP.get(pc['type']), + 'demands': d}}) policy[list(policy.keys())[0]]['properties']['resources'] = d related_policies.append(policy) # Need to override the default policies, here delete the outdated policy stored in the db @@ -194,9 +207,9 @@ def gen_attribute_policy(vnf_list, attribute_policy): properties = p_main[list(p_main.keys())[0]]['properties']['attributeProperties'] attribute_mapping = policy_config_mapping['filtering_attributes'] # wanted attributes and mapping p_new[p_main[list(p_main.keys())[0]]['properties']['identity']]['properties'] = { - 'evaluate': dict((attribute_mapping[k], properties.get(k) - if k != "cloudRegion" else gen_cloud_region(properties)) - for k in attribute_mapping.keys()) + 'evaluate': dict((attribute_mapping[k], properties.get(k) + if k != "cloudRegion" else gen_cloud_region(properties)) + for k in attribute_mapping.keys()) } return cur_policies # cur_policies gets updated in place... @@ -271,7 +284,7 @@ def get_policy_properties(demand, policies): policy_demands = set([x.lower() for x in policy[list(policy.keys())[0]]['properties']['resources']]) if policy_demands and demand['resourceModuleName'].lower() not in policy_demands: continue # no match for this policy - elif policy_demands == set(): # Append resource name for default policy + elif policy_demands == set(): # Append resource name for default policy policy[list(policy.keys())[0]]['properties'].update(resources=list(demand.get('resourceModuleName'))) for policy_property in policy[list(policy.keys())[0]]['properties']['vnfProperties']: yield policy_property @@ -285,35 +298,44 @@ def get_demand_properties(demand, policies): inventory_type=policy_property['inventoryType'], service_type=demand.get('serviceResourceId', ''), service_resource_id=demand.get('serviceResourceId', '')) + policy_property_mapping = {'filtering_attributes': 'attributes', + 'passthrough_attributes': 'passthroughAttributes', + 'default_attributes': 'defaultAttributes'} prop.update({'unique': policy_property['unique']} if 'unique' in policy_property and policy_property['unique'] else {}) prop['filtering_attributes'] = dict() - if policy_property.get('attributes'): - for attr_key, attr_val in policy_property['attributes'].items(): - update_converted_attribute(attr_key, attr_val, prop, 'filtering_attributes') - if policy_property.get('passthroughAttributes'): - prop['passthrough_attributes'] = dict() - for attr_key, attr_val in policy_property['passthroughAttributes'].items(): - update_converted_attribute(attr_key, attr_val, prop, 'passthrough_attributes') + for key, value in policy_property_mapping.items(): + get_demand_attributes(prop, policy_property, key, value) prop['filtering_attributes'].update({'global-customer-id': policy_property['customerId']} - if 'customerId' in policy_property and policy_property['customerId'] else {}) + if 'customerId' in policy_property and policy_property['customerId'] + else {}) prop['filtering_attributes'].update({'model-invariant-id': demand['resourceModelInfo']['modelInvariantId']} - if 'modelInvariantId' in demand['resourceModelInfo'] and demand['resourceModelInfo']['modelInvariantId'] else {}) + if 'modelInvariantId' in demand['resourceModelInfo'] + and demand['resourceModelInfo']['modelInvariantId'] else {}) prop['filtering_attributes'].update({'model-version-id': demand['resourceModelInfo']['modelVersionId']} - if 'modelVersionId' in demand['resourceModelInfo'] and demand['resourceModelInfo']['modelVersionId'] else {}) + if 'modelVersionId' in demand['resourceModelInfo'] + and demand['resourceModelInfo']['modelVersionId'] else {}) prop['filtering_attributes'].update({'equipment-role': policy_property['equipmentRole']} - if 'equipmentRole' in policy_property and policy_property['equipmentRole'] else {}) + if 'equipmentRole' in policy_property and policy_property['equipmentRole'] + else {}) prop.update(get_candidates_demands(demand)) demand_properties.append(prop) return demand_properties +def get_demand_attributes(prop, policy_property, attribute_type, key): + if policy_property.get(key): + prop[attribute_type] = dict() + for attr_key, attr_val in policy_property[key].items(): + update_converted_attribute(attr_key, attr_val, prop, attribute_type) + + def update_converted_attribute(attr_key, attr_val, properties, attribute_type): - """ - Updates dictonary of attributes with one specified in the arguments. + """Updates dictonary of attributes with one specified in the arguments. + Automatically translates key namr from camelCase to hyphens :param attribute_type: attribute section name :param attr_key: key of the attribute @@ -333,6 +355,7 @@ def update_converted_attribute(attr_key, attr_val, properties, attribute_type): def gen_demands(demands, vnf_policies): """Generate list of demands based on request and VNF policies + :param demands: A List of demands :param vnf_policies: Policies associated with demand resources (e.g. from grouped_policies['vnfPolicy']) @@ -349,6 +372,6 @@ def gen_demands(demands, vnf_policies): def gen_cloud_region(property): prop = {"cloud_region_attributes": dict()} if 'cloudRegion' in property: - for k,v in property['cloudRegion'].items(): + for k, v in property['cloudRegion'].items(): update_converted_attribute(k, v, prop, 'cloud_region_attributes') return prop["cloud_region_attributes"] -- cgit 1.2.3-korg