SEBA-420 rename varchar to text; validation on max_length

Change-Id: I71791d27024260572e552936d39cb1f07ddaab38
diff --git a/Makefile b/Makefile
index 58f9574..872f8d0 100644
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,10 @@
 	source ./venv-xos/bin/activate ; set -u ;\
 	xos-migrate --xos-dir . -s core --check
 
+core-xproto-test: venv-xos
+	source ./venv-xos/bin/activate ; set -u ;\
+	xosgenx xos/core/models/core.xproto --lint --strict
+
 clean:
 	find . -name '*.pyc' | xargs rm -f
 	find . -name '__pycache__' | xargs rm -rf
diff --git a/VERSION b/VERSION
index fd2a018..944880f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.1.0
+3.2.0
diff --git a/containers/chameleon/Dockerfile.chameleon b/containers/chameleon/Dockerfile.chameleon
index 50bc364..e49fb23 100644
--- a/containers/chameleon/Dockerfile.chameleon
+++ b/containers/chameleon/Dockerfile.chameleon
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/chameleon
-FROM xosproject/xos-base:3.1.0
+FROM xosproject/xos-base:3.2.0
 
 # xos-base already has protoc and dependencies installed
 
diff --git a/containers/xos/Dockerfile.client b/containers/xos/Dockerfile.client
index badba78..1018612 100644
--- a/containers/xos/Dockerfile.client
+++ b/containers/xos/Dockerfile.client
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-client
-FROM xosproject/xos-libraries:3.1.0
+FROM xosproject/xos-libraries:3.2.0
 
 # Label image
 ARG org_label_schema_schema_version=1.0
diff --git a/containers/xos/Dockerfile.libraries b/containers/xos/Dockerfile.libraries
index 6124e3b..ed1c7e8 100644
--- a/containers/xos/Dockerfile.libraries
+++ b/containers/xos/Dockerfile.libraries
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-libraries
-FROM xosproject/xos-base:3.1.0
+FROM xosproject/xos-base:3.2.0
 
 # Add libraries
 COPY lib /opt/xos/lib
diff --git a/containers/xos/Dockerfile.synchronizer-base b/containers/xos/Dockerfile.synchronizer-base
index b906e1a..f1b4263 100644
--- a/containers/xos/Dockerfile.synchronizer-base
+++ b/containers/xos/Dockerfile.synchronizer-base
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-synchronizer-base
-FROM xosproject/xos-client:3.1.0
+FROM xosproject/xos-client:3.2.0
 
 RUN mkdir -p /opt/xos/synchronizers
 
diff --git a/containers/xos/Dockerfile.xos-core b/containers/xos/Dockerfile.xos-core
index 8eaf7f3..7a659eb 100644
--- a/containers/xos/Dockerfile.xos-core
+++ b/containers/xos/Dockerfile.xos-core
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-core
-FROM xosproject/xos-libraries:3.1.0
+FROM xosproject/xos-libraries:3.2.0
 
 # Install XOS
 ADD xos /opt/xos
diff --git a/docs/dev/xosgenx.md b/docs/dev/xosgenx.md
index 53e7374..53a03e5 100644
--- a/docs/dev/xosgenx.md
+++ b/docs/dev/xosgenx.md
@@ -106,7 +106,7 @@
 
      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 string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True, varchar = 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, text = 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];
diff --git a/lib/xos-genx/xos-genx-tests/test_swagger.py b/lib/xos-genx/xos-genx-tests/test_swagger.py
index 1886a76..5b6d1f9 100644
--- a/lib/xos-genx/xos-genx-tests/test_swagger.py
+++ b/lib/xos-genx/xos-genx-tests/test_swagger.py
@@ -47,7 +47,7 @@
                  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", 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, varchar = True];
