List of canidate identifiers support
[optf/osdf.git] / osdf / optimizers / placementopt / conductor / translation.py
index 9111c81..d14f3e1 100644 (file)
 import copy
 import json
 import yaml
+import re
 
 from osdf.utils.data_conversion import text_to_symbol
+from osdf.utils.programming_utils import dot_notation
 
 policy_config_mapping = yaml.load(open('config/has_config.yaml')).get('policy_config_mapping')
 
 
+def get_opt_query_data(req_json, policies):
+    """
+    Fetch service and order specific details from the requestParameters field of a request.
+    :param req_json: a request file
+    :param policies: A set of policies
+    :return: A dictionary with service and order-specific attributes.
+    """
+    req_param_dict = {}
+    if 'requestParameters' in req_json["placementInfo"]:
+        req_params = req_json["placementInfo"]["requestParameters"]
+        for policy in policies:
+            for queryProp in policy['content']['queryProperties']:
+                attr_val = queryProp['value'] if 'value' in queryProp and queryProp['value'] != "" \
+                    else dot_notation(req_params, queryProp['attribute_location'])
+                if attr_val is not None:
+                    req_param_dict.update({queryProp['attribute']: attr_val})
+    return req_param_dict
+
+
 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
@@ -34,12 +55,13 @@ def gen_optimization_policy(vnf_list, optimization_policy):
     for policy in optimization_policy:
         content = policy['content']
         parameter_list = []
+        parameters = ["cloud_version", "hpa_score"]
 
         for attr in content['objectiveParameter']['parameterAttributes']:
-            parameter = attr['parameter'] if attr['parameter'] == "cloud_version" else attr['parameter']+"_between"
-            for res in attr['resource']:
-                vnf = get_matching_vnf(res, vnf_list)
-                value = [vnf] if attr['parameter'] == "cloud_version" else [attr['customerLocationInfo'], vnf]
+            parameter = attr['parameter'] if attr['parameter'] in parameters else attr['parameter']+"_between"
+            vnfs = get_matching_vnfs(attr['resources'], vnf_list)
+            for vnf in vnfs:
+                value = [vnf] if attr['parameter'] in parameters else [attr['customerLocationInfo'], vnf]
                 parameter_list.append({
                     attr['operator']: [attr['weight'], {parameter: value}]
                 })
@@ -50,14 +72,6 @@ def gen_optimization_policy(vnf_list, optimization_policy):
     return optimization_policy_list
 
 
-def get_matching_vnf(resource, vnf_list):
-
-    for vnf in vnf_list:
-        if resource in vnf:
-            return vnf
-    return resource
-
-
 def get_matching_vnfs(resources, vnf_list, match_type="intersection"):
     """Get a list of matching VNFs from the list of resources
     :param resources:
@@ -65,11 +79,13 @@ def get_matching_vnfs(resources, vnf_list, match_type="intersection"):
     :param match_type: "intersection" or "all" or "any" (any => send all_vnfs if there is any intersection)
     :return: List of matching VNFs
     """
+    resources_lcase = [x.lower() for x in resources]
     if match_type == "all":  # don't bother with any comparisons
-        return resources
-    common_vnfs = set(vnf_list) & set(resources)
+        return resources if set(resources_lcase) <= set(vnf_list) else None
+    common_vnfs = set(vnf_list) & set(resources_lcase)
+    common_resources = [x for x in resources if x.lower() in common_vnfs]
     if match_type == "intersection":  # specifically requested intersection
-        return list(common_vnfs)
+        return list(common_resources)
     return resources if common_vnfs else None  # "any" match => all resources to be returned
 
 
