CORD-1252 add leaf_model cast function to orm

Change-Id: I0c2de40f4799c9cc8711dd267b8a5915f76765c7
diff --git a/xos/xos_client/xosapi/fake_stub.py b/xos/xos_client/xosapi/fake_stub.py
index 36af58b..7e57f5e 100644
--- a/xos/xos_client/xosapi/fake_stub.py
+++ b/xos/xos_client/xosapi/fake_stub.py
@@ -24,6 +24,8 @@
 ContentTypeMap = {}
 
 class FakeObj(object):
+    BASES=[]
+
     def __init__(self, fields=[], **kwargs):
         super(FakeObj, self).__setattr__("is_set", {})
         super(FakeObj, self).__setattr__("fields", [])
@@ -114,10 +116,22 @@
 
         return fbn
 
+class User(FakeObj):
+    FIELDS = ( {"name": "id", "default": 0},
+               {"name": "email", "default": ""},
+               {"name": "site_id", "default": 0, "fk_model": "Site"}, )
+
+    def __init__(self, **kwargs):
+        return super(User, self).__init__(self.FIELDS, **kwargs)
+
+    DESCRIPTOR = FakeDescriptor("User")
+
 class Slice(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "name", "default": ""},
-               {"name": "site_id", "default": 0, "fk_model": "Site"} )
+               {"name": "site_id", "default": 0, "fk_model": "Site"},
+               {"name": "creator_id", "default": 0, "fk_model": "User"},
+               {"name": "leaf_model_name", "default": "Slice"} )
 
     def __init__(self, **kwargs):
         return super(Slice, self).__init__(self.FIELDS, **kwargs)
@@ -127,7 +141,8 @@
 class Site(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "name", "default": ""},
-               {"name": "slice_ids", "default": 0, "fk_reverse": "Slice"} )
+               {"name": "slice_ids", "default": 0, "fk_reverse": "Slice"},
+               {"name": "leaf_model_name", "default": "Site"})
 
     def __init__(self, **kwargs):
         return super(Site, self).__init__(self.FIELDS, **kwargs)
@@ -136,13 +151,26 @@
 
 class Service(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
-               {"name": "name", "default": ""}, )
+               {"name": "name", "default": ""},
+               {"name": "leaf_model_name", "default": "Service"})
 
     def __init__(self, **kwargs):
         return super(Service, self).__init__(self.FIELDS, **kwargs)
 
     DESCRIPTOR = FakeDescriptor("Service")
 
+class ONOSService(FakeObj):
+    FIELDS = ( {"name": "id", "default": 0},
+               {"name": "name", "default": ""},
+               {"name": "leaf_model_name", "default": "ONOSService"})
+
+    BASES = ["Service"]
+
+    def __init__(self, **kwargs):
+        return super(ONOSService, self).__init__(self.FIELDS, **kwargs)
+
+    DESCRIPTOR = FakeDescriptor("ONOSService")
+
 class Tag(FakeObj):
     FIELDS = ( {"name": "id", "default": 0},
                {"name": "service_id", "default": None},
@@ -150,7 +178,8 @@
                {"name": "value", "default": ""},
                {"name": "content_type", "default": None},
                {"name": "object_id", "default": None},
-               {"name": "slice_ids", "default": 0, "fk_reverse": "Slice"} )
+               {"name": "slice_ids", "default": 0, "fk_reverse": "Slice"},
+               {"name": "leaf_model_name", "default": "Tag"})
 
     def __init__(self, **kwargs):
         return super(Tag, self).__init__(self.FIELDS, **kwargs)
@@ -168,7 +197,7 @@
     def __init__(self):
         self.id_counter = 1
         self.objs = {}
-        for name in ["Slice", "Site", "Tag", "Service"]:
+        for name in ["Slice", "Site", "Tag", "Service", "ONOSService", "User"]:
             setattr(self, "Get%s" % name, functools.partial(self.get, name))
             setattr(self, "List%s" % name, functools.partial(self.list, name))
             setattr(self, "Create%s" % name, functools.partial(self.create, name))
@@ -196,6 +225,13 @@
         self.id_counter = self.id_counter + 1
         k = self.make_key(classname, FakeObj(id=obj.id))
         self.objs[k] = obj
+
+        for base_classname in obj.BASES:
+            base_class = globals()[base_classname]
+            base_obj = base_class(id=obj.id, leaf_model_name = classname)
+            k = self.make_key(base_classname, base_obj)
+            self.objs[k] = base_obj
+
         return obj
 
     def update(self, classname, obj, metadata=None):
@@ -211,7 +247,7 @@
 class FakeSymDb(object):
     def __init__(self):
         self._classes = {}
