Add config validation for vRouter and PIM configs

Change-Id: I97ddf4f745a19df6998b15ae47ebde5aa5f46238
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceConfig.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceConfig.java
index 48969c3..63ceb36 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceConfig.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PimInterfaceConfig.java
@@ -112,4 +112,20 @@
         }
         return Optional.of(Short.parseShort(node.path(OVERRIDE_INTERVAL).asText()));
     }
+
+    @Override
+    public boolean isValid() {
+        if (!hasOnlyFields(INTERFACE_NAME, ENABLED, HELLO_INTERVAL, HOLD_TIME,
+                PRIORITY, PROPAGATION_DELAY, OVERRIDE_INTERVAL)) {
+            return false;
+        }
+
+        return isString(INTERFACE_NAME, FieldPresence.MANDATORY) &&
+                isBoolean(ENABLED, FieldPresence.MANDATORY) &&
+                isIntegralNumber(HELLO_INTERVAL, FieldPresence.OPTIONAL) &&
+                isIntegralNumber(HOLD_TIME, FieldPresence.OPTIONAL) &&
+                isIntegralNumber(PRIORITY, FieldPresence.OPTIONAL) &&
+                isIntegralNumber(PROPAGATION_DELAY, FieldPresence.OPTIONAL) &&
+                isIntegralNumber(OVERRIDE_INTERVAL, FieldPresence.OPTIONAL);
+    }
 }
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/config/RouterConfig.java b/apps/routing-api/src/main/java/org/onosproject/routing/config/RouterConfig.java
index 9099019..dbd49ff 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/config/RouterConfig.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/config/RouterConfig.java
@@ -82,4 +82,19 @@
         return interfaces;
     }
 
+    @Override
+    public boolean isValid() {
+        if (!hasOnlyFields(INTERFACES, CP_CONNECT_POINT, OSPF_ENABLED, PIM_ENABLED)) {
+            return false;
+        }
+
+        JsonNode intfNode = object.path(INTERFACES);
+        if (!intfNode.isMissingNode() && !intfNode.isArray()) {
+            return false;
+        }
+
+        return isConnectPoint(CP_CONNECT_POINT, FieldPresence.MANDATORY) &&
+                isBoolean(OSPF_ENABLED, FieldPresence.OPTIONAL) &&
+                isBoolean(PIM_ENABLED, FieldPresence.OPTIONAL);
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/config/Config.java b/core/api/src/main/java/org/onosproject/net/config/Config.java
index 0119e07..e287538 100644
--- a/core/api/src/main/java/org/onosproject/net/config/Config.java
+++ b/core/api/src/main/java/org/onosproject/net/config/Config.java
@@ -24,7 +24,9 @@
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Lists;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
+import org.onosproject.net.ConnectPoint;
 
 import java.util.Collection;
 import java.util.List;
@@ -46,6 +48,9 @@
 @Beta
 public abstract class Config<S> {
 
+    private static final String TRUE_LITERAL = "true";
+    private static final String FALSE_LITERAL = "false";
+
     protected S subject;
     protected String key;
 
@@ -111,6 +116,8 @@
         //      isDecimal(path, [min, max])
         //      isMacAddress(path)
         //      isIpAddress(path)
+        //      isIpPrefix(path)
+        //      isConnectPoint(path)
         return true;
     }
 
@@ -424,6 +431,22 @@
     }
 
     /**
+     * Indicates whether the specified field of a particular node holds a valid
+     * MAC address.
+     *
+     * @param objectNode JSON node
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @return true if valid; false otherwise
+     * @throws IllegalArgumentException if field is present, but not valid MAC
+     */
+    protected boolean isMacAddress(ObjectNode objectNode, String field, FieldPresence presence) {
+        JsonNode node = objectNode.path(field);
+        return isValid(node, presence, node.isTextual() &&
+                MacAddress.valueOf(node.asText()) != null);
+    }
+
+    /**
      * Indicates whether the specified field holds a valid IP address.
      *
      * @param field    JSON field name
@@ -439,16 +462,76 @@
      * Indicates whether the specified field of a particular node holds a valid
      * IP address.
      *
-     * @param node     node from whom to access the field
+     * @param objectNode     node from whom to access the field
      * @param field    JSON field name
      * @param presence specifies if field is optional or mandatory
      * @return true if valid; false otherwise
      * @throws IllegalArgumentException if field is present, but not valid IP
      */