+                 optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True, text = 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];
diff --git a/lib/xos-genx/xos-genx-tests/test_validator.py b/lib/xos-genx/xos-genx-tests/test_validator.py
new file mode 100644
index 0000000..1a50082
--- /dev/null
+++ b/lib/xos-genx/xos-genx-tests/test_validator.py
@@ -0,0 +1,125 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import
+import unittest
+import os
+from xosgenx.validator import XProtoValidator
+from xosgenx.generator import XOSProcessor, XOSProcessorArgs
+from mock import patch
+import yaml
+
+# Generate other formats from xproto
+
+
+class XProtoValidatorTest(unittest.TestCase):
+    def test_suggested_max_length(self):
+        args = XOSProcessorArgs()
+        args.files = ["/tmp/testvalidator.xproto"]
+
+        open("/tmp/testvalidator.xproto", "w").write("""
+            option app_label = "test";
+
+            message Port (XOSBase){
+                required string foo = 1 [max_length=254];
+            }
+            """)
+        args.target = "modeldefs.xtarget"
+
+        with patch.object(XProtoValidator, "print_errors", autospec=True) as print_errors:
+            print_errors.return_value = None
+
+            output = XOSProcessor.process(args)
+
+            self.assertEqual(print_errors.call_count, 1)
+            validator = print_errors.call_args[0][0]
+
+            self.assertEqual(len(validator.errors), 1)
+            self.assertEqual(validator.errors[0]["severity"], "WARNING")
+            self.assertEqual(validator.errors[0]["message"], "max_length of 254 is close to suggested max_length of 256")
+
+    def test_max_length_okay(self):
+        args = XOSProcessorArgs()
+        args.files = ["/tmp/testvalidator.xproto"]
+
+        open("/tmp/testvalidator.xproto", "w").write("""
+            option app_label = "test";
+
+            message Port (XOSBase){
+                required string foo = 1 [max_length=256];
+            }
+            """)
+        args.target = "modeldefs.xtarget"
+
+        with patch.object(XProtoValidator, "print_errors", autospec=True) as print_errors:
+            print_errors.return_value = None
+
+            output = XOSProcessor.process(args)
+
+            self.assertEqual(print_errors.call_count, 0)
+
+    def test_max_length_zero(self):
+        args = XOSProcessorArgs()
+        args.files = ["/tmp/testvalidator.xproto"]
+
+        open("/tmp/testvalidator.xproto", "w").write("""
+            option app_label = "test";
+
+            message Port (XOSBase){
+                required string foo = 1 [max_length=0];
+            }
+            """)
+        args.target = "modeldefs.xtarget"
+
+        with patch.object(XProtoValidator, "print_errors", autospec=True) as print_errors:
+            print_errors.return_value = None
+
+            output = XOSProcessor.process(args)
+
+            self.assertEqual(print_errors.call_count, 1)
+            validator = print_errors.call_args[0][0]
+
+            self.assertEqual(len(validator.errors), 1)
+            self.assertEqual(validator.errors[0]["severity"], "ERROR")
+            self.assertEqual(validator.errors[0]["message"], "max_length should not be zero")
+
+
+    def test_charfield_missing_max_length(self):
+        args = XOSProcessorArgs()
+        args.files = ["/tmp/testvalidator.xproto"]
+
+        open("/tmp/testvalidator.xproto", "w").write("""
+            option app_label = "test";
+
+            message Port (XOSBase){
+                required string foo = 1 [];
+            }
+            """)
+        args.target = "modeldefs.xtarget"
+
+        with patch.object(XProtoValidator, "print_errors", autospec=True) as print_errors:
+            print_errors.return_value = None
+
+            output = XOSProcessor.process(args)
+
+            self.assertEqual(print_errors.call_count, 1)
+            validator = print_errors.call_args[0][0]
+
+            self.assertEqual(len(validator.errors), 1)
+            self.assertEqual(validator.errors[0]["severity"], "ERROR")
+            self.assertEqual(validator.errors[0]["message"], "String field should have a max_length or text=True")
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/lib/xos-genx/xosgenx/generator.py b/lib/xos-genx/xosgenx/generator.py
index 707f87b..d1dc99a 100644
--- a/lib/xos-genx/xosgenx/generator.py
+++ b/lib/xos-genx/xosgenx/generator.py
@@ -52,6 +52,7 @@
     )  # If neither include_models nor include_apps is specified, then all models will
     default_include_apps = []  # be included.
     default_strict_validation = False
+    default_lint = False
 
     def __init__(self, **kwargs):
         # set defaults
@@ -68,6 +69,7 @@
         self.include_models = XOSProcessorArgs.default_include_models
         self.include_apps = XOSProcessorArgs.default_include_apps
         self.strict_validation = XOSProcessorArgs.default_strict_validation
