[CORD-2349][CORD-2391] Adding TOSCA keys to support migration to the new TOSCA engine
[CORD-2429] Moving ip generation logic to the core

Change-Id: I818c7fd2974f2f8b95d2214490ae7e898e09601c
diff --git a/docs/dev/xproto.md b/docs/dev/xproto.md
index 0ebd782..95ca43a 100644
--- a/docs/dev/xproto.md
+++ b/docs/dev/xproto.md
@@ -251,6 +251,21 @@
 ```protobuf
 option tosca_key = True;
 ```
+Identify a field that is used as key by the TOSCA engine. This needs to be used in case a composite key can be composed by different combination of fields:
+```protobuf
+tosca_key_one_of = "<field_name>"
+```
+For example, in the `ServiceInstanceLink` model:
+```protobuf
+message ServiceInstanceLink (XOSBase) {
+     required manytoone provider_service_instance->ServiceInstance:provided_links = 1 [db_index = True, null = False, blank = False, tosca_key=True];
+     optional manytoone provider_service_interface->ServiceInterface:provided_links = 2 [db_index = True, null = True, blank = True];
+     optional manytoone subscriber_service_instance->ServiceInstance:subscribed_links = 3 [db_index = True, null = True, blank = True];
+     optional manytoone subscriber_service->Service:subscribed_links = 4 [db_index = True, null = True, blank = True, tosca_key_one_of=subscriber_service_instance];
+     optional manytoone subscriber_network->Network:subscribed_links = 5 [db_index = True, null = True, blank = True, tosca_key_one_of=subscriber_service_instance];
+}
+```
+the key is composed by `provider_service_instance` and one of `subscriber_service_instance`, `subscriber_service`, `subscriber_network`
 
 ### Naming Conventions
 
diff --git a/lib/xos-genx/__init__.py b/lib/xos-genx/__init__.py
new file mode 100644
index 0000000..42722a8
--- /dev/null
+++ b/lib/xos-genx/__init__.py
@@ -0,0 +1,14 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
diff --git a/lib/xos-genx/xos-genx-tests/test_tosca.py b/lib/xos-genx/xos-genx-tests/test_tosca.py
index a771cbb..f829273 100644
--- a/lib/xos-genx/xos-genx-tests/test_tosca.py
+++ b/lib/xos-genx/xos-genx-tests/test_tosca.py
@@ -122,4 +122,49 @@
         args.target = self.target_tosca_keys
         output = XOSGenerator.generate(args)
         self.assertNotIn('name', output)
-        self.assertIn('provider_service_instance_id', output)
\ No newline at end of file
+        self.assertIn('provider_service_instance_id', output)
+
+    def test_xproto_model_to_oneof_key(self):
+        """
+        [XOS-GenX] in some models we need to have a combine key on variable fields, for example, keys can be subscriber_service_id + oneof(provider_service_id, provider_network_id)
+        """
+        xproto = \
+            """
+            option app_label = "test";
+
+            message Foo {
+            
+                option tosca_key = "key1, oneof(key_2, key_3)";
+            
+                required string name = 1 [ null = "False", blank="False"];
+                required string key_1 = 2 [ null = "False", blank="False", tosca_key_one_of = "key_2"];
+                required string key_2 = 3 [ null = "False", blank="False", tosca_key_one_of = "key_1"];
+                required string key_3 = 4 [ null = "False", blank="False", tosca_key_one_of = "key_4"];
+                required string key_4 = 5 [ null = "False", blank="False", tosca_key_one_of = "key_3"];
+            }
+            """
+
+        args = FakeArgs()
+        args.inputs = xproto
+        args.target = self.target_tosca_keys
+        output = XOSGenerator.generate(args)
+        self.assertIn("['name', ['key_4', 'key_3'], ['key_1', 'key_2']]", output)
+
+        xproto = \
+            """
+            option app_label = "test";
+
+            message Foo {
+
+                option tosca_key = "key1, oneof(key_2, key_3)";
+
+                required string name = 1 [ null = "False", blank="False"];
+                required manytoone key_1->Bar:key_1s = 2;
+                required manytoone key_2->Bar:key_2s = 3 [tosca_key_one_of = "key_1"];
+                required manytoone key_3->Bar:key_3s = 4 [tosca_key_one_of = "key_1"];
+            }
+            """
+
+        args.inputs = xproto
+        output = XOSGenerator.generate(args)
+        self.assertIn("['name', ['key_1_id', 'key_3_id', 'key_2_id']]", output)
\ No newline at end of file
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/tosca.py b/lib/xos-genx/xosgenx/jinja2_extensions/tosca.py
index a46770d..51cd2bb 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/tosca.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/tosca.py
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xosgenx.jinja2_extensions import xproto_field_graph_components
+
 def xproto_tosca_required(null, blank, default=None):
 
     if null == 'True' or blank == 'True' or default != 'False':