-    protected boolean isIpAddress(ObjectNode node, String field, FieldPresence presence) {
-        JsonNode innerNode = node.path(field);
-        return isValid(innerNode, presence, innerNode.isTextual() &&
-                IpAddress.valueOf(innerNode.asText()) != null);
+    protected boolean isIpAddress(ObjectNode objectNode, String field, FieldPresence presence) {
+        JsonNode node = objectNode.path(field);
+        return isValid(node, presence, node.isTextual() &&
+                IpAddress.valueOf(node.asText()) != null);
+    }
+
+    /**
+     * Indicates whether the specified field holds a valid IP prefix.
+     *
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @return true if valid; false otherwise
+     * @throws IllegalArgumentException if field is present, but not valid IP
+     * prefix
+     */
+    protected boolean isIpPrefix(String field, FieldPresence presence) {
+        return isIpPrefix(object, field, presence);
+    }
+
+    /**
+     * Indicates whether the specified field of a particular node holds a valid
+     * IP prefix.
+     *
+     * @param objectNode     node from whom to access the field
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @return true if valid; false otherwise
+     * @throws IllegalArgumentException if field is present, but not valid IP
+     * prefix
+     */
+    protected boolean isIpPrefix(ObjectNode objectNode, String field, FieldPresence presence) {
+        JsonNode node = objectNode.path(field);
+        return isValid(node, presence, node.isTextual() &&
+                IpPrefix.valueOf(node.asText()) != null);
+    }
+
+    /**
+     * Indicates whether the specified field holds a valid connect point string.
+     *
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @return true if valid; false otherwise
+     * @throws IllegalArgumentException if field is present, but not valid
+     * connect point string representation
+     */
+    protected boolean isConnectPoint(String field, FieldPresence presence) {
+        return isConnectPoint(object, field, presence);
+    }
+
+    /**
+     * Indicates whether the specified field of a particular node holds a valid
+     * connect point string.
+     *
+     * @param objectNode JSON node
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @return true if valid; false otherwise
+     * @throws IllegalArgumentException if field is present, but not valid
+     * connect point string representation
+     */
+    protected boolean isConnectPoint(ObjectNode objectNode, String field, FieldPresence presence) {
+        JsonNode node = objectNode.path(field);
+        return isValid(node, presence, node.isTextual() &&
+                ConnectPoint.deviceConnectPoint(node.asText()) != null);
     }
 
     /**
@@ -458,10 +541,26 @@
      * @param presence specifies if field is optional or mandatory
      * @param pattern  optional regex pattern
      * @return true if valid; false otherwise
-     * @throws IllegalArgumentException if field is present, but not valid MAC
+     * @throws IllegalArgumentException if field is present, but not valid string
      */
     protected boolean isString(String field, FieldPresence presence, String... pattern) {
-        JsonNode node = object.path(field);
+        return isString(object, field, presence, pattern);
+    }
+
+    /**
+     * Indicates whether the specified field on a particular node holds a valid
+     * string value.
+     *
+     * @param objectNode JSON node
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @param pattern  optional regex pattern
+     * @return true if valid; false otherwise
+     * @throws IllegalArgumentException if field is present, but not valid string
+     */
+    protected boolean isString(ObjectNode objectNode, String field,
+                               FieldPresence presence, String... pattern) {
+        JsonNode node = objectNode.path(field);
         return isValid(node, presence, node.isTextual() &&
                 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1));
     }
@@ -507,10 +606,29 @@
      * @throws IllegalArgumentException if field is present, but not valid
      */
     protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