@@ -87,8 +103,8 @@ def gen_policy_instance(vnf_list, resource_policy, match_type="intersection", rt
     related_policies = []
     for policy in resource_policy:
         pc = policy['content']
-        demands = get_matching_vnfs(pc['resourceInstanceType'], vnf_list, match_type=match_type)
-        resource = {pc['identity']: {'type': pc['type'], 'demands': demands}}
+        demands = get_matching_vnfs(pc['resources'], vnf_list, match_type=match_type)
+        resource = {pc['identity']: {'type': pc['policyType'], 'demands': demands}}
 
         if rtype:
             resource[pc['identity']]['properties'] = {'controller': pc[rtype]['controller'],
@@ -127,10 +143,10 @@ def gen_distance_to_location_policy(vnf_list, distance_to_location_policy):
     """Get policies governing distance-to-location for VNFs in order to populate the Conductor API call"""
     cur_policies, related_policies = gen_policy_instance(vnf_list, distance_to_location_policy, rtype=None)
     for p_new, p_main in zip(cur_policies, related_policies):  # add additional fields to each policy
-        properties = p_main['content']['distanceToLocationProperty']
-        pcp_d = properties['distanceCondition']
+        properties = p_main['content']['distanceProperties']
+        pcp_d = properties['distance']
         p_new[p_main['content']['identity']]['properties'] = {
-            'distance': text_to_symbol[pcp_d['operator']] + " " + pcp_d['value'].lower(),
+            'distance': pcp_d['operator'] + " " + pcp_d['value'].lower() + " " + pcp_d['unit'].lower(),
             'location': properties['locationInfo']
         }
     return cur_policies
@@ -150,13 +166,31 @@ def gen_attribute_policy(vnf_list, attribute_policy):
 
 def gen_zone_policy(vnf_list, zone_policy):
     """Get zone policies in order to populate the Conductor API call"""
-    cur_policies, related_policies = gen_policy_instance(vnf_list, zone_policy, rtype=None)
+    cur_policies, related_policies = gen_policy_instance(vnf_list, zone_policy, match_type="all", rtype=None)
     for p_new, p_main in zip(cur_policies, related_policies):  # add additional fields to each policy
-        pmz = p_main['content']['zoneProperty']
+        pmz = p_main['content']['affinityProperty']
         p_new[p_main['content']['identity']]['properties'] = {'category': pmz['category'], 'qualifier': pmz['qualifier']}
     return cur_policies
 
 
+def gen_capacity_policy(vnf_list, capacity_policy):
+    """Get zone policies in order to populate the Conductor API call"""
+    cur_policies, related_policies = gen_policy_instance(vnf_list, capacity_policy, rtype=None)
+    for p_new, p_main in zip(cur_policies, related_policies):  # add additional fields to each policy
+        pmz = p_main['content']['capacityProperty']
+        p_new[p_main['content']['identity']]['properties'] = \
+            {"controller": pmz['controller'], 'request': json.loads(pmz['request'])}
+    return cur_policies
+
+
+def gen_hpa_policy(vnf_list, hpa_policy):
+    """Get zone policies in order to populate the Conductor API call"""
+    cur_policies, related_policies = gen_policy_instance(vnf_list, hpa_policy, rtype=None)
+    for p_new, p_main in zip(cur_policies, related_policies):  # add additional fields to each policy
+        p_new[p_main['content']['identity']]['properties'] = {'evaluate': p_main['content']['flavorFeatures']}
+    return cur_policies
+
+
 def get_augmented_policy_attributes(policy_property, demand):
     """Get policy attributes and augment them using policy_config_mapping and demand information"""
     attributes = copy.copy(policy_property['attributes'])
@@ -172,16 +206,17 @@ def get_candidates_demands(demand):
     for k, v in policy_config_mapping['candidates'].items():
         if k not in demand:
             continue
-        res[v] = [{'inventory_type': x['candidateType'], 'candidate_id': x['candidates']} for x in demand[k]]
+        res[v] = [{'inventory_type': x['identifierType'], 'candidate_id': x['identifiers']} for x in demand[k]]
     return res
 
 
 def get_policy_properties(demand, policies):
     """Get policy_properties for cases where there is a match with the demand"""
     for policy in policies:
-        if not set(policy['content'].get('resourceInstanceType', [])) & set(demand['resourceModuleName']):
+        policy_demands = set([x.lower() for x in policy['content'].get('resources', [])])
+        if demand['resourceModuleName'].lower() not in policy_demands:
             continue  # no match for this policy
-        for policy_property in policy['content']['property']:
+        for policy_property in policy['content']['vnfProperties']:
             yield policy_property
 
 
@@ -191,17 +226,48 @@ def get_demand_properties(demand, policies):
     for policy_property in get_policy_properties(demand, policies):
         prop = dict(inventory_provider=policy_property['inventoryProvider'],
                     inventory_type=policy_property['inventoryType'],
+                    service_type=demand['serviceResourceId'],
                     service_resource_id=demand['serviceResourceId'])
-        if 'attributes' in policy_property:
-            prop['attributes'] = get_augmented_policy_attributes(policy_property, demand)
-        for k1, v1, k2, v2 in policy_config_mapping['extra_fields']:
-            if k1 == v1:
-                prop[k2] = v2
-        prop.update(get_candidates_demands(demand))  # for excluded and partial-rehoming cases
+
+        prop.update({'unique': demand['unique']} if demand.get('unique') else {})
+        prop['attributes'] = dict()
+        prop['attributes'].update({'global-customer-id': policy_property['customerId']}
+                                  if policy_property['customerId'] else {})
+        prop['attributes'].update({'model-invariant-id': demand['resourceModelInfo']['modelInvariantId']}
+                                  if demand['resourceModelInfo']['modelInvariantId'] else {})
+        prop['attributes'].update({'model-version-id': demand['resourceModelInfo']['modelVersionId']}
+                                  if demand['resourceModelInfo']['modelVersionId'] else {})
+        prop['attributes'].update({'equipment-role': policy_property['equipmentRole']}
+                                  if policy_property['equipmentRole'] else {})
+
+        if policy_property.get('attributes'):
+            for attr_key, attr_val in policy_property['attributes'].items():
+                update_converted_attribute(attr_key, attr_val, prop)
+
+        prop.update(get_candidates_demands(demand))
         demand_properties.append(prop)
     return demand_properties
 
 
+def update_converted_attribute(attr_key, attr_val, properties):
+    """
+    Updates dictonary of attributes with one specified in the arguments.
+    Automatically translates key namr from camelCase to hyphens
+    :param attr_key: key of the attribute
+    :param attr_val: value of the attribute
+    :param properties: dictionary with attributes to update
+    :return:
+    """
+    if attr_val:
+        remapping = policy_config_mapping['attributes']
+        if remapping.get(attr_key):
+            key_value = remapping.get(attr_key)
+        else:
+            key_value = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', attr_key)
+            key_value = re.sub('([a-z0-9])([A-Z])', r'\1-\2', key_value).lower()
+        properties['attributes'].update({key_value: attr_val})
+
+
 def gen_demands(req_json, vnf_policies):
     """Generate list of demands based on request and VNF policies
     :param req_json: Request object from the client (e.g. MSO)
@@ -210,6 +276,7 @@ def gen_demands(req_json, vnf_policies):
     """
     demand_dictionary = {}
     for demand in req_json['placementInfo']['placementDemands']:
-        demand_dictionary.update(
-            {demand['resourceModuleName']: get_demand_properties(demand, vnf_policies)})
+        prop = get_demand_properties(demand, vnf_policies)
+        if len(prop) > 0:
+            demand_dictionary.update({demand['resourceModuleName']: prop})
     return demand_dictionary