+        self.lint = XOSProcessorArgs.default_lint
 
         # override defaults with kwargs
         for (k, v) in kwargs.items():
@@ -267,19 +269,6 @@
         else:
             raise Exception("[XosGenX] No inputs provided!")
 
-        if not operator:
-            operator = args.target
-            template_path = XOSProcessor._get_template(operator)
-        else:
-            template_path = operator
-
-        [template_folder, template_name] = os.path.split(template_path)
-        os_template_loader = jinja2.FileSystemLoader(searchpath=[template_folder])
-        os_template_env = jinja2.Environment(loader=os_template_loader)
-        os_template_env = XOSProcessor._load_jinja2_extensions(
-            os_template_env, args.attic
-        )
-        template = os_template_env.get_template(template_name)
         context = XOSProcessor._add_context(args)
 
         parser = plyxproto.ProtobufAnalyzer()
@@ -337,9 +326,27 @@
         if validator.errors:
             if args.strict_validation or (args.verbosity >= 0):
                 validator.print_errors()
-            if args.strict_validation:
+            fatal_errors = [x for x in validator.errors if x["severity"] == "ERROR"]
+            if fatal_errors and args.strict_validation:
                 sys.exit(-1)
 
+        if args.lint:
+            return ""
+
+        if not operator:
+            operator = args.target
+            template_path = XOSProcessor._get_template(operator)
+        else:
+            template_path = operator
+
+        [template_folder, template_name] = os.path.split(template_path)
+        os_template_loader = jinja2.FileSystemLoader(searchpath=[template_folder])
+        os_template_env = jinja2.Environment(loader=os_template_loader)
+        os_template_env = XOSProcessor._load_jinja2_extensions(
+            os_template_env, args.attic
+        )
+        template = os_template_env.get_template(template_name)
+
         if args.output is not None and args.write_to_file == "model":
             rendered = {}
             for i, model in enumerate(v.models):
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/base.py b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
index 7e10e7d..fd4808e 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/base.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
@@ -318,15 +318,13 @@
 
 
 def xproto_string_type(xptags):
-    # FIXME: this try/except block assigns but never uses max_length?
-    #   try:
-    #       max_length = eval(xptags["max_length"])
-    #   except BaseException:
-    #       max_length = 1024
-
-    if "varchar" not in xptags:
+    if "text" not in xptags:
+        # String fields have a mandatory maximum length.
+        # They are intended for relatively short strings.
         return "string"
     else:
+        # Text fields have an optional maximuim length.
+        # They are intended for long, potentially multiline strings.
         return "text"
 
 
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/django.py b/lib/xos-genx/xosgenx/jinja2_extensions/django.py
index 6fac823..613462c 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/django.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/django.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import, print_function
-from .base import unquote
+from .base import unquote, xproto_string_type
 import re
 import sys
 from six.moves import map
@@ -41,17 +41,23 @@
 
 
 def django_string_type(xptags):
-    try:
-        max_length = eval(xptags["max_length"])
-    except BaseException:
-        max_length = 1024 * 1024
-
-    if "content_type" in xptags:
-        return django_content_type_string(xptags)
-    elif int(max_length) < 1024 * 1024:
-        return "CharField"
-    else:
+    # xproto_string_type will return "string" if the options.text=False or "text" if options.text=True
+    xtype = xproto_string_type(xptags)
+    if xtype == "string":
+        if "content_type" in xptags:
+            return django_content_type_string(xptags)
+        else:
+            # TODO(smbaker): This is a workaround for incorrect xproto in many services. Prior behavior was to
+            # toggle between Charfield and Textfield when max_length was unspecified, rather than to require
+            # max_length to be specified. Remove this workaround as soon as services have been migrated.
+            if "max_length" in xptags:
+                return "CharField"
+            else:
+                return "TextField"
+    elif xtype == "text":
         return "TextField"
+    else:
+        raise Exception("Unknown xproto_string type %s" % xtype, xptags=xptags)
 
 
 def xproto_django_type(xptype, xptags):
