CORD-1386: Port existing code to new data validation strategy

Change-Id: Id50d8cf76afa9f6cc7752b0d3cc451492a0a66f3
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py b/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
index 336373f..ca39b17 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
@@ -207,7 +207,7 @@
         self.verdict_next()
         function_str = """
 def %(fn_name)s(obj, ctx):
-    if not %(vvar)s: raise ValidationError("%(message)s")
+    if not %(vvar)s: raise ValidationError("%(message)s".format(obj=obj, ctx=ctx))
         """ % {'fn_name': policy_function_name, 'vvar': self.verdict_variable, 'message': message}
 
         function_ast = self.str_to_ast(function_str)
@@ -367,7 +367,7 @@
                 entry = f.pop()
 
                 python_str = """
-%(verdict_var)s = %(model)s.objects.filter(%(query)s)[0]
+%(verdict_var)s = not not %(model)s.objects.filter(%(query)s)
                 """ % {'verdict_var': verdict_var, 'model': var, 'query': entry}
 
                 python_ast = ast.parse(python_str)
@@ -393,7 +393,7 @@
                 vvar = self.verdict_variable
 
                 python_str = """
-%(verdict_var)s = %(model)s.objects.filter(%(query)s)[0]
+%(verdict_var)s = not not %(model)s.objects.filter(%(query)s)
                 """ % {'verdict_var': vvar, 'model': var, 'query': entry}
 
                 python_ast = ast.parse(python_str)
diff --git a/lib/xos-genx/xosgenx/targets/django-split.xtarget b/lib/xos-genx/xosgenx/targets/django-split.xtarget
index 8a0082a..a6ccab5 100644
--- a/lib/xos-genx/xosgenx/targets/django-split.xtarget
+++ b/lib/xos-genx/xosgenx/targets/django-split.xtarget
@@ -14,6 +14,9 @@
 {% endif %}
 {% endfor %}
 
+{% for policy,error in xproto_validations(m.options) %}
+{{ xproto_fol_to_python_validator(policy, proto.policies[policy], m, error) }}
+{% endfor %}
 
 class {{ m.name }}{{ xproto_base_def(m.name, m.bases) }}:
   plural_name = "{{ xproto_pluralize(m) }}"
@@ -37,8 +40,22 @@
       unique_together = {{ xproto_tuplify(uniques) }}
   {%- endif %}
   {% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
-  pass
 
+  {% if m.name!='XOSBase' and 'Mixin' not in m.name %}
+
+  # Generated methods
+  def save(self, *args, **kwds):
+      try:
+          self.__xos_save_base(*args, **kwds)
+      except AttributeError:
+          pass
+
+      {% for policy,error in xproto_validations(m.options) %}
+      policy_{{policy}}_validator(self, None)
+      {% endfor %}
+      super({{ m.name }}, self).save(*args, **kwds)
+
+  {% endif %}
 {% if file_exists(xproto_base_name(m.name)|lower+'_bottom.py') -%}{{ include_file(xproto_base_name(m.name)|lower+'_bottom.py') }}{% endif %}
 +++ {{m.name|lower}}.py
 {% endif %}{% endfor %}
diff --git a/lib/xos-genx/xosgenx/targets/django.xtarget b/lib/xos-genx/xosgenx/targets/django.xtarget
index 67e25c5..400596c 100644
--- a/lib/xos-genx/xosgenx/targets/django.xtarget
+++ b/lib/xos-genx/xosgenx/targets/django.xtarget
@@ -1,11 +1,10 @@
 {% for m in proto.messages %}{% if not m.options.skip_django -%}
 {% if file_exists(xproto_base_name(m.name)|lower+'_header.py') -%}from {{xproto_base_name(m.name)|lower }}_header import *{%- else -%}from 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 }}
+from core.models.{{ l.peer.name | lower }} import {{ l.peer.name }} 
 {% endif %}
 
 {%- endfor %}
@@ -15,6 +14,9 @@
 {% endif %}
 {% endfor %}
 
+{% for policy,error in xproto_validations(m.options) %}
+{{ xproto_fol_to_python_validator(policy, proto.policies[policy], m, error) }}
+{% endfor %}
 
 class {{ m.name }}{{ xproto_base_def(m.name, m.bases) }}:
   plural_name = "{{ xproto_pluralize(m) }}"
