CORD-1382: Support operations on field sets and generate unique_together
Change-Id: I5d7e67492734b9de116ca954ef815ac03ba2108f
diff --git a/xos/core/models/attic/controllerimages_model.py b/xos/core/models/attic/controllerimages_model.py
deleted file mode 100644
index f97e0c7..0000000
--- a/xos/core/models/attic/controllerimages_model.py
+++ /dev/null
@@ -1,2 +0,0 @@
-class Meta:
- unique_together = ('image', 'controller')
diff --git a/xos/core/models/attic/controllernetwork_model.py b/xos/core/models/attic/controllernetwork_model.py
index dfd7e05..43bec3d 100644
--- a/xos/core/models/attic/controllernetwork_model.py
+++ b/xos/core/models/attic/controllernetwork_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('network', 'controller')
-
def tologdict(self):
d=super(ControllerNetwork,self).tologdict()
try:
diff --git a/xos/core/models/attic/controllersite_model.py b/xos/core/models/attic/controllersite_model.py
deleted file mode 100644
index 987ce4c..0000000
--- a/xos/core/models/attic/controllersite_model.py
+++ /dev/null
@@ -1,2 +0,0 @@
-class Meta:
- unique_together = ('site', 'controller')
diff --git a/xos/core/models/attic/controllersiteprivilege_model.py b/xos/core/models/attic/controllersiteprivilege_model.py
index 0c3e4a2..b9d8af4 100644
--- a/xos/core/models/attic/controllersiteprivilege_model.py
+++ b/xos/core/models/attic/controllersiteprivilege_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('controller', 'site_privilege', 'role_id')
-
def can_update(self, user):
if user.is_readonly:
return False
diff --git a/xos/core/models/attic/controllerslice_model.py b/xos/core/models/attic/controllerslice_model.py
index fb41fd3..d8f4b4d 100644
--- a/xos/core/models/attic/controllerslice_model.py
+++ b/xos/core/models/attic/controllerslice_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('controller', 'slice')
-
def tologdict(self):
d=super(ControllerSlice,self).tologdict()
try:
diff --git a/xos/core/models/attic/controllersliceprivilege_model.py b/xos/core/models/attic/controllersliceprivilege_model.py
index 12c01a3..aaf6053 100644
--- a/xos/core/models/attic/controllersliceprivilege_model.py
+++ b/xos/core/models/attic/controllersliceprivilege_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('controller', 'slice_privilege')
-
def can_update(self, user):
if user.is_readonly:
return False
diff --git a/xos/core/models/attic/controlleruser_model.py b/xos/core/models/attic/controlleruser_model.py
index 354ae4a..ae2f3ac 100644
--- a/xos/core/models/attic/controlleruser_model.py
+++ b/xos/core/models/attic/controlleruser_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('user', 'controller')
-
@staticmethod
def select_by_user(user):
if user.is_admin:
diff --git a/xos/core/models/attic/deploymentprivilege_model.py b/xos/core/models/attic/deploymentprivilege_model.py
index 24281b3..8fc40d4 100644
--- a/xos/core/models/attic/deploymentprivilege_model.py
+++ b/xos/core/models/attic/deploymentprivilege_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('user', 'deployment', 'role')
-
def can_update(self, user):
return user.can_update_deployment(self)
diff --git a/xos/core/models/attic/imagedeployments_model.py b/xos/core/models/attic/imagedeployments_model.py
index 4c3ad952..ad51017 100644
--- a/xos/core/models/attic/imagedeployments_model.py
+++ b/xos/core/models/attic/imagedeployments_model.py
@@ -1,5 +1,2 @@
-class Meta:
- unique_together = ('image', 'deployment')
-
def can_update(self, user):
return user.can_update_deployment(self.deployment)
diff --git a/xos/core/models/attic/networkslice_model.py b/xos/core/models/attic/networkslice_model.py
index 1160806..43104cd 100644
--- a/xos/core/models/attic/networkslice_model.py
+++ b/xos/core/models/attic/networkslice_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('network', 'slice')
-
def save(self, *args, **kwds):
slice = self.slice
if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
diff --git a/xos/core/models/attic/port_model.py b/xos/core/models/attic/port_model.py
index 00f59ad..5baf1b8 100644
--- a/xos/core/models/attic/port_model.py
+++ b/xos/core/models/attic/port_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('network', 'instance')
-
def save(self, *args, **kwds):
if self.instance:
slice = self.instance.slice
diff --git a/xos/core/models/attic/serviceprivilege_model.py b/xos/core/models/attic/serviceprivilege_model.py
index 294fd90..bac83cc 100644
--- a/xos/core/models/attic/serviceprivilege_model.py
+++ b/xos/core/models/attic/serviceprivilege_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('user', 'service', 'role')
-
@classmethod
def select_by_user(cls, user):
if user.is_admin:
diff --git a/xos/core/models/attic/sitedeployment_model.py b/xos/core/models/attic/sitedeployment_model.py
deleted file mode 100644
index 8d79f23..0000000
--- a/xos/core/models/attic/sitedeployment_model.py
+++ /dev/null
@@ -1,3 +0,0 @@
-class Meta:
- unique_together = ('site', 'deployment', 'controller')
-
diff --git a/xos/core/models/attic/sliceprivilege_model.py b/xos/core/models/attic/sliceprivilege_model.py
index 8516454..e2599a3 100644
--- a/xos/core/models/attic/sliceprivilege_model.py
+++ b/xos/core/models/attic/sliceprivilege_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('user', 'slice', 'role')
-
def can_update(self, user):
return user.can_update_slice(self.slice)
diff --git a/xos/core/models/attic/tenantrootprivilege_model.py b/xos/core/models/attic/tenantrootprivilege_model.py
index 8dee9a1..c3048f0 100644
--- a/xos/core/models/attic/tenantrootprivilege_model.py
+++ b/xos/core/models/attic/tenantrootprivilege_model.py
@@ -1,6 +1,3 @@
-class Meta:
- unique_together = ('user', 'tenant_root', 'role')
-
def can_update(self, user):
return user.can_update_tenant_root_privilege(self)
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index f09ac28..197adc9 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -86,14 +86,14 @@
message ControllerImages (XOSBase) {
- required manytoone image->Image:controllerimages = 1 [db_index = True, null = False, blank = False];
+ required manytoone image->Image:controllerimages = 1 [db_index = True, null = False, blank = False, unique_with = "controller"];
required manytoone controller->Controller:controllerimages = 2 [db_index = True, null = False, blank = False];
optional string glance_image_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Glance image id", null = True, db_index = False];
}
message ControllerNetwork (XOSBase) {
- required manytoone network->Network:controllernetworks = 1 [db_index = True, null = False, blank = False];
+ required manytoone network->Network:controllernetworks = 1 [db_index = True, null = False, blank = False, unique_with = "controller"];
required manytoone controller->Controller:controllernetworks = 2 [db_index = True, null = False, blank = False];
required string subnet = 3 [db_index = False, max_length = 32, null = False, blank = True];
required string start_ip = 4 [db_index = False, max_length = 32, null = False, blank = True];
@@ -112,28 +112,28 @@
message ControllerSite (XOSBase) {
- required manytoone site->Site:controllersite = 1 [db_index = True, null = False, blank = False];
+ required manytoone site->Site:controllersite = 1 [db_index = True, null = False, blank = False, unique_with="controller"];
optional manytoone controller->Controller:controllersite = 2 [db_index = True, null = True, blank = True];
optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = True];
}
message ControllerSitePrivilege (XOSBase) {
- required manytoone controller->Controller:controllersiteprivileges = 1 [db_index = True, null = False, blank = False];
- required manytoone site_privilege->SitePrivilege:controllersiteprivileges = 2 [db_index = True, null = False, blank = False];
+ required manytoone controller->Controller:controllersiteprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "site_privilege"];
+ required manytoone site_privilege->SitePrivilege:controllersiteprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role_id"];
optional string role_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone id", null = True, db_index = True];
}
message ControllerSlice (XOSBase) {
- required manytoone controller->Controller:controllerslices = 1 [db_index = True, null = False, blank = False];
+ required manytoone controller->Controller:controllerslices = 1 [db_index = True, null = False, blank = False, unique_with = "slice"];
required manytoone slice->Slice:controllerslices = 2 [db_index = True, null = False, blank = False];
optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = False];
}
message ControllerSlicePrivilege (XOSBase) {
- required manytoone controller->Controller:controllersliceprivileges = 1 [db_index = True, null = False, blank = False];
+ required manytoone controller->Controller:controllersliceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "slice_privilege"];
required manytoone slice_privilege->SlicePrivilege:controllersliceprivileges = 2 [db_index = True, null = False, blank = False];
optional string role_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone id", null = True, db_index = True];
}
@@ -141,7 +141,7 @@
message ControllerUser (XOSBase) {
required manytoone user->User:controllerusers = 1 [db_index = True, null = False, blank = False];
- required manytoone controller->Controller:controllersusers = 2 [db_index = True, null = False, blank = False];
+ required manytoone controller->Controller:controllersusers = 2 [db_index = True, null = False, blank = False, unique_with = "user"];
optional string kuser_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone user id", null = True, db_index = False];
}
@@ -164,8 +164,8 @@
message DeploymentPrivilege (XOSBase) {
- required manytoone user->User:deploymentprivileges = 1 [db_index = True, null = False, blank = False];
- required manytoone deployment->Deployment:deploymentprivileges = 2 [db_index = True, null = False, blank = False];
+ required manytoone user->User:deploymentprivileges = 1 [db_index = True, null = False, blank = False, unique_with="deployment"];
+ required manytoone deployment->Deployment:deploymentprivileges = 2 [db_index = True, null = False, blank = False, unique_with="role"];
required manytoone role->DeploymentRole:deploymentprivileges = 3 [db_index = True, null = False, blank = False];
}
@@ -200,7 +200,7 @@
message ImageDeployments (XOSBase) {
- required manytoone image->Image:imagedeployments = 1 [db_index = True, null = False, blank = False];
+ required manytoone image->Image:imagedeployments = 1 [db_index = True, null = False, blank = False, unique_with = "deployment"];
required manytoone deployment->Deployment:imagedeployments = 2 [db_index = True, null = False, blank = False];
}
@@ -255,7 +255,7 @@
message NetworkSlice (XOSBase) {
- required manytoone network->Network:networkslices = 1 [db_index = True, null = False, blank = False];
+ required manytoone network->Network:networkslices = 1 [db_index = True, null = False, blank = False, unique_with = "slice"];
required manytoone slice->Slice:networkslices = 2 [db_index = True, null = False, blank = False];
}
message NetworkTemplate (XOSBase) {
@@ -279,7 +279,7 @@
required manytomany node->Node/NodeLabel_node:nodelabels = 2 [db_index = False, null = False, blank = True];
}
message Port (XOSBase) {
- required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False, unique_with = "instance"];
optional manytoone instance->Instance:ports = 2 [db_index = True, null = True, blank = True];
optional string ip = 3 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
optional string port_id = 4 [help_text = "Neutron port id", max_length = 256, null = True, db_index = False, blank = True];
@@ -330,8 +330,8 @@
message ServicePrivilege (XOSBase) {
- required manytoone user->User:serviceprivileges = 1 [db_index = True, null = False, blank = False];
- required manytoone service->Service:serviceprivileges = 2 [db_index = True, null = False, blank = False];
+ required manytoone user->User:serviceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "service"];
+ required manytoone service->Service:serviceprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role"];
required manytoone role->ServiceRole:serviceprivileges = 3 [db_index = True, null = False, blank = False];
}
@@ -357,8 +357,8 @@
message SiteDeployment (XOSBase) {
- required manytoone site->Site:sitedeployments = 1 [db_index = True, null = False, blank = False];
- required manytoone deployment->Deployment:sitedeployments = 2 [db_index = True, null = False, blank = False];
+ required manytoone site->Site:sitedeployments = 1 [db_index = True, null = False, blank = False, unique_with = "deployment"];
+ required manytoone deployment->Deployment:sitedeployments = 2 [db_index = True, null = False, blank = False, unique_with = "controller"];
optional manytoone controller->Controller:sitedeployments = 3 [db_index = True, null = True, blank = True];
optional string availability_zone = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack availability zone", null = True, db_index = False];
}
@@ -394,8 +394,8 @@
message SlicePrivilege (XOSBase) {
- required manytoone user->User:sliceprivileges = 1 [db_index = True, null = False, blank = False];
- required manytoone slice->Slice:sliceprivileges = 2 [db_index = True, null = False, blank = False];
+ required manytoone user->User:sliceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "slice"];
+ required manytoone slice->Slice:sliceprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role"];
required manytoone role->SliceRole:sliceprivileges = 3 [db_index = True, null = False, blank = False];
}
@@ -457,8 +457,8 @@
message TenantRootPrivilege (XOSBase) {
- required manytoone user->User:tenant_root_privileges = 1 [db_index = True, null = False, blank = False];
- required manytoone tenant_root->TenantRoot:tenant_root_privileges = 2 [db_index = True, null = False, blank = False];
+ required manytoone user->User:tenant_root_privileges = 1 [db_index = True, null = False, blank = False, unique_with = "tenant_root"];
+ required manytoone tenant_root->TenantRoot:tenant_root_privileges = 2 [db_index = True, null = False, blank = False, unique_with = "role"];
required manytoone role->TenantRootRole:tenant_root_privileges = 3 [db_index = True, null = False, blank = False];
}
diff --git a/xos/genx/targets/django-split.xtarget b/xos/genx/targets/django-split.xtarget
index ccb4068..28a1ea4 100644
--- a/xos/genx/targets/django-split.xtarget
+++ b/xos/genx/targets/django-split.xtarget
@@ -30,6 +30,12 @@
{{ l.src_port }} = {{ xproto_django_link_type(l) }}( {%- if peer_name==m.name -%}'self'{%- else -%}{{ peer_name }} {%- endif -%}, {{ xproto_django_link_options_str(l, l.dst_port ) }} )
{%- endfor %}
+ # Meta
+ {%- set uniques = xproto_field_graph_components(m.fields) %}
+ {%- if uniques %}
+ class Meta:
+ unique_together = {{ xproto_tuplify(uniques) }}
+ {%- endif %}
{% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
pass
diff --git a/xos/genx/tool/generator.py b/xos/genx/tool/generator.py
index 72eb65a..9e78673 100755
--- a/xos/genx/tool/generator.py
+++ b/xos/genx/tool/generator.py
@@ -139,7 +139,7 @@
print '-'*60
traceback.print_exc(file=sys.stdout)
print '-'*60
- exit(1)
+ raise e
def main():
generator = XOSGenerator(args)
diff --git a/xos/genx/tool/lib.py b/xos/genx/tool/lib.py
index e9acbe5..ed55134 100644
--- a/xos/genx/tool/lib.py
+++ b/xos/genx/tool/lib.py
@@ -2,6 +2,10 @@
import re
from pattern import en
+class FieldNotFound(Exception):
+ def __init__(self, message):
+ super(FieldNotFound, self).__init__(message)
+
def xproto_unquote(s):
return unquote(s)
@@ -331,3 +335,56 @@
return 'number'
elif f['type'] in ['double','float']:
return 'string'
+
+def xproto_tuplify(nested_list_or_set):
+ if not isinstance(nested_list_or_set, list) and not isinstance(nested_list_or_set, set):
+ return nested_list_or_set
+ else:
+ return tuple([xproto_tuplify(i) for i in nested_list_or_set])
+
+def xproto_field_graph_components(fields, tag='unique_with'):
+ def find_components(graph):
+ pending = set(graph.keys())
+ components = []
+
+ while pending:
+ front = { pending.pop() }
+ component = set()
+
+ while front:
+ node = front.pop()
+ neighbours = graph[node]
+ neighbours-=component # These we have already visited
+ front |= neighbours
+
+ pending-=neighbours
+ component |= neighbours
+
+ components.append(component)
+
+ return components
+
+ field_graph = {}
+ field_names = {f['name'] for f in fields}
+
+ for f in fields:
+ try:
+ tagged_str = unquote(f['options'][tag])
+ tagged_fields = tagged_str.split(',')
+
+ for uf in tagged_fields:
+ if uf not in field_names:
+ raise FieldNotFound('Field %s not found'%uf)
+
+ field_graph.setdefault(f['name'],set()).add(uf)
+ field_graph.setdefault(uf,set()).add(f['name'])
+ except KeyError:
+ pass
+
+ components = find_components(field_graph)
+ return components
+
+
+
+
+
diff --git a/xos/genx/tool/tests/field_graph_test.py b/xos/genx/tool/tests/field_graph_test.py
new file mode 100644
index 0000000..4572b87
--- /dev/null
+++ b/xos/genx/tool/tests/field_graph_test.py
@@ -0,0 +1,68 @@
+from xproto_test_base import *
+from lib import FieldNotFound
+
+class XProtoFieldGraphTest(XProtoTest):
+ def test_field_graph(self):
+ xproto = \
+"""
+message VRouterDevice (PlCoreBase){
+ optional string name = 1 [help_text = "device friendly name", max_length = 20, null = True, db_index = False, blank = True, unique_with="openflow_id"];
+ required string openflow_id = 2 [help_text = "device identifier in ONOS", max_length = 20, null = False, db_index = False, blank = False, unique_with="name"];
+ required string config_key = 3 [default = "basic", max_length = 32, blank = False, help_text = "configuration key", null = False, db_index = False, unique_with="driver"];
+ required string driver = 4 [help_text = "driver type", max_length = 32, null = False, db_index = False, blank = False, unique_with="vrouter_service"];
+ required manytoone vrouter_service->VRouterService:devices = 5 [db_index = True, null = False, blank = False];
+ required string A = 6 [unique_with="B"];
+ required string B = 7 [unique_with="C"];
+ required string C = 8 [unique_with="A"];
+ required string D = 9;
+ required string E = 10 [unique_with="F,G"];
+ required string F = 11;
+ required string G = 12;
+}
+"""
+ target = \
+"""
+{{ xproto_field_graph_components(proto.messages.0.fields) }}
+"""
+
+ self.generate(xproto = xproto, target = target)
+ components = eval(self.get_output())
+ self.assertIn({'A','B','C'}, components)
+ self.assertIn({'openflow_id','name'}, components)
+ self.assertIn({'config_key','vrouter_service','driver'}, components)
+ self.assertIn({'E','F','G'}, components)
+
+ union = reduce(lambda acc,x: acc | x, components)
+ self.assertNotIn('D', union)
+
+ def test_missing_field(self):
+ xproto = \
+"""
+message VRouterDevice (PlCoreBase){
+ optional string name = 1 [help_text = "device friendly name", max_length = 20, null = True, db_index = False, blank = True, unique_with="hamburger"];
+ required string openflow_id = 2 [help_text = "device identifier in ONOS", max_length = 20, null = False, db_index = False, blank = False, unique_with="name"];
+ required string config_key = 3 [default = "basic", max_length = 32, blank = False, help_text = "configuration key", null = False, db_index = False, unique_with="driver"];
+ required string driver = 4 [help_text = "driver type", max_length = 32, null = False, db_index = False, blank = False, unique_with="vrouter_service"];
+ required manytoone vrouter_service->VRouterService:devices = 5 [db_index = True, null = False, blank = False];
+ required string A = 6 [unique_with="B"];
+ required string B = 7 [unique_with="C"];
+ required string C = 8 [unique_with="A"];
+ required string D = 9;
+}
+"""
+ target = \
+"""
+{{ xproto_field_graph_components(proto.messages.0.fields) }}
+"""
+
+ def generate():
+ self.generate(xproto = xproto, target = target)
+
+ # The following call generates some output, which should disappear
+ # when Matteo merges his refactoring of XOSGenerator.
+ self.assertRaises(FieldNotFound, generate)
+
+if __name__ == '__main__':
+ unittest.main()
+
+