models for VBNG; cache linked models; set caller
diff --git a/xos/cord/models.py b/xos/cord/models.py
index 594eb31..2ae2b72 100644
--- a/xos/cord/models.py
+++ b/xos/cord/models.py
@@ -6,6 +6,7 @@
 from django.forms.models import model_to_dict
 from django.db.models import Q
 from operator import itemgetter, attrgetter, methodcaller
+import traceback
 
 """
 import os
@@ -30,6 +31,10 @@
 for v in VCPETenant.objects.all():
     v.caller = User.objects.all()[0]
     v.delete()
+
+for v in VOLTTenant.objects.all():
+    v.caller = User.objects.all()[0]
+    v.delete()
 """
 
 class ConfigurationError(Exception):
@@ -55,20 +60,26 @@
 
     @property
     def vcpe(self):
+        if getattr(self, "cached_vcpe", None):
+            return self.cached_vcpe
         vcpe_id=self.get_attribute("vcpe_id")
         if not vcpe_id:
             return None
         vcpes=VCPETenant.objects.filter(id=vcpe_id)
         if not vcpes:
             return None
-        return vcpes[0]
+        vcpe=vcpes[0]
+        vcpe.caller = getattr(self, "caller", None)
+        self.cached_vcpe = vcpe
+        return vcpe
 
     @vcpe.setter
     def vcpe(self, value):
         if value:
-            self.set_attribute("vcpe_id", value.id)
-        else:
-            self.set_attribute("vcpe_id", None)
+            value = value.id
+        if (value != self.get_attribute("vcpe_id", None)):
+            self.cached_vcpe=None
+        self.set_attribute("vcpe_id", value)
 
     def manage_vcpe(self):
         # Each VOLT object owns exactly one VCPE object
@@ -88,7 +99,7 @@
 
             try:
                 self.vcpe = vcpe
-                self.save()
+                super(VOLTTenant, self).save()
             except:
                 vcpe.delete()
                 raise
@@ -133,6 +144,11 @@
                           "cdn_enable": False,
                           "sliver_id": None}
 
+    def __init__(self, *args, **kwargs):
+        super(VCPETenant, self).__init__(*args, **kwargs)
+        self.cached_vbng=None
+        self.cached_sliver=None
+
     @property
     def image(self):
         # TODO: logic to pick an image based on the feature set
@@ -141,20 +157,49 @@
 
     @property
     def sliver(self):
+        if getattr(self, "cached_sliver", None):
+            return self.cached_sliver
         sliver_id=self.get_attribute("sliver_id")
         if not sliver_id:
             return None
         slivers=Sliver.objects.filter(id=sliver_id)
         if not slivers:
             return None
-        return slivers[0]
+        sliver=slivers[0]
+        sliver.caller = getattr(self, "caller", None)
+        self.cached_sliver = sliver
+        return sliver
 
     @sliver.setter
     def sliver(self, value):
         if value:
-            self.set_attribute("sliver_id", value.id)
-        else:
-            self.set_attribute("sliver_id", None)
+            value = value.id
+        if (value != self.get_attribute("sliver_id", None)):
+            self.cached_sliver=None
+        self.set_attribute("sliver_id", value)
+
+    @property
+    def vbng(self):
+        if getattr(self, "cached_vbng", None):
+            return self.cached_vbng
+        vbng_id=self.get_attribute("vbng_id")
+        if not vbng_id:
+            return None
+        vbngs=VBNGTenant.objects.filter(id=vbng_id)
+        if not vbngs:
+            return None
+        vbng=vbngs[0]
+        vbng.caller = getattr(self, "caller", None)
+        self.cached_vbng = vbng
+        return vbng
+
+    @vbng.setter
+    def vbng(self, value):
+        if value:
+            value = value.id
+        if (value != self.get_attribute("vbng_id", None)):
+            self.cached_vbng=None
+        self.set_attribute("vbng_id", value)
 
     @property
     def firewall_enable(self):
@@ -226,7 +271,7 @@
 
             try:
                 self.sliver = sliver
-                self.save()
+                super(VCPETenant, self).save()
             except:
                 sliver.delete()
                 raise
@@ -236,13 +281,70 @@
             self.sliver.delete()
             self.sliver = None
 
+    def manage_vbng(self):
+        # Each vCPE object owns exactly one vBNG object
+
+        if self.deleted:
+            return
+
+        if self.vbng is None:
+            vbngServices = VBNGService.get_service_objects().all()
+            if not vbngServices:
+                raise ConfigurationError("No VBNG Services available")
+
+            vbng = VBNGTenant(provider_service = vbngServices[0],
+                              subscriber_tenant = self)
+            vbng.caller = self.caller
+            vbng.save()
+
+            try:
+                self.vbng = vbng
+                super(VCPETenant, self).save()
+            except:
+                vbng.delete()
+                raise
+
+    def cleanup_vbng(self):
+        if self.vbng:
+            self.vbng.delete()
+            self.vbng = None
+
     def save(self, *args, **kwargs):
         if not getattr(self, "caller", None):
             raise TypeError("VCPETenant's self.caller was not set")
         super(VCPETenant, self).save(*args, **kwargs)
         self.manage_sliver()
+        self.manage_vbng()
 
     def delete(self, *args, **kwargs):
+        self.cleanup_vbng()
         self.cleanup_sliver()
         super(VCPETenant, self).delete(*args, **kwargs)
 
+#----------------------------------------------------------------------------
+# vBNG
+#----------------------------------------------------------------------------
+
+class VBNGService(Service):
+    KIND = "vBNG"
+
+    class Meta:
+        app_label = "cord"
+        verbose_name = "vBNG Service"
+        proxy = True
+
+class VBNGTenant(Tenant):
+    class Meta:
+        proxy = True
+
+    KIND = "vBNG"
+
+    default_attributes = {"routeable_subnet": ""}
+
+    @property
+    def routeable_subnet(self):
+        return self.get_attribute("routeable_subnet", self.default_attributes["routeable_subnet"])
+
+    @routeable_subnet.setter
+    def routeable_subnet(self, value):
+        self.set_attribute("routeable_subnet", value)