/* * Copyright 2015-present Open Networking Laboratory * * 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. */ package org.onosproject.codec.impl; import java.util.List; import java.util.Set; import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; import org.onosproject.net.ConnectPoint; import org.onosproject.net.DeviceId; import org.onosproject.net.Link; import org.onosproject.net.NetworkResource; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criterion; import org.onosproject.net.flow.instructions.Instruction; import org.onosproject.net.intent.ConnectivityIntent; import org.onosproject.net.intent.Constraint; import org.onosproject.net.intent.HostToHostIntent; import org.onosproject.net.intent.Intent; import org.onosproject.net.intent.PointToPointIntent; import org.onosproject.net.intent.constraint.AnnotationConstraint; import org.onosproject.net.intent.constraint.BandwidthConstraint; import org.onosproject.net.intent.constraint.LatencyConstraint; import org.onosproject.net.intent.constraint.LinkTypeConstraint; import org.onosproject.net.intent.constraint.ObstacleConstraint; import org.onosproject.net.intent.constraint.WaypointConstraint; import com.fasterxml.jackson.databind.JsonNode; /** * Hamcrest matcher to check that an intent representation in JSON matches * the actual intent. */ public final class IntentJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> { private final Intent intent; /** * Constructor is private, use factory method. * * @param intentValue the intent object to compare against */ private IntentJsonMatcher(Intent intentValue) { intent = intentValue; } /** * Matches the JSON representation of a host to host intent. * * @param jsonIntent JSON representation of the intent * @param description Description object used for recording errors * @return true if the JSON matches the intent, false otherwise */ private boolean matchHostToHostIntent(JsonNode jsonIntent, Description description) { final HostToHostIntent hostToHostIntent = (HostToHostIntent) intent; // check host one final String host1 = hostToHostIntent.one().toString(); final String jsonHost1 = jsonIntent.get("one").asText(); if (!host1.equals(jsonHost1)) { description.appendText("host one was " + jsonHost1); return false; } // check host 2 final String host2 = hostToHostIntent.two().toString(); final String jsonHost2 = jsonIntent.get("two").asText(); if (!host2.equals(jsonHost2)) { description.appendText("host two was " + jsonHost2); return false; } return true; } /** * Matches the JSON representation of a point to point intent. * * @param jsonIntent JSON representation of the intent * @param description Description object used for recording errors * @return true if the JSON matches the intent, false otherwise */ private boolean matchPointToPointIntent(JsonNode jsonIntent, Description description) { final PointToPointIntent pointToPointIntent = (PointToPointIntent) intent; // check ingress connection final ConnectPoint ingress = pointToPointIntent.ingressPoint(); final ConnectPointJsonMatcher ingressMatcher = ConnectPointJsonMatcher.matchesConnectPoint(ingress); final JsonNode jsonIngress = jsonIntent.get("ingressPoint"); final boolean ingressMatches = ingressMatcher.matchesSafely(jsonIngress, description); if (!ingressMatches) { description.appendText("ingress was " + jsonIngress); return false; } // check egress connection final ConnectPoint egress = pointToPointIntent.egressPoint(); final ConnectPointJsonMatcher egressMatcher = ConnectPointJsonMatcher.matchesConnectPoint(egress); final JsonNode jsonEgress = jsonIntent.get("egressPoint"); final boolean egressMatches = egressMatcher.matchesSafely(jsonEgress, description); if (!egressMatches) { description.appendText("egress was " + jsonEgress); return false; } return true; } /** * Matches a bandwidth constraint against a JSON representation of the * constraint. * * @param bandwidthConstraint constraint object to match * @param constraintJson JSON representation of the constraint * @return true if the constraint and JSON match, false otherwise. */ private boolean matchBandwidthConstraint(BandwidthConstraint bandwidthConstraint, JsonNode constraintJson) { final JsonNode bandwidthJson = constraintJson.get("bandwidth"); return bandwidthJson != null && constraintJson.get("bandwidth").asDouble() == bandwidthConstraint.bandwidth().bps(); } /** * Matches a link type constraint against a JSON representation of the * constraint. * * @param linkTypeConstraint constraint object to match * @param constraintJson JSON representation of the constraint * @return true if the constraint and JSON match, false otherwise. */ private boolean matchLinkTypeConstraint(LinkTypeConstraint linkTypeConstraint, JsonNode constraintJson) { final JsonNode inclusiveJson = constraintJson.get("inclusive"); final JsonNode typesJson = constraintJson.get("types"); if (typesJson.size() != linkTypeConstraint.types().size()) { return false; } int foundType = 0; for (Link.Type type : linkTypeConstraint.types()) { for (int jsonIndex = 0; jsonIndex < typesJson.size(); jsonIndex++) { if (type.name().equals(typesJson.get(jsonIndex).asText())) { foundType++; break; } } } return (inclusiveJson != null && inclusiveJson.asBoolean() == linkTypeConstraint.isInclusive()) && foundType == typesJson.size(); } /** * Matches an annotation constraint against a JSON representation of the * constraint. * * @param annotationConstraint constraint object to match * @param constraintJson JSON representation of the constraint * @return true if the constraint and JSON match, false otherwise. */ private boolean matchAnnotationConstraint(AnnotationConstraint annotationConstraint, JsonNode constraintJson) { final JsonNode keyJson = constraintJson.get("key"); final JsonNode thresholdJson = constraintJson.get("threshold"); return (keyJson != null && keyJson.asText().equals(annotationConstraint.key())) && (thresholdJson != null && thresholdJson.asDouble() == annotationConstraint.threshold()); } /** * Matches a latency constraint against a JSON representation of the * constraint. * * @param latencyConstraint constraint object to match * @param constraintJson JSON representation of the constraint * @return true if the constraint and JSON match, false otherwise. */ private boolean matchLatencyConstraint(LatencyConstraint latencyConstraint, JsonNode constraintJson) { final JsonNode latencyJson = constraintJson.get("latencyMillis"); return (latencyJson != null && latencyJson.asInt() == latencyConstraint.latency().toMillis()); } /** * Matches an obstacle constraint against a JSON representation of the * constraint. * * @param obstacleConstraint constraint object to match * @param constraintJson JSON representation of the constraint * @return true if the constraint and JSON match, false otherwise. */ private boolean matchObstacleConstraint(ObstacleConstraint obstacleConstraint, JsonNode constraintJson) { final JsonNode obstaclesJson = constraintJson.get("obstacles"); if (obstaclesJson.size() != obstacleConstraint.obstacles().size()) { return false; } for (int obstaclesIndex = 0; obstaclesIndex < obstaclesJson.size(); obstaclesIndex++) { boolean obstacleFound = false; final String obstacleJson = obstaclesJson.get(obstaclesIndex) .asText(); for (DeviceId obstacle : obstacleConstraint.obstacles()) { if (obstacle.toString().equals(obstacleJson)) { obstacleFound = true; } } if (!obstacleFound) { return false; } } return true; } /** * Matches a waypoint constraint against a JSON representation of the * constraint. * * @param waypointConstraint constraint object to match * @param constraintJson JSON representation of the constraint * @return true if the constraint and JSON match, false otherwise. */ private boolean matchWaypointConstraint(WaypointConstraint waypointConstraint, JsonNode constraintJson) { final JsonNode waypointsJson = constraintJson.get("waypoints"); if (waypointsJson.size() != waypointConstraint.waypoints().size()) { return false; } for (int waypointsIndex = 0; waypointsIndex < waypointsJson.size(); waypointsIndex++) { boolean waypointFound = false; final String waypointJson = waypointsJson.get(waypointsIndex) .asText(); for (DeviceId waypoint : waypointConstraint.waypoints()) { if (waypoint.toString().equals(waypointJson)) { waypointFound = true; } } if (!waypointFound) { return false; } } return true; } /** * Matches a constraint against a JSON representation of the * constraint. * * @param constraint constraint object to match * @param constraintJson JSON representation of the constraint * @return true if the constraint and JSON match, false otherwise. */ private boolean matchConstraint(Constraint constraint, JsonNode constraintJson) { final JsonNode typeJson = constraintJson.get("type"); if (!typeJson.asText().equals(constraint.getClass().getSimpleName())) { return false; } if (constraint instanceof BandwidthConstraint) { return matchBandwidthConstraint((BandwidthConstraint) constraint, constraintJson); } else if (constraint instanceof LinkTypeConstraint) { return matchLinkTypeConstraint((LinkTypeConstraint) constraint, constraintJson); } else if (constraint instanceof AnnotationConstraint) { return matchAnnotationConstraint((AnnotationConstraint) constraint, constraintJson); } else if (constraint instanceof LatencyConstraint) { return matchLatencyConstraint((LatencyConstraint) constraint, constraintJson); } else if (constraint instanceof ObstacleConstraint) { return matchObstacleConstraint((ObstacleConstraint) constraint, constraintJson); } else if (constraint instanceof WaypointConstraint) { return matchWaypointConstraint((WaypointConstraint) constraint, constraintJson); } return true; } /** * Matches the JSON representation of a connectivity intent. Calls the * matcher for the connectivity intent subtype. * * @param jsonIntent JSON representation of the intent * @param description Description object used for recording errors * @return true if the JSON matches the intent, false otherwise */ private boolean matchConnectivityIntent(JsonNode jsonIntent, Description description) { final ConnectivityIntent connectivityIntent = (ConnectivityIntent) intent; // check selector final JsonNode jsonSelector = jsonIntent.get("selector"); final TrafficSelector selector = connectivityIntent.selector(); final Set<Criterion> criteria = selector.criteria(); final JsonNode jsonCriteria = jsonSelector.get("criteria"); if (jsonCriteria.size() != criteria.size()) { description.appendText("size of criteria array is " + Integer.toString(jsonCriteria.size())); return false; } for (Criterion criterion : criteria) { boolean criterionFound = false; for (int criterionIndex = 0; criterionIndex < jsonCriteria.size(); criterionIndex++) { final CriterionJsonMatcher criterionMatcher = CriterionJsonMatcher.matchesCriterion(criterion); if (criterionMatcher.matches(jsonCriteria.get(criterionIndex))) { criterionFound = true; break; } } if (!criterionFound) { description.appendText("criterion not found " + criterion.toString()); return false; } } // check treatment final JsonNode jsonTreatment = jsonIntent.get("treatment"); final TrafficTreatment treatment = connectivityIntent.treatment(); final List<Instruction> instructions = treatment.immediate(); final JsonNode jsonInstructions = jsonTreatment.get("instructions"); if (jsonInstructions.size() != instructions.size()) { description.appendText("size of instructions array is " + Integer.toString(jsonInstructions.size())); return false; } for (Instruction instruction : instructions) { boolean instructionFound = false; for (int instructionIndex = 0; instructionIndex < jsonInstructions.size(); instructionIndex++) { final InstructionJsonMatcher instructionMatcher = InstructionJsonMatcher.matchesInstruction(instruction); if (instructionMatcher.matches(jsonInstructions.get(instructionIndex))) { instructionFound = true; break; } } if (!instructionFound) { description.appendText("instruction not found " + instruction.toString()); return false; } } // Check constraints final JsonNode jsonConstraints = jsonIntent.get("constraints"); if (connectivityIntent.constraints() != null) { if (connectivityIntent.constraints().size() != jsonConstraints.size()) { description.appendText("constraints array size was " + Integer.toString(jsonConstraints.size())); return false; } for (final Constraint constraint : connectivityIntent.constraints()) { boolean constraintFound = false; for (int constraintIndex = 0; constraintIndex < jsonConstraints.size(); constraintIndex++) { final JsonNode value = jsonConstraints.get(constraintIndex); if (matchConstraint(constraint, value)) { constraintFound = true; } } if (!constraintFound) { final String constraintString = constraint.toString(); description.appendText("constraint missing " + constraintString); return false; } } } else if (jsonConstraints.size() != 0) { description.appendText("constraint array not empty"); return false; } if (connectivityIntent instanceof HostToHostIntent) { return matchHostToHostIntent(jsonIntent, description); } else if (connectivityIntent instanceof PointToPointIntent) { return matchPointToPointIntent(jsonIntent, description); } else { description.appendText("class of connectivity intent is unknown"); return false; } } @Override public boolean matchesSafely(JsonNode jsonIntent, Description description) { // check id final String jsonId = jsonIntent.get("id").asText(); final String id = intent.id().toString(); if (!jsonId.equals(id)) { description.appendText("id was " + jsonId); return false; } // check application id final JsonNode jsonAppIdNode = jsonIntent.get("appId"); final String jsonAppId = jsonAppIdNode.asText(); final String appId = intent.appId().name(); if (!jsonAppId.equals(appId)) { description.appendText("appId was " + jsonAppId); return false; } // check intent type final String jsonType = jsonIntent.get("type").asText(); final String type = intent.getClass().getSimpleName(); if (!jsonType.equals(type)) { description.appendText("type was " + jsonType); return false; } // check resources array final JsonNode jsonResources = jsonIntent.get("resources"); if (intent.resources() != null) { if (intent.resources().size() != jsonResources.size()) { description.appendText("resources array size was " + Integer.toString(jsonResources.size())); return false; } for (final NetworkResource resource : intent.resources()) { boolean resourceFound = false; final String resourceString = resource.toString(); for (int resourceIndex = 0; resourceIndex < jsonResources.size(); resourceIndex++) { final JsonNode value = jsonResources.get(resourceIndex); if (value.asText().equals(resourceString)) { resourceFound = true; } } if (!resourceFound) { description.appendText("resource missing " + resourceString); return false; } } } else if (jsonResources.size() != 0) { description.appendText("resources array empty"); return false; } if (intent instanceof ConnectivityIntent) { return matchConnectivityIntent(jsonIntent, description); } else { description.appendText("class of intent is unknown"); return false; } } @Override public void describeTo(Description description) { description.appendText(intent.toString()); } /** * Factory to allocate an intent matcher. * * @param intent intent object we are looking for * @return matcher */ public static IntentJsonMatcher matchesIntent(Intent intent) { return new IntentJsonMatcher(intent); } }