@@ -32,14 +34,32 @@
         return type
 
 def xproto_fields_to_tosca_keys(fields):
-	keys = []
-	# look for explicit keys
-	for f in fields:
-		if 'tosca_key' in f['options'] and f['options']['tosca_key'] and 'link' not in f:
-			keys.append(f['name'])
-		if 'tosca_key' in f['options'] and f['options']['tosca_key'] and ('link' in f and f['link']):
-			keys.append("%s_id" % f['name'])
-	# if not keys are specified and there is a name field, use that as key.
-	if len(keys) == 0 and 'name' in map(lambda f: f['name'], fields):
-		keys.append('name')
-	return keys
\ No newline at end of file
+    keys = []
+
+    # look for one_of keys
+    _one_of = xproto_field_graph_components(fields, 'tosca_key_one_of')
+    one_of = [list(i) for i in _one_of]
+
+    # look for explicit keys
+    for f in fields:
+        if 'tosca_key' in f['options'] and f['options']['tosca_key'] and 'link' not in f:
+            keys.append(f['name'])
+        if 'tosca_key' in f['options'] and f['options']['tosca_key'] and ('link' in f and f['link']):
+            keys.append("%s_id" % f['name'])
+    # if not keys are specified and there is a name field, use that as key.
+    if len(keys) == 0 and 'name' in map(lambda f: f['name'], fields):
+        keys.append('name')
+
+    for of in one_of:
+        # check if the field is a link, and in case add _id
+        for index, f in enumerate(of):
+            try:
+                field = [x for x in fields if x['name'] == f and ('link' in x and x['link'])][0]
+                of[index] = "%s_id" % f
+            except IndexError, e:
+                # the field is not a link
+                pass
+
+        keys.append(of)
+
+    return keys
\ No newline at end of file
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 6760938..4cef3bd 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -196,8 +196,8 @@
 
 
 message ControllerSite (XOSBase) {
-     required manytoone site->Site:controllersite = 1 [db_index = True, null = False, blank = False, unique_with="controller"];
-     optional manytoone controller->Controller:controllersite = 2 [db_index = True, null = True, blank = True];
+     required manytoone site->Site:controllersite = 1 [db_index = True, null = False, blank = False, unique_with="controller", tosca_key = True];
+     optional manytoone controller->Controller:controllersite = 2 [db_index = True, null = True, blank = True, tosca_key = True];
      optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = True];
 }
 
@@ -260,7 +260,7 @@
 
 
 message DeploymentRole (XOSBase) {
-     required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False, tosca_key=True];
 }
 
 
@@ -497,7 +497,7 @@
 
 
 message SiteRole (XOSBase) {
-     required string role = 1 [choices = "(('admin', 'Admin'), ('pi', 'PI'), ('tech', 'Tech'), ('billing', 'Billing'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     required string role = 1 [choices = "(('admin', 'Admin'), ('pi', 'PI'), ('tech', 'Tech'), ('billing', 'Billing'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False, tosca_key=True];
 }
 
 policy slice_name < obj.id | {{ obj.name.startswith(obj.site.login_base) }} >
@@ -536,7 +536,7 @@
 
 
 message SliceRole (XOSBase) {
-     required string role = 1 [choices = "(('admin', 'Admin'), ('default', 'Default'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     required string role = 1 [choices = "(('admin', 'Admin'), ('default', 'Default'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False, tosca_key=True];
 }
 
 policy tag_policy < ctx.user.is_admin >
@@ -572,8 +572,8 @@
      required manytoone provider_service_instance->ServiceInstance:provided_links = 1 [db_index = True, null = False, blank = False, tosca_key=True];
      optional manytoone provider_service_interface->ServiceInterface:provided_links = 2 [db_index = True, null = True, blank = True];
      optional manytoone subscriber_service_instance->ServiceInstance:subscribed_links = 3 [db_index = True, null = True, blank = True];
-     optional manytoone subscriber_service->Service:subscribed_links = 4 [db_index = True, null = True, blank = True];
-     optional manytoone subscriber_network->Network:subscribed_links = 5 [db_index = True, null = True, blank = True];
+     optional manytoone subscriber_service->Service:subscribed_links = 4 [db_index = True, null = True, blank = True, tosca_key_one_of=subscriber_service_instance];
+     optional manytoone subscriber_network->Network:subscribed_links = 5 [db_index = True, null = True, blank = True, tosca_key_one_of=subscriber_service_instance];
 }
 
 message ServiceInstanceAttribute (XOSBase) {