[CORD-2391] Supporting one_of keys

Change-Id: I9d62ca3069f7218839c532270cf789a6fd33b120
diff --git a/src/grpc_client/models_accessor.py b/src/grpc_client/models_accessor.py
index 918c04d..5d54644 100644
--- a/src/grpc_client/models_accessor.py
+++ b/src/grpc_client/models_accessor.py
@@ -41,10 +41,22 @@
 
         filter = {}
         for k in filter_keys:
-            try:
-                filter[k] = data[k]
-            except KeyError, e:
-                raise Exception("[XOS-TOSCA] Model %s doesn't have a property for the specified tosca_key (%s)" % (class_name, e))
+            if isinstance(k, str):
+                try:
+                    filter[k] = data[k]
+                except KeyError, e:
+                    raise Exception("[XOS-TOSCA] Model %s doesn't have a property for the specified tosca_key (%s)" % (class_name, e))
+            elif isinstance(k, list):
+                # one of they keys in this list has to be set
+                one_of_key = None
+                for i in k:
+                    if i in data:
+                        one_of_key = i
+                        one_of_key_val = data[i]
+                if not one_of_key:
+                    raise Exception("[XOS-TOSCA] Model %s doesn't have a property for the specified tosca_key_one_of (%s)" % (class_name, k))
+                else:
+                    filter[one_of_key] = one_of_key_val
 
         key = "%s~%s" % (username, password)
         if not key in RESOURCES:
diff --git a/test/test_grpc_models_accessor.py b/test/test_grpc_models_accessor.py
index 729fac3..60e86e0 100644
--- a/test/test_grpc_models_accessor.py
+++ b/test/test_grpc_models_accessor.py
@@ -37,7 +37,8 @@
     'username~pass': {
         'test-model': FakeResource,
         'single-key': FakeResource,
-        'double-key': FakeResource
+        'double-key': FakeResource,
+        'one-of-key': FakeResource
     }
 }
 
@@ -47,6 +48,7 @@
     'empty-key': [],
     'single-key': ['fake_key'],
     'double-key': ['key_1', 'key_2'],
+    'one-of-key': ['key_1', ['key_2', 'key_3']],
 }
 
 USERNAME = 'username'
@@ -134,6 +136,46 @@
     @patch.object(FakeResource.objects, "filter")
     @patch.object(FakeResource.objects, "new", MagicMock(return_value=FakeModel))
     @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
+    def test_one_of_key(self, mock_filter):
+        """
+        [GRPCModelsAccessor] get_model_from_classname: should use a composite with one_of key to lookup a model
+        """
+        # NOTE it should be valid for items with either one of the keys
+        data2 = {
+            "name": "test",
+            "key_1": "key1",
+            "key_2": "key2"
+        }
+        with patch.dict(RESOURCES, mock_resources, clear=True):
+            model = GRPCModelsAccessor.get_model_from_classname('one-of-key', data2, USERNAME, PASSWORD)
+            mock_filter.assert_called_with(key_1="key1", key_2="key2")
+            self.assertEqual(model, FakeModel)
+
+        data3 = {
+            "name": "test",
+            "key_1": "key1",
+            "key_3": "key3"
+        }
+        with patch.dict(RESOURCES, mock_resources, clear=True):
+            model = GRPCModelsAccessor.get_model_from_classname('one-of-key', data3, USERNAME, PASSWORD)
+            mock_filter.assert_called_with(key_1="key1", key_3="key3")
+            self.assertEqual(model, FakeModel)
+
+    @patch.object(FakeResource.objects, "filter")
+    @patch.object(FakeResource.objects, "new", MagicMock(return_value=FakeModel))
+    @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
+    def test_one_of_key_error(self, mock_filter):
+        data = {
+            "name": "test",
+            "key_1": "key1"
+        }
+        with self.assertRaises(Exception) as e:
+            GRPCModelsAccessor.get_model_from_classname('one-of-key', data, USERNAME, PASSWORD)
+        self.assertEqual(e.exception.message, "[XOS-TOSCA] Model one-of-key doesn't have a property for the specified tosca_key_one_of (['key_2', 'key_3'])")
+
+    @patch.object(FakeResource.objects, "filter")
+    @patch.object(FakeResource.objects, "new", MagicMock(return_value=FakeModel))
+    @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
     def test_new_model(self, mock_filter):
         """
         [GRPCModelsAccessor] get_model_from_classname: should create a new model
diff --git a/test/test_tosca_parser_e2e.py b/test/test_tosca_parser_e2e.py
index 154638f..6a44b6f 100644
--- a/test/test_tosca_parser_e2e.py
+++ b/test/test_tosca_parser_e2e.py
@@ -57,7 +57,7 @@
     @patch.dict(RESOURCES, mock_resources, clear=True)
     @patch.object(FakeGuiExt.objects, 'filter', MagicMock(return_value=[FakeModel]))
     @patch.object(FakeModel, 'save')
-    def _test_basic_creation(self, mock_save):
+    def test_basic_creation(self, mock_save):
         """
         [TOSCA_Parser] Should save models defined in a TOSCA recipe
         """
@@ -98,7 +98,7 @@
     @patch.dict(RESOURCES, mock_resources, clear=True)
     @patch.object(FakeGuiExt.objects, 'filter', MagicMock(return_value=[FakeModel]))
     @patch.object(FakeModel, 'delete')
-    def _test_basic_deletion(self, mock_delete):
+    def test_basic_deletion(self, mock_delete):
         """
         [TOSCA_Parser] Should delete models defined in a TOSCA recipe
         """
@@ -135,7 +135,7 @@
     @patch.object(FakeSite.objects, 'filter', MagicMock(return_value=[FakeModel]))
     @patch.object(FakeUser.objects, 'filter', MagicMock(return_value=[FakeModel]))
     @patch.object(FakeModel, 'save')
-    def _test_related_models_creation(self, mock_save):
+    def test_related_models_creation(self, mock_save):
         """
         [TOSCA_Parser] Should save related models defined in a TOSCA recipe
         """
@@ -161,7 +161,7 @@
         hosts_nodes: True
 
     # User
-    user_test:
+    usertest:
       type: tosca.nodes.User
       properties:
         username: test@opencord.org
@@ -183,20 +183,20 @@
         self.assertEqual(mock_save.call_count, 2)
 
         self.assertIsNotNone(parser.templates_by_model_name['site_onlab'])
-        self.assertIsNotNone(parser.templates_by_model_name['user_test'])
-        self.assertEqual(parser.ordered_models_name, ['site_onlab', 'user_test'])
+        self.assertIsNotNone(parser.templates_by_model_name['usertest'])
+        self.assertEqual(parser.ordered_models_name, ['site_onlab', 'usertest'])
 
         # check that the model was saved with the expected values
         saved_site = parser.saved_model_by_name['site_onlab']
         self.assertEqual(saved_site.name, 'Open Networking Lab')
 
-        saved_user = parser.saved_model_by_name['user_test']
+        saved_user = parser.saved_model_by_name['usertest']
         self.assertEqual(saved_user.firstname, 'User')
         self.assertEqual(saved_user.site_id, 1)
 
     @patch.dict(RESOURCES, mock_resources, clear=True)
     @patch.object(FakeSite.objects, 'filter', MagicMock(return_value=[]))
-    def _test_must_exist_fail(self):
+    def test_must_exist_fail(self):
         """
         [TOSCA_Parser] Should throw an error if an object with 'must_exist' does not exist
         """