diff --git a/lib/xos-genx/xosgenx/validator.py b/lib/xos-genx/xosgenx/validator.py
index bf73b84..d95c5d7 100644
--- a/lib/xos-genx/xosgenx/validator.py
+++ b/lib/xos-genx/xosgenx/validator.py
@@ -32,7 +32,7 @@
                   "feedback_state", "unique", "unique_with"]
 
 # Options that must be either "True" or "False"
-BOOLEAN_OPTIONS = ["blank", "db_index", "feedback_state", "gui_hidden", "null", "tosca_key", "unique", "varchar"]
+BOOLEAN_OPTIONS = ["blank", "db_index", "feedback_state", "gui_hidden", "null", "tosca_key", "unique", "text"]
 
 
 class XProtoValidator(object):
@@ -45,7 +45,7 @@
         self.line_map = line_map
         self.errors = []
 
-    def error(self, model, field, message):
+    def error(self, model, field, message, severity="ERROR"):
         if field and field.get("_linespan"):
             error_first_line_number = field["_linespan"][0]
             error_last_line_number = field["_linespan"][1]
@@ -61,7 +61,8 @@
             error_filename = fn
             error_line_offset = start_line
 
-        self.errors.append({"model": model,
+        self.errors.append({"severity": severity,
+                            "model": model,
                             "field": field,
                             "message": message,
                             "filename": error_filename,
@@ -69,6 +70,9 @@
                             "last_line_number": error_last_line_number - error_line_offset,
                             "absolute_line_number": error_first_line_number})
 
+    def warning(self, *args, **kwargs):
+        self.error(*args, severity="WARNING", **kwargs)
+
     def print_errors(self):
         # Sort by line number
         for error in sorted(self.errors, key=lambda error: error["absolute_line_number"]):
@@ -83,12 +87,13 @@
             else:
                 linestr = "%d" % first_line_number
 
-            print("[ERROR] %s:%s %s.%s (Type %s): %s" % (os.path.basename(error["filename"]),
-                                                         linestr,
-                                                         model.get("name"),
-                                                         field.get("name"),
-                                                         field.get("type"),
-                                                         message), file=sys.stderr)
+            print("[%s] %s:%s %s.%s (Type %s): %s" % (error["severity"],
+                                                      os.path.basename(error["filename"]),
+                                                      linestr,
+                                                      model.get("name"),
+                                                      field.get("name"),
+                                                      field.get("type"),
+                                                      message), file=sys.stderr)
 
     def is_option_true(self, field, name):
         options = field.get("options")
@@ -185,7 +190,30 @@
         self.check_modifier_consistent(model, field)
         self.allow_options(model, field,
                            ["blank", "choices", "content_type", "db_index", "default",
-                            "max_length", "modifier", "null", "varchar"])
+                            "max_length", "modifier", "null", "text"])
+
+        # max_length is a mandatory argument of CharField.
+        if (content_type in [None]) and \
+           (not self.is_option_true(field, "text")) and \
+           ("max_length" not in field["options"]):
+            self.error(model, field, "String field should have a max_length or text=True")
+
+        if "max_length" in field["options"]:
+            max_length = field["options"]["max_length"]
+            try:
+                max_length = int(max_length)
+                if (max_length == 0):
+                    self.error(model, field, "max_length should not be zero")
+
+                if 0 < abs(256-max_length) < 3:
+                    self.warning(model, field,
+                                 "max_length of %s is close to suggested max_length of 256" % max_length)
+
+                if 0 < abs(1024-max_length) < 3:
+                    self.warning(model, field,
+                                 "max_length of %s is close to suggested max_length of 1024" % max_length)
+            except ValueError:
+                self.error(model, field, "max_length must be a number")
 
     def validate_field_bool(self, model, field):
         self.check_modifier_consistent(model, field)
diff --git a/lib/xos-genx/xosgenx/xosgen.py b/lib/xos-genx/xosgenx/xosgen.py
index 608cae7..e223660 100755
--- a/lib/xos-genx/xosgenx/xosgen.py
+++ b/lib/xos-genx/xosgenx/xosgen.py
@@ -116,6 +116,14 @@
     default=XOSProcessorArgs.default_checkers,
     help="Comma-separated list of static checkers",
 )
