[CORD-1440] Moving the generative toolchain in a library
Change-Id: Ifa8e8f930ac34e1f8952099b7e34842a52f4664d
diff --git a/lib/xos-genx/tests/__init__.py b/lib/xos-genx/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/xos-genx/tests/__init__.py
diff --git a/lib/xos-genx/tests/attics/xosmodel_bottom.py b/lib/xos-genx/tests/attics/xosmodel_bottom.py
new file mode 100644
index 0000000..d71c506
--- /dev/null
+++ b/lib/xos-genx/tests/attics/xosmodel_bottom.py
@@ -0,0 +1,2 @@
+def bottom():
+ return 'bottom'
\ No newline at end of file
diff --git a/lib/xos-genx/tests/attics/xosmodel_header.py b/lib/xos-genx/tests/attics/xosmodel_header.py
new file mode 100644
index 0000000..cafc869
--- /dev/null
+++ b/lib/xos-genx/tests/attics/xosmodel_header.py
@@ -0,0 +1,2 @@
+def header():
+ return 'header'
\ No newline at end of file
diff --git a/lib/xos-genx/tests/attics/xosmodel_model.py b/lib/xos-genx/tests/attics/xosmodel_model.py
new file mode 100644
index 0000000..78e3e63
--- /dev/null
+++ b/lib/xos-genx/tests/attics/xosmodel_model.py
@@ -0,0 +1,2 @@
+def model():
+ return 'model'
\ No newline at end of file
diff --git a/lib/xos-genx/tests/attics/xosmodel_top.py b/lib/xos-genx/tests/attics/xosmodel_top.py
new file mode 100644
index 0000000..93dbd0d
--- /dev/null
+++ b/lib/xos-genx/tests/attics/xosmodel_top.py
@@ -0,0 +1,2 @@
+def top():
+ return 'top'
\ No newline at end of file
diff --git a/lib/xos-genx/tests/counts b/lib/xos-genx/tests/counts
new file mode 100755
index 0000000..8ab44d4
--- /dev/null
+++ b/lib/xos-genx/tests/counts
@@ -0,0 +1,34 @@
+#!/usr/bin/python
+
+import core.models
+import inspect
+from core.models import XOSBase, PlModelMixIn
+import pdb
+
+def count(lst):
+ c = 0
+ for l in lst[0]:
+ ll = l.lstrip()
+ if (ll and not ll.startswith('#') and ll.rstrip()!='pass' and 'ModelLink' not in ll and 'CHOICES' not in ll):
+ c+=1
+ return c
+
+def is_model_class(model):
+ """ Return True if 'model' is something that we're interested in """
+ if not inspect.isclass(model):
+ return False
+ if model.__name__ in ["PlModelMixIn"]:
+ return False
+ bases = inspect.getmro(model)
+ bases = [x.__name__ for x in bases]
+ if ("XOSBase" in bases) or ("PlModelMixIn" in bases):
+ return True
+
+ return False
+
+for a in dir(core.models):
+ x = getattr(core.models,a)
+ if (is_model_class(x)):
+ lines = inspect.getsourcelines(x)
+ print x.__name__,":",count(lines)
+
diff --git a/lib/xos-genx/tests/django_generator_test.py b/lib/xos-genx/tests/django_generator_test.py
new file mode 100644
index 0000000..08997b7
--- /dev/null
+++ b/lib/xos-genx/tests/django_generator_test.py
@@ -0,0 +1,27 @@
+import unittest
+import os
+from xosgenx.generator import XOSGenerator
+from helpers import FakeArgs
+
+VROUTER_XPROTO = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/xproto/vrouterport.xproto")
+
+# Generate Protobuf from Xproto and then parse the resulting Protobuf
+class XProtoProtobufGeneratorTest(unittest.TestCase):
+ def test_proto_generator(self):
+ """
+ [XOS-GenX] Generate DJANGO models, verify Fields and Foreign Keys
+ """
+ args = FakeArgs()
+ args.files = [VROUTER_XPROTO]
+ args.target = 'django.xtarget'
+ output = XOSGenerator.generate(args)
+
+ fields = filter(lambda s:'Field(' in s, output.splitlines())
+ self.assertEqual(len(fields), 2)
+ links = filter(lambda s:'Key(' in s, output.splitlines())
+ self.assertEqual(len(links), 2)
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/tests/field_graph_test.py b/lib/xos-genx/tests/field_graph_test.py
new file mode 100644
index 0000000..ea3d52d
--- /dev/null
+++ b/lib/xos-genx/tests/field_graph_test.py
@@ -0,0 +1,76 @@
+import unittest
+from xosgenx.jinja2_extensions import FieldNotFound
+from helpers import FakeArgs, OUTPUT_DIR, XProtoTestHelpers
+from xosgenx.generator import XOSGenerator
+
+class XProtoFieldGraphTest(unittest.TestCase):
+ 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 = XProtoTestHelpers.write_tmp_target(
+"""
+{{ xproto_field_graph_components(proto.messages.0.fields) }}
+""")
+
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = target
+ output = XOSGenerator.generate(args)
+ output = eval(output)
+ self.assertIn({'A','B','C'}, output)
+ self.assertIn({'openflow_id','name'}, output)
+ self.assertIn({'config_key','vrouter_service','driver'}, output)
+ self.assertIn({'E','F','G'}, output)
+
+ union = reduce(lambda acc,x: acc | x, output)
+ 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 = XProtoTestHelpers.write_tmp_target(
+"""
+{{ xproto_field_graph_components(proto.messages.0.fields) }}
+""")
+
+ def generate():
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = target
+ output = XOSGenerator.generate(args)
+
+ # 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()
+
+
diff --git a/lib/xos-genx/tests/graph_test.py b/lib/xos-genx/tests/graph_test.py
new file mode 100644
index 0000000..cc03c56
--- /dev/null
+++ b/lib/xos-genx/tests/graph_test.py
@@ -0,0 +1,301 @@
+import unittest
+from xosgenx.generator import XOSGenerator
+from helpers import FakeArgs, XProtoTestHelpers
+
+class XProtoGraphTests(unittest.TestCase):
+ def test_cross_model(self):
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+ {% for m in proto.messages %}
+ {{ m.name }} {
+ {%- for l in m.links %}
+ {%- if proto.message_table[l.peer.fqn] -%}
+ {%- set model = proto.message_table[l.peer.fqn] %}
+ {% for f in model.fields %}
+ {{ f.type }} {{ f.name }};
+ {% endfor %}
+ {%- endif -%}
+ {% endfor %}
+ }
+ {% endfor %}
+""")
+
+ proto = \
+"""
+message Port (PlCoreBase,ParameterMixin){
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ 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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+
+message Instance (PlCoreBase){
+ optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+ optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+ required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+ optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+ optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+ required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+ optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+ required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+ required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+ required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+ required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+ required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+ optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+ required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+ optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+ required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
+
+message Network (PlCoreBase,ParameterMixin) {
+ required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+ required manytoone template->NetworkTemplate:network = 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];
+ required string end_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+ optional string ports = 6 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string labels = 7 [db_index = False, max_length = 1024, null = True, blank = True];
+ required manytoone owner->Slice:ownedNetworks = 8 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
+ required int32 guaranteed_bandwidth = 9 [default = 0, null = False, db_index = False, blank = False];
+ required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
+ optional string topology_parameters = 11 [db_index = False, null = True, blank = True];
+ optional string controller_url = 12 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string controller_parameters = 13 [db_index = False, null = True, blank = True];
+ optional string network_id = 14 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+ optional string router_id = 15 [help_text = "Quantum router id", max_length = 256, null = True, db_index = False, blank = True];
+ optional string subnet_id = 16 [help_text = "Quantum subnet id", max_length = 256, null = True, db_index = False, blank = True];
+ required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
+ required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
+ required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
+ required manytomany instances->Instance/Port:networks = 20 [db_index = False, null = False, blank = True];
+}
+
+message Slice (PlCoreBase){
+ required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
+ required bool enabled = 2 [help_text = "Status for this Slice", default = True, null = False, db_index = False, blank = True];
+ required bool omf_friendly = 3 [default = False, null = False, db_index = False, blank = True];
+ required string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True];
+ required string slice_url = 5 [db_index = False, max_length = 512, null = False, content_type = "url", blank = True];
+ required manytoone site->Site:slices = 6 [help_text = "The Site this Slice belongs to", null = False, db_index = True, blank = False];
+ required int32 max_instances = 7 [default = 10, null = False, db_index = False, blank = False];
+ optional manytoone service->Service:slices = 8 [db_index = True, null = True, blank = True];
+ optional string network = 9 [blank = True, max_length = 256, null = True, db_index = False, choices = "((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))"];
+ optional string exposed_ports = 10 [db_index = False, max_length = 256, null = True, blank = True];
+ optional manytoone serviceClass->ServiceClass:slices = 11 [db_index = True, null = True, blank = True];
+ optional manytoone creator->User:slices = 12 [db_index = True, null = True, blank = True];
+ optional manytoone default_flavor->Flavor:slices = 13 [db_index = True, null = True, blank = True];
+ optional manytoone default_image->Image:slices = 14 [db_index = True, null = True, blank = True];
+ optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
+ optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
+ required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
+}
+"""
+
+ args = FakeArgs()
+ args.inputs = proto
+ args.target = target
+ output = XOSGenerator.generate(args)
+ num_semis = output.count(';')
+ self.assertGreater(num_semis, 3) # 3 is the number of links, each of which contains at least one field
+
+ def test_base_class_fields(self):
+ target = \
+"""
+ {% for m in proto.messages %}
+ {{ m.name }} {
+ {%- for l in m.links %}
+ {%- if proto.message_table[l.peer.fqn] -%}
+ {%- set model = proto.message_table[l.peer.fqn] %}
+ {% for f in model.fields %}
+ {{ f.type }} {{ f.name }};
+ {% endfor %}
+ {%- endif -%}
+ {% endfor %}
+ }
+ {% endfor %}
+"""
+ xtarget = XProtoTestHelpers.write_tmp_target(target)
+
+ proto = \
+"""
+message Port (PlCoreBase,ParameterMixin){
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ 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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+
+message Instance (PlCoreBase){
+ optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+ optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+ required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+ optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+ optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+ required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+ optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+ required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+ required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+ required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+ required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+ required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+ optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+ required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+ optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+ required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
+
+message Network (PlCoreBase,ParameterMixin) {
+ required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+ required manytoone template->NetworkTemplate:network = 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];
+ required string end_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+ optional string ports = 6 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string labels = 7 [db_index = False, max_length = 1024, null = True, blank = True];
+ required manytoone owner->Slice:ownedNetworks = 8 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
+ required int32 guaranteed_bandwidth = 9 [default = 0, null = False, db_index = False, blank = False];
+ required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
+ optional string topology_parameters = 11 [db_index = False, null = True, blank = True];
+ optional string controller_url = 12 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string controller_parameters = 13 [db_index = False, null = True, blank = True];
+ optional string network_id = 14 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+ optional string router_id = 15 [help_text = "Quantum router id", max_length = 256, null = True, db_index = False, blank = True];
+ optional string subnet_id = 16 [help_text = "Quantum subnet id", max_length = 256, null = True, db_index = False, blank = True];
+ required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
+ required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
+ required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
+ required manytomany instances->Instance/Port:networks = 20 [db_index = False, null = False, blank = True];
+}
+
+message Slice (PlCoreBase){
+ required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
+ required bool enabled = 2 [help_text = "Status for this Slice", default = True, null = False, db_index = False, blank = True];
+ required bool omf_friendly = 3 [default = False, null = False, db_index = False, blank = True];
+ required string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True];
+ required string slice_url = 5 [db_index = False, max_length = 512, null = False, content_type = "url", blank = True];
+ required manytoone site->Site:slices = 6 [help_text = "The Site this Slice belongs to", null = False, db_index = True, blank = False];
+ required int32 max_instances = 7 [default = 10, null = False, db_index = False, blank = False];
+ optional manytoone service->Service:slices = 8 [db_index = True, null = True, blank = True];
+ optional string network = 9 [blank = True, max_length = 256, null = True, db_index = False, choices = "((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))"];
+ optional string exposed_ports = 10 [db_index = False, max_length = 256, null = True, blank = True];
+ optional manytoone serviceClass->ServiceClass:slices = 11 [db_index = True, null = True, blank = True];
+ optional manytoone creator->User:slices = 12 [db_index = True, null = True, blank = True];
+ optional manytoone default_flavor->Flavor:slices = 13 [db_index = True, null = True, blank = True];
+ optional manytoone default_image->Image:slices = 14 [db_index = True, null = True, blank = True];
+ optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
+ optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
+ required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
+}
+"""
+
+ args = FakeArgs()
+ args.inputs = proto
+ args.target = xtarget
+ output = XOSGenerator.generate(args)
+
+ num_semis = output.count(';')
+ self.assertGreater(num_semis, 3)
+
+ def test_from_base(self):
+ target = \
+"""
+ {% for f in xproto_base_fields(proto.messages.3, proto.message_table) %}
+ {{ f.type }} {{ f.name }};
+ {% endfor %}
+"""
+ xtarget = XProtoTestHelpers.write_tmp_target(target)
+ proto = \
+"""
+message Port (PlCoreBase,ParameterMixin){
+ required string easter_egg = 1;
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ 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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+
+message Instance (Port){
+ optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+ optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+ required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+ optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+ optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+ required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+ optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+ required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+ required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+ required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+ required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+ required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+ optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+ required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+ optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+ required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
+
+message Network (Instance) {
+ required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+ required manytoone template->NetworkTemplate:network = 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];
+ required string end_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+ optional string ports = 6 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string labels = 7 [db_index = False, max_length = 1024, null = True, blank = True];
+ required manytoone owner->Slice:ownedNetworks = 8 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
+ required int32 guaranteed_bandwidth = 9 [default = 0, null = False, db_index = False, blank = False];
+ required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
+ optional string topology_parameters = 11 [db_index = False, null = True, blank = True];
+ optional string controller_url = 12 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string controller_parameters = 13 [db_index = False, null = True, blank = True];
+ optional string network_id = 14 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+ optional string router_id = 15 [help_text = "Quantum router id", max_length = 256, null = True, db_index = False, blank = True];
+ optional string subnet_id = 16 [help_text = "Quantum subnet id", max_length = 256, null = True, db_index = False, blank = True];
+ required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
+ required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
+ required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
+ required manytomany instances->Instance/Port:networks = 20 [db_index = False, null = False, blank = True];
+}
+
+message Slice (Network){
+ required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
+ required bool enabled = 2 [help_text = "Status for this Slice", default = True, null = False, db_index = False, blank = True];
+ required bool omf_friendly = 3 [default = False, null = False, db_index = False, blank = True];
+ required string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True];
+ required string slice_url = 5 [db_index = False, max_length = 512, null = False, content_type = "url", blank = True];
+ required manytoone site->Site:slices = 6 [help_text = "The Site this Slice belongs to", null = False, db_index = True, blank = False];
+ required int32 max_instances = 7 [default = 10, null = False, db_index = False, blank = False];
+ optional manytoone service->Service:slices = 8 [db_index = True, null = True, blank = True];
+ optional string network = 9 [blank = True, max_length = 256, null = True, db_index = False, choices = "((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))"];
+ optional string exposed_ports = 10 [db_index = False, max_length = 256, null = True, blank = True];
+ optional manytoone serviceClass->ServiceClass:slices = 11 [db_index = True, null = True, blank = True];
+ optional manytoone creator->User:slices = 12 [db_index = True, null = True, blank = True];
+ optional manytoone default_flavor->Flavor:slices = 13 [db_index = True, null = True, blank = True];
+ optional manytoone default_image->Image:slices = 14 [db_index = True, null = True, blank = True];
+ optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
+ optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
+ required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
+}
+"""
+ args = FakeArgs()
+ args.inputs = proto
+ args.target = xtarget
+ output = XOSGenerator.generate(args)
+ self.assertIn('easter_egg', output)
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/tests/helpers.py b/lib/xos-genx/tests/helpers.py
new file mode 100644
index 0000000..02877a3
--- /dev/null
+++ b/lib/xos-genx/tests/helpers.py
@@ -0,0 +1,20 @@
+import os
+
+# Constants
+OUTPUT_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/out/")
+
+TMP_TARGET_PATH = os.path.join(OUTPUT_DIR, 'tmp.xtarget')
+
+# Store in this class the args to pass at the generator
+class FakeArgs:
+ pass
+
+class XProtoTestHelpers:
+
+ @staticmethod
+ def write_tmp_target(target):
+ tmp_file = open(TMP_TARGET_PATH, 'w')
+ tmp_file.write(target)
+ tmp_file.close()
+ return TMP_TARGET_PATH
+
diff --git a/lib/xos-genx/tests/out/.gitignore b/lib/xos-genx/tests/out/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/lib/xos-genx/tests/out/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/lib/xos-genx/tests/package_test.py b/lib/xos-genx/tests/package_test.py
new file mode 100644
index 0000000..a20295b
--- /dev/null
+++ b/lib/xos-genx/tests/package_test.py
@@ -0,0 +1,426 @@
+import unittest
+import os
+from xosgenx.generator import XOSGenerator
+from helpers import FakeArgs, XProtoTestHelpers
+
+class XProtoPackageTest(unittest.TestCase):
+ def test_package_fqn(self):
+ args = FakeArgs()
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+ {% for m in proto.messages %}
+ {{ m.name }},{{ m.package }},{{ m.fqn }}
+ {% endfor %}
+""")
+
+ xproto =\
+"""
+package xos.core;
+
+message Port (PlCoreBase,ParameterMixin) {
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ 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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = target
+
+ output = XOSGenerator.generate(args)
+
+ self.assertIn('Port,xos.core,xos.core.Port', output)
+
+ def test_cross_model(self):
+ target = XProtoTestHelpers.write_tmp_target( \
+"""
+ {% for m in proto.messages %}
+ {{ m.fqn }} {
+ {%- for l in m.links %}
+ {%- if proto.message_table[l.peer.fqn] %}
+ {{ l.peer.name }} {
+ {%- set model = proto.message_table[l.peer.fqn] %}
+ {% for f in model.fields %}
+ {{ f.type }} {{ f.name }};
+ {% endfor %}
+ }
+ {%- endif -%}
+ {%- if proto.message_table[m.package + '.' + l.peer.name] %}
+ {{ l.peer.name }} {
+ {%- set model = proto.message_table[m.package + '.' + l.peer.name] %}
+ {% for f in model.fields %}
+ {{ f.type }} {{ f.name }};
+ {% endfor %}
+ }
+ {%- endif -%}
+ {% endfor %}
+ }
+ {% endfor %}
+""")
+
+ xproto = \
+"""
+package xos.network;
+
+message Port (PlCoreBase,ParameterMixin){
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ optional manytoone instance->xos.core.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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+
+
+package xos.core;
+
+message Instance (PlCoreBase){
+ optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+ optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+ required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+ optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+ optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+ required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+ optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+ required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+ required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+ required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+ required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+ required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+ optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+ required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+ optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+ required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
+
+package xos.network;
+
+message Network (PlCoreBase,ParameterMixin) {
+ required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+ required manytoone template->NetworkTemplate:network = 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];
+ required string end_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+ optional string ports = 6 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string labels = 7 [db_index = False, max_length = 1024, null = True, blank = True];
+ required manytoone owner->Slice:ownedNetworks = 8 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
+ required int32 guaranteed_bandwidth = 9 [default = 0, null = False, db_index = False, blank = False];
+ required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
+ optional string topology_parameters = 11 [db_index = False, null = True, blank = True];
+ optional string controller_url = 12 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string controller_parameters = 13 [db_index = False, null = True, blank = True];
+ optional string network_id = 14 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+ optional string router_id = 15 [help_text = "Quantum router id", max_length = 256, null = True, db_index = False, blank = True];
+ optional string subnet_id = 16 [help_text = "Quantum subnet id", max_length = 256, null = True, db_index = False, blank = True];
+ required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
+ required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
+ required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
+ required manytomany instances->xos.core.Instance/xos.network.Port:networks = 20 [db_index = False, null = False, blank = True];
+}
+
+message Slice (PlCoreBase){
+ required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
+ required bool enabled = 2 [help_text = "Status for this Slice", default = True, null = False, db_index = False, blank = True];
+ required bool omf_friendly = 3 [default = False, null = False, db_index = False, blank = True];
+ required string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True];
+ required string slice_url = 5 [db_index = False, max_length = 512, null = False, content_type = "url", blank = True];
+ required manytoone site->Site:slices = 6 [help_text = "The Site this Slice belongs to", null = False, db_index = True, blank = False];
+ required int32 max_instances = 7 [default = 10, null = False, db_index = False, blank = False];
+ optional manytoone service->Service:slices = 8 [db_index = True, null = True, blank = True];
+ optional string network = 9 [blank = True, max_length = 256, null = True, db_index = False, choices = "((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))"];
+ optional string exposed_ports = 10 [db_index = False, max_length = 256, null = True, blank = True];
+ optional manytoone serviceClass->ServiceClass:slices = 11 [db_index = True, null = True, blank = True];
+ optional manytoone creator->User:slices = 12 [db_index = True, null = True, blank = True];
+ optional manytoone default_flavor->Flavor:slices = 13 [db_index = True, null = True, blank = True];
+ optional manytoone default_image->Image:slices = 14 [db_index = True, null = True, blank = True];
+ optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
+ optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
+ required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
+}
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = target
+ output = XOSGenerator.generate(args)
+
+ self.assertIn('numberCores', output) # Instance showed up via cross-package call
+ self.assertIn('ip;', output) # Network showed up via cross-package call
+ self.assertIn('max_instances', output) # Slice showed up via implicit in-package call
+
+ def test_base_class_fields(self):
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+ {% for m in proto.messages %}
+ {{ m.name }} {
+ {%- for b in m.bases %}
+ {%- if proto.message_table[b.fqn] -%}
+ {%- set model = proto.message_table[b.fqn] %}
+ {% for f in model.fields %}
+ {{ f.type }} {{ f.name }};
+ {% endfor %}
+ {%- endif -%}
+ {% endfor %}
+ }
+ {% endfor %}
+""")
+
+ xproto =\
+"""
+package xos.network;
+
+message Port (PlCoreBase,ParameterMixin){
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ 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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+
+package xos.someotherpackage;
+
+message Instance (xos.network.Port){
+ optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+ optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+ required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+ optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+ optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+ required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+ optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+ required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+ required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+ required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+ required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+ required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+ optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+ required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+ optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+ required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = target
+ output = XOSGenerator.generate(args)
+
+ self.assertIn('xos_created', output)
+
+ def test_from_base(self):
+ target = XProtoTestHelpers.write_tmp_target( \
+"""
+ {% for f in xproto_base_fields(proto.messages.3, proto.message_table) %}
+ {{ f.type }} {{ f.name }};
+ {% endfor %}
+""")
+
+ xproto =\
+"""
+option app_name = "firstapp";
+
+message Port (PlCoreBase,ParameterMixin){
+ required string easter_egg = 1;
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ 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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+
+package A;
+
+message Instance (Port){
+ optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+ optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+ required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+ optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+ optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+ required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+ optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+ required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+ required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+ required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+ required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+ required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+ optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+ required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+ optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+ required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
+
+package B;
+
+option app_name="networkapp";
+
+message Network (A.Instance) {
+ required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+ required manytoone template->NetworkTemplate:network = 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];
+ required string end_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+ optional string ports = 6 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string labels = 7 [db_index = False, max_length = 1024, null = True, blank = True];
+ required manytoone owner->Slice:ownedNetworks = 8 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
+ required int32 guaranteed_bandwidth = 9 [default = 0, null = False, db_index = False, blank = False];
+ required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
+ optional string topology_parameters = 11 [db_index = False, null = True, blank = True];
+ optional string controller_url = 12 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string controller_parameters = 13 [db_index = False, null = True, blank = True];
+ optional string network_id = 14 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+ optional string router_id = 15 [help_text = "Quantum router id", max_length = 256, null = True, db_index = False, blank = True];
+ optional string subnet_id = 16 [help_text = "Quantum subnet id", max_length = 256, null = True, db_index = False, blank = True];
+ required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
+ required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
+ required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
+ required manytomany instances->Instance/Port:networks = 20 [db_index = False, null = False, blank = True];
+}
+
+option app_name="sliceapp";
+
+message Slice (Network){
+ required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
+ required bool enabled = 2 [help_text = "Status for this Slice", default = True, null = False, db_index = False, blank = True];
+ required bool omf_friendly = 3 [default = False, null = False, db_index = False, blank = True];
+ required string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True];
+ required string slice_url = 5 [db_index = False, max_length = 512, null = False, content_type = "url", blank = True];
+ required manytoone site->Site:slices = 6 [help_text = "The Site this Slice belongs to", null = False, db_index = True, blank = False];
+ required int32 max_instances = 7 [default = 10, null = False, db_index = False, blank = False];
+ optional manytoone service->Service:slices = 8 [db_index = True, null = True, blank = True];
+ optional string network = 9 [blank = True, max_length = 256, null = True, db_index = False, choices = "((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))"];
+ optional string exposed_ports = 10 [db_index = False, max_length = 256, null = True, blank = True];
+ optional manytoone serviceClass->ServiceClass:slices = 11 [db_index = True, null = True, blank = True];
+ optional manytoone creator->User:slices = 12 [db_index = True, null = True, blank = True];
+ optional manytoone default_flavor->Flavor:slices = 13 [db_index = True, null = True, blank = True];
+ optional manytoone default_image->Image:slices = 14 [db_index = True, null = True, blank = True];
+ optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
+ optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
+ required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
+}
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = target
+ output = XOSGenerator.generate(args)
+
+ self.assertIn('easter_egg', output)
+
+ def test_model_options(self):
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+ Options:
+
+ {{ proto.options }}
+ {% for m in proto.messages %}
+ {{ m.options.app_name }}
+ {% endfor %}
+""")
+
+ xproto =\
+"""
+option app_name = "firstapp";
+
+message Port (PlCoreBase,ParameterMixin){
+ required string easter_egg = 1;
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ 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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+
+package A;
+
+message Instance (Port){
+ optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+ optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+ required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+ optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+ optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+ required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+ optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+ required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+ required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+ required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+ required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+ required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+ optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+ required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+ optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+ required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
+
+package B;
+
+option app_name = "networkapp";
+
+message Network (A.Instance) {
+ required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+ required manytoone template->NetworkTemplate:network = 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];
+ required string end_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+ optional string ports = 6 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string labels = 7 [db_index = False, max_length = 1024, null = True, blank = True];
+ required manytoone owner->Slice:ownedNetworks = 8 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
+ required int32 guaranteed_bandwidth = 9 [default = 0, null = False, db_index = False, blank = False];
+ required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
+ optional string topology_parameters = 11 [db_index = False, null = True, blank = True];
+ optional string controller_url = 12 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string controller_parameters = 13 [db_index = False, null = True, blank = True];
+ optional string network_id = 14 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+ optional string router_id = 15 [help_text = "Quantum router id", max_length = 256, null = True, db_index = False, blank = True];
+ optional string subnet_id = 16 [help_text = "Quantum subnet id", max_length = 256, null = True, db_index = False, blank = True];
+ required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
+ required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
+ required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
+ required manytomany instances->Instance/Port:networks = 20 [db_index = False, null = False, blank = True];
+}
+
+
+option app_name = "networkapp";
+
+message Slice (Network){
+ required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
+ required bool enabled = 2 [help_text = "Status for this Slice", default = True, null = False, db_index = False, blank = True];
+ required bool omf_friendly = 3 [default = False, null = False, db_index = False, blank = True];
+ required string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True];
+ required string slice_url = 5 [db_index = False, max_length = 512, null = False, content_type = "url", blank = True];
+ required manytoone site->Site:slices = 6 [help_text = "The Site this Slice belongs to", null = False, db_index = True, blank = False];
+ required int32 max_instances = 7 [default = 10, null = False, db_index = False, blank = False];
+ optional manytoone service->Service:slices = 8 [db_index = True, null = True, blank = True];
+ optional string network = 9 [blank = True, max_length = 256, null = True, db_index = False, choices = "((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))"];
+ optional string exposed_ports = 10 [db_index = False, max_length = 256, null = True, blank = True];
+ optional manytoone serviceClass->ServiceClass:slices = 11 [db_index = True, null = True, blank = True];
+ optional manytoone creator->User:slices = 12 [db_index = True, null = True, blank = True];
+ optional manytoone default_flavor->Flavor:slices = 13 [db_index = True, null = True, blank = True];
+ optional manytoone default_image->Image:slices = 14 [db_index = True, null = True, blank = True];
+ optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
+ optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
+ required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
+}
+"""
+
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = target
+ output = XOSGenerator.generate(args)
+
+ self.assertEqual(output.count('firstapp'), 2)
+ self.assertEqual(output.count('networkapp'), 2)
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/tests/parse_test.py b/lib/xos-genx/tests/parse_test.py
new file mode 100644
index 0000000..d8d4edc
--- /dev/null
+++ b/lib/xos-genx/tests/parse_test.py
@@ -0,0 +1,114 @@
+import unittest
+from xosgenx.generator import XOSGenerator
+from helpers import FakeArgs, XProtoTestHelpers
+
+class XProtoParseTests(unittest.TestCase):
+ def test_global_options(self):
+
+ xtarget = XProtoTestHelpers.write_tmp_target("{{ options }}")
+
+ xproto = \
+"""
+ option kind = "vsg";
+ option verbose_name = "vSG Service";
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = xtarget
+ output = XOSGenerator.generate(args)
+ self.assertIn("vsg", output)
+ self.assertIn("vSG Service", output)
+
+ def test_basic_proto(self):
+ xtarget = XProtoTestHelpers.write_tmp_target("{{ proto }}")
+
+ xproto = \
+"""
+message Person {
+ required string name = 1;
+ required int32 id = 2; // Unique ID number for this person.
+ optional string email = 3 [symphony = "da da da dum"];
+
+ enum PhoneType {
+ MOBILE = 0;
+ HOME = 1;
+ WORK = 2;
+ }
+
+ required string number = 1;
+ optional PhoneType type = 2;
+
+ repeated PhoneNumber phones = 4;
+}
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = xtarget
+ output = XOSGenerator.generate(args)
+ self.assertIn("PhoneNumber", output)
+
+ def test_link_extensions(self):
+
+ xtarget = XProtoTestHelpers.write_tmp_target("{{ proto.messages.0.links }}")
+ xproto = \
+"""
+message links {
+ required manytoone vrouter_service->VRouterService:device_ports = 4 [db_index = True, null = False, blank = False];
+}
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = xtarget
+ output = XOSGenerator.generate(args)
+ self.assertIn("VRouterService", output)
+
+ pass
+
+ def test_through_extensions(self):
+ xtarget = XProtoTestHelpers.write_tmp_target("{{ proto.messages.0.links.0.through }}")
+ xproto = \
+"""
+message links {
+ required manytomany vrouter_service->VRouterService/ServiceProxy:device_ports = 4 [db_index = True, null = False, blank = False];
+}
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = xtarget
+ output = XOSGenerator.generate(args)
+ self.assertIn("ServiceProxy", output)
+
+ def test_message_options(self):
+ xtarget = XProtoTestHelpers.write_tmp_target("{{ proto.messages.0.options.type }}")
+ xproto = \
+"""
+message link {
+ option type = "e1000";
+}
+"""
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = xtarget
+ output = XOSGenerator.generate(args)
+ self.assertIn("e1000", output)
+
+ pass
+
+ def test_message_base(self):
+ xtarget = XProtoTestHelpers.write_tmp_target("{{ proto.messages.0.bases }}")
+ xproto = \
+"""
+message base(Base) {
+}
+"""
+
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = xtarget
+ output = XOSGenerator.generate(args)
+ self.assertIn("Base", output)
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/tests/pure_proto_test.py b/lib/xos-genx/tests/pure_proto_test.py
new file mode 100644
index 0000000..b7330d0
--- /dev/null
+++ b/lib/xos-genx/tests/pure_proto_test.py
@@ -0,0 +1,90 @@
+
+import unittest
+from xosgenx.generator import XOSGenerator
+from helpers import FakeArgs, XProtoTestHelpers
+
+# Generate from xproto, then generate from equivalent proto
+class XPureProtobufGenerator(unittest.TestCase):
+ #FIXME this test is failinf
+ def _test_pure_proto(self):
+ xproto = \
+"""
+message VRouterPort (XOSBase){
+ optional string name = 1 [help_text = "port friendly name", max_length = 20, null = True, db_index = False, blank = True];
+ required string openflow_id = 2 [help_text = "port identifier in ONOS", max_length = 21, null = False, db_index = False, blank = False];
+ required manytoone vrouter_device->VRouterDevice:ports = 3 [db_index = True, null = False, blank = False];
+ required manytoone vrouter_service->VRouterService:device_ports = 4 [db_index = True, null = False, blank = False];
+}
+"""
+
+ proto = \
+"""
+message VRouterPort {
+ option bases = "XOSBase";
+ optional string name = 1 [ null = "True", max_length = "20", blank = "True", help_text = "port friendly name", modifier = "optional", db_index = "False" ];
+ required string openflow_id = 2 [ null = "False", max_length = "21", blank = "False", help_text = "port identifier in ONOS", modifier = "required", db_index = "False" ];
+ required int32 vrouter_device = 3 [ null = "False", blank = "False", model = "VRouterDevice", modifier = "required", type = "link", port = "ports", db_index = "True", link = "manytoone"];
+ required int32 vrouter_service = 4 [ null = "False", blank = "False", model = "VRouterService", modifier = "required", type = "link", port = "device_ports", db_index = "True", link = "manytoone"];
+}
+"""
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+from header import *
+{% for m in proto.messages %}
+{% if file_exists(xproto_base_name(m.name)|lower+'_header.py') -%}from {{xproto_base_name(m.name)|lower }}_header import *{% endif %}
+{% if file_exists(xproto_base_name(m.name)|lower+'_top.py') -%}{{ include_file(xproto_base_name(m.name)|lower+'_top.py') }} {% endif %}
+
+{%- for l in m.links %}
+
+{% if l.peer.name != m.name %}
+from core.models.{{ l.peer.name | lower }} import {{ l.peer.name }}
+{% endif %}
+
+{%- endfor %}
+{% for b in m.bases %}
+{% if b!='XOSBase' and 'Mixin' not in b%}
+from core.models.{{b.name | lower}} import {{ b.name }}
+{% endif %}
+{% endfor %}
+
+
+class {{ m.name }}{{ xproto_base_def(m, m.bases) }}:
+ # Primitive Fields (Not Relations)
+ {% for f in m.fields %}
+ {%- if not f.link -%}
+ {{ f.name }} = {{ xproto_django_type(f.type, f.options) }}( {{ xproto_django_options_str(f) }} )
+ {% endif %}
+ {%- endfor %}
+
+ # Relations
+ {% for l in m.links %}
+ {{ l.src_port }} = {{ xproto_django_link_type(l) }}( {%- if l.peer.name==m.name -%}'self'{%- else -%}{{ l.peer.name }} {%- endif -%}, {{ xproto_django_link_options_str(l, l.dst_port ) }} )
+ {%- endfor %}
+
+ {% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
+ pass
+
+{% if file_exists(xproto_base_name(m.name)|lower+'_bottom.py') -%}{{ include_file(xproto_base_name(m.name)|lower+'_bottom.py') }}{% endif %}
+{% endfor %}
+""")
+
+ args_xproto = FakeArgs()
+ args_xproto.inputs = xproto
+ args_xproto.target = target
+ xproto_gen = XOSGenerator.generate(args_xproto)
+
+ count1 = len(xproto_gen.split('\n'))
+
+ args_proto = FakeArgs()
+ args_proto.inputs = proto
+ args_proto.target = target
+ args_proto.rev = True
+ proto_gen = XOSGenerator.generate(args_proto)
+ count2 = len(proto_gen.split('\n'))
+
+ self.assertEqual(count1, count2)
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/tests/rlinks_test.py b/lib/xos-genx/tests/rlinks_test.py
new file mode 100644
index 0000000..0c7029a
--- /dev/null
+++ b/lib/xos-genx/tests/rlinks_test.py
@@ -0,0 +1,50 @@
+import unittest
+from xosgenx.generator import XOSGenerator
+from helpers import FakeArgs, XProtoTestHelpers
+
+class XProtoRlinkTests(unittest.TestCase):
+ def test_proto_generator(self):
+ target = XProtoTestHelpers.write_tmp_target("""
+{% for m in proto.messages %}
+ {% for r in m.rlinks %}
+ {{ r }}
+ {% endfor %}
+{% endfor %}
+""")
+
+ xproto = \
+"""
+message VRouterPort (PlCoreBase){
+ optional string name = 1 [help_text = "port friendly name", max_length = 20, null = True, db_index = False, blank = True];
+ required string openflow_id = 2 [help_text = "port identifier in ONOS", max_length = 21, null = False, db_index = False, blank = False];
+ required manytoone vrouter_device->VRouterDevice:ports = 3 [db_index = True, null = False, blank = False];
+ required manytoone vrouter_service->VRouterService:device_ports = 4 [db_index = True, null = False, blank = False];
+}
+
+message VRouterService (Service) {
+ optional string rest_hostname = 1 [db_index = False, max_length = 255, null = True, content_type = "stripped", blank = True];
+ required int32 rest_port = 2 [default = 8181, null = False, db_index = False, blank = False];
+ required string rest_user = 3 [default = "onos", max_length = 255, content_type = "stripped", blank = False, null = False, db_index = False];
+ required string rest_pass = 4 [default = "rocks", max_length = 255, content_type = "stripped", blank = False, null = False, db_index = False];
+}
+
+message VRouterDevice (PlCoreBase){
+ optional string name = 1 [help_text = "device friendly name", max_length = 20, null = True, db_index = False, blank = True];
+ required string openflow_id = 2 [help_text = "device identifier in ONOS", max_length = 20, null = False, db_index = False, blank = False];
+ required string config_key = 3 [default = "basic", max_length = 32, blank = False, help_text = "configuration key", null = False, db_index = False];
+ required string driver = 4 [help_text = "driver type", max_length = 32, null = False, db_index = False, blank = False];
+ required manytoone vrouter_service->VRouterService:devices = 5 [db_index = True, null = False, blank = False];
+}
+"""
+
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = target
+ output = XOSGenerator.generate(args)
+ self.assertIn("'src_port': 'device_ports'", output)
+ self.assertIn("'src_port': 'ports'", output)
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/tests/target_test.py b/lib/xos-genx/tests/target_test.py
new file mode 100644
index 0000000..4a244b3
--- /dev/null
+++ b/lib/xos-genx/tests/target_test.py
@@ -0,0 +1,113 @@
+import unittest
+import os
+from xosgenx.generator import XOSGenerator
+from helpers import FakeArgs, XProtoTestHelpers, OUTPUT_DIR
+
+TEST_FILE = "test_file"
+
+TEST_OUTPUT = "Do re mi fa so la ti do"
+
+class XProtoTargetTests(unittest.TestCase):
+
+ def setUp(self):
+ test_file = open(os.path.join(OUTPUT_DIR, TEST_FILE), 'w')
+ test_file.write(TEST_OUTPUT)
+ test_file.close()
+
+ def test_file_methods(self):
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+ {%% if file_exists("%s") %%}
+ {{ include_file("%s") }}
+ {%% endif %%}
+"""%(TEST_FILE, TEST_FILE)
+ )
+
+ args = FakeArgs()
+ args.inputs = ''
+ args.target = target
+ args.attic = OUTPUT_DIR
+ output = XOSGenerator.generate(args)
+ self.assertIn(TEST_OUTPUT, output)
+
+ def test_xproto_lib(self):
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+ {{ xproto_first_non_empty([None, None, None, None, None, None, "Eureka"]) }}
+""")
+ args = FakeArgs()
+ args.inputs = ''
+ args.target = target
+ output = XOSGenerator.generate(args)
+ self.assertIn("Eureka", output)
+
+ def test_context(self):
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+ {{ context.what }}
+""")
+ args = FakeArgs()
+ args.inputs = ''
+ args.target = target
+ args.kv='what:what is what'
+ output = XOSGenerator.generate(args)
+ self.assertIn("what is what", output)
+
+ def test_singularize(self):
+ proto = \
+"""
+ message TestSingularize {
+ // The following field has an explicitly specified singular
+ required int many = 1 [singular = "one"];
+ // The following fields have automatically computed singulars
+ required int sheep = 2;
+ required int radii = 2;
+ required int slices = 2;
+ required int networks = 2;
+ required int omf_friendlies = 2;
+ }
+"""
+
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+{% for m in proto.messages.0.fields -%}
+{{ xproto_singularize(m) }},
+{%- endfor %}
+""")
+ args = FakeArgs()
+ args.inputs = proto
+ args.target = target
+ output = XOSGenerator.generate(args)
+ self.assertEqual("one,sheep,radius,slice,network,omf_friendly", output.lstrip().rstrip().rstrip(','))
+
+ def test_pluralize(self):
+ proto = \
+"""
+ message TestPluralize {
+ // The following field has an explicitly specified plural
+ required int anecdote = 1 [plural = "data"];
+ // The following fields have automatically computed plurals
+ required int sheep = 2;
+ required int radius = 2;
+ required int slice = 2;
+ required int network = 2;
+ required int omf_friendly = 2;
+ }
+"""
+
+ target = XProtoTestHelpers.write_tmp_target(
+"""
+{% for m in proto.messages.0.fields -%}
+{{ xproto_pluralize(m) }},
+{%- endfor %}
+""")
+ args = FakeArgs()
+ args.inputs = proto
+ args.target = target
+ output = XOSGenerator.generate(args)
+ self.assertEqual("data,sheep,radii,slices,networks,omf_friendlies", output.lstrip().rstrip().rstrip(','))
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/tests/test_cli.py b/lib/xos-genx/tests/test_cli.py
new file mode 100644
index 0000000..af21253
--- /dev/null
+++ b/lib/xos-genx/tests/test_cli.py
@@ -0,0 +1,39 @@
+import unittest
+import os
+from mock import patch
+from xosgenx.xosgen import XosGen
+
+class Args:
+ pass
+
+class XOSGeneratorTest(unittest.TestCase):
+ """
+ Testing the CLI binding for the XOS Generative Toolchain
+ """
+
+ def test_generator(self):
+ """
+ [XOS-GenX] The CLI entry point should correctly parse params
+ """
+ args = Args()
+ args.files = ['tests/xproto/test.xproto']
+ args.target = 'tests/xtarget/test.xtarget'
+ args.output = 'tests/out/dir/'
+ args.write_to_file = "target"
+ args.dest_file = None
+ args.dest_extension = None
+
+ expected_args = Args()
+ expected_args.files = [os.path.abspath(os.getcwd() + '/' + args.files[0])]
+ expected_args.target = os.path.abspath(os.getcwd() + '/' + args.target)
+ expected_args.output = os.path.abspath(os.getcwd() + '/' + args.output)
+
+ with patch("xosgenx.xosgen.XOSGenerator.generate") as generator:
+ XosGen.init(args)
+ actual_args = generator.call_args[0][0]
+ self.assertEqual(actual_args.files, expected_args.files)
+ self.assertEqual(actual_args.target, expected_args.target)
+ self.assertEqual(actual_args.output, expected_args.output)
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/lib/xos-genx/tests/test_generator.py b/lib/xos-genx/tests/test_generator.py
new file mode 100644
index 0000000..a13fb38
--- /dev/null
+++ b/lib/xos-genx/tests/test_generator.py
@@ -0,0 +1,212 @@
+import unittest
+import os
+from helpers import FakeArgs, OUTPUT_DIR
+from xosgenx.generator import XOSGenerator
+
+TEST_EXPECTED_OUTPUT = """
+ name: XOSModel
+ fields:
+ name:
+ type: string
+ description: "Help Name"
+ files:
+ type: string
+ description: "Help Files"
+"""
+
+BASE_XPROTO = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/xproto/base.xproto")
+TEST_XPROTO = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/xproto/test.xproto")
+SKIP_DJANGO_XPROTO = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/xproto/skip_django.xproto")
+VROUTER_XPROTO = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/xproto/vrouterport.xproto")
+TEST_TARGET = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/xtarget/test.xtarget")
+SPLIT_TARGET = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/xtarget/split.xtarget")
+
+TEST_ATTICS = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/attics/")
+
+class XOSGeneratorTest(unittest.TestCase):
+ """
+ Testing the XOS Generative Toolchain
+ """
+
+ def setUp(self):
+ filesToRemove = [f for f in os.listdir(OUTPUT_DIR)]
+ for f in filesToRemove:
+ if not f.startswith('.'):
+ os.remove(OUTPUT_DIR + '/' + f)
+
+ def _test_generator_custom_target_from_file(self):
+ """
+ [XOS-GenX] Generate output from base.xproto
+ """
+ args = FakeArgs()
+ args.files = [TEST_XPROTO]
+ args.target = TEST_TARGET
+ output = XOSGenerator.generate(args)
+ self.assertEqual(output, TEST_EXPECTED_OUTPUT)
+
+ def _test_generator_custom_target_from_inputs(self):
+ """
+ [XOS-GenX] Generate output from base.xproto
+ """
+ args = FakeArgs()
+ args.inputs = open(TEST_XPROTO).read()
+ args.target = TEST_TARGET
+ output = XOSGenerator.generate(args)
+ self.assertEqual(output, TEST_EXPECTED_OUTPUT)
+
+ def _test_generator_tosca(self):
+ """
+ [XOS-GenX] Generate TOSCA from base.xproto, and write to file
+ """
+ args = FakeArgs()
+ args.files = [TEST_XPROTO, VROUTER_XPROTO]
+ args.target = 'tosca.xtarget'
+ args.output = OUTPUT_DIR
+ args.write_to_file = 'single'
+ args.dest_file = 'base.yaml'
+ XOSGenerator.generate(args)
+
+ dest_file = OUTPUT_DIR + '/' + args.dest_file
+ self.assertTrue(os.path.isfile(dest_file))
+
+ output = open(dest_file, "r").read()
+ self.assertIn("tosca.nodes.XOSModel", output)
+ self.assertIn("tosca.nodes.VRouterPort", output)
+
+ def _test_django_with_attic(self):
+ """
+ [XOS-GenX] Generate django output from test.xproto
+ """
+ args = FakeArgs()
+ args.files = [TEST_XPROTO, VROUTER_XPROTO]
+ args.target = 'django.xtarget'
+ args.attic = TEST_ATTICS
+ args.output = OUTPUT_DIR
+ args.dest_extension = 'py'
+ args.write_to_file = 'model'
+ output = XOSGenerator.generate(args)
+
+ # xosmodel has custom header attic
+ self.assertIn('from xosmodel_header import *', output['XOSModel'])
+ self.assertIn('class XOSModel(XOSBase):', output['XOSModel'])
+
+ # vrouter port use the default header
+ self.assertIn('header import *', output['VRouterPort'])
+ self.assertIn('class VRouterPort(XOSBase):', output['VRouterPort'])
+
+ #verify files
+ xosmodel = OUTPUT_DIR + '/xosmodel.py'
+ self.assertTrue(os.path.isfile(xosmodel))
+ xmf = open(xosmodel).read()
+ self.assertIn('from xosmodel_header import *', xmf)
+ self.assertIn('class XOSModel(XOSBase):', xmf)
+
+ vrouterport = OUTPUT_DIR + '/vrouterport.py'
+ self.assertTrue(os.path.isfile(vrouterport))
+ vrpf = open(vrouterport).read()
+ self.assertIn('header import *', vrpf)
+ self.assertIn('class VRouterPort(XOSBase):', vrpf)
+
+ def _test_django_with_base(self):
+ args = FakeArgs()
+ args.files = [TEST_XPROTO, BASE_XPROTO]
+ args.target = 'django.xtarget'
+ args.attic = TEST_ATTICS
+ args.output = OUTPUT_DIR
+ args.dest_extension = 'py'
+ args.write_to_file = 'model'
+ output = XOSGenerator.generate(args)
+
+ # verify files
+ xosmodel = OUTPUT_DIR + '/xosmodel.py'
+ self.assertTrue(os.path.isfile(xosmodel))
+ xmf = open(xosmodel).read()
+ self.assertIn('from xosmodel_header import *', xmf)
+ self.assertIn('class XOSModel(XOSBase):', xmf)
+
+ xosbase = OUTPUT_DIR + '/xosbase.py'
+ self.assertTrue(os.path.isfile(xosbase))
+ xbf = open(xosbase).read()
+ self.assertIn('header import *', xbf)
+ self.assertIn('class XOSBase(models.Model, PlModelMixIn):', xbf)
+
+ def _test_write_multiple_files(self):
+ """
+ [XOS-GenX] read multiple models as input, print one file per model
+ """
+ args = FakeArgs()
+ args.files = [TEST_XPROTO, VROUTER_XPROTO]
+ args.target = TEST_TARGET
+ args.output = OUTPUT_DIR
+ args.dest_extension = 'txt'
+ args.write_to_file = 'model'
+ XOSGenerator.generate(args)
+
+ generated_files = [f for f in os.listdir(OUTPUT_DIR) if not f.startswith('.')]
+ self.assertEqual(len(generated_files), 2)
+
+ xosmodel = open(os.path.join(OUTPUT_DIR, 'xosmodel.txt'), "r").read()
+ vrouterport = open(os.path.join(OUTPUT_DIR, 'vrouterport.txt'), "r").read()
+
+ self.assertIn("name: XOSModel", xosmodel)
+ self.assertIn("name: VRouterPort", vrouterport)
+
+ def test_write_multiple_files_from_xtarget(self):
+ """
+ [XOS-GenX] read multiple models as input, print separate files based on +++
+ """
+ args = FakeArgs()
+ args.files = [TEST_XPROTO, VROUTER_XPROTO]
+ args.target = SPLIT_TARGET
+ args.output = OUTPUT_DIR
+ args.write_to_file = 'target'
+ XOSGenerator.generate(args)
+
+ generated_files = [f for f in os.listdir(OUTPUT_DIR) if not f.startswith('.')]
+ self.assertEqual(len(generated_files), 2)
+
+ xosmodel = open(os.path.join(OUTPUT_DIR, 'xosmodel.txt'), "r").read()
+ vrouterport = open(os.path.join(OUTPUT_DIR, 'vrouterport.txt'), "r").read()
+
+ self.assertIn("name: XOSModel", xosmodel)
+ self.assertIn("name: VRouterPort", vrouterport)
+
+ def _test_skip_django(self):
+ args = FakeArgs()
+ args.files = [SKIP_DJANGO_XPROTO]
+ args.target = 'django.xtarget'
+ args.output = OUTPUT_DIR
+ args.dest_extension = 'py'
+ args.write_to_file = 'model'
+ output = XOSGenerator.generate(args)
+
+ # should not print a file if options.skip_django = True
+ file = OUTPUT_DIR + '/user.py'
+ self.assertFalse(os.path.isfile(file))
+
+ def test_service_order(self):
+ args = FakeArgs()
+ args.files = [BASE_XPROTO, TEST_XPROTO, VROUTER_XPROTO]
+ args.target = 'service.xtarget'
+ args.output = OUTPUT_DIR
+ args.write_to_file = 'target'
+ output = XOSGenerator.generate(args)
+
+ model = OUTPUT_DIR + '/models.py'
+ self.assertTrue(os.path.isfile(model))
+ line_num = 0
+
+ for line in open(model).readlines():
+ line_num += 1
+ if line.find('class XOSBase(models.Model, PlModelMixIn):') >= 0:
+ base_line = line_num
+ if line.find('XOSModel(XOSBase):') >= 0:
+ xosmodel_line = line_num
+ if line.find('class VRouterPort(XOSBase):') >= 0:
+ vrouter_line = line_num
+ self.assertLess(base_line, xosmodel_line)
+ self.assertLess(xosmodel_line, vrouter_line)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/lib/xos-genx/tests/translator_test.py b/lib/xos-genx/tests/translator_test.py
new file mode 100644
index 0000000..a8212f4
--- /dev/null
+++ b/lib/xos-genx/tests/translator_test.py
@@ -0,0 +1,117 @@
+import unittest
+import os
+from xosgenx.generator import XOSGenerator
+from helpers import FakeArgs
+import yaml
+
+PROTO_EXPECTED_OUTPUT = """
+message VRouterPort {
+ option bases = "XOSBase";
+ optional string name = 1 [ null = "True", max_length = "20", blank = "True", help_text = ""port friendly name"", modifier = "optional", db_index = "False" ];
+ required string openflow_id = 2 [ null = "False", max_length = "21", blank = "False", help_text = ""port identifier in ONOS"", modifier = "required", db_index = "False" ];
+ required int32 vrouter_device = 3 [ null = "False", blank = "False", model = "VRouterDevice", modifier = "required", type = "link", port = "ports", link_type = "manytoone", db_index = "True" ];
+ required int32 vrouter_service = 4 [ null = "False", blank = "False", model = "VRouterService", modifier = "required", type = "link", port = "device_ports", link_type = "manytoone", db_index = "True" ];
+}
+"""
+VROUTER_XPROTO = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/xproto/vrouterport.xproto")
+
+# Generate other formats from xproto
+class XProtoTranslatorTest(unittest.TestCase):
+ def _test_proto_generator(self):
+ args = FakeArgs()
+ args.files = [VROUTER_XPROTO]
+ args.target = 'proto.xtarget'
+ output = XOSGenerator.generate(args)
+ self.assertEqual(output, PROTO_EXPECTED_OUTPUT)
+
+ def test_yaml_generator(self):
+ xproto = \
+"""
+option app_label = "test";
+
+message Port (PlCoreBase,ParameterMixin){
+ required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+ 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];
+ optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+ required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
+
+message Instance (PlCoreBase){
+ optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+ optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+ required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+ optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+ optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+ required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+ optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+ required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+ required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+ required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+ required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+ required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+ optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+ required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+ optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+ required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
+
+message Network (PlCoreBase,ParameterMixin) {
+ required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+ required manytoone template->NetworkTemplate:network = 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];
+ required string end_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+ optional string ports = 6 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string labels = 7 [db_index = False, max_length = 1024, null = True, blank = True];
+ required manytoone owner->Slice:ownedNetworks = 8 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
+ required int32 guaranteed_bandwidth = 9 [default = 0, null = False, db_index = False, blank = False];
+ required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
+ optional string topology_parameters = 11 [db_index = False, null = True, blank = True];
+ optional string controller_url = 12 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string controller_parameters = 13 [db_index = False, null = True, blank = True];
+ optional string network_id = 14 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+ optional string router_id = 15 [help_text = "Quantum router id", max_length = 256, null = True, db_index = False, blank = True];
+ optional string subnet_id = 16 [help_text = "Quantum subnet id", max_length = 256, null = True, db_index = False, blank = True];
+ required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
+ required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
+ required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
+ required manytomany instances->Instance/Port:networks = 20 [db_index = False, null = False, blank = True];
+}
+
+message Slice (PlCoreBase){
+ required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
+ required bool enabled = 2 [help_text = "Status for this Slice", default = True, null = False, db_index = False, blank = True];
+ required bool omf_friendly = 3 [default = False, null = False, db_index = False, blank = True];
+ required string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True];
+ required string slice_url = 5 [db_index = False, max_length = 512, null = False, content_type = "url", blank = True];
+ required manytoone site->Site:slices = 6 [help_text = "The Site this Slice belongs to", null = False, db_index = True, blank = False];
+ required int32 max_instances = 7 [default = 10, null = False, db_index = False, blank = False];
+ optional manytoone service->Service:slices = 8 [db_index = True, null = True, blank = True];
+ optional string network = 9 [blank = True, max_length = 256, null = True, db_index = False, choices = "((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))"];
+ optional string exposed_ports = 10 [db_index = False, max_length = 256, null = True, blank = True];
+ optional manytoone serviceClass->ServiceClass:slices = 11 [db_index = True, null = True, blank = True];
+ optional manytoone creator->User:slices = 12 [db_index = True, null = True, blank = True];
+ optional manytoone default_flavor->Flavor:slices = 13 [db_index = True, null = True, blank = True];
+ optional manytoone default_image->Image:slices = 14 [db_index = True, null = True, blank = True];
+ optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
+ optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
+ required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+ required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
+}
+"""
+
+ args = FakeArgs()
+ args.inputs = xproto
+ args.target = 'modeldefs.xtarget'
+ output = XOSGenerator.generate(args)
+
+ yaml_ir = yaml.load(output)
+ self.assertEqual(len(yaml_ir['items']), 4)
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/lib/xos-genx/tests/xproto/base.xproto b/lib/xos-genx/tests/xproto/base.xproto
new file mode 100644
index 0000000..78cc4e8
--- /dev/null
+++ b/lib/xos-genx/tests/xproto/base.xproto
@@ -0,0 +1,18 @@
+option app_name = "core";
+
+message XOSBase {
+ option skip_init = True;
+ required string created = 1 [content_type = "date", auto_now_add = True];
+ required string updated = 2 [default = "now()", content_type = "date"];
+ optional string enacted = 3 [null = True, content_type = "date", blank = True, default = None];
+ optional string policed = 4 [null = True, content_type = "date", blank = True, default = None];
+ optional string backend_register = 5 [default = "{}", max_length = 1024, null = True];
+ required bool backend_need_delete = 6 [default = False];
+ required bool backend_need_reap = 7 [default = False];
+ required string backend_status = 8 [default = "0 - Provisioning in progress", max_length = 1024];
+ required bool deleted = 9 [default = False];
+ required bool write_protect = 10 [default = False];
+ required bool lazy_blocked = 11 [default = False];
+ required bool no_sync = 12 [default = False];
+ required bool no_policy = 13 [default = False];
+}
\ No newline at end of file
diff --git a/lib/xos-genx/tests/xproto/skip_django.xproto b/lib/xos-genx/tests/xproto/skip_django.xproto
new file mode 100644
index 0000000..8dd1039
--- /dev/null
+++ b/lib/xos-genx/tests/xproto/skip_django.xproto
@@ -0,0 +1,4 @@
+message User (AbstractBaseUser,PlModelMixIn) {
+ option skip_django = True;
+ required string email = 1 [db_index = True, max_length = 255, null = False, blank = False];
+}
\ No newline at end of file
diff --git a/lib/xos-genx/tests/xproto/test.xproto b/lib/xos-genx/tests/xproto/test.xproto
new file mode 100644
index 0000000..54420fa
--- /dev/null
+++ b/lib/xos-genx/tests/xproto/test.xproto
@@ -0,0 +1,4 @@
+message XOSModel (XOSBase) {
+ required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Help Name", null = False, db_index = False];
+ required string files = 2 [max_length = 1024, content_type = "stripped", blank = False, help_text = "Help Files", null = False, db_index = False];
+}
\ No newline at end of file
diff --git a/lib/xos-genx/tests/xproto/vrouterport.xproto b/lib/xos-genx/tests/xproto/vrouterport.xproto
new file mode 100644
index 0000000..1b092ac
--- /dev/null
+++ b/lib/xos-genx/tests/xproto/vrouterport.xproto
@@ -0,0 +1,6 @@
+message VRouterPort (XOSBase){
+ optional string name = 1 [help_text = "port friendly name", max_length = 20, null = True, db_index = False, blank = True];
+ required string openflow_id = 2 [help_text = "port identifier in ONOS", max_length = 21, null = False, db_index = False, blank = False];
+ required manytoone vrouter_device->VRouterDevice:ports = 3 [db_index = True, null = False, blank = False];
+ required manytoone vrouter_service->VRouterService:device_ports = 4 [db_index = True, null = False, blank = False];
+}
\ No newline at end of file
diff --git a/lib/xos-genx/tests/xtarget/split.xtarget b/lib/xos-genx/tests/xtarget/split.xtarget
new file mode 100644
index 0000000..2d55f43
--- /dev/null
+++ b/lib/xos-genx/tests/xtarget/split.xtarget
@@ -0,0 +1,10 @@
+{% for m in proto.messages %}
+ name: {{ m.name }}
+ fields:
+ {%- for f in m.fields %}
+ {{ f.name }}:
+ type: {{ f.type }}
+ description: {{ f.options.help_text }}
+ {%- endfor %}
++++ {{ m.name }}.txt
+{% endfor %}
diff --git a/lib/xos-genx/tests/xtarget/test.xtarget b/lib/xos-genx/tests/xtarget/test.xtarget
new file mode 100644
index 0000000..658784c
--- /dev/null
+++ b/lib/xos-genx/tests/xtarget/test.xtarget
@@ -0,0 +1,9 @@
+{% for m in proto.messages %}
+ name: {{ m.name }}
+ fields:
+ {%- for f in m.fields %}
+ {{ f.name }}:
+ type: {{ f.type }}
+ description: {{ f.options.help_text }}
+ {%- endfor %}
+{% endfor %}
\ No newline at end of file