-        JsonNode node = object.path(field);
-        return isValid(node, presence, node.isIntegralNumber() &&
-                (minMax.length > 0 && minMax[0] <= node.asLong() || minMax.length < 1) &&
-                (minMax.length > 1 && minMax[1] > node.asLong() || minMax.length < 2));
+        return isIntegralNumber(object, field, presence, minMax);
+    }
+
+    /**
+     * Indicates whether the specified field of a particular node holds a valid
+     * integer.
+     *
+     * @param objectNode JSON node
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @param minMax   optional min/max values
+     * @return true if valid; false otherwise
+     * @throws IllegalArgumentException if field is present, but not valid
+     */
+    protected boolean isIntegralNumber(ObjectNode objectNode, String field,
+                                       FieldPresence presence, long... minMax) {
+        JsonNode node = objectNode.path(field);
+
+        return isValid(node, presence, n -> {
+            long number = (node.isIntegralNumber()) ? n.asLong() : Long.parseLong(n.asText());
+            return (minMax.length > 0 && minMax[0] <= number || minMax.length < 1) &&
+                    (minMax.length > 1 && minMax[1] > number || minMax.length < 2);
+        });
     }
 
     /**
@@ -535,11 +653,35 @@
      * @param field    JSON field name
      * @param presence specifies if field is optional or mandatory
      * @return true if valid; false otherwise
-     * @throws IllegalArgumentException if field is present, but not valid
      */
     protected boolean isBoolean(String field, FieldPresence presence) {
-        JsonNode node = object.path(field);
-        return isValid(node, presence, node.isBoolean());
+        return isBoolean(object, field, presence);
+    }
+
+    /**
+     * Indicates whether the specified field of a particular node holds a valid
+     * boolean value.
+     *
+     * @param objectNode JSON object node
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @return true if valid; false otherwise
+     */
+    protected boolean isBoolean(ObjectNode objectNode, String field, FieldPresence presence) {
+        JsonNode node = objectNode.path(field);
+        return isValid(node, presence, node.isBoolean() ||
+                (node.isTextual() && isBooleanString(node.asText())));
+    }
+
+    /**
+     * Indicates whether a string holds a boolean literal value.
+     *
+     * @param str string to test
+     * @return true if the string contains "true" or "false" (case insensitive),
+     * otherwise false
+     */
+    private boolean isBooleanString(String str) {
+        return str.equalsIgnoreCase(TRUE_LITERAL) || str.equalsIgnoreCase(FALSE_LITERAL);
     }
 
     /**
@@ -552,7 +694,30 @@
      * @return true if the field is as expected
      */
     private boolean isValid(JsonNode node, FieldPresence presence, boolean correctValue) {
+        return isValid(node, presence, n -> correctValue);
+    }
+
+    /**
+     * Indicates whether the node is present and of correct value or not
+     * mandatory and absent.
+     *
+     * @param node JSON node
+     * @param presence specified if field is optional or mandatory
+     * @param validationFunction function which can be used to verify if the
+     *                           node has the correct value
+     * @return true if the field is as expected
+     */
+    private boolean isValid(JsonNode node, FieldPresence presence,
+                            Function<JsonNode, Boolean> validationFunction) {
         boolean isMandatory = presence == FieldPresence.MANDATORY;
-        return isMandatory && correctValue || !isMandatory && !node.isNull() || correctValue;
+        if (isMandatory && validationFunction.apply(node)) {
+            return true;
+        }
+
+        if (!isMandatory && (node.isNull() || node.isMissingNode())) {
+            return true;
+        }
+
+        return validationFunction.apply(node);
     }
 }
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
index 8d938ba..8903521 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
@@ -22,6 +22,7 @@
 import com.google.common.annotations.Beta;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onosproject.incubator.net.intf.Interface;
@@ -50,6 +51,31 @@
     private static final String INTF_NULL_ERROR = "Interface cannot be null";
     private static final String INTF_NAME_ERROR = "Interface must have a valid name";
 
+    @Override
+    public boolean isValid() {
+        for (JsonNode node : array) {
+            if (!hasOnlyFields((ObjectNode) node, NAME, IPS, MAC, VLAN)) {
+                return false;
+            }
+
+            ObjectNode obj = (ObjectNode) node;
+
+            if (!(isString(obj, NAME, FieldPresence.OPTIONAL) &&
+                    isMacAddress(obj, MAC, FieldPresence.OPTIONAL) &&
+                    isIntegralNumber(obj, VLAN, FieldPresence.OPTIONAL, 0, VlanId.MAX_VLAN))) {
+                return false;
+            }
+
+
+            for (JsonNode ipNode : node.path(IPS)) {
+                if (!ipNode.isTextual() || IpPrefix.valueOf(ipNode.asText()) == null) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     /**
      * Retrieves all interfaces configured on this port.
      *