-        for name in ["Slice", "Site", "ID", "Tag", "Service"]:
+        for name in ["Slice", "Site", "ID", "Tag", "Service", "ONOSService", "User"]:
             self._classes["xos.%s" % name] = globals()[name]
 
 
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index 3bd76ea..202ecd8 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -247,6 +247,15 @@
         return d
 
     @property
+    def leaf_model(self):
+        # Easy case - this model is already the leaf
+        if self.leaf_model_name == self._wrapped_class.__class__.__name__:
+            return self
+
+        # This model is not the leaf, so use the stub to fetch the leaf model
+        return getattr(self.stub, self.leaf_model_name).objects.get(id=self.id)
+
+    @property
     def model_name(self):
         return self._wrapped_class.__class__.__name__
 
diff --git a/xos/xos_client/xosapi/orm_test.py b/xos/xos_client/xosapi/orm_test.py
index 23cba6d..61d37b9 100644
--- a/xos/xos_client/xosapi/orm_test.py
+++ b/xos/xos_client/xosapi/orm_test.py
@@ -15,7 +15,9 @@
 
 
 import exceptions
+import random
 import shutil
+import string
 import sys
 import unittest
 
@@ -25,6 +27,7 @@
 if "-R" in sys.argv:
     USE_FAKE_STUB = False
     sys.argv.remove("-R")
+    # Note: will leave lots of litter (users, sites, etc) behind in the database
 else:
     USE_FAKE_STUB = True
 
@@ -124,7 +127,10 @@
         site = orm.Site(name="mysite")
         site.save()
         self.assertTrue(site.id > 0)
-        slice = orm.Slice(name="mysite_foo", site_id = site.id)
+        user = orm.User(email="fake_" + ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)), site_id=site.id)
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = orm.Slice(name="mysite_foo", site_id = site.id, creator_id = user.id)
         slice.save()
         self.assertTrue(slice.id > 0)
         self.assertNotEqual(slice.site, None)
@@ -135,7 +141,10 @@
         site = orm.Site(name="mysite")
         site.save()
         self.assertTrue(site.id > 0)
-        slice = orm.Slice(name="mysite_foo", site = site)
+        user = orm.User(email="fake_" + ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)), site_id=site.id)
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = orm.Slice(name="mysite_foo", site = site, creator_id=user.id)
         slice.save()
         slice.invalidate_cache()
         self.assertTrue(slice.id > 0)
@@ -147,7 +156,10 @@
         site = orm.Site(name="mysite")
         site.save()
         self.assertTrue(site.id > 0)
-        slice = orm.Slice(name="mysite_foo", site = site, service=None)
+        user = orm.User(email="fake_" + ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)), site_id=site.id)
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = orm.Slice(name="mysite_foo", site = site, service=None, creator_id=user.id)
         slice.save()
         slice.invalidate_cache()
         self.assertTrue(slice.id > 0)
@@ -158,11 +170,14 @@
         site = orm.Site(name="mysite")
         site.save()
         self.assertTrue(site.id > 0)
+        user = orm.User(email="fake_" + ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)), site_id=site.id)
+        user.save()
+        self.assertTrue(user.id > 0)
         service = orm.Service(name="myservice")
         service.save()
         self.assertTrue(service.id > 0)
         # start out slice.service is non-None
-        slice = orm.Slice(name="mysite_foo", site = site, service=service)
+        slice = orm.Slice(name="mysite_foo", site = site, service=service, creator_id=user.id)
         slice.save()
         slice.invalidate_cache()
         self.assertTrue(slice.id > 0)
@@ -205,6 +220,31 @@
         self.assertNotEqual(tag.content_object, None)
         self.assertEqual(tag.content_object.id, site.id)
 
+    def test_leaf_model_trivial(self):
+        orm = self.make_coreapi()
+        service = orm.Service(name="myservice")
+        service.save()
+        self.assertEqual(service.leaf_model_name, "Service")
+
+    def test_leaf_model_descendant(self):
+        orm = self.make_coreapi()
+        onos_service = orm.ONOSService(name="myservice")
+        onos_service.save()
+        self.assertEqual(onos_service.model_name, "ONOSService")
+        self.assertEqual(onos_service.leaf_model_name, "ONOSService")
+
+        service = orm.Service.objects.get(id=onos_service.id)
+        self.assertEqual(service.id, onos_service.id)
+        self.assertEqual(service.model_name, "Service")
+        self.assertEqual(service.leaf_model_name, "ONOSService")
+
+        onos_service_cast = service.leaf_model
+        self.assertEqual(onos_service_cast.model_name, "ONOSService")
+        self.assertEqual(onos_service_cast.leaf_model_name, "ONOSService")
+        self.assertEqual(onos_service_cast.id, onos_service.id)
+
+
+
 if USE_FAKE_STUB:
     sys.path.append("..")