Add passthrough attributes 86/98386/3
authorLukasz Rajewski <lukasz.rajewski@orange.com>
Wed, 13 Nov 2019 21:21:55 +0000 (22:21 +0100)
committerLukasz Rajewski <lukasz.rajewski@orange.com>
Tue, 19 Nov 2019 09:25:10 +0000 (10:25 +0100)
Added passthrough attributes in placement request.
Current attributes HAS request section changed to
filtering_attributes and new passthrough attributes
added in HAS request - this one is read from vnf policy
passthroughAttributes section.

Issue-ID: OPTFRA-610
Signed-off-by: Lukasz Rajewski <lukasz.rajewski@orange.com>
Change-Id: Ie2719fdd94bb0b42ef7cf2cbf47f5f182a4e7347

conductor/conductor/controller/translator.py
conductor/conductor/data/plugins/inventory_provider/aai.py
conductor/conductor/solver/service.py
conductor/conductor/tests/unit/controller/test_translator.py
conductor/conductor/tests/unit/data/plugins/inventory_provider/demand_list.json
conductor/conductor/tests/unit/data/plugins/inventory_provider/service_demand_list.json
conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py
conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_candidates.json
conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_demand_list.json

index 85f63af..45e0ee2 100644 (file)
@@ -47,7 +47,7 @@ INVENTORY_TYPES = ['cloud', 'service', 'transport', 'vfmodule']
 DEFAULT_INVENTORY_PROVIDER = INVENTORY_PROVIDERS[0]
 CANDIDATE_KEYS = ['candidate_id', 'cost', 'inventory_type', 'location_id',
                   'location_type']
-DEMAND_KEYS = ['attributes', 'candidates', 'complex', 'conflict_identifier',
+DEMAND_KEYS = ['filtering_attributes', 'passthrough_attributes', 'candidates', 'complex', 'conflict_identifier',
                'customer_id', 'default_cost', 'excluded_candidates',
                'existing_placement', 'flavor', 'inventory_provider',
                'inventory_type', 'port_key', 'region', 'required_candidates',
@@ -448,11 +448,11 @@ class Translator(object):
                     # For service and vfmodule inventories, customer_id and
                     # service_type MUST be specified
                     if inventory_type == 'service' or inventory_type == 'vfmodule':
-                        attributes = requirement.get('attributes')
+                        filtering_attributes = requirement.get('filtering_attributes')
 
-                        if attributes:
-                            customer_id = attributes.get('customer-id')
-                            global_customer_id = attributes.get('global-customer-id')
+                        if filtering_attributes:
+                            customer_id = filtering_attributes.get('customer-id')
+                            global_customer_id = filtering_attributes.get('global-customer-id')
                             if global_customer_id:
                                 customer_id = global_customer_id
                         else:
@@ -465,7 +465,7 @@ class Translator(object):
                                 "Customer ID not specified for "
                                 "demand {}".format(name)
                             )
-                        if not attributes and not service_type:
+                        if not filtering_attributes and not service_type:
                             raise TranslatorException(
                                 "Service Type not specified for "
                                 "demand {}".format(name)
index b69655c..26b390a 100644 (file)
@@ -1187,26 +1187,27 @@ class AAI(base.InventoryProviderBase):
                 inventory_type = requirement.get('inventory_type').lower()
                 service_subscription = requirement.get('service_subscription')
                 candidate_uniqueness = requirement.get('unique', 'true')
-                attributes = requirement.get('attributes')
+                filtering_attributes = requirement.get('filtering_attributes')
+                passthrough_attributes = requirement.get('passthrough_attributes')
                 #TODO: may need to support multiple service_type and customer_id in the futrue
 
                 #TODO: make it consistent for dash and underscore
 
-                if attributes:
+                if filtering_attributes:
                     # catch equipment-role and service-type from template
-                    equipment_role = attributes.get('equipment-role')
-                    service_type = attributes.get('service-type')
+                    equipment_role = filtering_attributes.get('equipment-role')
+                    service_type = filtering_attributes.get('service-type')
                     if equipment_role:
                         service_type = equipment_role
                     # catch global-customer-id and customer-id from template
-                    global_customer_id = attributes.get('global-customer-id')
-                    customer_id = attributes.get('customer-id')
+                    global_customer_id = filtering_attributes.get('global-customer-id')
+                    customer_id = filtering_attributes.get('customer-id')
                     if global_customer_id:
                         customer_id = global_customer_id
 
-                    model_invariant_id = attributes.get('model-invariant-id')
-                    model_version_id = attributes.get('model-version-id')
-                    service_role = attributes.get('service-role')
+                    model_invariant_id = filtering_attributes.get('model-invariant-id')
+                    model_version_id = filtering_attributes.get('model-version-id')
+                    service_role = filtering_attributes.get('service-role')
                 # For earlier
                 else:
                     service_type = equipment_role = requirement.get('service_type')
@@ -1306,7 +1307,7 @@ class AAI(base.InventoryProviderBase):
                         cloud_region_attr['complex-name'] = region['complex_name']
                         cloud_region_attr['physical-location-id'] = region['physical_location_id']
 
-                        if attributes and (not self.match_inventory_attributes(attributes, cloud_region_attr, candidate['candidate_id'])):
+                        if filtering_attributes and (not self.match_inventory_attributes(filtering_attributes, cloud_region_attr, candidate['candidate_id'])):
                             self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
                                                                          reason='attributes and match invetory attributes')
                             continue
@@ -1330,6 +1331,7 @@ class AAI(base.InventoryProviderBase):
                         if required_candidates and not self.match_candidate_by_list(candidate, required_candidates, False, name, triage_translator_data):
                             continue
 
+                        self.add_passthrough_attributes(candidate, passthrough_attributes, name, triage_translator_data)
                         # add candidate to demand candidates
                         resolved_demands[name].append(candidate)
                         LOG.debug(">>>>>>> Candidate <<<<<<<")
@@ -1340,7 +1342,7 @@ class AAI(base.InventoryProviderBase):
 
                     # First level query to get the list of generic vnfs
                     vnf_by_model_invariant = list()
-                    if attributes and model_invariant_id:
+                    if filtering_attributes and model_invariant_id:
 
                         raw_path = '/network/generic-vnfs/' \
                                    '?model-invariant-id={}&depth=0'.format(model_invariant_id)
@@ -1477,7 +1479,7 @@ class AAI(base.InventoryProviderBase):
                         vnf['cloud-region-id'] = cloud_region_id
                         vnf['physical-location-id'] = candidate.get('physical_location_id')
 
-                        if attributes and not self.match_inventory_attributes(attributes, vnf, candidate['candidate_id']):
+                        if filtering_attributes and not self.match_inventory_attributes(filtering_attributes, vnf, candidate['candidate_id']):
                             self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
                                                                          reason="attibute check error")
                             continue