@@ -40,5 +42,19 @@
   {% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
   pass
 
+  {% if m.name!='XOSBase' and 'Mixin' not in m.name %}
+  # Generated methods
+  def save(self, *args, **kwds):
+      try:
+          self.__xos_save_base(*args, **kwds)
+      except AttributeError:
+          pass
+
+      {% for policy,error in xproto_validations(m.options) %}
+      policy_{{policy}}_validator(self, None)
+      {% endfor %}
+      super({{ m.name }}, self).save(*args, **kwds)
+  {% endif %}
+    
 {% if file_exists(xproto_base_name(m.name)|lower+'_bottom.py') -%}{{ include_file(xproto_base_name(m.name)|lower+'_bottom.py') }}{% endif %}
 {% endif %}{% endfor %}
diff --git a/lib/xos-genx/xosgenx/targets/service.xtarget b/lib/xos-genx/xosgenx/targets/service.xtarget
index e70f0d6..a48abb7 100644
--- a/lib/xos-genx/xosgenx/targets/service.xtarget
+++ b/lib/xos-genx/xosgenx/targets/service.xtarget
@@ -26,6 +26,10 @@
 {%- endif -%}
 {% endfor %}
 
+{% for policy,error in xproto_validations(m.options) %}
+{{ xproto_fol_to_python_validator(policy, proto.policies[policy], m, error) }}
+{% endfor %}
+
 {% endfor %}
 
 {% for m in proto.messages %}
@@ -55,6 +59,18 @@
   {% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
   pass
 
+  # Generated methods
+  def save(self, *args, **kwds):
+      try:
+          self.__xos_save_base(*args, **kwds)
+      except AttributeError:
+          pass
+
+      {% for policy,error in xproto_validations(m.options) %}
+      policy_{{policy}}_validator(self, None)
+      {% endfor %}
+      super({{ m.name }}{{ legacy_tag }}, self).save(*args, **kwds)
+
 {% if file_exists(m.name|lower+'_bottom.py') -%}{{ include_file(m.name|lower+'_bottom.py') }}{% endif %} 
 {% endfor %}
 +++ models{{ legacy_tag }}.py
diff --git a/xos/core/models/attic/header.py b/xos/core/models/attic/header.py
index b6b8d31..a03046a 100644
--- a/xos/core/models/attic/header.py
+++ b/xos/core/models/attic/header.py
@@ -5,6 +5,7 @@
 import operator
 from operator import attrgetter
 from core.models.xosbase import *
+from core.models.privilege import *
 from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
 from django.contrib.contenttypes.models import ContentType
 from django.utils.timezone import now
diff --git a/xos/core/models/attic/instance_model.py b/xos/core/models/attic/instance_model.py
index a86e53d..3ea46fb 100644
--- a/xos/core/models/attic/instance_model.py
+++ b/xos/core/models/attic/instance_model.py
@@ -10,40 +10,11 @@
         pass
     return d
 
-def save(self, *args, **kwds):
+def __xos_save_base(self, *args, **kwds):
     if not self.name:
         self.name = self.slice.name
     if not self.creator and hasattr(self, 'caller'):
         self.creator = self.caller
-    if not self.creator:
-        raise ValidationError('instance has no creator')
-
-    if (self.isolation == "container") or (self.isolation == "container_vm"):
-        if (self.image.kind != "container"):
-           raise ValidationError("Container instance must use container image")
-    elif (self.isolation == "vm"):
-        if (self.image.kind != "vm"):
-           raise ValidationError("VM instance must use VM image")
-
-    if (self.isolation == "container_vm") and (not self.parent):
-        raise ValidationError("Container-vm instance must have a parent")
-
-    if (self.parent) and (self.isolation != "container_vm"):
-        raise ValidationError("Parent field can only be set on Container-vm instances")
-
-    if (self.slice.creator != self.creator):
-        from core.models.privilege import Privilege
-        # Check to make sure there's a slice_privilege for the user. If there
-        # isn't, then keystone will throw an exception inside the observer.
-        slice_privs = Privilege.objects.filter(object_id=self.slice.id, accessor_id=self.creator.id, object_type='Slice')
-        if not slice_privs:
-            raise ValidationError('instance creator has no privileges on slice')
-
-# XXX smbaker - disabled for now, was causing fault in tenant view create slice
-#        if not self.controllerNetwork.test_acl(slice=self.slice):
-#            raise exceptions.ValidationError("Deployment %s's ACL does not allow any of this slice %s's users" % (self.controllerNetwork.name, self.slice.name))
-
-    super(Instance, self).save(*args, **kwds)
 
 def can_update(self, user):
     return user.can_update_slice(self.slice)
diff --git a/xos/core/models/attic/networkslice_model.py b/xos/core/models/attic/networkslice_model.py
index 43104cd..1f33390 100644
--- a/xos/core/models/attic/networkslice_model.py
+++ b/xos/core/models/attic/networkslice_model.py
@@ -1,14 +1,3 @@
-def save(self, *args, **kwds):
-    slice = self.slice
-    if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
-        # to add a instance to the network, then one of the following must be true:
-        #   1) instance's slice is in network's permittedSlices list,
-        #   2) instance's slice is network's owner, or
-        #   3) network's permitAllSlices is true
-        raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
-
-    super(NetworkSlice, self).save(*args, **kwds)
-
 def can_update(self, user):
     return user.can_update_slice(self.slice)
 
diff --git a/xos/core/models/attic/port_model.py b/xos/core/models/attic/port_model.py
index 5baf1b8..57617c6 100644
--- a/xos/core/models/attic/port_model.py
+++ b/xos/core/models/attic/port_model.py
@@ -1,15 +1,3 @@
-def save(self, *args, **kwds):
-    if self.instance:
-        slice = self.instance.slice
-        if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
-            # to add a instance to the network, then one of the following must be true:
-            #   1) instance's slice is in network's permittedSlices list,
-            #   2) instance's slice is network's owner, or
-            #   3) network's permitAllSlices is true
-            raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
-
-    super(Port, self).save(*args, **kwds)
-
 def can_update(self, user):
     if self.instance:
         return user.can_update_slice(self.instance.slice)
diff --git a/xos/core/models/attic/slice_model.py b/xos/core/models/attic/slice_model.py
index 7154c73..37ec321 100644
--- a/xos/core/models/attic/slice_model.py
+++ b/xos/core/models/attic/slice_model.py
@@ -4,18 +4,8 @@
 def slicename(self):
     return "%s_%s" % (self.site.login_base, self.name)
 
-def save(self, *args, **kwds):
+def __xos_save_base(self, *args, **kwds):
     site = Site.objects.get(id=self.site.id)
-    # allow preexisting slices to keep their original name for now
-    if not self.id and not self.name.startswith(site.login_base):
-        raise XOSValidationError('slice name must begin with %s' % site.login_base, {'name' : 'slice name must begin with %s' % site.login_base})
-
-    if self.name == site.login_base+"_":
-        raise XOSValidationError('slice name is too short', {'name': 'slice name is too short'})
-
-    if " " in self.name:
-        raise XOSValidationError('slice name must not contain spaces', {'name': 'slice name must not contain spaces'})
-
     # set creator on first save
     if not self.creator and hasattr(self, 'caller'):
         self.creator = self.caller
@@ -32,16 +22,11 @@
             raise PermissionDenied("Insufficient privileges to change slice creator",
                                    {'creator': "Insufficient privileges to change slice creator"})
     
-    if not self.creator:
-        raise XOSValidationError('slice has no creator', {'creator': 'slice has no creator'})
-
     if self.network=="Private Only":
         # "Private Only" was the default from the old Tenant View
         self.network=None
     self.enforce_choices(self.network, self.NETWORK_CHOICES)
 
-    super(Slice, self).save(*args, **kwds)
-
 def can_update(self, user):
     return user.can_update_slice(self)
 
diff --git a/xos/core/models/attic/tenant_model.py b/xos/core/models/attic/tenant_model.py
index e283c5a..be011fb 100644
--- a/xos/core/models/attic/tenant_model.py
+++ b/xos/core/models/attic/tenant_model.py
@@ -26,14 +26,13 @@
             raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
                                   "service_specific_id": "duplicate key"})
 