+group.add_argument(
+    "--lint",
+    dest="lint",
+    action="store_true",
+    default=XOSProcessorArgs.default_lint,
+    help="Parse the xproto but don't execute any xtargets",
+)
+
 
 parse.add_argument(
     "files",
@@ -135,6 +143,7 @@
 
 CHECK = 1
 GEN = 2
+LINT = 3
 
 
 class XosGen:
@@ -148,17 +157,17 @@
         if args.target:
             op = GEN
             subdir = "/targets/"
+            operators = [args.target]
         elif args.checkers:
             op = CHECK
             subdir = "/checkers/"
+            operators = args.checkers
+        elif args.lint:
+            op = LINT
+            subdir = None
+            operators = []
         else:
-            parse.error("At least one of --target and --checkers is required")
-
-        operators = (
-            args.checkers.split(",")
-            if hasattr(args, "checkers") and args.checkers
-            else [args.target]
-        )
+            parse.error("At least one of --target, --checkers, or --lint is required")
 
         for i in range(len(operators)):
             if "/" not in operators[i]:
@@ -202,9 +211,12 @@
                     '--dest-extension requires --write-to-file to be set to "model"'
                 )
 
-        else:
+        elif op == CHECK:
             if args.write_to_file or args.dest_extension:
                 parse.error("Checkers cannot write to files")
+        elif op == LINT:
+            # no outputs, so nothing to check
+            pass
 
         inputs = []
 
@@ -238,3 +250,5 @@
                     exit(1)
                 else:
                     print("%s: OK" % o)
+        elif op == LINT:
+            XOSProcessor.process(args, None)
diff --git a/xos/core/migrations/0010_auto_20190408_2122.py b/xos/core/migrations/0010_auto_20190408_2122.py
new file mode 100644
index 0000000..02f368a
--- /dev/null
+++ b/xos/core/migrations/0010_auto_20190408_2122.py
@@ -0,0 +1,49 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-04-09 01:22
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0009_auto_20190313_1442'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='service_decl',
+            name='description',
+            field=models.TextField(blank=True, help_text=b'Description of Service', null=True),
+        ),
+        migrations.AlterField(
+            model_name='service_decl',
+            name='public_key',
+            field=models.TextField(blank=True, help_text=b'Public key string', null=True),
+        ),
+        migrations.AlterField(
+            model_name='slice_decl',
+            name='description',
+            field=models.TextField(blank=True, help_text=b'High level description of the slice and expected activities', null=True),
+        ),
+        migrations.AlterField(
+            model_name='trustdomain_decl',
+            name='name',
+            field=models.CharField(db_index=True, help_text=b'Name of this trust domain', max_length=256),
+        ),
+    ]
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 69bcf41..8fbd9b6 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -50,16 +50,16 @@
      option description = "An XOS User";
 
      // field 1 is reserved for "id"
-     required string email = 2 [db_index = True, max_length = 255, blank = False, tosca_key=True];
-     required string username = 3 [default = "Something", max_length = 255, content_type = "stripped", blank = False, db_index = False];
-     required string password = 4 [default = "Something", max_length = 255, blank = False, db_index = False];
+     required string email = 2 [db_index = True, max_length = 256, blank = False, tosca_key=True];
+     required string username = 3 [default = "Something", max_length = 256, content_type = "stripped", blank = False, db_index = False];
+     required string password = 4 [default = "Something", max_length = 256, blank = False, db_index = False];
      optional string last_login = 5 [db_index = False, content_type = "date", blank = True];
      required string firstname = 6 [max_length = 200, content_type = "stripped", blank = False, help_text = "person's given name", db_index = False];
      required string lastname = 7 [max_length = 200, content_type = "stripped", blank = False, help_text = "person's surname", db_index = False];
      optional string phone = 8 [max_length = 100, content_type = "stripped", blank = True, help_text = "phone number contact", db_index = False];
      optional string user_url = 9 [db_index = False, max_length = 200, content_type = "url", blank = True];
      required manytoone site->Site:users = 10:1001 [help_text = "Site this user will be homed too", db_index = True, blank = False];
-     optional string public_key = 11 [help_text = "Public key string", max_length = 1024, db_index = False, blank = True, varchar = True];
+     optional string public_key = 11 [help_text = "Public key string", db_index = False, blank = True, text = True];
      required bool is_active = 12 [default = True, db_index = False];
      required bool is_admin = 13 [default = False, db_index = False];
      required bool is_staff = 14 [default = True, db_index = False];
