SEBA-513 Validation of xproto
Change-Id: I300e86c3b7b6839aa12d726d6bdf9ab59adece94
diff --git a/VERSION b/VERSION
index 23a63f5..a6333e4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.2.8
+2.2.9
diff --git a/containers/chameleon/Dockerfile.chameleon b/containers/chameleon/Dockerfile.chameleon
index ce1dd45..51fe650 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:2.2.8
+FROM xosproject/xos-base:2.2.9
# xos-base already has protoc and dependencies installed
diff --git a/containers/xos/Dockerfile.client b/containers/xos/Dockerfile.client
index 775524a..5f0e13f 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:2.2.8
+FROM xosproject/xos-libraries:2.2.9
# Install XOS client
COPY lib/xos-api /tmp/xos-api
diff --git a/containers/xos/Dockerfile.libraries b/containers/xos/Dockerfile.libraries
index ad02937..57aa334 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:2.2.8
+FROM xosproject/xos-base:2.2.9
# Add libraries
COPY lib /opt/xos/lib
diff --git a/containers/xos/Dockerfile.synchronizer-base b/containers/xos/Dockerfile.synchronizer-base
index 1fe2e05..3e5793a 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:2.2.8
+FROM xosproject/xos-client:2.2.9
COPY xos/synchronizers/new_base /opt/xos/synchronizers/new_base
COPY xos/xos/logger.py /opt/xos/xos/logger.py
diff --git a/containers/xos/Dockerfile.xos-core b/containers/xos/Dockerfile.xos-core
index 916d147..1c3d962 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:2.2.8
+FROM xosproject/xos-libraries:2.2.9
# Install XOS
ADD xos /opt/xos
diff --git a/lib/xos-genx/xosgenx/generator.py b/lib/xos-genx/xosgenx/generator.py
index 9a6a249..ec21d34 100644
--- a/lib/xos-genx/xosgenx/generator.py
+++ b/lib/xos-genx/xosgenx/generator.py
@@ -19,10 +19,12 @@
import plyxproto.parser as plyxproto
import yaml
from colorama import Fore
+import sys
from . import jinja2_extensions
from .proto2xproto import Proto2XProto
from .xos2jinja import XOS2Jinja
+from .validator import XProtoValidator
loader = jinja2.PackageLoader(__name__, "templates")
env = jinja2.Environment(loader=loader)
@@ -49,6 +51,7 @@
[]
) # If neither include_models nor include_apps is specified, then all models will
default_include_apps = [] # be included.
+ default_strict_validation = False
def __init__(self, **kwargs):
# set defaults
@@ -64,6 +67,7 @@
self.default_checkers = XOSProcessorArgs.default_target
self.include_models = XOSProcessorArgs.default_include_models
self.include_apps = XOSProcessorArgs.default_include_apps
+ self.strict_validation = XOSProcessorArgs.default_strict_validation
# override defaults with kwargs
for (k, v) in kwargs.items():
@@ -73,11 +77,18 @@
class XOSProcessor:
@staticmethod
def _read_input_from_files(files):
+ """ Read the files and return the combined text read.
+
+ Also returns a list of (line_number, filename) tuples that tell which
+ starting line corresponds to each file.
+ """
+ line_map = []
input = ""
for fname in files:
with open(fname) as infile:
+ line_map.append( (len(input.split("\n")), fname) )
input += infile.read()
- return input
+ return (input, line_map)
@staticmethod
def _attach_parser(ast, args):
@@ -249,9 +260,10 @@
raise Exception("[XosGenX] The output dir (%s) must be a directory!" % args.output)
if hasattr(args, "files"):
- inputs = XOSProcessor._read_input_from_files(args.files)
+ (inputs, line_map) = XOSProcessor._read_input_from_files(args.files)
elif hasattr(args, "inputs"):
inputs = args.inputs
+ line_map = []
else:
raise Exception("[XosGenX] No inputs provided!")
@@ -320,6 +332,14 @@
for message in v.messages:
message["is_included"] = True
+ validator = XProtoValidator(v.models, line_map)
+ validator.validate()
+ if validator.errors:
+ if args.strict_validation or (args.verbosity>=0):
+ validator.print_errors()
+ if args.strict_validation:
+ sys.exit(-1)
+
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/django.py b/lib/xos-genx/xosgenx/jinja2_extensions/django.py
index 0c34d3e..6fac823 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/django.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/django.py
@@ -124,11 +124,7 @@
if modifier == "required":
- if field_type == "string":
- if "blank" not in out: # if blank is already set, honor that value
- mod_out["blank"] = 'True' # by default, required strings can be blank
- else:
- mod_out["blank"] = 'False' # but other required fields can't be blank
+ mod_out["blank"] = 'False'
if link_type != "manytomany":
mod_out["null"] = 'False'
diff --git a/lib/xos-genx/xosgenx/validator.py b/lib/xos-genx/xosgenx/validator.py
new file mode 100644
index 0000000..384689b
--- /dev/null
+++ b/lib/xos-genx/xosgenx/validator.py
@@ -0,0 +1,246 @@
+# 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.
+
+"""
+ This module is used to validate xproto models and fields. The basic guiding principle is everything that isn't
+ specifically allowed here should be denied by default.
+
+ Note: While xproto must maintain some compatibility with django give the implementation choice of using django
+ in the core, it's the case that the allowable set of xproto options may be a subset of what is allowed under
+ django. For example, there may be django features that do not need exposure in xproto and/or are incompatible
+ with other design aspects of XOS such as the XOS gRPC API implementation.
+"""
+
+
+from __future__ import print_function
+import sys
+import os
+
+
+# Options that are always allowed
+COMMON_OPTIONS = ["help_text", "gui_hidden", "tosca_key", "tosca_key_one_of", "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"]
+
+class XProtoValidator(object):
+ def __init__(self, models, line_map):
+ """
+ models: a list of model definitions. Each model is a dictionary.
+ line_map: a list of tuples (start_line_no, filename) that tells which file goes with which line number.
+ """
+ self.models = models
+ self.line_map = line_map
+ self.errors = []
+
+ def error(self, model, field, message):
+ if field and field.get("_linespan"):
+ error_first_line_number = field["_linespan"][0]
+ error_last_line_number = field["_linespan"][1]
+ else:
+ error_first_line_number = model["_linespan"][0]
+ error_last_line_number = model["_linespan"][1]
+
+ error_filename = "unknown"
+ error_line_offset = 0
+ for (start_line, fn) in self.line_map:
+ if start_line>error_first_line_number:
+ break
+ error_filename = fn
+ error_line_offset = start_line
+
+ self.errors.append({"model": model,
+ "field": field,
+ "message": message,
+ "filename": error_filename,
+ "first_line_number": error_first_line_number - error_line_offset,
+ "last_line_number": error_last_line_number - error_line_offset,
+ "absolute_line_number": error_first_line_number})
+
+ def print_errors(self):
+ # Sort by line number
+ for error in sorted(self.errors, key=lambda error:error["absolute_line_number"]):
+ model = error["model"]
+ field = error["field"]
+ message = error["message"]
+ first_line_number = error["first_line_number"]
+ last_line_number = error["last_line_number"]
+
+ if first_line_number != last_line_number:
+ linestr = "%d-%d" % (first_line_number, last_line_number)
+ 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)
+
+ def is_option_true(self, field, name):
+ options = field.get("options")
+ if not options:
+ return False
+ option = options.get(name)
+ return option == "True"
+
+ def allow_options(self, model, field, options):
+ """ Only allow the options specified in `options`. If some option is present that isn't in allowed, then
+ register an error.
+
+ `options` is a list of options which can either be simple names, or `name=value`.
+ """
+ options = COMMON_OPTIONS + options
+
+ for (k, v) in field.get("options", {}).items():
+ allowed = False
+ for option in options:
+ if "=" in option:
+ (optname, optval) = option.split("=")
+ if optname==k and optval==v:
+ allowed = True
+ else:
+ if option==k:
+ allowed = True
+
+ if not allowed:
+ self.error(model, field, "Option %s=%s is not allowed" % (k,v))
+
+ if k in BOOLEAN_OPTIONS and (v not in ["True", "False"]):
+ self.error(model, field, "Option `%s` must be either True or False, but is '%s'" % (k, v))
+
+ def require_options(self, model, field, options):
+ """ Require an option to be present.
+ """
+ for optname in options:
+ if not field.get(optname):
+ self.error(model, field, "Required option '%s' is not present" % optname)
+
+ def check_modifier_consistent(self, model, field):
+ """ Validates that "modifier" is consistent with options.
+
+ Required/optional imply some settings for blank= and null=. These settings are dependent on the type
+ of field. See also jinja2_extensions/django.py which has to implement some of the same logic.
+ """
+ field_type = field["type"]
+ options = field.get("options", {})
+ modifier = options.get('modifier')
+ link_type = field.get("link_type")
+ mod_out = {}
+
+ if modifier == "required":
+ mod_out["blank"] = 'False'
+
+ if link_type != "manytomany":
+ mod_out["null"] = 'False'
+
+ elif modifier == "optional":
+ mod_out["blank"] = 'True'
+
+ # set defaults on link types
+ if link_type != "manytomany" and field_type != "bool":
+ mod_out["null"] = 'True'
+
+ else:
+ self.error(model, field, "Unknown modifier type '%s'" % modifier)
+
+ # print an error if there's a field conflict
+ for kmo in mod_out.keys():
+ if (kmo in options) and (options[kmo] != mod_out[kmo]):
+ self.error(model, field, "Option `%s`=`%s` is inconsistent with modifier `%s`" % (kmo, options[kmo], modifier))
+
+ def validate_field_date(self, model, field):
+ self.check_modifier_consistent(model, field)
+ self.allow_options(model, field, ["auto_now_add", "blank", "db_index", "default", "max_length", "modifier", "null", "content_type"])
+
+ def validate_field_string(self, model, field):
+ # A string with a `content_type="date"` is actually a date
+ # TODO: Investigate why there are double-quotes around "date"
+ content_type = field.get("options", {}).get("content_type")
+ if content_type in ["\"date\""]:
+ self.validate_field_date(model, field)
+ return
+
+ # TODO: Investigate why there are double-quotes around the content types
+ if content_type and content_type not in ["\"stripped\"", "\"ip\"", "\"url\""]:
+ self.error(model, field, "Content type %s is not allowed" % content_type)
+
+ self.check_modifier_consistent(model, field)
+ self.allow_options(model, field,
+ ["blank", "choices", "content_type", "db_index", "default", "max_length", "modifier", "null",
+ "varchar"])
+
+ def validate_field_bool(self, model, field):
+ self.check_modifier_consistent(model, field)
+ self.allow_options(model, field, ["db_index", "default=True", "default=False", "modifier", "null=False"])
+ self.require_options(model, field, ["default"])
+
+ def validate_field_float(self, model, field):
+ self.check_modifier_consistent(model, field)
+ self.allow_options(model, field, ["blank", "db_index", "default", "modifier", "null"])
+
+ def validate_field_link_onetomany(self, model, field):
+ self.check_modifier_consistent(model, field)
+ self.allow_options(model, field,
+ ["blank", "db_index", "default", "model", "link_type=manytoone",
+ "modifier", "null", "port", "type=link"])
+
+ def validate_field_link_manytomany(self, model, field):
+ self.check_modifier_consistent(model, field)
+ self.allow_options(model, field,
+ ["blank", "db_index", "default", "model", "link_type=manytomany",
+ "modifier", "null", "port", "type=link"])
+
+ def validate_field_link(self, model, field):
+ link_type = field.get("options",{}).get("link_type")
+ if link_type == "manytoone":
+ self.validate_field_link_onetomany(model, field)
+ elif link_type == "manytomany":
+ self.validate_field_link_manytomany(model, field)
+ else:
+ self.error("Unknown link_type %s" % link_type)
+
+ def validate_field_integer(self, model, field):
+ # An integer with an option "type=link" is actually a link
+ if field.get("options", {}).get("type") == "link":
+ self.validate_field_link(model, field)
+ return
+
+ self.check_modifier_consistent(model, field)
+ self.allow_options(model, field, ["blank", "db_index", "default", "max_value", "min_value", "modifier", "null"])
+
+ if self.is_option_true(field, "blank") and not self.is_option_true(field, "null"):
+ self.error(model, field, "If blank is true then null must also be true")
+
+ def validate_field(self, model, field):
+ if field["type"] == "string":
+ self.validate_field_string(model, field)
+ elif field["type"] in ["int32", "uint32"]:
+ self.validate_field_integer(model, field)
+ elif field["type"] == "float":
+ self.validate_field_float(model, field)
+ elif field["type"] == "bool":
+ self.validate_field_bool(model, field)
+ else:
+ self.error(model, field, "Unknown field type %s" % field["type"])
+
+ def validate_model(self, model):
+ for field in model["fields"]:
+ self.validate_field(model, field)
+
+ def validate(self):
+ """ Validate all models. This is the main entrypoint for validating xproto. """
+ for (name, model) in self.models.items():
+ self.validate_model(model)
\ No newline at end of file
diff --git a/lib/xos-genx/xosgenx/xos2jinja.py b/lib/xos-genx/xosgenx/xos2jinja.py
index e4ff7db..c61bf55 100644
--- a/lib/xos-genx/xosgenx/xos2jinja.py
+++ b/lib/xos-genx/xosgenx/xos2jinja.py
@@ -267,6 +267,7 @@
except BaseException:
pass
s["_type"] = "field"
+ s["_linespan"] = obj.linespan
self.stack.push(s)
return True
@@ -327,6 +328,7 @@
"package": self.package,
"fqn": model_name,
"rlinks": [],
+ "_linespan": obj.linespan, # first and last line number
}
try:
model_def["policy"] = obj.policy.pval
diff --git a/lib/xos-genx/xosgenx/xosgen.py b/lib/xos-genx/xosgenx/xosgen.py
index 2da8962..608cae7 100755
--- a/lib/xos-genx/xosgenx/xosgen.py
+++ b/lib/xos-genx/xosgenx/xosgen.py
@@ -125,6 +125,14 @@
help="xproto files to compile",
)
+parse.add_argument(
+ "--strict-validation",
+ dest="strict_validation",
+ action="store_true",
+ default=XOSProcessorArgs.default_strict_validation,
+ help="Exit if validation fails",
+)
+
CHECK = 1
GEN = 2
diff --git a/xos/core/migrations/0007_auto_20190307_1227.py b/xos/core/migrations/0007_auto_20190307_1227.py
new file mode 100644
index 0000000..b029d75
--- /dev/null
+++ b/xos/core/migrations/0007_auto_20190307_1227.py
@@ -0,0 +1,546 @@
+# 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.11 on 2019-03-07 17:27
+from __future__ import unicode_literals
+
+import core.models.xosbase_header
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0006_auto_20190304_2346'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='addresspool_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='addresspool_decl',
+ name='cidr',
+ field=models.CharField(help_text=b'Subnet for this AddressPool', max_length=32),
+ ),
+ migrations.AlterField(
+ model_name='addresspool_decl',
+ name='gateway_ip',
+ field=models.CharField(help_text=b'Gateway IP address for this AddressPool', max_length=32),
+ ),
+ migrations.AlterField(
+ model_name='addresspool_decl',
+ name='gateway_mac',
+ field=models.CharField(help_text=b'Gateway MAC address for this AddressPool', max_length=32),
+ ),
+ migrations.AlterField(
+ model_name='addresspool_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='addresspool_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='flavor_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='flavor_decl',
+ name='flavor',
+ field=core.models.xosbase_header.StrippedCharField(help_text=b'flavor string used to configure deployments', max_length=32),
+ ),
+ migrations.AlterField(
+ model_name='flavor_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='flavor_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='image_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='image_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='image_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='interfacetype_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='interfacetype_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='interfacetype_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='network_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='network_decl',
+ name='end_ip',
+ field=models.CharField(blank=True, max_length=32, null=True),
+ ),
+ migrations.AlterField(
+ model_name='network_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='network_decl',
+ name='start_ip',
+ field=models.CharField(blank=True, max_length=32, null=True),
+ ),
+ migrations.AlterField(
+ model_name='network_decl',
+ name='subnet',
+ field=models.CharField(blank=True, max_length=32, null=True),
+ ),
+ migrations.AlterField(
+ model_name='network_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='networkparameter_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='networkparameter_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='networkparameter_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='networkparametertype_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='networkparametertype_decl',
+ name='description',
+ field=models.CharField(blank=True, max_length=1024, null=True),
+ ),
+ migrations.AlterField(
+ model_name='networkparametertype_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='networkparametertype_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='networkslice_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='networkslice_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='networkslice_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='networktemplate_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='networktemplate_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='networktemplate_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='node_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='node_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='node_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='nodelabel_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='nodelabel_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='nodelabel_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='port_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='port_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='port_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='principal_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='principal_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='principal_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='privilege_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='privilege_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='privilege_decl',
+ name='permission',
+ field=models.CharField(default=b'all', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='privilege_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='role_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='role_decl',
+ name='description',
+ field=core.models.xosbase_header.StrippedCharField(blank=True, max_length=120, null=True),
+ ),
+ migrations.AlterField(
+ model_name='role_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='role_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='service_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='service_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='service_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='serviceattribute_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceattribute_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceattribute_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='servicedependency_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='servicedependency_decl',
+ name='connect_method',
+ field=models.CharField(choices=[(b'none', b'None'), (b'private', b'Private'), (b'public', b'Public')], default=b'none', help_text=b'method to connect the two services', max_length=30),
+ ),
+ migrations.AlterField(
+ model_name='servicedependency_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='servicedependency_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='servicegraphconstraint_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='servicegraphconstraint_decl',
+ name='constraints',
+ field=core.models.xosbase_header.StrippedCharField(help_text=b'A composite array defining positions, eg [volt, vsg, [address_manager, vrouter]]', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='servicegraphconstraint_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='servicegraphconstraint_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstance_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstance_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstance_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstanceattribute_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstanceattribute_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstanceattribute_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstancelink_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstancelink_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceinstancelink_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='serviceinterface_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceinterface_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceinterface_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='serviceport_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceport_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='serviceport_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='site_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='site_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='site_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='slice_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='slice_decl',
+ name='description',
+ field=models.CharField(blank=True, help_text=b'High level description of the slice and expected activities', max_length=1024, null=True),
+ ),
+ migrations.AlterField(
+ model_name='slice_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='slice_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='tag_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='tag_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='tag_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='trustdomain_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='trustdomain_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='trustdomain_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='xoscore_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='xoscore_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='xoscore_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ migrations.AlterField(
+ model_name='xosguiextension_decl',
+ name='backend_status',
+ field=models.CharField(default=b'Provisioning in progress', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='xosguiextension_decl',
+ name='leaf_model_name',
+ field=models.CharField(help_text=b'The most specialized model in this chain of inheritance, often defined by a service developer', max_length=1024),
+ ),
+ migrations.AlterField(
+ model_name='xosguiextension_decl',
+ name='updated',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'Time this model was changed by a non-synchronizer'),
+ ),
+ ]
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 3c0d63b..12d00af 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -162,7 +162,7 @@
message Flavor (XOSBase) {
required string name = 1 [max_length = 32, content_type = "stripped", blank = False, help_text = "name of this flavor, as displayed to users", null = False, db_index = False, unique = True];
- optional string description = 2 [db_index = False, max_length = 1024, null = True, content_type = "stripped", blank = True];
+ optional string description = 2 [db_index = False, max_length = 1024, null = True, content_type = "stripped"];
required string flavor = 3 [max_length = 32, content_type = "stripped", blank = True, help_text = "flavor string used to configure deployments", null = False, db_index = False];
}
@@ -181,11 +181,11 @@
message Network::network_policy (XOSBase) {
required string name = 1 [db_index = False, max_length = 32, null = False, blank = False, unique = True];
required manytoone template->NetworkTemplate:network = 2:1001 [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];
+ optional string subnet = 3 [db_index = False, max_length = 32];
+ optional string start_ip = 4 [db_index = False, max_length = 32];
+ optional string end_ip = 5 [db_index = False, max_length = 32];
+ optional string ports = 6 [db_index = False, max_length = 1024];
+ optional string labels = 7 [db_index = False, max_length = 1024];
required manytoone owner->Slice:ownedNetworks = 8:1004 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
required bool permit_all_slices = 10 [default = False, null = False, 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];
@@ -203,7 +203,7 @@
message NetworkParameterType (XOSBase) {
required string name = 1 [help_text = "The name of this parameter", max_length = 128, null = False, db_index = True, blank = False, unique = True];
- required string description = 2 [db_index = False, max_length = 1024, null = False, blank = True];
+ optional string description = 2 [db_index = False, max_length = 1024, null = False];
}
policy network_slice_validator < (obj.slice in obj.network.permitted_slices.all()) | (obj.slice = obj.network.owner) | obj.network.permit_all_slices >
@@ -217,7 +217,7 @@
message NetworkTemplate (XOSBase) {
required string name = 1 [db_index = False, max_length = 32, null = False, blank = False, unique = True];
- optional string description = 2 [db_index = False, max_length = 1024, null = True, blank = True];
+ optional string description = 2 [db_index = False, max_length = 1024];
required string visibility = 4 [default = "private", choices = "(('public', 'public'), ('private', 'private'))", max_length = 30, blank = False, null = False, db_index = False];
required string translation = 5 [default = "none", choices = "(('none', 'none'), ('NAT', 'NAT'))", max_length = 30, blank = False, null = False, db_index = False];
optional string access = 6 [choices = "((None, 'None'), ('indirect', 'Indirect'), ('direct', 'Direct'))", max_length = 30, blank = True, help_text = "Advertise this network as a means for other slices to contact this slice", null = True, db_index = False];
@@ -260,7 +260,7 @@
message Role (XOSBase) {
required string role_type = 1 [db_index = False, max_length = 80, null = False, content_type = "stripped", blank = False];
optional string role = 2 [db_index = False, max_length = 80, null = True, content_type = "stripped", blank = True];
- required string description = 3 [db_index = False, max_length = 120, null = False, content_type = "stripped", blank = False];
+ optional string description = 3 [db_index = False, max_length = 120, content_type = "stripped"];
}
policy service_policy <ctx.user.is_admin | exists Privilege: Privilege.accessor_id = ctx.user.id & Privilege.accessor_type = "User" & Privilege.object_type = "Service" & Privilege.object_id = obj.id >
@@ -270,10 +270,8 @@
optional string description = 1 [
help_text = "Description of Service",
- blank = True,
db_index = False,
max_length = 254,
- null = True,
varchar = True];
required bool enabled = 2 [
help_text = "Whether or not service is Enabled",
@@ -450,7 +448,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, unique = True];
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];
+ optional string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, db_index = False, varchar = True];
required manytoone site->Site:slices = 6:1005 [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:1006 [db_index = True, null = True, blank = True];