-def save(self, *args, **kwargs):
+def __xos_save_base(self, *args, **kwargs):
     subCount = sum([1 for e in [self.subscriber_service, self.subscriber_tenant,
                                 self.subscriber_user, self.subscriber_root] if e is not None])
     if (subCount > 1):
         raise XOSConflictingField(
             "Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
 
-    super(Tenant, self).save(*args, **kwargs)
 
 def get_subscribed_tenants(self, tenant_class):
     ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
diff --git a/xos/core/models/attic/tenantwithcontainer_model.py b/xos/core/models/attic/tenantwithcontainer_model.py
index 5e87c04..f2c5405 100644
--- a/xos/core/models/attic/tenantwithcontainer_model.py
+++ b/xos/core/models/attic/tenantwithcontainer_model.py
@@ -134,8 +134,7 @@
             self.instance.delete()
         self.instance = None
 
-def save(self, *args, **kwargs):
+def __xos_save_base(self, *args, **kwargs):
     if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
         self.creator = self.caller
-    super(TenantWithContainer, self).save(*args, **kwargs)
 
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 4bae7ec..44dc67c 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -226,14 +226,14 @@
 
 policy instance_name < obj.name = obj.slice.name >
 policy instance_creator < obj.creator >
-policy instance_isolation < (obj.isolation = "container" | self.isolation = "container_vm" ) -> (obj.image.kind = "container") >
-policy instance_isolation_parent < obj.isolation -> obj.parent >
+policy instance_isolation < (obj.isolation = "container" | obj.isolation = "container_vm" ) -> (obj.image.kind = "container") >
+policy instance_isolation_container_vm_parent < (obj.isolation = "container_vm") -> obj.parent >
+policy instance_parent_isolation_container_vm < obj.parent -> ( obj.isolation = "container_vm" ) >
 policy instance_isolation_vm < (obj.isolation = "vm") -> (obj.image.kind = "vm") >
-policy instance_isolation_container_vm < (obj.isolation = "container_vm") -> (obj.image.kind = "vm") >
 policy instance_creator_privilege < not (obj.slice.creator = obj.creator) -> exists Privilege:Privilege.object_id = obj.slice.id & Privilege.accessor_id = obj.creator.id & Privilege.object_type = "Slice" >
 
 message Instance (XOSBase) {
-     option validators = "instance_name:Instance name set improperly, instance_creator:Instance has no creator, instance_isolation: Container instance must use container image, instance_isolation_parent:Container-vm instance must have a parent, instance_isolation_vm: VM Instance must use VM image, instance_isolation_container_vm:Parent field can only be set on Container-vm instances, instance_creator_privilege: instance creator has no privileges on slice";
+     option validators = "instance_name:Instance name set improperly - {obj.name} should be {obj.slice.name}, instance_creator:Instance has no creator, instance_isolation: Container instance { obj.name } must use container image, instance_isolation_container_vm_parent:Container-vm instance {obj.name} must have a parent, instance_parent_isolation_container_vm:Parent field can only be set on Container-vm instances ({ obj.name }), instance_isolation_vm: VM Instance { obj.name } must use VM image, instance_creator_privilege: instance creator has no privileges on slice";
     
      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];
@@ -282,10 +282,10 @@
      required string description = 2 [db_index = False, max_length = 1024, null = False, blank = False];
 }
 
