- Explicit annotations of models with dependency information
- New tool (mldeps) for using this explicitly annotaed dependency
information
Change-Id: I75de06b65f9656ca17956885bc99df61f4106d3c
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index d889a26..3630d9d 100644
--- a/xos/core/models/__init__.py
+++ b/xos/core/models/__init__.py
@@ -1,4 +1,4 @@
-from .plcorebase import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,PlModelMixIn
+from .plcorebase import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,PlModelMixIn,ModelLink
from .project import Project
from .singletonmodel import SingletonModel
from .xosmodel import XOS, XOSVolume
diff --git a/xos/core/models/billing.py b/xos/core/models/billing.py
index 48c8a38..083149a 100644
--- a/xos/core/models/billing.py
+++ b/xos/core/models/billing.py
@@ -2,7 +2,7 @@
import os
import socket
from django.db import models
-from core.models import PlCoreBase, Site, Slice, Instance, Deployment
+from core.models import PlCoreBase, Site, Slice, Instance, Deployment, ModelLink
from core.models.plcorebase import StrippedCharField
from django.contrib.contenttypes.models import ContentType
from django.db.models import Sum
@@ -11,6 +11,8 @@
class Account(PlCoreBase):
site = models.ForeignKey(Site, related_name="accounts", help_text="Site for this account")
+ xos_links = [ModelLink(Site,via='site')]
+
@property
def total_invoices(self):
# Since the amount of an invoice is the sum of it's charges, we can
@@ -38,6 +40,8 @@
date = models.DateTimeField()
account = models.ForeignKey(Account, related_name="invoices")
+ xos_links = [ModelLink(Account,via='account')]
+
@property
def amount(self):
return str(self.charges.all().aggregate(Sum('amount'))["amount__sum"])
@@ -54,6 +58,8 @@
amount = models.FloatField(default=0.0)
date = models.DateTimeField(default=timezone.now)
+ xos_links = [ModelLink(Account, via='account')]
+
def __unicode__(self): return u'%s-%0.2f-%s' % (self.account.site.name, self.amount, str(self.date))
class Charge(PlCoreBase):
@@ -69,6 +75,7 @@
amount = models.FloatField(default=0.0)
coreHours = models.FloatField(default=0.0)
invoice = models.ForeignKey(Invoice, blank=True, null=True, related_name="charges")
+ xos_links = [ModelLink(Account,via='account'),ModelLink(Slice,via='slice'),ModelLink(Invoice,via='invoice')]
def __unicode__(self): return u'%s-%0.2f-%s' % (self.account.site.name, self.amount, str(self.date))
diff --git a/xos/core/models/controlleruser.py b/xos/core/models/controlleruser.py
index 1031e12..0e05cfa 100644
--- a/xos/core/models/controlleruser.py
+++ b/xos/core/models/controlleruser.py
@@ -3,7 +3,9 @@
from collections import defaultdict
from django.db import models
from django.db.models import F, Q
-from core.models import PlCoreBase,User,Controller
+from core.models import PlCoreBase,User,Controller,ModelLink
+from core.models.site import SitePrivilege
+from core.models.slice import SlicePrivilege
from core.models.plcorebase import StrippedCharField
from core.models import Controller,ControllerLinkManager,ControllerLinkDeletionManager
@@ -15,6 +17,8 @@
controller = models.ForeignKey(Controller,related_name='controllersusers')
kuser_id = StrippedCharField(null=True, blank=True, max_length=200, help_text="Keystone user id")
+ xos_links = [ModelLink(Controller,via='controller'), ModelLink(User,via='user')]
+
class Meta:
unique_together = ('user', 'controller')
@@ -41,6 +45,7 @@
controller = models.ForeignKey('Controller', related_name='controllersiteprivileges')
site_privilege = models.ForeignKey('SitePrivilege', related_name='controllersiteprivileges')
role_id = StrippedCharField(null=True, blank=True, max_length=200, db_index=True, help_text="Keystone id")
+ xos_links = [ModelLink(Controller,via='controller'), ModelLink(SitePrivilege,via='site_privilege')]
class Meta:
unique_together = ('controller', 'site_privilege', 'role_id')
@@ -75,6 +80,7 @@
controller = models.ForeignKey('Controller', related_name='controllersliceprivileges')
slice_privilege = models.ForeignKey('SlicePrivilege', related_name='controllersliceprivileges')
role_id = StrippedCharField(null=True, blank=True, max_length=200, db_index=True, help_text="Keystone id")
+ xos_links = [ModelLink(Controller,via='controller'), ModelLink(SlicePrivilege,via='slice_privilege')]
class Meta:
diff --git a/xos/core/models/credential.py b/xos/core/models/credential.py
index ba58360..166b546 100644
--- a/xos/core/models/credential.py
+++ b/xos/core/models/credential.py
@@ -1,6 +1,6 @@
import os
from django.db import models
-from core.models import PlCoreBase
+from core.models import PlCoreBase,ModelLink
from core.models import User,Site,Slice,Controller
from core.models.plcorebase import StrippedCharField
from encrypted_fields import EncryptedCharField
@@ -13,6 +13,8 @@
key_id = StrippedCharField(help_text="The backend id of this credential", max_length=1024)
enc_value = EncryptedCharField(help_text="The key value of this credential", max_length=1024)
+ xos_links = [ModelLink(User,via='user')]
+
def __unicode__(self):
return self.name
@@ -24,6 +26,7 @@
key_id = StrippedCharField(help_text="The backend id of this credential", max_length=1024)
enc_value = EncryptedCharField(help_text="The key value of this credential", max_length=1024)
+ xos_links = [ModelLink(Site,via='site')]
def __unicode__(self):
return self.name
@@ -36,6 +39,9 @@
enc_value = EncryptedCharField(help_text="The key value of this credential", max_length=1024)
+ xos_links = [ModelLink(Slice,via='slice')]
+
+
def __unicode__(self):
return self.name
@@ -48,6 +54,9 @@
key_id = models.CharField(help_text="The backend id of this credential", max_length=1024)
enc_value = EncryptedCharField(help_text="The key value of this credential", max_length=1024)
+
+ xos_links = [ModelLink(Controller,via='controller')]
+
def __unicode__(self):
return self.name
diff --git a/xos/core/models/dashboard.py b/xos/core/models/dashboard.py
index 9b80ba8..52dc306 100644
--- a/xos/core/models/dashboard.py
+++ b/xos/core/models/dashboard.py
@@ -1,7 +1,7 @@
import os
from django.db import models
from core.models import PlCoreBase, Controller, Deployment
-from core.models.plcorebase import StrippedCharField
+from core.models.plcorebase import StrippedCharField, ModelLink
from core.models.site import ControllerLinkManager, ControllerLinkDeletionManager
class DashboardView(PlCoreBase):
@@ -13,6 +13,7 @@
icon_active = models.CharField(max_length=200, default="default-icon-active.png", help_text="Icon for active Dashboard")
deployments = models.ManyToManyField(Deployment, blank=True, null=True, related_name="dashboardviews", help_text="Deployments that should be included in this view")
+
def __unicode__(self): return u'%s' % (self.name)
class ControllerDashboardView(PlCoreBase):
@@ -22,6 +23,7 @@
dashboardView = models.ForeignKey(DashboardView, related_name='controllerdashboardviews')
enabled = models.BooleanField(default=True)
url = StrippedCharField(max_length=1024, help_text="URL of Dashboard")
+ xos_links=[ModelLink(Controller,via='controller'),ModelLink(DashboardView,via='dashboardview')]
diff --git a/xos/core/models/image.py b/xos/core/models/image.py
index 69d0f03..8f48458 100644
--- a/xos/core/models/image.py
+++ b/xos/core/models/image.py
@@ -1,7 +1,7 @@
import os
from django.db import models
from core.models import PlCoreBase
-from core.models.plcorebase import StrippedCharField
+from core.models.plcorebase import StrippedCharField,ModelLink
from core.models import Deployment, DeploymentPrivilege, Controller,ControllerLinkManager,ControllerLinkDeletionManager
# Create your models here.
@@ -23,6 +23,7 @@
class ImageDeployments(PlCoreBase):
image = models.ForeignKey(Image,related_name='imagedeployments')
deployment = models.ForeignKey(Deployment,related_name='imagedeployments')
+ xos_links = [ModelLink(Image,'image'),ModelLink(Deployment,'deployment')]
class Meta:
unique_together = ('image', 'deployment')
@@ -38,6 +39,7 @@
image = models.ForeignKey(Image,related_name='controllerimages')
controller = models.ForeignKey(Controller,related_name='controllerimages')
glance_image_id = StrippedCharField(null=True, blank=True, max_length=200, help_text="Glance image id")
+ xos_links = [ModelLink(Image,'image'),ModelLink(Controller,'controller')]
class Meta:
unique_together = ('image', 'controller')
diff --git a/xos/core/models/instance.py b/xos/core/models/instance.py
index 6e73af8..b8a9f11 100644
--- a/xos/core/models/instance.py
+++ b/xos/core/models/instance.py
@@ -2,7 +2,7 @@
from django.db import models
from django.db.models import Q
from django.core import exceptions
-from core.models import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager
+from core.models import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,ModelLink
from core.models.plcorebase import StrippedCharField
from core.models import Image
from core.models import Slice, SlicePrivilege
@@ -101,6 +101,8 @@
volumes = models.TextField(null=True, blank=True, help_text="Comma-separated list of directories to expose to parent context")
parent = models.ForeignKey("Instance", null=True, blank=True, help_text="Parent Instance for containers nested inside of VMs")
+ xos_links = [ModelLink(dest=Slice,via='slice'),ModelLink(dest=Image,via='image')]
+
def get_controller (self):
return self.node.site_deployment.controller
diff --git a/xos/core/models/network.py b/xos/core/models/network.py
index d53017f..a2b61c2 100644
--- a/xos/core/models/network.py
+++ b/xos/core/models/network.py
@@ -2,7 +2,7 @@
import socket
import sys
from django.db import models, transaction
-from core.models import PlCoreBase, Site, Slice, Instance, Controller, Service
+from core.models import PlCoreBase, Site, Slice, Instance, Controller, Service, ModelLink
from core.models import ControllerLinkManager,ControllerLinkDeletionManager
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
@@ -214,6 +214,7 @@
subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Neutron subnet id") # feedback state
gateway = models.CharField(max_length=32, blank=True, null=True) # feedback state
segmentation_id = models.CharField(max_length=32, blank=True, null=True) # feedback state
+ xos_links = [ModelLink(Controller,via='controller'),ModelLink(Network,via='network')]
class Meta:
unique_together = ('network', 'controller')
@@ -244,6 +245,8 @@
network = models.ForeignKey(Network,related_name='networkslices')
slice = models.ForeignKey(Slice,related_name='networkslices')
+ xos_links = [ModelLink(Network,via='network'),ModelLink(Slice,via='slice')]
+
class Meta:
unique_together = ('network', 'slice')
@@ -281,6 +284,8 @@
mac = models.CharField(null=True, blank=True, max_length=256, help_text="MAC address associated with this port")
xos_created = models.BooleanField(default=False) # True if XOS created this port in Neutron, False if port created by Neutron and observed by XOS
+ xos_links = [ModelLink(Network,via='network'), ModelLink('Instance',via='instance')]
+
class Meta:
unique_together = ('network', 'instance')
diff --git a/xos/core/models/node.py b/xos/core/models/node.py
index cefb48d..5cfc9b8 100644
--- a/xos/core/models/node.py
+++ b/xos/core/models/node.py
@@ -1,6 +1,6 @@
import os
from django.db import models
-from core.models import PlCoreBase
+from core.models import PlCoreBase,ModelLink
from core.models.plcorebase import StrippedCharField
from core.models.site import Site, SiteDeployment, SitePrivilege
from core.models import Tag
@@ -13,6 +13,8 @@
site_deployment = models.ForeignKey(SiteDeployment, related_name='nodes')
site = models.ForeignKey(Site, null=True, blank=True, related_name='nodes')
tags = GenericRelation(Tag)
+ xos_links = [ModelLink(Site,'site')]
+
def __unicode__(self): return u'%s' % (self.name)
diff --git a/xos/core/models/plcorebase.py b/xos/core/models/plcorebase.py
index e2f4b48..891356d 100644
--- a/xos/core/models/plcorebase.py
+++ b/xos/core/models/plcorebase.py
@@ -312,3 +312,10 @@
d = {}
return d
+
+class ModelLink:
+ def __init__(self,dest,via,into=None):
+ self.dest=dest
+ self.via=via
+ self.into=into
+
diff --git a/xos/core/models/reservation.py b/xos/core/models/reservation.py
index ecf207c..6b6e7a1 100644
--- a/xos/core/models/reservation.py
+++ b/xos/core/models/reservation.py
@@ -1,7 +1,7 @@
import os
import datetime
from django.db import models
-from core.models import PlCoreBase
+from core.models import PlCoreBase, ModelLink
from core.models import Instance
from core.models import Slice
from core.models import ServiceResource
@@ -13,6 +13,8 @@
slice = models.ForeignKey(Slice, related_name="reservations")
duration = models.IntegerField(default=1)
+ xos_links = [ModelLink(Slice,via='slice')]
+
def __unicode__(self): return u'%s to %s' % (self.startTime, self.endTime)
@property
@@ -36,6 +38,8 @@
resource = models.ForeignKey(ServiceResource, related_name="reservedresources")
quantity = models.IntegerField(default=1)
reservationSet = models.ForeignKey(Reservation, related_name="reservedresources")
+
+ xos_links = [ModelLink(Instance,'instance'),ModelLink(ServiceResource,'resource')]
class Meta(PlCoreBase.Meta):
verbose_name_plural = "Reserved Resources"
diff --git a/xos/core/models/site.py b/xos/core/models/site.py
index e0d2fc5..069fec9 100644
--- a/xos/core/models/site.py
+++ b/xos/core/models/site.py
@@ -4,7 +4,7 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import PermissionDenied
# from geoposition.fields import GeopositionField
-from core.models import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager
+from core.models import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,ModelLink
from core.models import Tag
from core.models.plcorebase import StrippedCharField
from core.acl import AccessControlList
@@ -112,6 +112,7 @@
deployments = models.ManyToManyField('Deployment', through='SiteDeployment', blank=True, help_text="Select which sites are allowed to host nodes in this deployment", related_name='sites')
tags = GenericRelation(Tag)
+
def __unicode__(self): return u'%s' % (self.name)
def can_update(self, user):
@@ -130,6 +131,8 @@
site = models.ForeignKey('Site', related_name='siteprivileges')
role = models.ForeignKey('SiteRole',related_name='siteprivileges')
+ xos_links = [ModelLink(Site,'site'),ModelLink('User','user'),ModelLink('Role','role')]
+
def __unicode__(self): return u'%s %s %s' % (self.site, self.user, self.role)
def save(self, *args, **kwds):
@@ -224,6 +227,9 @@
user = models.ForeignKey('User', related_name='deploymentprivileges')
deployment = models.ForeignKey('Deployment', related_name='deploymentprivileges')
role = models.ForeignKey('DeploymentRole',related_name='deploymentprivileges')
+
+ xos_links = [ModelLink(Deployment,'deployment'),ModelLink('User','user'),ModelLink('Role','role')]
+
class Meta:
unique_together = ('user', 'deployment', 'role')
@@ -268,6 +274,8 @@
rabbit_password = StrippedCharField(max_length=200, null=True, blank=True, help_text="Password of rabbitmq server at this controller")
deployment = models.ForeignKey(Deployment,related_name='controllerdeployments')
+ xos_links = [ModelLink(Deployment, via='deployment')]
+
def __init__(self, *args, **kwargs):
super(Controller, self).__init__(*args, **kwargs)
self.no_sync=True
@@ -300,6 +308,8 @@
controller = models.ForeignKey(Controller, null=True, blank=True, related_name='sitedeployments')
availability_zone = StrippedCharField(max_length=200, null=True, blank=True, help_text="OpenStack availability zone")
+ xos_links = [ModelLink(Site,'site'),ModelLink(Deployment,'deployment'),ModelLink(Controller,'controller')]
+
class Meta:
unique_together = ('site', 'deployment', 'controller')
@@ -311,6 +321,8 @@
controller = models.ForeignKey(Controller, null=True, blank=True, related_name='controllersite')
tenant_id = StrippedCharField(null=True, blank=True, max_length=200, db_index=True, help_text="Keystone tenant id")
+ xos_links = [ModelLink(Controller,via='controller'),ModelLink(Site,via='site')]
+
def delete(self, *args, **kwds):
super(ControllerSite, self).delete(*args, **kwds)
diff --git a/xos/core/models/slice.py b/xos/core/models/slice.py
index 33e5521..183241f 100644
--- a/xos/core/models/slice.py
+++ b/xos/core/models/slice.py
@@ -1,6 +1,6 @@
import os
from django.db import models
-from core.models import PlCoreBase
+from core.models import PlCoreBase,ModelLink
from core.models import Site
from core.models.site import SitePrivilege
from core.models import User
@@ -45,6 +45,7 @@
mount_data_sets = StrippedCharField(default="GenBank",null=True, blank=True, max_length=256)
default_isolation = models.CharField(null=False, blank=False, max_length=30, choices=ISOLATION_CHOICES, default="vm")
+ xos_links = [ModelLink(Site,'site'),ModelLink(User,'user')]
def __unicode__(self): return u'%s' % (self.name)
@@ -136,6 +137,8 @@
slice = models.ForeignKey('Slice', related_name='sliceprivileges')
role = models.ForeignKey('SliceRole',related_name='sliceprivileges')
+ xos_links=[ModelLink(User,via='user'),ModelLink(Slice,via='slice'),ModelLink(Role,via='role')]
+
class Meta:
unique_together = ('user', 'slice', 'role')
@@ -176,6 +179,8 @@
controller = models.ForeignKey(Controller, related_name='controllerslices')
slice = models.ForeignKey(Slice, related_name='controllerslices')
tenant_id = StrippedCharField(null=True, blank=True, max_length=200, help_text="Keystone tenant id")
+
+ xos_links = [ModelLink(Controller,via='controller'),ModelLink(Slice,via='slice')]
class Meta:
unique_together = ('controller', 'slice')
diff --git a/xos/core/models/slicetag.py b/xos/core/models/slicetag.py
index a335ce0..7c0b57a 100644
--- a/xos/core/models/slicetag.py
+++ b/xos/core/models/slicetag.py
@@ -1,6 +1,6 @@
import os
from django.db import models
-from core.models import PlCoreBase
+from core.models import PlCoreBase,ModelLink
from core.models import Slice
from core.models.plcorebase import StrippedCharField
@@ -11,6 +11,8 @@
name = StrippedCharField(help_text="The name of this tag", max_length=30, choices=NAME_CHOICES)
value = StrippedCharField(help_text="The value of this tag", max_length=1024)
+ xos_links = [ModelLink(Slice,via='slice')]
+
def can_update(self, user):
return user.can_update_slice(self.slice)
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index aa00d44..a081afd 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -6,7 +6,7 @@
from operator import attrgetter, itemgetter
from core.middleware import get_request
-from core.models import DashboardView, PlCoreBase, PlModelMixIn, Site
+from core.models import DashboardView, PlCoreBase, PlModelMixIn, Site, ModelLink
from core.models.plcorebase import StrippedCharField
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.core.exceptions import PermissionDenied
@@ -147,6 +147,8 @@
no_sync = models.BooleanField(default=False) # prevent object sync
no_policy = models.BooleanField(default=False) # prevent model_policy run
+ #xos_links = [ModelLink(Site,via='site')]
+
timezone = TimeZoneField()
dashboards = models.ManyToManyField(
@@ -537,3 +539,5 @@
dashboardView = models.ForeignKey(
DashboardView, related_name='userdashboardviews')
order = models.IntegerField(default=0)
+
+ xos_links = [ModelLink(User, via='user'), ModelLink(DashboardView,via='userdashboardviews')]
diff --git a/xos/tools/mldeps b/xos/tools/mldeps
new file mode 100755
index 0000000..c4dc68a
--- /dev/null
+++ b/xos/tools/mldeps
@@ -0,0 +1,132 @@
+#!/usr/bin/python
+
+import os
+import pdb
+import sys
+import json
+
+sys.path.append('.')
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
+
+from django.db.models.fields.related import ForeignKey
+
+# try to make sure we're running from the right place
+if (not os.path.exists("core")):
+ if (os.path.exists("../core")):
+ os.chdir("..")
+ else:
+ print >> sys.stderr, "Are you sure you're running dmdot from the root of an XOS installation"
+ sys.exit(-1)
+
+from core.models import ModelLink
+
+# defaults
+apps = ["core"]#, "services.hpc", "services.requestrouter", "services.onos"]
+output = "-json"
+
+# syntax: mldeps [-json | -dot] [app_name]
+
+# poor man's argument parser
+for arg in sys.argv[1:]:
+ if arg.startswith("-"):
+ output = arg
+ else:
+ apps+= [arg]
+
+model_classes = []
+class_names = []
+lower_class_names = {}
+synonyms = {
+ 'user':'creator'
+}
+
+for app in apps:
+ app = app + ".models"
+ #models_module = imp.load_source(app, ".")
+ models_module = __import__(app)
+ for part in app.split(".")[1:]:
+ if hasattr(models_module, "PlCoreBase"):
+ break
+ models_module = getattr(models_module,part)
+
+ PlCoreBase = getattr(models_module,"PlCoreBase")
+
+ for classname in dir(models_module):
+ c = getattr(models_module, classname, None)
+ if type(c)==type(PlCoreBase):
+ model_classes.append(c)
+ class_names.append(c.__name__)
+ lower_class_names[c.__name__.lower()] = c
+ try:
+ synonym = synonyms[c.__name__.lower()]
+ lower_class_names[synonym] = c
+ except:
+ pass
+
+# django doesn't use the correct case in field.name.title() for objects that
+# have CamelCased class names. So, compare everything in lower case.
+
+if (output=='-dot'):
+ print "digraph plstack {";
+ for c in model_classes:
+ fields = c._meta.fields
+
+ for f in fields:
+ if type(f)==ForeignKey and f.name.lower().split('_') in lower_class_names:
+ linked_class = lower_class_names[f.name.lower()]
+ if ('backref' in f.name):
+ print '\t"%s"->"%s";'%(linked_class.__name__,c.__name__)
+ else:
+ print '\t"%s"->"%s";'%(c.__name__,linked_class.__name__)
+ print "}\n";
+elif (output=='-json'):
+ d = {}
+ tl = {}
+ for c in model_classes:
+ fields = c._meta.fields
+ try:
+ exp_links = c.xos_links
+ except AttributeError:
+ exp_links = []
+ for f in fields:
+ field_type = f.name.lower().split('_')[0]
+ if type(f)==ForeignKey and field_type in lower_class_names:
+ linked_class = lower_class_names[field_type]
+
+
+ if ('backref' in f.name.lower()):
+ a = linked_class.__name__
+ b = c.__name__
+ else:
+ b = linked_class.__name__
+ a = c.__name__
+
+ try:
+
+ if (b not in d[a]):
+ d[a].append(b)
+ except KeyError:
+ d[c.__name__]=[linked_class.__name__]
+
+ for l in exp_links:
+ linked_class = l.dest
+ if (type(linked_class)==str):
+ linked_class = lower_class_names[linked_class.lower()]
+ via = l.via
+ if ('backref' in via):
+ a = linked_class.__name__
+ b = c.__name__
+ else:
+ b = linked_class.__name__
+ a = c.__name__
+
+ try:
+ if (b not in tl[a]):
+ tl[a].append(b)
+ except KeyError:
+ tl[c.__name__]=[linked_class.__name__]
+
+
+ #d['ControllerNetwork'].append('SliceDeployments')
+ print json.dumps(tl,indent=4)