SEBA-108 Allow get_westbound method to get fields directly from si;
Use base class convenience wrapper if no descendant is available

Change-Id: I087465a5694bd73b67493710678266df3dd6c8e3
diff --git a/xos/xos_client/xosapi/convenience/serviceinstance.py b/xos/xos_client/xosapi/convenience/serviceinstance.py
index ffb6bc3..76beb7b 100644
--- a/xos/xos_client/xosapi/convenience/serviceinstance.py
+++ b/xos/xos_client/xosapi/convenience/serviceinstance.py
@@ -74,7 +74,10 @@
             link = ServiceInstanceLink(provider_service_instance=eastbound_si, subscriber_service_instance=si)
             link.save()
 
-    def get_westbound_service_instance_properties(self, prop_name):
+    def get_westbound_service_instance_properties(self, prop_name, include_self=False):
+        if include_self and hasattr(self, prop_name):
+            return getattr(self, prop_name)
+
         wi = self.westbound_service_instances
 
         if len(wi) == 0:
diff --git a/xos/xos_client/xosapi/fake_stub.py b/xos/xos_client/xosapi/fake_stub.py
index ac35805..88a54b7 100644
--- a/xos/xos_client/xosapi/fake_stub.py
+++ b/xos/xos_client/xosapi/fake_stub.py
@@ -142,7 +142,8 @@
 class Controller(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "name", "default": ""},
-               {"name": "deployment_id", "default": 0, "fk_model": "Deployment"}
+               {"name": "deployment_id", "default": 0, "fk_model": "Deployment"},
+               {"name": "class_names", "default": "Controller"}
              )
 
     def __init__(self, **kwargs):
@@ -153,6 +154,7 @@
 class Deployment(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "name", "default": ""},
+               {"name": "class_names", "default": "Deployment"}
              )
 
     def __init__(self, **kwargs):
@@ -163,7 +165,8 @@
 class User(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "email", "default": ""},
-               {"name": "site_id", "default": 0, "fk_model": "Site"}, )
+               {"name": "site_id", "default": 0, "fk_model": "Site"},
+               {"name": "class_names", "default": "User"} )
 
     def __init__(self, **kwargs):
         return super(User, self).__init__(self.FIELDS, **kwargs)
@@ -178,7 +181,8 @@
                {"name": "creator_id", "default": 0, "fk_model": "User"},
                {"name": "networks_ids", "default": [], "fk_reverse": "Network"},
                {"name": "network", "default": ""},
-               {"name": "leaf_model_name", "default": "Slice"} )
+               {"name": "leaf_model_name", "default": "Slice"},
+               {"name": "class_names", "default": "Slice"} )
 
     def __init__(self, **kwargs):
         return super(Slice, self).__init__(self.FIELDS, **kwargs)
@@ -189,7 +193,8 @@
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "name", "default": ""},
                {"name": "slices_ids", "default": [], "fk_reverse": "Slice"},
-               {"name": "leaf_model_name", "default": "Site"})
+               {"name": "leaf_model_name", "default": "Site"},
+               {"name": "class_names", "default": "Site"})
 
     def __init__(self, **kwargs):
         return super(Site, self).__init__(self.FIELDS, **kwargs)
@@ -200,7 +205,8 @@
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "name", "default": ""},
                {"name": "slices_ids", "default": [], "fk_reverse": "Slice"},
-               {"name": "leaf_model_name", "default": "Service"})
+               {"name": "leaf_model_name", "default": "Service"},
+               {"name": "class_names", "default": "Service"})
 
     def __init__(self, **kwargs):
         return super(Service, self).__init__(self.FIELDS, **kwargs)
@@ -210,7 +216,8 @@
 class ServiceInstance(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "owher", "default": 0, "fk_model": "Service"},
-               {"name": "leaf_model_name", "default": "ServiceInstance"})
+               {"name": "leaf_model_name", "default": "ServiceInstance"},
+               {"name": "class_names", "default": "ServiceInstance"})
 
     def __init__(self, **kwargs):
         return super(ServiceInstance, self).__init__(self.FIELDS, **kwargs)
@@ -220,7 +227,8 @@
 class ONOSService(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "name", "default": ""},
-               {"name": "leaf_model_name", "default": "ONOSService"})
+               {"name": "leaf_model_name", "default": "ONOSService"},
+               {"name": "class_names", "default": "ONOSService,Service"})
 
     BASES = ["Service"]
 
@@ -235,7 +243,8 @@
                {"name": "owner_id", "default": 0, "fk_model": "Slice"},
                {"name": "template_id", "default": 0, "fk_model": "NetworkTemplate"},
                {"name": "controllernetworks_ids", "default": [], "fk_reverse": "ControllerNetwork"},