-policy network_slice_policy < (obj.slice in obj.network.permitted_slices.all()) | (obj.slice = obj.network.owner) | (not obj.network.permit_all_slices) >
+policy network_slice_validator < (obj.slice in obj.network.permitted_slices.all()) | (obj.slice = obj.network.owner) | obj.network.permit_all_slices >
 
 message NetworkSlice (XOSBase) {
-     option validators = "network_slice_policy:Slice is not allowed to connect to networks";
+     option validators = "network_slice_validator:Slice { obj.slice.name } is not allowed to connect to networks { obj.network }";
      required manytoone network->Network:networkslices = 1 [db_index = True, null = False, blank = False, unique_with = "slice"];
      required manytoone slice->Slice:networkslices = 2 [db_index = True, null = False, blank = False];
 }
@@ -311,10 +311,10 @@
      required manytomany node->Node/NodeLabel_node:nodelabels = 2 [db_index = False, null = False, blank = True];
 }
 
-policy port_policy < (obj.instance.slice in obj.network.permitted_slices.all()) | (obj.instance.slice = obj.network.owner) | (not obj.network.permit_all_slices) >
+policy port_validator < (obj.instance.slice in obj.network.permitted_slices.all()) | (obj.instance.slice = obj.network.owner) | obj.network.permit_all_slices >
 
 message Port (XOSBase) {
-     option validators = "port_policy:Slice is not allowed to connect to network";
+     option validators = "port_validator:Slice is not allowed to connect to network";
      required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False, unique_with = "instance"];
      optional manytoone instance->Instance:ports = 2 [db_index = True, null = True, blank = True];
      optional string ip = 3 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
@@ -414,12 +414,12 @@
      required string role = 1 [choices = "(('admin', 'Admin'), ('pi', 'PI'), ('tech', 'Tech'), ('billing', 'Billing'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
 }
 
-policy slice_name < not obj.id -> {{ obj.name.startswith(obj.site.login_base) }} >
+policy slice_name < obj.id | {{ obj.name.startswith(obj.site.login_base) }} >
 policy slice_name_length_and_no_spaces < {{ len(obj.site.login_base) + 1 < len(obj.name) and ' ' not in obj.name }} >
 policy slice_has_creator < obj.creator >
 
 message Slice (XOSBase) {
-     option validators = "slice_name:Slice name must begin with site login_base, slice_name_length_and_no_spaces:Slice name too short or contains spaces, slice_has_creator:Slice has no creator";
+     option validators = "slice_name:Slice name ({ obj.name}) must begin with site login_base ({ obj.site.login_base}), slice_name_length_and_no_spaces:Slice name too short or contains spaces, slice_has_creator:Slice has no creator";
      option plural = "Slices";
 
      required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];