Jonathan Hart
Committed by Gerrit Code Review

Add config validation for vRouter and PIM configs

Change-Id: I97ddf4f745a19df6998b15ae47ebde5aa5f46238
......@@ -112,4 +112,20 @@ public class PimInterfaceConfig extends Config<ConnectPoint> {
}
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);
}
}
......
......@@ -82,4 +82,19 @@ public class RouterConfig extends Config<ApplicationId> {
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);
}
}
......
......@@ -24,7 +24,9 @@ import com.google.common.collect.ImmutableSet;
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 @@ import static com.google.common.base.Preconditions.checkState;
@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 @@ public abstract class Config<S> {
// isDecimal(path, [min, max])
// isMacAddress(path)
// isIpAddress(path)
// isIpPrefix(path)
// isConnectPoint(path)
return true;
}
......@@ -424,6 +431,22 @@ public abstract class Config<S> {
}
/**
* 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 @@ public abstract class Config<S> {
* 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 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 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 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 @@ public abstract class Config<S> {
* @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 @@ public abstract class Config<S> {
* @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 @@ public abstract class Config<S> {
* @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 @@ public abstract class Config<S> {
* @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);
}
}
......
......@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
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 @@ public class InterfaceConfig extends Config<ConnectPoint> {
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.
*
......