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> { ...@@ -112,4 +112,20 @@ public class PimInterfaceConfig extends Config<ConnectPoint> {
112 } 112 }
113 return Optional.of(Short.parseShort(node.path(OVERRIDE_INTERVAL).asText())); 113 return Optional.of(Short.parseShort(node.path(OVERRIDE_INTERVAL).asText()));
114 } 114 }
115 +
116 + @Override
117 + public boolean isValid() {
118 + if (!hasOnlyFields(INTERFACE_NAME, ENABLED, HELLO_INTERVAL, HOLD_TIME,
119 + PRIORITY, PROPAGATION_DELAY, OVERRIDE_INTERVAL)) {
120 + return false;
121 + }
122 +
123 + return isString(INTERFACE_NAME, FieldPresence.MANDATORY) &&
124 + isBoolean(ENABLED, FieldPresence.MANDATORY) &&
125 + isIntegralNumber(HELLO_INTERVAL, FieldPresence.OPTIONAL) &&
126 + isIntegralNumber(HOLD_TIME, FieldPresence.OPTIONAL) &&
127 + isIntegralNumber(PRIORITY, FieldPresence.OPTIONAL) &&
128 + isIntegralNumber(PROPAGATION_DELAY, FieldPresence.OPTIONAL) &&
129 + isIntegralNumber(OVERRIDE_INTERVAL, FieldPresence.OPTIONAL);
130 + }
115 } 131 }
......
...@@ -82,4 +82,19 @@ public class RouterConfig extends Config<ApplicationId> { ...@@ -82,4 +82,19 @@ public class RouterConfig extends Config<ApplicationId> {
82 return interfaces; 82 return interfaces;
83 } 83 }
84 84
85 + @Override
86 + public boolean isValid() {
87 + if (!hasOnlyFields(INTERFACES, CP_CONNECT_POINT, OSPF_ENABLED, PIM_ENABLED)) {
88 + return false;
89 + }
90 +
91 + JsonNode intfNode = object.path(INTERFACES);
92 + if (!intfNode.isMissingNode() && !intfNode.isArray()) {
93 + return false;
94 + }
95 +
96 + return isConnectPoint(CP_CONNECT_POINT, FieldPresence.MANDATORY) &&
97 + isBoolean(OSPF_ENABLED, FieldPresence.OPTIONAL) &&
98 + isBoolean(PIM_ENABLED, FieldPresence.OPTIONAL);
99 + }
85 } 100 }
......
...@@ -24,7 +24,9 @@ import com.google.common.collect.ImmutableSet; ...@@ -24,7 +24,9 @@ import com.google.common.collect.ImmutableSet;
24 import com.google.common.collect.Iterators; 24 import com.google.common.collect.Iterators;
25 import com.google.common.collect.Lists; 25 import com.google.common.collect.Lists;
26 import org.onlab.packet.IpAddress; 26 import org.onlab.packet.IpAddress;
27 +import org.onlab.packet.IpPrefix;
27 import org.onlab.packet.MacAddress; 28 import org.onlab.packet.MacAddress;
29 +import org.onosproject.net.ConnectPoint;
28 30
29 import java.util.Collection; 31 import java.util.Collection;
30 import java.util.List; 32 import java.util.List;
...@@ -46,6 +48,9 @@ import static com.google.common.base.Preconditions.checkState; ...@@ -46,6 +48,9 @@ import static com.google.common.base.Preconditions.checkState;
46 @Beta 48 @Beta
47 public abstract class Config<S> { 49 public abstract class Config<S> {
48 50
51 + private static final String TRUE_LITERAL = "true";
52 + private static final String FALSE_LITERAL = "false";
53 +
49 protected S subject; 54 protected S subject;
50 protected String key; 55 protected String key;
51 56
...@@ -111,6 +116,8 @@ public abstract class Config<S> { ...@@ -111,6 +116,8 @@ public abstract class Config<S> {
111 // isDecimal(path, [min, max]) 116 // isDecimal(path, [min, max])
112 // isMacAddress(path) 117 // isMacAddress(path)
113 // isIpAddress(path) 118 // isIpAddress(path)
119 + // isIpPrefix(path)
120 + // isConnectPoint(path)
114 return true; 121 return true;
115 } 122 }
116 123
...@@ -424,6 +431,22 @@ public abstract class Config<S> { ...@@ -424,6 +431,22 @@ public abstract class Config<S> {
424 } 431 }
425 432
426 /** 433 /**
434 + * Indicates whether the specified field of a particular node holds a valid
435 + * MAC address.
436 + *
437 + * @param objectNode JSON node
438 + * @param field JSON field name
439 + * @param presence specifies if field is optional or mandatory
440 + * @return true if valid; false otherwise
441 + * @throws IllegalArgumentException if field is present, but not valid MAC
442 + */
443 + protected boolean isMacAddress(ObjectNode objectNode, String field, FieldPresence presence) {
444 + JsonNode node = objectNode.path(field);
445 + return isValid(node, presence, node.isTextual() &&
446 + MacAddress.valueOf(node.asText()) != null);
447 + }
448 +
449 + /**
427 * Indicates whether the specified field holds a valid IP address. 450 * Indicates whether the specified field holds a valid IP address.
428 * 451 *
429 * @param field JSON field name 452 * @param field JSON field name
...@@ -439,16 +462,76 @@ public abstract class Config<S> { ...@@ -439,16 +462,76 @@ public abstract class Config<S> {
439 * Indicates whether the specified field of a particular node holds a valid 462 * Indicates whether the specified field of a particular node holds a valid
440 * IP address. 463 * IP address.
441 * 464 *
442 - * @param node node from whom to access the field 465 + * @param objectNode node from whom to access the field
466 + * @param field JSON field name
467 + * @param presence specifies if field is optional or mandatory
468 + * @return true if valid; false otherwise
469 + * @throws IllegalArgumentException if field is present, but not valid IP
470 + */
471 + protected boolean isIpAddress(ObjectNode objectNode, String field, FieldPresence presence) {
472 + JsonNode node = objectNode.path(field);
473 + return isValid(node, presence, node.isTextual() &&
474 + IpAddress.valueOf(node.asText()) != null);
475 + }
476 +
477 + /**
478 + * Indicates whether the specified field holds a valid IP prefix.
479 + *
480 + * @param field JSON field name
481 + * @param presence specifies if field is optional or mandatory
482 + * @return true if valid; false otherwise
483 + * @throws IllegalArgumentException if field is present, but not valid IP
484 + * prefix
485 + */
486 + protected boolean isIpPrefix(String field, FieldPresence presence) {
487 + return isIpPrefix(object, field, presence);
488 + }
489 +
490 + /**
491 + * Indicates whether the specified field of a particular node holds a valid
492 + * IP prefix.
493 + *
494 + * @param objectNode node from whom to access the field
443 * @param field JSON field name 495 * @param field JSON field name
444 * @param presence specifies if field is optional or mandatory 496 * @param presence specifies if field is optional or mandatory
445 * @return true if valid; false otherwise 497 * @return true if valid; false otherwise
446 * @throws IllegalArgumentException if field is present, but not valid IP 498 * @throws IllegalArgumentException if field is present, but not valid IP
499 + * prefix
447 */ 500 */
448 - protected boolean isIpAddress(ObjectNode node, String field, FieldPresence presence) { 501 + protected boolean isIpPrefix(ObjectNode objectNode, String field, FieldPresence presence) {
449 - JsonNode innerNode = node.path(field); 502 + JsonNode node = objectNode.path(field);
450 - return isValid(innerNode, presence, innerNode.isTextual() && 503 + return isValid(node, presence, node.isTextual() &&
451 - IpAddress.valueOf(innerNode.asText()) != null); 504 + IpPrefix.valueOf(node.asText()) != null);
505 + }
506 +
507 + /**
508 + * Indicates whether the specified field holds a valid connect point string.
509 + *
510 + * @param field JSON field name
511 + * @param presence specifies if field is optional or mandatory
512 + * @return true if valid; false otherwise
513 + * @throws IllegalArgumentException if field is present, but not valid
514 + * connect point string representation
515 + */
516 + protected boolean isConnectPoint(String field, FieldPresence presence) {
517 + return isConnectPoint(object, field, presence);
518 + }
519 +
520 + /**
521 + * Indicates whether the specified field of a particular node holds a valid
522 + * connect point string.
523 + *
524 + * @param objectNode JSON node
525 + * @param field JSON field name
526 + * @param presence specifies if field is optional or mandatory
527 + * @return true if valid; false otherwise
528 + * @throws IllegalArgumentException if field is present, but not valid
529 + * connect point string representation
530 + */
531 + protected boolean isConnectPoint(ObjectNode objectNode, String field, FieldPresence presence) {
532 + JsonNode node = objectNode.path(field);
533 + return isValid(node, presence, node.isTextual() &&
534 + ConnectPoint.deviceConnectPoint(node.asText()) != null);
452 } 535 }
453 536
454 /** 537 /**
...@@ -458,10 +541,26 @@ public abstract class Config<S> { ...@@ -458,10 +541,26 @@ public abstract class Config<S> {
458 * @param presence specifies if field is optional or mandatory 541 * @param presence specifies if field is optional or mandatory
459 * @param pattern optional regex pattern 542 * @param pattern optional regex pattern
460 * @return true if valid; false otherwise 543 * @return true if valid; false otherwise
461 - * @throws IllegalArgumentException if field is present, but not valid MAC 544 + * @throws IllegalArgumentException if field is present, but not valid string
462 */ 545 */
463 protected boolean isString(String field, FieldPresence presence, String... pattern) { 546 protected boolean isString(String field, FieldPresence presence, String... pattern) {
464 - JsonNode node = object.path(field); 547 + return isString(object, field, presence, pattern);
548 + }
549 +
550 + /**
551 + * Indicates whether the specified field on a particular node holds a valid
552 + * string value.
553 + *
554 + * @param objectNode JSON node
555 + * @param field JSON field name
556 + * @param presence specifies if field is optional or mandatory
557 + * @param pattern optional regex pattern
558 + * @return true if valid; false otherwise
559 + * @throws IllegalArgumentException if field is present, but not valid string
560 + */
561 + protected boolean isString(ObjectNode objectNode, String field,
562 + FieldPresence presence, String... pattern) {
563 + JsonNode node = objectNode.path(field);
465 return isValid(node, presence, node.isTextual() && 564 return isValid(node, presence, node.isTextual() &&
466 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1)); 565 (pattern.length > 0 && node.asText().matches(pattern[0]) || pattern.length < 1));
467 } 566 }
...@@ -507,10 +606,29 @@ public abstract class Config<S> { ...@@ -507,10 +606,29 @@ public abstract class Config<S> {
507 * @throws IllegalArgumentException if field is present, but not valid 606 * @throws IllegalArgumentException if field is present, but not valid
508 */ 607 */
509 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) { 608 protected boolean isIntegralNumber(String field, FieldPresence presence, long... minMax) {
510 - JsonNode node = object.path(field); 609 + return isIntegralNumber(object, field, presence, minMax);
511 - return isValid(node, presence, node.isIntegralNumber() && 610 + }
512 - (minMax.length > 0 && minMax[0] <= node.asLong() || minMax.length < 1) && 611 +
513 - (minMax.length > 1 && minMax[1] > node.asLong() || minMax.length < 2)); 612 + /**
613 + * Indicates whether the specified field of a particular node holds a valid
614 + * integer.
615 + *
616 + * @param objectNode JSON node
617 + * @param field JSON field name
618 + * @param presence specifies if field is optional or mandatory
619 + * @param minMax optional min/max values
620 + * @return true if valid; false otherwise
621 + * @throws IllegalArgumentException if field is present, but not valid
622 + */
623 + protected boolean isIntegralNumber(ObjectNode objectNode, String field,
624 + FieldPresence presence, long... minMax) {
625 + JsonNode node = objectNode.path(field);
626 +
627 + return isValid(node, presence, n -> {
628 + long number = (node.isIntegralNumber()) ? n.asLong() : Long.parseLong(n.asText());
629 + return (minMax.length > 0 && minMax[0] <= number || minMax.length < 1) &&
630 + (minMax.length > 1 && minMax[1] > number || minMax.length < 2);
631 + });
514 } 632 }
515 633
516 /** 634 /**
...@@ -535,11 +653,35 @@ public abstract class Config<S> { ...@@ -535,11 +653,35 @@ public abstract class Config<S> {
535 * @param field JSON field name 653 * @param field JSON field name
536 * @param presence specifies if field is optional or mandatory 654 * @param presence specifies if field is optional or mandatory
537 * @return true if valid; false otherwise 655 * @return true if valid; false otherwise
538 - * @throws IllegalArgumentException if field is present, but not valid
539 */ 656 */
540 protected boolean isBoolean(String field, FieldPresence presence) { 657 protected boolean isBoolean(String field, FieldPresence presence) {
541 - JsonNode node = object.path(field); 658 + return isBoolean(object, field, presence);
542 - return isValid(node, presence, node.isBoolean()); 659 + }
660 +
661 + /**
662 + * Indicates whether the specified field of a particular node holds a valid
663 + * boolean value.
664 + *
665 + * @param objectNode JSON object node
666 + * @param field JSON field name
667 + * @param presence specifies if field is optional or mandatory
668 + * @return true if valid; false otherwise
669 + */
670 + protected boolean isBoolean(ObjectNode objectNode, String field, FieldPresence presence) {
671 + JsonNode node = objectNode.path(field);
672 + return isValid(node, presence, node.isBoolean() ||
673 + (node.isTextual() && isBooleanString(node.asText())));
674 + }
675 +
676 + /**
677 + * Indicates whether a string holds a boolean literal value.
678 + *
679 + * @param str string to test
680 + * @return true if the string contains "true" or "false" (case insensitive),
681 + * otherwise false
682 + */
683 + private boolean isBooleanString(String str) {
684 + return str.equalsIgnoreCase(TRUE_LITERAL) || str.equalsIgnoreCase(FALSE_LITERAL);
543 } 685 }
544 686
545 /** 687 /**
...@@ -552,7 +694,30 @@ public abstract class Config<S> { ...@@ -552,7 +694,30 @@ public abstract class Config<S> {
552 * @return true if the field is as expected 694 * @return true if the field is as expected
553 */ 695 */
554 private boolean isValid(JsonNode node, FieldPresence presence, boolean correctValue) { 696 private boolean isValid(JsonNode node, FieldPresence presence, boolean correctValue) {
697 + return isValid(node, presence, n -> correctValue);
698 + }
699 +
700 + /**
701 + * Indicates whether the node is present and of correct value or not
702 + * mandatory and absent.
703 + *
704 + * @param node JSON node
705 + * @param presence specified if field is optional or mandatory
706 + * @param validationFunction function which can be used to verify if the
707 + * node has the correct value
708 + * @return true if the field is as expected
709 + */
710 + private boolean isValid(JsonNode node, FieldPresence presence,
711 + Function<JsonNode, Boolean> validationFunction) {
555 boolean isMandatory = presence == FieldPresence.MANDATORY; 712 boolean isMandatory = presence == FieldPresence.MANDATORY;
556 - return isMandatory && correctValue || !isMandatory && !node.isNull() || correctValue; 713 + if (isMandatory && validationFunction.apply(node)) {
714 + return true;
715 + }
716 +
717 + if (!isMandatory && (node.isNull() || node.isMissingNode())) {
718 + return true;
719 + }
720 +
721 + return validationFunction.apply(node);
557 } 722 }
558 } 723 }
......
...@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; ...@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
22 import com.google.common.annotations.Beta; 22 import com.google.common.annotations.Beta;
23 import com.google.common.collect.Lists; 23 import com.google.common.collect.Lists;
24 import com.google.common.collect.Sets; 24 import com.google.common.collect.Sets;
25 +import org.onlab.packet.IpPrefix;
25 import org.onlab.packet.MacAddress; 26 import org.onlab.packet.MacAddress;
26 import org.onlab.packet.VlanId; 27 import org.onlab.packet.VlanId;
27 import org.onosproject.incubator.net.intf.Interface; 28 import org.onosproject.incubator.net.intf.Interface;
...@@ -50,6 +51,31 @@ public class InterfaceConfig extends Config<ConnectPoint> { ...@@ -50,6 +51,31 @@ public class InterfaceConfig extends Config<ConnectPoint> {
50 private static final String INTF_NULL_ERROR = "Interface cannot be null"; 51 private static final String INTF_NULL_ERROR = "Interface cannot be null";
51 private static final String INTF_NAME_ERROR = "Interface must have a valid name"; 52 private static final String INTF_NAME_ERROR = "Interface must have a valid name";
52 53
54 + @Override
55 + public boolean isValid() {
56 + for (JsonNode node : array) {
57 + if (!hasOnlyFields((ObjectNode) node, NAME, IPS, MAC, VLAN)) {
58 + return false;
59 + }
60 +
61 + ObjectNode obj = (ObjectNode) node;
62 +
63 + if (!(isString(obj, NAME, FieldPresence.OPTIONAL) &&
64 + isMacAddress(obj, MAC, FieldPresence.OPTIONAL) &&
65 + isIntegralNumber(obj, VLAN, FieldPresence.OPTIONAL, 0, VlanId.MAX_VLAN))) {
66 + return false;
67 + }
68 +
69 +
70 + for (JsonNode ipNode : node.path(IPS)) {
71 + if (!ipNode.isTextual() || IpPrefix.valueOf(ipNode.asText()) == null) {
72 + return false;
73 + }
74 + }
75 + }
76 + return true;
77 + }
78 +
53 /** 79 /**
54 * Retrieves all interfaces configured on this port. 80 * Retrieves all interfaces configured on this port.
55 * 81 *
......