@@ -1502,6 +1504,7 @@ class AAI(base.InventoryProviderBase):
                                                  triage_translator_data):
                             continue
                         else:
+                            self.add_passthrough_attributes(candidate, passthrough_attributes, name, triage_translator_data)
                             resolved_demands[name].append(candidate)
                             LOG.debug(">>>>>>> Candidate <<<<<<<")
                             LOG.debug(json.dumps(candidate, indent=4))
@@ -1511,7 +1514,7 @@ class AAI(base.InventoryProviderBase):
 
                     # First level query to get the list of generic vnfs
                     vnf_by_model_invariant = list()
-                    if attributes and model_invariant_id:
+                    if filtering_attributes and model_invariant_id:
 
                         raw_path = '/network/generic-vnfs/' \
                                    '?model-invariant-id={}&depth=0'.format(model_invariant_id)
@@ -1715,7 +1718,7 @@ class AAI(base.InventoryProviderBase):
                             vnf_vf_module_inventory['physical-location-id'] = candidate.get('physical_location_id')
                             vnf_vf_module_inventory['service_instance_id'] = vs_service_instance_id
 
-                            if attributes and not self.match_inventory_attributes(attributes, vnf_vf_module_inventory,
+                            if filtering_attributes and not self.match_inventory_attributes(filtering_attributes, vnf_vf_module_inventory,
                                                                                   candidate['candidate_id']):
                                 self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
                                                                               candidate['location_id'], name,
@@ -1744,6 +1747,7 @@ class AAI(base.InventoryProviderBase):
                                                      triage_translator_data):
                                 continue
                             else:
+                                self.add_passthrough_attributes(candidate, passthrough_attributes, name, triage_translator_data)
                                 resolved_demands[name].append(candidate)
                                 LOG.debug(">>>>>>> Candidate <<<<<<<")
                                 LOG.debug(json.dumps(candidate, indent=4))
@@ -1870,6 +1874,7 @@ class AAI(base.InventoryProviderBase):
                             candidate['region'] = \
                                 complex_info.get('region')
 
+                            self.add_passthrough_attributes(candidate, passthrough_attributes, name, triage_translator_data)
                             # add candidate to demand candidates
                             resolved_demands[name].append(candidate)
 
@@ -1878,6 +1883,14 @@ class AAI(base.InventoryProviderBase):
                               " {}".format(inventory_type))
         return resolved_demands
 
+    def add_passthrough_attributes(self, candidate, passthrough_attributes, demand_name, triage_translator_data):
+        if passthrough_attributes is None:
+            return
+        if len(passthrough_attributes.items()) > 0:
+            candidate['passthrough_attributes'] = dict()
+        for key, value in passthrough_attributes.items():
+            candidate['passthrough_attributes'][key] = value
+
     def match_region(self, candidate, restricted_region_id, restricted_complex_id, demand_name, triage_translator_data):
         if self.match_candidate_attribute(
                 candidate,
index 978f735..fb7b2f0 100644 (file)
@@ -556,7 +556,13 @@ class SolverService(cotyledon.Service):
                             if resource.get('conflict_id'):
                                 rec["candidate"]["conflict_id"] = resource.get("conflict_id")
 
-
+                        if resource.get('passthrough_attributes'):
+                            for key, value in resource.get('passthrough_attributes').items():
+                                if key in rec["attributes"]:
+                                    LOG.error('Passthrough attribute {} in demand {} already exist for candidate {}'.
+                                              format(key, demand_name, rec['candidate_id']))
+                                else:
+                                    rec["attributes"][key] = value
                         # TODO(snarayanan): Add total value to recommendations?
                         # msg = "--- total value of decision = {}"
                         # LOG.debug(msg.format(_best_path.total_value))
index 0d4048a..c30d937 100644 (file)
@@ -180,7 +180,7 @@ class TestNoExceptionTranslator(unittest.TestCase):
                     "candidate_id": ["e765d576-8755-4145-8536-0bb6d9b1dc9a"],
                     "inventory_type": "vfmodule"
                 }],