-               {"name": "leaf_model_name", "default": "Network"})
+               {"name": "leaf_model_name", "default": "Network"},
+               {"name": "class_names", "default": "Network"})
 
     def __init__(self, **kwargs):
         return super(Network, self).__init__(self.FIELDS, **kwargs)
@@ -246,7 +255,8 @@
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "name", "default": ""},
                {"name": "vtn_kind", "default": ""},
-               {"name": "leaf_model_name", "default": "NetworkTemplate"})
+               {"name": "leaf_model_name", "default": "NetworkTemplate"},
+               {"name": "class_names", "default": "NetworkTemplate"})
 
     def __init__(self, **kwargs):
         return super(NetworkTemplate, self).__init__(self.FIELDS, **kwargs)
@@ -257,7 +267,8 @@
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "network_id", "default": 0, "fk_model": "Network"},
                {"name": "controller_id", "default": 0, "fk_model": "Controller"},
-               {"name": "leaf_model_name", "default": "ControllerNetwork"})
+               {"name": "leaf_model_name", "default": "ControllerNetwork"},
+               {"name": "class_names", "default": "ControllerNetwork"})
 
     def __init__(self, **kwargs):
         return super(ControllerNetwork, self).__init__(self.FIELDS, **kwargs)
@@ -268,7 +279,8 @@
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "network_id", "default": 0, "fk_model": "Network"},
                {"name": "slice_id", "default": 0, "fk_model": "Slice"},
-               {"name": "leaf_model_name", "default": "ControllerNetwork"})
+               {"name": "leaf_model_name", "default": "NetworkSlice"},
+               {"name": "class_names", "default": "NetworkSlice"})
 
     def __init__(self, **kwargs):
         return super(NetworkSlice, self).__init__(self.FIELDS, **kwargs)
@@ -282,7 +294,8 @@
                {"name": "value", "default": ""},
                {"name": "content_type", "default": None},
                {"name": "object_id", "default": None},
-               {"name": "leaf_model_name", "default": "Tag"})
+               {"name": "leaf_model_name", "default": "Tag"},
+               {"name": "class_names", "default": "Tag"})
 
     def __init__(self, **kwargs):
         return super(Tag, self).__init__(self.FIELDS, **kwargs)
@@ -291,7 +304,8 @@
 
 class TestModel(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
-               {"name": "intfield", "default": 0} )
+               {"name": "intfield", "default": 0},
+               {"name": "class_names", "default": "TestModel"} )
 
     def __init__(self, **kwargs):
         return super(TestModel, self).__init__(self.FIELDS, **kwargs)
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index 7d6d263..40af7dd 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -644,9 +644,26 @@
     convenience_wrappers[class_name] = wrapper
 
 def make_ORMWrapper(wrapped_class, *args, **kwargs):
-    if wrapped_class.__class__.__name__ in convenience_wrappers:
+    cls = None
+
+    if (not cls) and wrapped_class.__class__.__name__ in convenience_wrappers:
         cls = convenience_wrappers[wrapped_class.__class__.__name__]
-    else:
+
+    if (not cls):
+        # Search the list of class names for this model to see if we have any applicable wrappers. The list is always
+        # sorted from most specific to least specific, so the first one we find will automatically be the most relevant
+        # one. If we don't find any, then default to ORMWrapper
+
+        # Note: Only works on objects that have been fetched from the server, not objects that are created on the
+        # client. This is because wrapped_class.class_names is filled in by the server.
+
+        # TODO(smbaker): Ought to be able to make this work with newly created objects after they are saved.
+
+        for name in wrapped_class.class_names.split(","):
+            if name in convenience_wrappers:
+                cls = convenience_wrappers[name]
+
+    if (not cls):
         cls = ORMWrapper
 
     return cls(wrapped_class, *args, **kwargs)
diff --git a/xos/xos_client/xosapi/test_wrapper.py b/xos/xos_client/xosapi/test_wrapper.py
index 6eef1f3..cd185a7 100644
--- a/xos/xos_client/xosapi/test_wrapper.py
+++ b/xos/xos_client/xosapi/test_wrapper.py
@@ -132,6 +132,22 @@
 
         self.assertEqual(service_one.get_service_instance_class().model_name, "ServiceInstance")
 
+    def test_wrapper_from__class__dot_name(self):
+        """ The Service model has a wrapper, so it should be returned when make_ORMWrapper looks for a wrapper based
+            on the class name.
+        """
+        orm = self.make_coreapi()
+        obj = orm.Service()
+        self.assertEqual(obj.__class__.__name__, "ORMWrapperService")
+
+    def test_wrapper_from_class_names(self):
+        """ ONOSService._wrapped_class.class_names is "ONOSService, Service" so we should fall back to getting the
+            Service wrapper.
+        """
+        orm = self.make_coreapi()
+        obj = orm.ONOSService()
+        self.assertEqual(obj.__class__.__name__, "ORMWrapperService")
+
 def main():
     global USE_FAKE_STUB
     global xos_grpc_client