@@ -111,11 +111,11 @@
 
 message AddressPool (XOSBase) {
      required string name = 1 [db_index = False, max_length = 32, blank = False, unique = True, help_text="Name of this AddressPool"];
-     optional string addresses = 2 [db_index = False, blank = True, varchar = True, help_text="Space-separated list of available addresses"];
+     optional string addresses = 2 [db_index = False, blank = True, text = True, help_text="Space-separated list of available addresses"];
      required string gateway_ip = 3 [max_length = 32, help_text="Gateway IP address for this AddressPool"];
      required string gateway_mac = 4 [max_length = 32, help_text="Gateway MAC address for this AddressPool"];
      required string cidr = 5 [max_length = 32, help_text="Subnet for this AddressPool"];
-     optional string inuse = 6 [db_index = False, blank = True, varchar = True, help_text="Space-separated list of inuse addresses"];
+     optional string inuse = 6 [db_index = False, blank = True, text = True, help_text="Space-separated list of inuse addresses"];
      optional manytoone service->Service:addresspools = 7:1001 [db_index = True, blank = True, help_text="Service this AddressPool belongs to"];
 }
 
@@ -270,8 +270,7 @@
 
      optional string description = 1 [
          help_text = "Description of Service",
-         max_length = 254,
-         varchar = True];
+         text = True];
      required bool enabled = 2 [
          help_text = "Whether or not service is Enabled",
          default = True,
@@ -302,8 +301,7 @@
      optional string public_key = 9 [
          help_text = "Public key string",
          gui_hidden = True,
-         max_length = 4096,
-         varchar = True]; // likely only used by VM-based services. deprecated?
+         text = True]; // likely only used by VM-based services. deprecated?
      optional string private_key_fn = 10 [
          help_text = "Filename of private key file, located within core container",
          content_type = "stripped",
@@ -316,7 +314,7 @@
      optional string service_specific_attribute = 12 [
          help_text = "Service-specific string attribute, opaque to XOS core",
          gui_hidden = True,
-         varchar = True];
+         text = True];
 }
 
 message ServicePort (XOSBase) {
@@ -347,7 +345,7 @@
          unique_with="service"];
      required string value = 2 [
          help_text = "Attribute Value",
-         varchar = True];
+         text = True];
      required manytoone service->Service:serviceattributes = 3:1003 [
          help_text = "The Service this attribute is associated with",
          db_index = True];
@@ -395,7 +393,7 @@
 
      required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", db_index = False, unique = True];
      required bool enabled = 2 [help_text = "Status for this Slice", default = True];
-     optional string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, varchar = True];
+     optional string description = 4 [help_text = "High level description of the slice and expected activities", text = True];
      required manytoone site->Site:slices = 6:1005 [help_text = "The Site this Slice belongs to", db_index = True, blank = False];
      required int32 max_instances = 7 [default = 10, db_index = False, blank = False];
      optional manytoone service->Service:slices = 8:1006 [db_index = True, blank = True];
@@ -470,7 +468,7 @@
      optional string service_specific_attribute = 10 [
          help_text = "Service-specific text attribute, opaque to the XOS core",
          gui_hidden = True,
-         varchar = True];
+         text = True];
      optional uint32 link_deleted_count = 11 [
          help_text = "Incremented each time a provided_link is deleted from this ServiceInstance",
          default = 0,
@@ -511,14 +509,15 @@
          max_length = 128,
          unique_with = "service_instance"];
      required string value = 2 [
-         help_text = "Attribute Value"];
+         help_text = "Attribute Value",
+         text = True];
      required manytoone service_instance->ServiceInstance:service_instance_attributes = 3:1006 [
          help_text = "The Tenant this attribute is associated with",
          db_index = True];
 }
 
 message TrustDomain (XOSBase) {
-     required string name = 1 [max_length = 255, db_index = True, blank = False, help_text = "Name of this trust domain"];
+     required string name = 1 [max_length = 256, db_index = True, blank = False, help_text = "Name of this trust domain"];
      required manytoone owner->Service:owned_trust_domains = 2:1011 [db_index = True, blank = False, help_text = "Service partioned by this trust domain"];
 }