-                "attributes": {
+                "filtering_attributes": {
                     "prov-status": "ACTIVE",
                     "global-customer-id": "Demonstration",
                     "model-version-id": "763731df-84fd-494b-b824-01fc59a5ff2d",
@@ -209,7 +209,7 @@ class TestNoExceptionTranslator(unittest.TestCase):
                     "candidate_id": ["e765d576-8755-4145-8536-0bb6d9b1dc9a"],
                     "inventory_type": "vfmodule"
                 }],
-                "attributes": {
+                "filtering_attributes": {
                     "prov-status": "ACTIVE",
                     "global-customer-id": "Demonstration",
                     "model-version-id": "763731df-84fd-494b-b824-01fc59a5ff2d",
@@ -236,7 +236,7 @@ class TestNoExceptionTranslator(unittest.TestCase):
                     "service_resource_id": "vFW-SINK-XX",
                     "vlan_key": "vlan_key",
                     "service_type": "vFW-SINK-XX",
-                    "attributes": {
+                    "filtering_attributes": {
                         "cloud-region-id": {
                             "get_param": "chosen_region"
                         },
index c817fbb..8a54a25 100644 (file)
@@ -1,7 +1,7 @@
  {
       "demand_name": [
       {
-        "attributes": {
+        "filtering_attributes": {
             "global-customer-id": "customer-123",
             "equipment-role": "TEST",
             "service-type": "TEST"
index fb1059a..cffba57 100644 (file)
@@ -5,7 +5,7 @@
         "inventory_type": "service",
         "port_key": "vlan_port",
         "vlan_key": "vlan_key",
-        "attributes": {
+        "filtering_attributes": {
             "global-customer-id": "Demonstration",
             "model-version-id": "e02a7e5c-9d27-4360-ab7c-73bb83b07e3b",
             "model-invariant-id": "762472ef-5284-4daa-ab32-3e7bee2ec355"
index d77b644..9d1245d 100644 (file)
@@ -610,6 +610,26 @@ tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d
         self.assertIsNone(self.aai_ep.resolve_service_instance_id_for_vnf(candidate, bad_vnf, customer_id, service_type,
                                                                           demand_name, triage_translator_data))
 
+    def test_add_passthrough_parameters(self):
+        triage_translator_data = None
+
+        candidate = dict()
+        candidate['candidate_id'] = 'some_id'
+        candidate['location_id'] = 'some_location_id'
+        candidate['inventory_type'] = 'service'
+
+        parameters = dict()
+        parameters['param_one'] = "value"
+        parameters['param_two'] = "value"
+
+        candidate_info = copy.deepcopy(candidate)
+        candidate_info['passthrough_attributes'] = dict()
+        candidate_info['passthrough_attributes']['param_one'] = "value"
+        candidate_info['passthrough_attributes']['param_two'] = "value"
+
+        self.aai_ep.add_passthrough_attributes(candidate, parameters, 'demand', None)
+        self.assertDictEqual(candidate, candidate_info)
+
     def test_match_candidate_by_list(self):
         TraigeTranslator.collectDroppedCandiate = mock.MagicMock(return_value=None)
         triage_translator_data = None
index 7343f34..e7b398e 100644 (file)
@@ -51,6 +51,9 @@
             "cloud_owner": "CloudOwner",
             "vnf-type": "5G_EVE_Demo/5G_EVE_PKG 0",
             "nf-name": "vFW-PKG-MC",
+            "passthrough_attributes": {
+              "td-role": "anchor"
+            },
             "inventory_type": "vfmodule",
             "sriov_automation": "false",
             "uniqueness": "false",
index 8069e0f..c6d34aa 100644 (file)
@@ -6,7 +6,7 @@
         "port_key": "vlan_port",
         "vlan_key": "vlan_key",
         "unique": "false",
-        "attributes": {
+        "filtering_attributes": {
             "prov-status": "ACTIVE",
             "global-customer-id": "Demonstration",
             "model-version-id": "e02a7e5c-9d27-4360-ab7c-73bb83b07e3b",
@@ -15,6 +15,9 @@
             "cloud-region-id": "RegionOne",
             "service_instance_id": "3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c"
         },
+       "passthrough_attributes": {
+            "td-role": "anchor"
+        },
         "service_type": "vPGN-XX",
         "excluded_candidates": [{
             "inventory_type": "vfmodule",