From 13d05bc8458758ee39cb829098241e89616717ee Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Wed, 9 Sep 2015 22:15:21 -0700 Subject: ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60 Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd --- framework/src/onos/core/common/pom.xml | 58 ++ .../org/onosproject/codec/impl/AnnotatedCodec.java | 64 ++ .../onosproject/codec/impl/AnnotationsCodec.java | 49 ++ .../onosproject/codec/impl/ApplicationCodec.java | 49 ++ .../org/onosproject/codec/impl/CodecManager.java | 132 ++++ .../onosproject/codec/impl/ConnectPointCodec.java | 74 +++ .../codec/impl/ConnectivityIntentCodec.java | 118 ++++ .../onosproject/codec/impl/ConstraintCodec.java | 63 ++ .../codec/impl/ControllerNodeCodec.java | 57 ++ .../org/onosproject/codec/impl/CriterionCodec.java | 78 +++ .../codec/impl/DecodeConstraintCodecHelper.java | 225 +++++++ .../codec/impl/DecodeCriterionCodecHelper.java | 449 +++++++++++++ .../codec/impl/DecodeInstructionCodecHelper.java | 235 +++++++ .../org/onosproject/codec/impl/DeviceCodec.java | 93 +++ .../org/onosproject/codec/impl/DriverCodec.java | 78 +++ .../codec/impl/EncodeConstraintCodecHelper.java | 201 ++++++ .../codec/impl/EncodeCriterionCodecHelper.java | 396 ++++++++++++ .../codec/impl/EncodeInstructionCodecHelper.java | 243 +++++++ .../org/onosproject/codec/impl/EthernetCodec.java | 58 ++ .../org/onosproject/codec/impl/FlowEntryCodec.java | 70 ++ .../org/onosproject/codec/impl/FlowRuleCodec.java | 94 +++ .../onosproject/codec/impl/GroupBucketCodec.java | 64 ++ .../org/onosproject/codec/impl/GroupCodec.java | 79 +++ .../java/org/onosproject/codec/impl/HostCodec.java | 55 ++ .../onosproject/codec/impl/HostLocationCodec.java | 39 ++ .../codec/impl/HostToHostIntentCodec.java | 70 ++ .../onosproject/codec/impl/InstructionCodec.java | 73 +++ .../org/onosproject/codec/impl/IntentCodec.java | 112 ++++ .../java/org/onosproject/codec/impl/LinkCodec.java | 80 +++ .../java/org/onosproject/codec/impl/LoadCodec.java | 45 ++ .../java/org/onosproject/codec/impl/PathCodec.java | 47 ++ .../codec/impl/PointToPointIntentCodec.java | 80 +++ .../java/org/onosproject/codec/impl/PortCodec.java | 160 +++++ .../codec/impl/TopologyClusterCodec.java | 41 ++ .../org/onosproject/codec/impl/TopologyCodec.java | 41 ++ .../codec/impl/TrafficSelectorCodec.java | 71 ++ .../codec/impl/TrafficTreatmentCodec.java | 76 +++ .../org/onosproject/codec/impl/package-info.java | 20 + .../org/onosproject/common/DefaultTopology.java | 502 +++++++++++++++ .../onosproject/common/DefaultTopologyGraph.java | 43 ++ .../onosproject/common/app/ApplicationArchive.java | 432 +++++++++++++ .../org/onosproject/common/app/package-info.java | 20 + .../java/org/onosproject/common/package-info.java | 21 + .../codec/impl/ConnectPointJsonMatcher.java | 71 ++ .../codec/impl/ConstraintCodecTest.java | 202 ++++++ .../onosproject/codec/impl/CriterionCodecTest.java | 445 +++++++++++++ .../codec/impl/CriterionJsonMatcher.java | 609 +++++++++++++++++ .../onosproject/codec/impl/DeviceCodecTest.java | 59 ++ .../onosproject/codec/impl/DriverCodecTest.java | 65 ++ .../onosproject/codec/impl/DriverJsonMatcher.java | 118 ++++ .../onosproject/codec/impl/EthernetCodecTest.java | 55 ++ .../codec/impl/EthernetJsonMatcher.java | 122 ++++ .../onosproject/codec/impl/FlowRuleCodecTest.java | 546 ++++++++++++++++ .../codec/impl/GroupBucketJsonMatcher.java | 87 +++ .../org/onosproject/codec/impl/GroupCodecTest.java | 61 ++ .../onosproject/codec/impl/GroupJsonMatcher.java | 120 ++++ .../codec/impl/ImmutableCodecsTest.java | 65 ++ .../codec/impl/InstructionCodecTest.java | 247 +++++++ .../codec/impl/InstructionJsonMatcher.java | 438 +++++++++++++ .../onosproject/codec/impl/IntentCodecTest.java | 288 +++++++++ .../onosproject/codec/impl/IntentJsonMatcher.java | 512 +++++++++++++++ .../org/onosproject/codec/impl/JsonCodecUtils.java | 83 +++ .../org/onosproject/codec/impl/LinkCodecTest.java | 54 ++ .../org/onosproject/codec/impl/LoadCodecTest.java | 47 ++ .../onosproject/codec/impl/MockCodecContext.java | 64 ++ .../org/onosproject/codec/impl/PortCodecTest.java | 66 ++ .../onosproject/common/DefaultTopologyTest.java | 141 ++++ .../common/app/ApplicationArchiveTest.java | 157 +++++ .../common/event/impl/TestEventDispatcher.java | 48 ++ .../org/onosproject/store/trivial/PathKey.java | 55 ++ .../store/trivial/SimpleApplicationIdStore.java | 70 ++ .../store/trivial/SimpleApplicationStore.java | 170 +++++ .../store/trivial/SimpleApplicationStoreTest.java | 154 +++++ .../store/trivial/SimpleClusterStore.java | 139 ++++ .../store/trivial/SimpleComponentConfigStore.java | 62 ++ .../store/trivial/SimpleDeviceStore.java | 691 ++++++++++++++++++++ .../store/trivial/SimpleDeviceStoreTest.java | 530 +++++++++++++++ .../store/trivial/SimpleFlowRuleStore.java | 327 ++++++++++ .../store/trivial/SimpleGroupStore.java | 717 +++++++++++++++++++++ .../store/trivial/SimpleGroupStoreTest.java | 482 ++++++++++++++ .../onosproject/store/trivial/SimpleHostStore.java | 293 +++++++++ .../store/trivial/SimpleIdBlockStore.java | 48 ++ .../store/trivial/SimpleIntentStore.java | 212 ++++++ .../store/trivial/SimpleLeadershipManager.java | 135 ++++ .../store/trivial/SimpleLinkResourceStore.java | 286 ++++++++ .../store/trivial/SimpleLinkResourceStoreTest.java | 307 +++++++++ .../onosproject/store/trivial/SimpleLinkStore.java | 366 +++++++++++ .../store/trivial/SimpleLinkStoreTest.java | 542 ++++++++++++++++ .../store/trivial/SimpleMastershipStore.java | 388 +++++++++++ .../store/trivial/SimpleMastershipStoreTest.java | 184 ++++++ .../store/trivial/SimplePacketStore.java | 64 ++ .../store/trivial/SimpleStatisticStore.java | 211 ++++++ .../store/trivial/SimpleTopologyStore.java | 157 +++++ .../store/trivial/SystemClockTimestamp.java | 83 +++ .../onosproject/store/trivial/package-info.java | 21 + .../codec/impl/AnnotationConstraint.json | 5 + .../codec/impl/AsymmetricPathConstraint.json | 3 + .../codec/impl/BandwidthConstraint.json | 4 + .../onosproject/codec/impl/HostToHostIntent.json | 19 + .../onosproject/codec/impl/LambdaConstraint.json | 4 + .../onosproject/codec/impl/LatencyConstraint.json | 4 + .../onosproject/codec/impl/LinkTypeConstraint.json | 5 + .../onosproject/codec/impl/ObstacleConstraint.json | 4 + .../onosproject/codec/impl/PointToPointIntent.json | 38 ++ .../onosproject/codec/impl/WaypointConstraint.json | 4 + .../org/onosproject/codec/impl/criteria-flow.json | 44 ++ .../onosproject/codec/impl/instructions-flow.json | 39 ++ .../org/onosproject/codec/impl/sigid-flow.json | 20 + .../org/onosproject/codec/impl/simple-flow.json | 12 + .../resources/org/onosproject/common/app/app.xml | 29 + .../resources/org/onosproject/common/app/app.zip | Bin 0 -> 1450 bytes 111 files changed, 16753 insertions(+) create mode 100644 framework/src/onos/core/common/pom.xml create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/common/app/package-info.java create mode 100644 framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java create mode 100644 framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/package-info.java create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml create mode 100644 framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zip (limited to 'framework/src/onos/core/common') diff --git a/framework/src/onos/core/common/pom.xml b/framework/src/onos/core/common/pom.xml new file mode 100644 index 00000000..71c0fe40 --- /dev/null +++ b/framework/src/onos/core/common/pom.xml @@ -0,0 +1,58 @@ + + + + 4.0.0 + + + org.onosproject + onos-core + 1.3.0-SNAPSHOT + ../pom.xml + + + onos-core-common + bundle + + ONOS utilities common to the core modules + + + + org.osgi + org.osgi.core + + + org.onosproject + onos-api + + + + org.onosproject + onos-api + tests + test + + + + org.easymock + easymock + test + + + + diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java new file mode 100644 index 00000000..8c714625 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014-2015 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 com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.Annotated; +import org.onosproject.net.Annotations; +import org.onosproject.net.DefaultAnnotations; + +/** + * Base JSON codec for annotated entities. + */ +public abstract class AnnotatedCodec extends JsonCodec { + + /** + * Adds JSON encoding of the given item annotations to the specified node. + * + * @param node node to add annotations to + * @param entity annotated entity + * @param context encode context + * @return the given node + */ + protected ObjectNode annotate(ObjectNode node, T entity, CodecContext context) { + if (!entity.annotations().keys().isEmpty()) { + JsonCodec codec = context.codec(Annotations.class); + node.set("annotations", codec.encode(entity.annotations(), context)); + } + return node; + } + + /** + * Extracts annotations of given Object. + * + * @param objNode annotated JSON object node + * @param context decode context + * @return extracted Annotations + */ + protected Annotations extractAnnotations(ObjectNode objNode, CodecContext context) { + + JsonCodec codec = context.codec(Annotations.class); + if (objNode.has("annotations") && objNode.isObject()) { + return codec.decode(get(objNode, "annotations"), context); + } else { + return DefaultAnnotations.EMPTY; + } + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java new file mode 100644 index 00000000..de6ca1b9 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014-2015 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 com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.Annotations; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultAnnotations.Builder; + +/** + * Annotations JSON codec. + */ +public final class AnnotationsCodec extends JsonCodec { + + @Override + public ObjectNode encode(Annotations annotations, CodecContext context) { + ObjectNode result = context.mapper().createObjectNode(); + for (String key : annotations.keys()) { + result.put(key, annotations.value(key)); + } + return result; + } + + @Override + public Annotations decode(ObjectNode json, CodecContext context) { + Builder builder = DefaultAnnotations.builder(); + + json.fields().forEachRemaining(e -> + builder.set(e.getKey(), e.getValue().asText())); + + return builder.build(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java new file mode 100644 index 00000000..b2cab094 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 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 com.fasterxml.jackson.databind.node.ObjectNode; +import org.onosproject.app.ApplicationService; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.core.Application; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Application JSON codec. + */ +public final class ApplicationCodec extends JsonCodec { + + @Override + public ObjectNode encode(Application app, CodecContext context) { + checkNotNull(app, "Application cannot be null"); + ApplicationService service = context.getService(ApplicationService.class); + ObjectNode result = context.mapper().createObjectNode() + .put("name", app.id().name()) + .put("id", app.id().id()) + .put("version", app.version().toString()) + .put("description", app.description()) + .put("origin", app.origin()) + .put("permissions", app.permissions().toString()) + .put("featuresRepo", app.featuresRepo().isPresent() ? + app.featuresRepo().get().toString() : "") + .put("features", app.features().toString()) + .put("state", service.getState(app.id()).toString()); + return result; + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java new file mode 100644 index 00000000..eb53152e --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java @@ -0,0 +1,132 @@ +/* + * Copyright 2014-2015 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 com.google.common.collect.ImmutableSet; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onlab.packet.Ethernet; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.codec.CodecService; +import org.onosproject.codec.JsonCodec; +import org.onosproject.core.Application; +import org.onosproject.net.Annotations; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.Host; +import org.onosproject.net.HostLocation; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.Port; +import org.onosproject.net.driver.Driver; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +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.group.Group; +import org.onosproject.net.group.GroupBucket; +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.statistic.Load; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyCluster; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Implementation of the JSON codec brokering service. + */ +@Component(immediate = true) +@Service +public class CodecManager implements CodecService { + + private static Logger log = LoggerFactory.getLogger(CodecManager.class); + + private final Map, JsonCodec> codecs = new ConcurrentHashMap<>(); + + @Activate + public void activate() { + codecs.clear(); + registerCodec(Application.class, new ApplicationCodec()); + registerCodec(ControllerNode.class, new ControllerNodeCodec()); + registerCodec(Annotations.class, new AnnotationsCodec()); + registerCodec(Device.class, new DeviceCodec()); + registerCodec(Port.class, new PortCodec()); + registerCodec(ConnectPoint.class, new ConnectPointCodec()); + registerCodec(Link.class, new LinkCodec()); + registerCodec(Host.class, new HostCodec()); + registerCodec(HostLocation.class, new HostLocationCodec()); + registerCodec(HostToHostIntent.class, new HostToHostIntentCodec()); + registerCodec(PointToPointIntent.class, new PointToPointIntentCodec()); + registerCodec(Intent.class, new IntentCodec()); + registerCodec(ConnectivityIntent.class, new ConnectivityIntentCodec()); + registerCodec(FlowEntry.class, new FlowEntryCodec()); + registerCodec(FlowRule.class, new FlowRuleCodec()); + registerCodec(TrafficTreatment.class, new TrafficTreatmentCodec()); + registerCodec(TrafficSelector.class, new TrafficSelectorCodec()); + registerCodec(Instruction.class, new InstructionCodec()); + registerCodec(Criterion.class, new CriterionCodec()); + registerCodec(Ethernet.class, new EthernetCodec()); + registerCodec(Constraint.class, new ConstraintCodec()); + registerCodec(Topology.class, new TopologyCodec()); + registerCodec(TopologyCluster.class, new TopologyClusterCodec()); + registerCodec(Path.class, new PathCodec()); + registerCodec(Group.class, new GroupCodec()); + registerCodec(Driver.class, new DriverCodec()); + registerCodec(GroupBucket.class, new GroupBucketCodec()); + registerCodec(Load.class, new LoadCodec()); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + codecs.clear(); + log.info("Stopped"); + } + + @Override + public Set> getCodecs() { + return ImmutableSet.copyOf(codecs.keySet()); + } + + @Override + @SuppressWarnings("unchecked") + public JsonCodec getCodec(Class entityClass) { + return codecs.get(entityClass); + } + + @Override + public void registerCodec(Class entityClass, JsonCodec codec) { + codecs.putIfAbsent(entityClass, codec); + } + + @Override + public void unregisterCodec(Class entityClass) { + codecs.remove(entityClass); + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java new file mode 100644 index 00000000..ac23ef89 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014-2015 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 com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.ElementId; +import org.onosproject.net.HostId; +import org.onosproject.net.PortNumber; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.PortNumber.portNumber; + +/** + * Connection point JSON codec. + */ +public final class ConnectPointCodec extends JsonCodec { + + // JSON field names + private static final String ELEMENT_HOST = "host"; + private static final String ELEMENT_DEVICE = "device"; + private static final String PORT = "port"; + + @Override + public ObjectNode encode(ConnectPoint point, CodecContext context) { + checkNotNull(point, "Connect point cannot be null"); + ObjectNode root = context.mapper().createObjectNode() + .put(PORT, point.port().toString()); + + if (point.elementId() instanceof DeviceId) { + root.put(ELEMENT_DEVICE, point.deviceId().toString()); + } else if (point.elementId() instanceof HostId) { + root.put(ELEMENT_HOST, point.hostId().toString()); + } + + return root; + } + + @Override + public ConnectPoint decode(ObjectNode json, CodecContext context) { + if (json == null || !json.isObject()) { + return null; + } + + ElementId elementId; + if (json.has(ELEMENT_DEVICE)) { + elementId = DeviceId.deviceId(json.get(ELEMENT_DEVICE).asText()); + } else if (json.has(ELEMENT_HOST)) { + elementId = HostId.hostId(json.get(ELEMENT_HOST).asText()); + } else { + // invalid JSON + return null; + } + PortNumber portNumber = portNumber(json.get(PORT).asText()); + return new ConnectPoint(elementId, portNumber); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java new file mode 100644 index 00000000..9e8cd86c --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015 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.ArrayList; +import java.util.stream.IntStream; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.ConnectivityIntent; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.Intent; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Connectivity intent codec. + */ +public final class ConnectivityIntentCodec extends JsonCodec { + + private static final String CONSTRAINTS = "constraints"; + private static final String SELECTOR = "selector"; + private static final String TREATMENT = "treatment"; + + @Override + public ObjectNode encode(ConnectivityIntent intent, CodecContext context) { + checkNotNull(intent, "Connectivity intent cannot be null"); + + final JsonCodec intentCodec = context.codec(Intent.class); + final ObjectNode result = intentCodec.encode(intent, context); + + if (intent.selector() != null) { + final JsonCodec selectorCodec = + context.codec(TrafficSelector.class); + result.set(SELECTOR, selectorCodec.encode(intent.selector(), context)); + } + + if (intent.treatment() != null) { + final JsonCodec treatmentCodec = + context.codec(TrafficTreatment.class); + result.set(TREATMENT, treatmentCodec.encode(intent.treatment(), context)); + } + + result.put(IntentCodec.PRIORITY, intent.priority()); + + if (intent.constraints() != null) { + final ArrayNode jsonConstraints = result.putArray(CONSTRAINTS); + + if (intent.constraints() != null) { + final JsonCodec constraintCodec = + context.codec(Constraint.class); + for (final Constraint constraint : intent.constraints()) { + final ObjectNode constraintNode = + constraintCodec.encode(constraint, context); + jsonConstraints.add(constraintNode); + } + } + } + + return result; + } + + /** + * Extracts connectivity intent specific attributes from a JSON object + * and adds them to a builder. + * + * @param json root JSON object + * @param context code context + * @param builder builder to use for storing the attributes. Constraints, + * selector and treatment are modified by this call. + */ + public static void intentAttributes(ObjectNode json, CodecContext context, + ConnectivityIntent.Builder builder) { + JsonNode constraintsJson = json.get(CONSTRAINTS); + if (constraintsJson != null) { + JsonCodec constraintsCodec = context.codec(Constraint.class); + ArrayList constraints = new ArrayList<>(constraintsJson.size()); + IntStream.range(0, constraintsJson.size()) + .forEach(i -> constraints.add( + constraintsCodec.decode(get(constraintsJson, i), + context))); + builder.constraints(constraints); + } + + ObjectNode selectorJson = get(json, SELECTOR); + if (selectorJson != null) { + JsonCodec selectorCodec = context.codec(TrafficSelector.class); + TrafficSelector selector = selectorCodec.decode(selectorJson, context); + builder.selector(selector); + } + + ObjectNode treatmentJson = get(json, TREATMENT); + if (treatmentJson != null) { + JsonCodec treatmentCodec = context.codec(TrafficTreatment.class); + TrafficTreatment treatment = treatmentCodec.decode(treatmentJson, context); + builder.treatment(treatment); + } + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java new file mode 100644 index 00000000..50738341 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.intent.Constraint; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Constraint JSON codec. + */ +public final class ConstraintCodec extends JsonCodec { + + protected static final String MISSING_MEMBER_MESSAGE = + " member is required in Constraint"; + protected static final String TYPE = "type"; + protected static final String TYPES = "types"; + protected static final String INCLUSIVE = "inclusive"; + protected static final String KEY = "key"; + protected static final String THRESHOLD = "threshold"; + protected static final String BANDWIDTH = "bandwidth"; + protected static final String LAMBDA = "lambda"; + protected static final String LATENCY_MILLIS = "latencyMillis"; + protected static final String OBSTACLES = "obstacles"; + protected static final String WAYPOINTS = "waypoints"; + + @Override + public ObjectNode encode(Constraint constraint, CodecContext context) { + checkNotNull(constraint, "Constraint cannot be null"); + + final EncodeConstraintCodecHelper encodeCodec = + new EncodeConstraintCodecHelper(constraint, context); + + return encodeCodec.encode(); + } + + @Override + public Constraint decode(ObjectNode json, CodecContext context) { + checkNotNull(json, "JSON cannot be null"); + + final DecodeConstraintCodecHelper decodeCodec = + new DecodeConstraintCodecHelper(json); + + return decodeCodec.decode(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java new file mode 100644 index 00000000..65d758ee --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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 com.fasterxml.jackson.databind.node.ObjectNode; +import org.onlab.packet.IpAddress; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.DefaultControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.cluster.DefaultControllerNode.DEFAULT_PORT; + +/** + * Device JSON codec. + */ +public final class ControllerNodeCodec extends JsonCodec { + + @Override + public ObjectNode encode(ControllerNode node, CodecContext context) { + checkNotNull(node, "Controller node cannot be null"); + ClusterService service = context.getService(ClusterService.class); + return context.mapper().createObjectNode() + .put("id", node.id().toString()) + .put("ip", node.ip().toString()) + .put("tcpPort", node.tcpPort()) + .put("status", service.getState(node.id()).toString()); + } + + + @Override + public ControllerNode decode(ObjectNode json, CodecContext context) { + checkNotNull(json, "JSON cannot be null"); + String ip = json.path("ip").asText(); + return new DefaultControllerNode(new NodeId(json.path("id").asText(ip)), + IpAddress.valueOf(ip), + json.path("tcpPort").asInt(DEFAULT_PORT)); + } + + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java new file mode 100644 index 00000000..76f621f2 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.flow.criteria.Criterion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Criterion codec. + */ +public final class CriterionCodec extends JsonCodec { + + protected static final Logger log = + LoggerFactory.getLogger(CriterionCodec.class); + + protected static final String TYPE = "type"; + protected static final String ETH_TYPE = "ethType"; + protected static final String MAC = "mac"; + protected static final String PORT = "port"; + protected static final String METADATA = "metadata"; + + protected static final String VLAN_ID = "vlanId"; + protected static final String PRIORITY = "priority"; + protected static final String IP_DSCP = "ipDscp"; + protected static final String IP_ECN = "ipEcn"; + protected static final String PROTOCOL = "protocol"; + protected static final String IP = "ip"; + protected static final String TCP_PORT = "tcpPort"; + protected static final String UDP_PORT = "udpPort"; + protected static final String SCTP_PORT = "sctpPort"; + protected static final String ICMP_TYPE = "icmpType"; + protected static final String ICMP_CODE = "icmpCode"; + protected static final String FLOW_LABEL = "flowLabel"; + protected static final String ICMPV6_TYPE = "icmpv6Type"; + protected static final String ICMPV6_CODE = "icmpv6Code"; + protected static final String TARGET_ADDRESS = "targetAddress"; + protected static final String LABEL = "label"; + protected static final String EXT_HDR_FLAGS = "exthdrFlags"; + protected static final String LAMBDA = "lambda"; + protected static final String GRID_TYPE = "gridType"; + protected static final String CHANNEL_SPACING = "channelSpacing"; + protected static final String SPACING_MULIPLIER = "spacingMultiplier"; + protected static final String SLOT_GRANULARITY = "slotGranularity"; + protected static final String OCH_SIGNAL_ID = "ochSignalId"; + protected static final String TUNNEL_ID = "tunnelId"; + + @Override + public ObjectNode encode(Criterion criterion, CodecContext context) { + EncodeCriterionCodecHelper encoder = new EncodeCriterionCodecHelper(criterion, context); + return encoder.encode(); + } + + @Override + public Criterion decode(ObjectNode json, CodecContext context) { + DecodeCriterionCodecHelper decoder = new DecodeCriterionCodecHelper(json); + return decoder.decode(); + } + + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java new file mode 100644 index 00000000..5746003c --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java @@ -0,0 +1,225 @@ +/* + * Copyright 2015 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.time.Duration; +import java.util.ArrayList; +import java.util.stream.IntStream; + +import org.onlab.util.Bandwidth; +import org.onosproject.net.DeviceId; +import org.onosproject.net.IndexedLambda; +import org.onosproject.net.Link; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.constraint.AnnotationConstraint; +import org.onosproject.net.intent.constraint.AsymmetricPathConstraint; +import org.onosproject.net.intent.constraint.BandwidthConstraint; +import org.onosproject.net.intent.constraint.LambdaConstraint; +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 org.onosproject.net.resource.link.BandwidthResource; +import org.onosproject.net.resource.link.LambdaResource; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static org.onlab.util.Tools.nullIsIllegal; + +/** + * Constraint JSON decoder. + */ +public final class DecodeConstraintCodecHelper { + private final ObjectNode json; + + /** + * Constructs a constraint decoder. + * + * @param json object node to decode + */ + public DecodeConstraintCodecHelper(ObjectNode json) { + this.json = json; + } + + /** + * Decodes a link type constraint. + * + * @return link type constraint object. + */ + private Constraint decodeLinkTypeConstraint() { + boolean inclusive = nullIsIllegal(json.get(ConstraintCodec.INCLUSIVE), + ConstraintCodec.INCLUSIVE + ConstraintCodec.MISSING_MEMBER_MESSAGE).asBoolean(); + + JsonNode types = nullIsIllegal(json.get(ConstraintCodec.TYPES), + ConstraintCodec.TYPES + ConstraintCodec.MISSING_MEMBER_MESSAGE); + if (types.size() < 1) { + throw new IllegalArgumentException( + "types array in link constraint must have at least one value"); + } + + ArrayList typesEntries = new ArrayList<>(types.size()); + IntStream.range(0, types.size()) + .forEach(index -> + typesEntries.add(Link.Type.valueOf(types.get(index).asText()))); + + return new LinkTypeConstraint(inclusive, + typesEntries.toArray(new Link.Type[types.size()])); + } + + /** + * Decodes an annotation constraint. + * + * @return annotation constraint object. + */ + private Constraint decodeAnnotationConstraint() { + String key = nullIsIllegal(json.get(ConstraintCodec.KEY), + ConstraintCodec.KEY + ConstraintCodec.MISSING_MEMBER_MESSAGE) + .asText(); + double threshold = nullIsIllegal(json.get(ConstraintCodec.THRESHOLD), + ConstraintCodec.THRESHOLD + ConstraintCodec.MISSING_MEMBER_MESSAGE) + .asDouble(); + + return new AnnotationConstraint(key, threshold); + } + + /** + * Decodes a lambda constraint. + * + * @return lambda constraint object. + */ + private Constraint decodeLambdaConstraint() { + long lambda = nullIsIllegal(json.get(ConstraintCodec.LAMBDA), + ConstraintCodec.LAMBDA + ConstraintCodec.MISSING_MEMBER_MESSAGE) + .asLong(); + + return new LambdaConstraint(LambdaResource.valueOf(new IndexedLambda(lambda))); + } + + /** + * Decodes a latency constraint. + * + * @return latency constraint object. + */ + private Constraint decodeLatencyConstraint() { + long latencyMillis = nullIsIllegal(json.get(ConstraintCodec.LATENCY_MILLIS), + ConstraintCodec.LATENCY_MILLIS + ConstraintCodec.MISSING_MEMBER_MESSAGE) + .asLong(); + + return new LatencyConstraint(Duration.ofMillis(latencyMillis)); + } + + /** + * Decodes an obstacle constraint. + * + * @return obstacle constraint object. + */ + private Constraint decodeObstacleConstraint() { + JsonNode obstacles = nullIsIllegal(json.get(ConstraintCodec.OBSTACLES), + ConstraintCodec.OBSTACLES + ConstraintCodec.MISSING_MEMBER_MESSAGE); + if (obstacles.size() < 1) { + throw new IllegalArgumentException( + "obstacles array in obstacles constraint must have at least one value"); + } + + ArrayList obstacleEntries = new ArrayList<>(obstacles.size()); + IntStream.range(0, obstacles.size()) + .forEach(index -> + obstacleEntries.add(DeviceId.deviceId(obstacles.get(index).asText()))); + + return new ObstacleConstraint( + obstacleEntries.toArray(new DeviceId[obstacles.size()])); + } + + /** + * Decodes a waypoint constraint. + * + * @return waypoint constraint object. + */ + private Constraint decodeWaypointConstraint() { + JsonNode waypoints = nullIsIllegal(json.get(ConstraintCodec.WAYPOINTS), + ConstraintCodec.WAYPOINTS + ConstraintCodec.MISSING_MEMBER_MESSAGE); + if (waypoints.size() < 1) { + throw new IllegalArgumentException( + "obstacles array in obstacles constraint must have at least one value"); + } + + ArrayList waypointEntries = new ArrayList<>(waypoints.size()); + IntStream.range(0, waypoints.size()) + .forEach(index -> + waypointEntries.add(DeviceId.deviceId(waypoints.get(index).asText()))); + + return new WaypointConstraint( + waypointEntries.toArray(new DeviceId[waypoints.size()])); + } + + /** + * Decodes an asymmetric path constraint. + * + * @return asymmetric path constraint object. + */ + private Constraint decodeAsymmetricPathConstraint() { + return new AsymmetricPathConstraint(); + } + + /** + * Decodes a bandwidth constraint. + * + * @return bandwidth constraint object. + */ + private Constraint decodeBandwidthConstraint() { + double bandwidth = nullIsIllegal(json.get(ConstraintCodec.BANDWIDTH), + ConstraintCodec.BANDWIDTH + ConstraintCodec.MISSING_MEMBER_MESSAGE) + .asDouble(); + + return new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(bandwidth))); + } + + /** + * Decodes the given constraint. + * + * @return constraint object. + */ + public Constraint decode() { + final String type = nullIsIllegal(json.get(ConstraintCodec.TYPE), + ConstraintCodec.TYPE + ConstraintCodec.MISSING_MEMBER_MESSAGE) + .asText(); + + if (type.equals(BandwidthConstraint.class.getSimpleName())) { + return decodeBandwidthConstraint(); + } else if (type.equals(LambdaConstraint.class.getSimpleName())) { + return decodeLambdaConstraint(); + } else if (type.equals(LinkTypeConstraint.class.getSimpleName())) { + return decodeLinkTypeConstraint(); + } else if (type.equals(AnnotationConstraint.class.getSimpleName())) { + return decodeAnnotationConstraint(); + } else if (type.equals(LatencyConstraint.class.getSimpleName())) { + return decodeLatencyConstraint(); + } else if (type.equals(ObstacleConstraint.class.getSimpleName())) { + return decodeObstacleConstraint(); + } else if (type.equals(WaypointConstraint.class.getSimpleName())) { + return decodeWaypointConstraint(); + } else if (type.equals(AsymmetricPathConstraint.class.getSimpleName())) { + return decodeAsymmetricPathConstraint(); + } else if (type.equals(LinkTypeConstraint.class.getSimpleName())) { + return decodeLinkTypeConstraint(); + } else if (type.equals(AnnotationConstraint.class.getSimpleName())) { + return decodeAnnotationConstraint(); + } + throw new IllegalArgumentException("Instruction type " + + type + " is not supported"); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java new file mode 100644 index 00000000..4e0f2bd9 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java @@ -0,0 +1,449 @@ +/* + * Copyright 2015 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.HashMap; +import java.util.Map; + +import org.onlab.packet.Ip6Address; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.TpPort; +import org.onlab.packet.VlanId; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.GridType; +import org.onosproject.net.Lambda; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.criteria.Criterion; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static org.onlab.util.Tools.nullIsIllegal; + +/** + * Decode portion of the criterion codec. + */ +public final class DecodeCriterionCodecHelper { + + private final ObjectNode json; + + protected static final String MISSING_MEMBER_MESSAGE = + " member is required in Criterion"; + + private interface CriterionDecoder { + Criterion decodeCriterion(ObjectNode json); + } + private final Map decoderMap; + + /** + * Creates a decode criterion codec object. + * Initializes the lookup map for criterion subclass decoders. + * + * @param json JSON object to decode + */ + public DecodeCriterionCodecHelper(ObjectNode json) { + this.json = json; + decoderMap = new HashMap<>(); + + decoderMap.put(Criterion.Type.IN_PORT.name(), new InPortDecoder()); + decoderMap.put(Criterion.Type.IN_PHY_PORT.name(), new InPhyPortDecoder()); + decoderMap.put(Criterion.Type.METADATA.name(), new MetadataDecoder()); + decoderMap.put(Criterion.Type.ETH_DST.name(), new EthDstDecoder()); + decoderMap.put(Criterion.Type.ETH_SRC.name(), new EthSrcDecoder()); + decoderMap.put(Criterion.Type.ETH_TYPE.name(), new EthTypeDecoder()); + decoderMap.put(Criterion.Type.VLAN_VID.name(), new VlanVidDecoder()); + decoderMap.put(Criterion.Type.VLAN_PCP.name(), new VlanPcpDecoder()); + decoderMap.put(Criterion.Type.IP_DSCP.name(), new IpDscpDecoder()); + decoderMap.put(Criterion.Type.IP_ECN.name(), new IpEcnDecoder()); + decoderMap.put(Criterion.Type.IP_PROTO.name(), new IpProtoDecoder()); + decoderMap.put(Criterion.Type.IPV4_SRC.name(), new IpV4SrcDecoder()); + decoderMap.put(Criterion.Type.IPV4_DST.name(), new IpV4DstDecoder()); + decoderMap.put(Criterion.Type.TCP_SRC.name(), new TcpSrcDecoder()); + decoderMap.put(Criterion.Type.TCP_DST.name(), new TcpDstDecoder()); + decoderMap.put(Criterion.Type.UDP_SRC.name(), new UdpSrcDecoder()); + decoderMap.put(Criterion.Type.UDP_DST.name(), new UdpDstDecoder()); + decoderMap.put(Criterion.Type.SCTP_SRC.name(), new SctpSrcDecoder()); + decoderMap.put(Criterion.Type.SCTP_DST.name(), new SctpDstDecoder()); + decoderMap.put(Criterion.Type.ICMPV4_TYPE.name(), new IcmpV4TypeDecoder()); + decoderMap.put(Criterion.Type.ICMPV4_CODE.name(), new IcmpV4CodeDecoder()); + decoderMap.put(Criterion.Type.IPV6_SRC.name(), new IpV6SrcDecoder()); + decoderMap.put(Criterion.Type.IPV6_DST.name(), new IpV6DstDecoder()); + decoderMap.put(Criterion.Type.IPV6_FLABEL.name(), new IpV6FLabelDecoder()); + decoderMap.put(Criterion.Type.ICMPV6_TYPE.name(), new IcmpV6TypeDecoder()); + decoderMap.put(Criterion.Type.ICMPV6_CODE.name(), new IcmpV6CodeDecoder()); + decoderMap.put(Criterion.Type.IPV6_ND_TARGET.name(), new V6NDTargetDecoder()); + decoderMap.put(Criterion.Type.IPV6_ND_SLL.name(), new V6NDSllDecoder()); + decoderMap.put(Criterion.Type.IPV6_ND_TLL.name(), new V6NDTllDecoder()); + decoderMap.put(Criterion.Type.MPLS_LABEL.name(), new MplsLabelDecoder()); + decoderMap.put(Criterion.Type.IPV6_EXTHDR.name(), new IpV6ExthdrDecoder()); + decoderMap.put(Criterion.Type.OCH_SIGID.name(), new OchSigIdDecoder()); + decoderMap.put(Criterion.Type.OCH_SIGTYPE.name(), new OchSigTypeDecoder()); + decoderMap.put(Criterion.Type.TUNNEL_ID.name(), new TunnelIdDecoder()); + } + + private class EthTypeDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + int ethType = nullIsIllegal(json.get(CriterionCodec.ETH_TYPE), + CriterionCodec.ETH_TYPE + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchEthType(ethType); + } + } + + private class EthDstDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + MacAddress mac = MacAddress.valueOf(nullIsIllegal(json.get(CriterionCodec.MAC), + CriterionCodec.MAC + MISSING_MEMBER_MESSAGE).asText()); + + return Criteria.matchEthDst(mac); + } + } + + private class EthSrcDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + MacAddress mac = MacAddress.valueOf(nullIsIllegal(json.get(CriterionCodec.MAC), + CriterionCodec.MAC + MISSING_MEMBER_MESSAGE).asText()); + + return Criteria.matchEthSrc(mac); + } + } + + private class InPortDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + PortNumber port = PortNumber.portNumber(nullIsIllegal(json.get(CriterionCodec.PORT), + CriterionCodec.PORT + MISSING_MEMBER_MESSAGE).asLong()); + + return Criteria.matchInPort(port); + } + } + + private class InPhyPortDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + PortNumber port = PortNumber.portNumber(nullIsIllegal(json.get(CriterionCodec.PORT), + CriterionCodec.PORT + MISSING_MEMBER_MESSAGE).asLong()); + + return Criteria.matchInPhyPort(port); + } + } + + private class MetadataDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + long metadata = nullIsIllegal(json.get(CriterionCodec.METADATA), + CriterionCodec.METADATA + MISSING_MEMBER_MESSAGE).asLong(); + + return Criteria.matchMetadata(metadata); + } + } + + private class VlanVidDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + short vlanId = (short) nullIsIllegal(json.get(CriterionCodec.VLAN_ID), + CriterionCodec.VLAN_ID + MISSING_MEMBER_MESSAGE).asInt(); + + return Criteria.matchVlanId(VlanId.vlanId(vlanId)); + } + } + + private class VlanPcpDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + byte priority = (byte) nullIsIllegal(json.get(CriterionCodec.PRIORITY), + CriterionCodec.VLAN_ID + MISSING_MEMBER_MESSAGE).asInt(); + + return Criteria.matchVlanPcp(priority); + } + } + + private class IpDscpDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + byte ipDscp = (byte) nullIsIllegal(json.get(CriterionCodec.IP_DSCP), + CriterionCodec.IP_DSCP + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIPDscp(ipDscp); + } + } + + private class IpEcnDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + byte ipEcn = (byte) nullIsIllegal(json.get(CriterionCodec.IP_ECN), + CriterionCodec.IP_ECN + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIPEcn(ipEcn); + } + } + + private class IpProtoDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + short proto = (short) nullIsIllegal(json.get(CriterionCodec.PROTOCOL), + CriterionCodec.PROTOCOL + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIPProtocol(proto); + } + } + + private class IpV4SrcDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + String ip = nullIsIllegal(json.get(CriterionCodec.IP), + CriterionCodec.IP + MISSING_MEMBER_MESSAGE).asText(); + return Criteria.matchIPSrc(IpPrefix.valueOf(ip)); + } + } + + private class IpV4DstDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + String ip = nullIsIllegal(json.get(CriterionCodec.IP), + CriterionCodec.IP + MISSING_MEMBER_MESSAGE).asText(); + return Criteria.matchIPDst(IpPrefix.valueOf(ip)); + } + } + + private class IpV6SrcDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + String ip = nullIsIllegal(json.get(CriterionCodec.IP), + CriterionCodec.IP + MISSING_MEMBER_MESSAGE).asText(); + return Criteria.matchIPv6Src(IpPrefix.valueOf(ip)); + } + } + + private class IpV6DstDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + String ip = nullIsIllegal(json.get(CriterionCodec.IP), + CriterionCodec.IP + MISSING_MEMBER_MESSAGE).asText(); + return Criteria.matchIPv6Dst(IpPrefix.valueOf(ip)); + } + } + + private class TcpSrcDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.TCP_PORT), + CriterionCodec.TCP_PORT + MISSING_MEMBER_MESSAGE).asInt()); + return Criteria.matchTcpSrc(tcpPort); + } + } + + private class TcpDstDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.TCP_PORT), + CriterionCodec.TCP_PORT + MISSING_MEMBER_MESSAGE).asInt()); + return Criteria.matchTcpDst(tcpPort); + } + } + + private class UdpSrcDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.UDP_PORT), + CriterionCodec.UDP_PORT + MISSING_MEMBER_MESSAGE).asInt()); + return Criteria.matchUdpSrc(udpPort); + } + } + + private class UdpDstDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.UDP_PORT), + CriterionCodec.UDP_PORT + MISSING_MEMBER_MESSAGE).asInt()); + return Criteria.matchUdpDst(udpPort); + } + } + + private class SctpSrcDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + TpPort sctpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.SCTP_PORT), + CriterionCodec.SCTP_PORT + MISSING_MEMBER_MESSAGE).asInt()); + return Criteria.matchSctpSrc(sctpPort); + } + } + + private class SctpDstDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + TpPort sctpPort = TpPort.tpPort(nullIsIllegal(json.get(CriterionCodec.SCTP_PORT), + CriterionCodec.SCTP_PORT + MISSING_MEMBER_MESSAGE).asInt()); + return Criteria.matchSctpDst(sctpPort); + } + } + + private class IcmpV4TypeDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + short type = (short) nullIsIllegal(json.get(CriterionCodec.ICMP_TYPE), + CriterionCodec.ICMP_TYPE + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIcmpType(type); + } + } + + private class IcmpV4CodeDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + short code = (short) nullIsIllegal(json.get(CriterionCodec.ICMP_CODE), + CriterionCodec.ICMP_CODE + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIcmpCode(code); + } + } + + private class IpV6FLabelDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + int flowLabel = nullIsIllegal(json.get(CriterionCodec.FLOW_LABEL), + CriterionCodec.FLOW_LABEL + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIPv6FlowLabel(flowLabel); + } + } + + private class IcmpV6TypeDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + short type = (short) nullIsIllegal(json.get(CriterionCodec.ICMPV6_TYPE), + CriterionCodec.ICMPV6_TYPE + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIcmpv6Type(type); + } + } + + private class IcmpV6CodeDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + short code = (short) nullIsIllegal(json.get(CriterionCodec.ICMPV6_CODE), + CriterionCodec.ICMPV6_CODE + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIcmpv6Code(code); + } + } + + private class V6NDTargetDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + Ip6Address target = Ip6Address.valueOf(nullIsIllegal(json.get(CriterionCodec.TARGET_ADDRESS), + CriterionCodec.TARGET_ADDRESS + MISSING_MEMBER_MESSAGE).asText()); + return Criteria.matchIPv6NDTargetAddress(target); + } + } + + private class V6NDSllDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + MacAddress mac = MacAddress.valueOf(nullIsIllegal(json.get(CriterionCodec.MAC), + CriterionCodec.MAC + MISSING_MEMBER_MESSAGE).asText()); + return Criteria.matchIPv6NDSourceLinkLayerAddress(mac); + } + } + + private class V6NDTllDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + MacAddress mac = MacAddress.valueOf(nullIsIllegal(json.get(CriterionCodec.MAC), + CriterionCodec.MAC + MISSING_MEMBER_MESSAGE).asText()); + return Criteria.matchIPv6NDTargetLinkLayerAddress(mac); + } + } + + private class MplsLabelDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + int label = nullIsIllegal(json.get(CriterionCodec.LABEL), + CriterionCodec.LABEL + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchMplsLabel(MplsLabel.mplsLabel(label)); + } + } + + private class IpV6ExthdrDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + int exthdrFlags = nullIsIllegal(json.get(CriterionCodec.EXT_HDR_FLAGS), + CriterionCodec.EXT_HDR_FLAGS + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchIPv6ExthdrFlags(exthdrFlags); + } + } + + private class OchSigIdDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + if (json.get(CriterionCodec.LAMBDA) != null) { + Lambda lambda = Lambda.indexedLambda(nullIsIllegal(json.get(CriterionCodec.LAMBDA), + CriterionCodec.LAMBDA + MISSING_MEMBER_MESSAGE).asInt()); + return Criteria.matchLambda(lambda); + } else { + JsonNode ochSignalId = nullIsIllegal(json.get(CriterionCodec.OCH_SIGNAL_ID), + CriterionCodec.GRID_TYPE + MISSING_MEMBER_MESSAGE); + GridType gridType = + GridType.valueOf( + nullIsIllegal(ochSignalId.get(CriterionCodec.GRID_TYPE), + CriterionCodec.GRID_TYPE + MISSING_MEMBER_MESSAGE).asText()); + ChannelSpacing channelSpacing = + ChannelSpacing.valueOf( + nullIsIllegal(ochSignalId.get(CriterionCodec.CHANNEL_SPACING), + CriterionCodec.CHANNEL_SPACING + MISSING_MEMBER_MESSAGE).asText()); + int spacingMultiplier = nullIsIllegal(ochSignalId.get(CriterionCodec.SPACING_MULIPLIER), + CriterionCodec.SPACING_MULIPLIER + MISSING_MEMBER_MESSAGE).asInt(); + int slotGranularity = nullIsIllegal(ochSignalId.get(CriterionCodec.SLOT_GRANULARITY), + CriterionCodec.SLOT_GRANULARITY + MISSING_MEMBER_MESSAGE).asInt(); + return Criteria.matchLambda( + Lambda.ochSignal(gridType, channelSpacing, + spacingMultiplier, slotGranularity)); + } + } + } + + private class OchSigTypeDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + return null; + } + } + + private class TunnelIdDecoder implements CriterionDecoder { + @Override + public Criterion decodeCriterion(ObjectNode json) { + long tunnelId = nullIsIllegal(json.get(CriterionCodec.TUNNEL_ID), + CriterionCodec.TUNNEL_ID + MISSING_MEMBER_MESSAGE).asLong(); + return Criteria.matchTunnelId(tunnelId); + } + } + + /** + * Decodes the JSON into a criterion object. + * + * @return Criterion object + * @throws IllegalArgumentException if the JSON is invalid + */ + public Criterion decode() { + String type = json.get(CriterionCodec.TYPE).asText(); + + CriterionDecoder decoder = decoderMap.get(type); + if (decoder != null) { + return decoder.decodeCriterion(json); + } + + throw new IllegalArgumentException("Type " + type + " is unknown"); + } + + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java new file mode 100644 index 00000000..6a97a076 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java @@ -0,0 +1,235 @@ +/* + * Copyright 2015 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 org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.TpPort; +import org.onlab.packet.VlanId; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.GridType; +import org.onosproject.net.Lambda; +import org.onosproject.net.OchSignal; +import org.onosproject.net.PortNumber; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction; +import org.onosproject.net.flow.instructions.L4ModificationInstruction; + +import static org.onlab.util.Tools.nullIsIllegal; + +/** + * Decoding portion of the instruction codec. + */ +public final class DecodeInstructionCodecHelper { + private final ObjectNode json; + + /** + * Creates a decode instruction codec object. + * + * @param json JSON object to decode + */ + public DecodeInstructionCodecHelper(ObjectNode json) { + this.json = json; + } + + /** + * Decodes a Layer 2 instruction. + * + * @return instruction object decoded from the JSON + * @throws IllegalArgumentException if the JSON is invalid + */ + private Instruction decodeL2() { + String subType = json.get(InstructionCodec.SUBTYPE).asText(); + + if (subType.equals(L2ModificationInstruction.L2SubType.ETH_SRC.name())) { + String mac = nullIsIllegal(json.get(InstructionCodec.MAC), + InstructionCodec.MAC + InstructionCodec.MISSING_MEMBER_MESSAGE).asText(); + return Instructions.modL2Src(MacAddress.valueOf(mac)); + } else if (subType.equals(L2ModificationInstruction.L2SubType.ETH_DST.name())) { + String mac = nullIsIllegal(json.get(InstructionCodec.MAC), + InstructionCodec.MAC + InstructionCodec.MISSING_MEMBER_MESSAGE).asText(); + return Instructions.modL2Dst(MacAddress.valueOf(mac)); + } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_ID.name())) { + short vlanId = (short) nullIsIllegal(json.get(InstructionCodec.VLAN_ID), + InstructionCodec.VLAN_ID + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt(); + return Instructions.modVlanId(VlanId.vlanId(vlanId)); + } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_PCP.name())) { + byte vlanPcp = (byte) nullIsIllegal(json.get(InstructionCodec.VLAN_PCP), + InstructionCodec.VLAN_PCP + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt(); + return Instructions.modVlanPcp(vlanPcp); + } else if (subType.equals(L2ModificationInstruction.L2SubType.MPLS_LABEL.name())) { + int label = nullIsIllegal(json.get(InstructionCodec.MPLS_LABEL), + InstructionCodec.MPLS_LABEL + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt(); + return Instructions.modMplsLabel(MplsLabel.mplsLabel(label)); + } else if (subType.equals(L2ModificationInstruction.L2SubType.MPLS_PUSH.name())) { + return Instructions.pushMpls(); + } else if (subType.equals(L2ModificationInstruction.L2SubType.MPLS_POP.name())) { + return Instructions.popMpls(); + } else if (subType.equals(L2ModificationInstruction.L2SubType.DEC_MPLS_TTL.name())) { + return Instructions.decMplsTtl(); + } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_POP.name())) { + return Instructions.popVlan(); + } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_PUSH.name())) { + return Instructions.pushVlan(); + } else if (subType.equals(L2ModificationInstruction.L2SubType.TUNNEL_ID.name())) { + long tunnelId = nullIsIllegal(json.get(InstructionCodec.TUNNEL_ID), + InstructionCodec.TUNNEL_ID + InstructionCodec.MISSING_MEMBER_MESSAGE).asLong(); + return Instructions.modTunnelId(tunnelId); + } + throw new IllegalArgumentException("L2 Instruction subtype " + + subType + " is not supported"); + } + + /** + * Decodes a Layer 3 instruction. + * + * @return instruction object decoded from the JSON + * @throws IllegalArgumentException if the JSON is invalid + */ + private Instruction decodeL3() { + String subType = json.get(InstructionCodec.SUBTYPE).asText(); + + if (subType.equals(L3ModificationInstruction.L3SubType.IPV4_SRC.name())) { + IpAddress ip = IpAddress.valueOf(nullIsIllegal(json.get(InstructionCodec.IP), + InstructionCodec.IP + InstructionCodec.MISSING_MEMBER_MESSAGE).asText()); + return Instructions.modL3Src(ip); + } else if (subType.equals(L3ModificationInstruction.L3SubType.IPV4_DST.name())) { + IpAddress ip = IpAddress.valueOf(nullIsIllegal(json.get(InstructionCodec.IP), + InstructionCodec.IP + InstructionCodec.MISSING_MEMBER_MESSAGE).asText()); + return Instructions.modL3Dst(ip); + } else if (subType.equals(L3ModificationInstruction.L3SubType.IPV6_SRC.name())) { + IpAddress ip = IpAddress.valueOf(nullIsIllegal(json.get(InstructionCodec.IP), + InstructionCodec.IP + InstructionCodec.MISSING_MEMBER_MESSAGE).asText()); + return Instructions.modL3IPv6Src(ip); + } else if (subType.equals(L3ModificationInstruction.L3SubType.IPV6_DST.name())) { + IpAddress ip = IpAddress.valueOf(nullIsIllegal(json.get(InstructionCodec.IP), + InstructionCodec.IP + InstructionCodec.MISSING_MEMBER_MESSAGE).asText()); + return Instructions.modL3IPv6Dst(ip); + } else if (subType.equals(L3ModificationInstruction.L3SubType.IPV6_FLABEL.name())) { + int flowLabel = nullIsIllegal(json.get(InstructionCodec.FLOW_LABEL), + InstructionCodec.FLOW_LABEL + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt(); + return Instructions.modL3IPv6FlowLabel(flowLabel); + } + throw new IllegalArgumentException("L3 Instruction subtype " + + subType + " is not supported"); + } + + /** + * Decodes a Layer 0 instruction. + * + * @return instruction object decoded from the JSON + * @throws IllegalArgumentException if the JSON is invalid + */ + private Instruction decodeL0() { + String subType = json.get(InstructionCodec.SUBTYPE).asText(); + + + if (subType.equals(L0ModificationInstruction.L0SubType.LAMBDA.name())) { + int lambda = nullIsIllegal(json.get(InstructionCodec.LAMBDA), + InstructionCodec.LAMBDA + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt(); + return Instructions.modL0Lambda(Lambda.indexedLambda(lambda)); + } else if (subType.equals(L0ModificationInstruction.L0SubType.OCH.name())) { + String gridTypeString = nullIsIllegal(json.get(InstructionCodec.GRID_TYPE), + InstructionCodec.GRID_TYPE + InstructionCodec.MISSING_MEMBER_MESSAGE).asText(); + GridType gridType = GridType.valueOf(gridTypeString); + if (gridType == null) { + throw new IllegalArgumentException("Unknown grid type " + + gridTypeString); + } + String channelSpacingString = nullIsIllegal(json.get(InstructionCodec.CHANNEL_SPACING), + InstructionCodec.CHANNEL_SPACING + InstructionCodec.MISSING_MEMBER_MESSAGE).asText(); + ChannelSpacing channelSpacing = ChannelSpacing.valueOf(channelSpacingString); + if (channelSpacing == null) { + throw new IllegalArgumentException("Unknown channel spacing " + + channelSpacingString); + } + int spacingMultiplier = nullIsIllegal(json.get(InstructionCodec.SPACING_MULTIPLIER), + InstructionCodec.SPACING_MULTIPLIER + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt(); + int slotGranularity = nullIsIllegal(json.get(InstructionCodec.SLOT_GRANULARITY), + InstructionCodec.SLOT_GRANULARITY + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt(); + return Instructions.modL0Lambda(new OchSignal(gridType, channelSpacing, + spacingMultiplier, slotGranularity)); + } + throw new IllegalArgumentException("L0 Instruction subtype " + + subType + " is not supported"); + } + + /** + * Decodes a Layer 4 instruction. + * + * @return instruction object decoded from the JSON + * @throws IllegalArgumentException if the JSON is invalid + */ + private Instruction decodeL4() { + String subType = json.get(InstructionCodec.SUBTYPE).asText(); + + if (subType.equals(L4ModificationInstruction.L4SubType.TCP_DST.name())) { + TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.TCP_PORT), + InstructionCodec.TCP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt()); + return Instructions.modTcpDst(tcpPort); + } else if (subType.equals(L4ModificationInstruction.L4SubType.TCP_SRC.name())) { + TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.TCP_PORT), + InstructionCodec.TCP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt()); + return Instructions.modTcpSrc(tcpPort); + } else if (subType.equals(L4ModificationInstruction.L4SubType.UDP_DST.name())) { + TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.UDP_PORT), + InstructionCodec.UDP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt()); + return Instructions.modUdpDst(udpPort); + } else if (subType.equals(L4ModificationInstruction.L4SubType.UDP_SRC.name())) { + TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.UDP_PORT), + InstructionCodec.UDP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt()); + return Instructions.modUdpSrc(udpPort); + } + throw new IllegalArgumentException("L4 Instruction subtype " + + subType + " is not supported"); + } + + /** + * Decodes the JSON into an instruction object. + * + * @return Criterion object + * @throws IllegalArgumentException if the JSON is invalid + */ + public Instruction decode() { + String type = json.get(InstructionCodec.TYPE).asText(); + + if (type.equals(Instruction.Type.OUTPUT.name())) { + PortNumber portNumber = + PortNumber.portNumber(nullIsIllegal(json.get(InstructionCodec.PORT), + InstructionCodec.PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asLong()); + return Instructions.createOutput(portNumber); + } else if (type.equals(Instruction.Type.DROP.name())) { + return Instructions.createDrop(); + } else if (type.equals(Instruction.Type.L0MODIFICATION.name())) { + return decodeL0(); + } else if (type.equals(Instruction.Type.L2MODIFICATION.name())) { + return decodeL2(); + } else if (type.equals(Instruction.Type.L3MODIFICATION.name())) { + return decodeL3(); + } else if (type.equals(Instruction.Type.L4MODIFICATION.name())) { + return decodeL4(); + } + throw new IllegalArgumentException("Instruction type " + + type + " is not supported"); + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java new file mode 100644 index 00000000..f1a4f786 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java @@ -0,0 +1,93 @@ +/* + * Copyright 2014-2015 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 com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onlab.packet.ChassisId; +import org.onosproject.codec.CodecContext; +import org.onosproject.net.Annotations; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.Device; +import org.onosproject.net.Device.Type; +import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.provider.ProviderId; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.DeviceId.deviceId; + +/** + * Device JSON codec. + */ +public final class DeviceCodec extends AnnotatedCodec { + + // JSON fieldNames + private static final String ID = "id"; + private static final String TYPE = "type"; + private static final String MFR = "mfr"; + private static final String HW = "hw"; + private static final String SW = "sw"; + private static final String SERIAL = "serial"; + private static final String CHASSIS_ID = "chassisId"; + + + @Override + public ObjectNode encode(Device device, CodecContext context) { + checkNotNull(device, "Device cannot be null"); + DeviceService service = context.getService(DeviceService.class); + ObjectNode result = context.mapper().createObjectNode() + .put(ID, device.id().toString()) + .put(TYPE, device.type().name()) + .put("available", service.isAvailable(device.id())) + .put("role", service.getRole(device.id()).toString()) + .put(MFR, device.manufacturer()) + .put(HW, device.hwVersion()) + .put(SW, device.swVersion()) + .put(SERIAL, device.serialNumber()) + .put(CHASSIS_ID, device.chassisId().toString()); + return annotate(result, device, context); + } + + + /** + * {@inheritDoc} + * + * Note: ProviderId is not part of JSON representation. + * Returned object will have random ProviderId set. + */ + @Override + public Device decode(ObjectNode json, CodecContext context) { + if (json == null || !json.isObject()) { + return null; + } + + DeviceId id = deviceId(json.get(ID).asText()); + // TODO: add providerId to JSON if we need to recover them. + ProviderId pid = new ProviderId(id.uri().getScheme(), "DeviceCodec"); + + Type type = Type.valueOf(json.get(TYPE).asText()); + String mfr = json.get(MFR).asText(); + String hw = json.get(HW).asText(); + String sw = json.get(SW).asText(); + String serial = json.get(SERIAL).asText(); + ChassisId chassisId = new ChassisId(json.get(CHASSIS_ID).asText()); + Annotations annotations = extractAnnotations(json, context); + + return new DefaultDevice(pid, id, type, mfr, hw, sw, serial, + chassisId, annotations); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java new file mode 100644 index 00000000..4935d992 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.driver.Driver; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * JSON codec for the Driver class. + */ +public final class DriverCodec extends JsonCodec { + private static final String PARENT = "parent"; + private static final String NAME = "name"; + private static final String MANUFACTURER = "manufacturer"; + private static final String HW_VERSION = "hwVersion"; + private static final String SW_VERSION = "swVersion"; + private static final String BEHAVIOURS = "behaviours"; + private static final String BEHAVIORS_NAME = "name"; + private static final String BEHAVIORS_IMPLEMENTATION_NAME = "implementationName"; + private static final String PROPERTIES = "properties"; + + @Override + public ObjectNode encode(Driver driver, CodecContext context) { + checkNotNull(driver, "Driver cannot be null"); + + ObjectNode result = context.mapper().createObjectNode() + .put(NAME, driver.name()) + .put(MANUFACTURER, driver.manufacturer()) + .put(HW_VERSION, driver.hwVersion()) + .put(SW_VERSION, driver.swVersion()); + + if (driver.parent() != null) { + result.put(PARENT, driver.parent().name()); + } + + ArrayNode behaviours = context.mapper().createArrayNode(); + driver.behaviours().forEach(behaviour -> { + ObjectNode entry = context.mapper().createObjectNode() + .put(BEHAVIORS_NAME, behaviour.getCanonicalName()) + .put(BEHAVIORS_IMPLEMENTATION_NAME, + driver.implementation(behaviour).getCanonicalName()); + + behaviours.add(entry); + }); + result.set(BEHAVIOURS, behaviours); + + ArrayNode properties = context.mapper().createArrayNode(); + driver.properties().forEach((name, value) -> { + ObjectNode entry = context.mapper().createObjectNode() + .put("name", name) + .put("value", value); + + properties.add(entry); + }); + result.set(PROPERTIES, properties); + + return result; + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java new file mode 100644 index 00000000..61f4dbf4 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java @@ -0,0 +1,201 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.constraint.AnnotationConstraint; +import org.onosproject.net.intent.constraint.BandwidthConstraint; +import org.onosproject.net.intent.constraint.LambdaConstraint; +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.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Implementation of encoder for constraint JSON codec. + */ +public final class EncodeConstraintCodecHelper { + + private final Constraint constraint; + private final CodecContext context; + + /** + * Constructs a constraint encoder. + * + * @param constraint constraint to encode + * @param context to use for look ups + */ + public EncodeConstraintCodecHelper(Constraint constraint, CodecContext context) { + this.constraint = constraint; + this.context = context; + } + + /** + * Encodes a latency constraint. + * + * @return JSON ObjectNode representing the constraint + */ + private ObjectNode encodeLatencyConstraint() { + checkNotNull(constraint, "Duration constraint cannot be null"); + final LatencyConstraint latencyConstraint = + (LatencyConstraint) constraint; + return context.mapper().createObjectNode() + .put("latencyMillis", latencyConstraint.latency().toMillis()); + } + + /** + * Encodes an obstacle constraint. + * + * @return JSON ObjectNode representing the constraint + */ + private ObjectNode encodeObstacleConstraint() { + checkNotNull(constraint, "Obstacle constraint cannot be null"); + final ObstacleConstraint obstacleConstraint = + (ObstacleConstraint) constraint; + + final ObjectNode result = context.mapper().createObjectNode(); + final ArrayNode jsonObstacles = result.putArray("obstacles"); + + for (DeviceId did : obstacleConstraint.obstacles()) { + jsonObstacles.add(did.toString()); + } + + return result; + } + + /** + * Encodes a waypoint constraint. + * + * @return JSON ObjectNode representing the constraint + */ + private ObjectNode encodeWaypointConstraint() { + checkNotNull(constraint, "Waypoint constraint cannot be null"); + final WaypointConstraint waypointConstraint = + (WaypointConstraint) constraint; + + final ObjectNode result = context.mapper().createObjectNode(); + final ArrayNode jsonWaypoints = result.putArray("waypoints"); + + for (DeviceId did : waypointConstraint.waypoints()) { + jsonWaypoints.add(did.toString()); + } + + return result; + } + + /** + * Encodes a annotation constraint. + * + * @return JSON ObjectNode representing the constraint + */ + private ObjectNode encodeAnnotationConstraint() { + checkNotNull(constraint, "Annotation constraint cannot be null"); + final AnnotationConstraint annotationConstraint = + (AnnotationConstraint) constraint; + return context.mapper().createObjectNode() + .put("key", annotationConstraint.key()) + .put("threshold", annotationConstraint.threshold()); + } + + /** + * Encodes a bandwidth constraint. + * + * @return JSON ObjectNode representing the constraint + */ + private ObjectNode encodeBandwidthConstraint() { + checkNotNull(constraint, "Bandwidth constraint cannot be null"); + final BandwidthConstraint bandwidthConstraint = + (BandwidthConstraint) constraint; + return context.mapper().createObjectNode() + .put("bandwidth", bandwidthConstraint.bandwidth().toDouble()); + } + + /** + * Encodes a lambda constraint. + * + * @return JSON ObjectNode representing the constraint + */ + private ObjectNode encodeLambdaConstraint() { + checkNotNull(constraint, "Lambda constraint cannot be null"); + final LambdaConstraint lambdaConstraint = + (LambdaConstraint) constraint; + + return context.mapper().createObjectNode() + .put("lambda", lambdaConstraint.lambda().toInt()); + } + + /** + * Encodes a link type constraint. + * + * @return JSON ObjectNode representing the constraint + */ + private ObjectNode encodeLinkTypeConstraint() { + checkNotNull(constraint, "Link type constraint cannot be null"); + + final LinkTypeConstraint linkTypeConstraint = + (LinkTypeConstraint) constraint; + + final ObjectNode result = context.mapper().createObjectNode() + .put(ConstraintCodec.INCLUSIVE, linkTypeConstraint.isInclusive()); + + final ArrayNode jsonTypes = result.putArray(ConstraintCodec.TYPES); + + if (linkTypeConstraint.types() != null) { + for (Link.Type type : linkTypeConstraint.types()) { + jsonTypes.add(type.name()); + } + } + + return result; + } + + /** + * Encodes the constraint in JSON. + * + * @return JSON node + */ + public ObjectNode encode() { + final ObjectNode result; + if (constraint instanceof BandwidthConstraint) { + result = encodeBandwidthConstraint(); + } else if (constraint instanceof LambdaConstraint) { + result = encodeLambdaConstraint(); + } else if (constraint instanceof LinkTypeConstraint) { + result = encodeLinkTypeConstraint(); + } else if (constraint instanceof AnnotationConstraint) { + result = encodeAnnotationConstraint(); + } else if (constraint instanceof LatencyConstraint) { + result = encodeLatencyConstraint(); + } else if (constraint instanceof ObstacleConstraint) { + result = encodeObstacleConstraint(); + } else if (constraint instanceof WaypointConstraint) { + result = encodeWaypointConstraint(); + } else { + result = context.mapper().createObjectNode(); + } + + result.put(ConstraintCodec.TYPE, constraint.getClass().getSimpleName()); + return result; + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java new file mode 100644 index 00000000..a962c0dd --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java @@ -0,0 +1,396 @@ +/* + * Copyright 2015 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.EnumMap; + +import org.onosproject.codec.CodecContext; +import org.onosproject.net.OchSignal; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.IPDscpCriterion; +import org.onosproject.net.flow.criteria.IPEcnCriterion; +import org.onosproject.net.flow.criteria.IPProtocolCriterion; +import org.onosproject.net.flow.criteria.IPv6ExthdrFlagsCriterion; +import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion; +import org.onosproject.net.flow.criteria.IPv6NDLinkLayerAddressCriterion; +import org.onosproject.net.flow.criteria.IPv6NDTargetAddressCriterion; +import org.onosproject.net.flow.criteria.IcmpCodeCriterion; +import org.onosproject.net.flow.criteria.IcmpTypeCriterion; +import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion; +import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion; +import org.onosproject.net.flow.criteria.MetadataCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; +import org.onosproject.net.flow.criteria.OchSignalCriterion; +import org.onosproject.net.flow.criteria.OchSignalTypeCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.SctpPortCriterion; +import org.onosproject.net.flow.criteria.TcpPortCriterion; +import org.onosproject.net.flow.criteria.TunnelIdCriterion; +import org.onosproject.net.flow.criteria.UdpPortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.criteria.VlanPcpCriterion; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Encode portion of the criterion codec. + */ +public final class EncodeCriterionCodecHelper { + + private final Criterion criterion; + private final CodecContext context; + + private final EnumMap formatMap; + + /** + * Creates an encoder object for a criterion. + * Initializes the formatter lookup map for the criterion subclasses. + * + * @param criterion Criterion to encode + * @param context context of the JSON encoding + */ + public EncodeCriterionCodecHelper(Criterion criterion, CodecContext context) { + this.criterion = criterion; + this.context = context; + + formatMap = new EnumMap<>(Criterion.Type.class); + + formatMap.put(Criterion.Type.IN_PORT, new FormatInPort()); + formatMap.put(Criterion.Type.IN_PHY_PORT, new FormatInPort()); + formatMap.put(Criterion.Type.METADATA, new FormatMetadata()); + formatMap.put(Criterion.Type.ETH_DST, new FormatEth()); + formatMap.put(Criterion.Type.ETH_SRC, new FormatEth()); + formatMap.put(Criterion.Type.ETH_TYPE, new FormatEthType()); + formatMap.put(Criterion.Type.VLAN_VID, new FormatVlanVid()); + formatMap.put(Criterion.Type.VLAN_PCP, new FormatVlanPcp()); + formatMap.put(Criterion.Type.IP_DSCP, new FormatIpDscp()); + formatMap.put(Criterion.Type.IP_ECN, new FormatIpEcn()); + formatMap.put(Criterion.Type.IP_PROTO, new FormatIpProto()); + formatMap.put(Criterion.Type.IPV4_SRC, new FormatIp()); + formatMap.put(Criterion.Type.IPV4_DST, new FormatIp()); + formatMap.put(Criterion.Type.TCP_SRC, new FormatTcp()); + formatMap.put(Criterion.Type.TCP_DST, new FormatTcp()); + formatMap.put(Criterion.Type.UDP_SRC, new FormatUdp()); + formatMap.put(Criterion.Type.UDP_DST, new FormatUdp()); + formatMap.put(Criterion.Type.SCTP_SRC, new FormatSctp()); + formatMap.put(Criterion.Type.SCTP_DST, new FormatSctp()); + formatMap.put(Criterion.Type.ICMPV4_TYPE, new FormatIcmpV4Type()); + formatMap.put(Criterion.Type.ICMPV4_CODE, new FormatIcmpV4Code()); + formatMap.put(Criterion.Type.IPV6_SRC, new FormatIp()); + formatMap.put(Criterion.Type.IPV6_DST, new FormatIp()); + formatMap.put(Criterion.Type.IPV6_FLABEL, new FormatIpV6FLabel()); + formatMap.put(Criterion.Type.ICMPV6_TYPE, new FormatIcmpV6Type()); + formatMap.put(Criterion.Type.ICMPV6_CODE, new FormatIcmpV6Code()); + formatMap.put(Criterion.Type.IPV6_ND_TARGET, new FormatV6NDTarget()); + formatMap.put(Criterion.Type.IPV6_ND_SLL, new FormatV6NDTll()); + formatMap.put(Criterion.Type.IPV6_ND_TLL, new FormatV6NDTll()); + formatMap.put(Criterion.Type.MPLS_LABEL, new FormatMplsLabel()); + formatMap.put(Criterion.Type.IPV6_EXTHDR, new FormatIpV6Exthdr()); + formatMap.put(Criterion.Type.OCH_SIGID, new FormatOchSigId()); + formatMap.put(Criterion.Type.OCH_SIGTYPE, new FormatOchSigType()); + formatMap.put(Criterion.Type.TUNNEL_ID, new FormatTunnelId()); + formatMap.put(Criterion.Type.DUMMY, new FormatDummyType()); + + // Currently unimplemented + formatMap.put(Criterion.Type.ARP_OP, new FormatUnknown()); + formatMap.put(Criterion.Type.ARP_SPA, new FormatUnknown()); + formatMap.put(Criterion.Type.ARP_TPA, new FormatUnknown()); + formatMap.put(Criterion.Type.ARP_SHA, new FormatUnknown()); + formatMap.put(Criterion.Type.ARP_THA, new FormatUnknown()); + formatMap.put(Criterion.Type.MPLS_TC, new FormatUnknown()); + formatMap.put(Criterion.Type.MPLS_BOS, new FormatUnknown()); + formatMap.put(Criterion.Type.PBB_ISID, new FormatUnknown()); + formatMap.put(Criterion.Type.UNASSIGNED_40, new FormatUnknown()); + formatMap.put(Criterion.Type.PBB_UCA, new FormatUnknown()); + formatMap.put(Criterion.Type.TCP_FLAGS, new FormatUnknown()); + formatMap.put(Criterion.Type.ACTSET_OUTPUT, new FormatUnknown()); + formatMap.put(Criterion.Type.PACKET_TYPE, new FormatUnknown()); + } + + private interface CriterionTypeFormatter { + ObjectNode encodeCriterion(ObjectNode root, Criterion criterion); + } + + private static class FormatUnknown implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + return root; + } + } + + private static class FormatInPort implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final PortCriterion portCriterion = (PortCriterion) criterion; + return root.put(CriterionCodec.PORT, portCriterion.port().toLong()); + } + } + + private static class FormatMetadata implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final MetadataCriterion metadataCriterion = + (MetadataCriterion) criterion; + return root.put(CriterionCodec.METADATA, metadataCriterion.metadata()); + } + } + + private static class FormatEth implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final EthCriterion ethCriterion = (EthCriterion) criterion; + return root.put(CriterionCodec.MAC, ethCriterion.mac().toString()); + } + } + + private static class FormatEthType implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final EthTypeCriterion ethTypeCriterion = + (EthTypeCriterion) criterion; + return root.put(CriterionCodec.ETH_TYPE, ethTypeCriterion.ethType().toShort()); + } + } + + private static class FormatVlanVid implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final VlanIdCriterion vlanIdCriterion = + (VlanIdCriterion) criterion; + return root.put(CriterionCodec.VLAN_ID, vlanIdCriterion.vlanId().toShort()); + } + } + + private static class FormatVlanPcp implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final VlanPcpCriterion vlanPcpCriterion = + (VlanPcpCriterion) criterion; + return root.put(CriterionCodec.PRIORITY, vlanPcpCriterion.priority()); + } + } + + private static class FormatIpDscp implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IPDscpCriterion ipDscpCriterion = + (IPDscpCriterion) criterion; + return root.put(CriterionCodec.IP_DSCP, ipDscpCriterion.ipDscp()); + } + } + + private static class FormatIpEcn implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IPEcnCriterion ipEcnCriterion = + (IPEcnCriterion) criterion; + return root.put(CriterionCodec.IP_ECN, ipEcnCriterion.ipEcn()); + } + } + + private static class FormatIpProto implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IPProtocolCriterion iPProtocolCriterion = + (IPProtocolCriterion) criterion; + return root.put(CriterionCodec.PROTOCOL, iPProtocolCriterion.protocol()); + } + } + + private static class FormatIp implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IPCriterion iPCriterion = (IPCriterion) criterion; + return root.put(CriterionCodec.IP, iPCriterion.ip().toString()); + } + } + + private static class FormatTcp implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final TcpPortCriterion tcpPortCriterion = + (TcpPortCriterion) criterion; + return root.put(CriterionCodec.TCP_PORT, tcpPortCriterion.tcpPort().toInt()); + } + } + + private static class FormatUdp implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final UdpPortCriterion udpPortCriterion = + (UdpPortCriterion) criterion; + return root.put(CriterionCodec.UDP_PORT, udpPortCriterion.udpPort().toInt()); + } + } + + private static class FormatSctp implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final SctpPortCriterion sctpPortCriterion = + (SctpPortCriterion) criterion; + return root.put(CriterionCodec.SCTP_PORT, sctpPortCriterion.sctpPort().toInt()); + } + } + + private static class FormatIcmpV4Type implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IcmpTypeCriterion icmpTypeCriterion = + (IcmpTypeCriterion) criterion; + return root.put(CriterionCodec.ICMP_TYPE, icmpTypeCriterion.icmpType()); + } + } + + private static class FormatIcmpV4Code implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IcmpCodeCriterion icmpCodeCriterion = + (IcmpCodeCriterion) criterion; + return root.put(CriterionCodec.ICMP_CODE, icmpCodeCriterion.icmpCode()); + } + } + + private static class FormatIpV6FLabel implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IPv6FlowLabelCriterion ipv6FlowLabelCriterion = + (IPv6FlowLabelCriterion) criterion; + return root.put(CriterionCodec.FLOW_LABEL, ipv6FlowLabelCriterion.flowLabel()); + } + } + + private static class FormatIcmpV6Type implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final Icmpv6TypeCriterion icmpv6TypeCriterion = + (Icmpv6TypeCriterion) criterion; + return root.put(CriterionCodec.ICMPV6_TYPE, icmpv6TypeCriterion.icmpv6Type()); + } + } + + private static class FormatIcmpV6Code implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final Icmpv6CodeCriterion icmpv6CodeCriterion = + (Icmpv6CodeCriterion) criterion; + return root.put(CriterionCodec.ICMPV6_CODE, icmpv6CodeCriterion.icmpv6Code()); + } + } + + private static class FormatV6NDTarget implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IPv6NDTargetAddressCriterion ipv6NDTargetAddressCriterion + = (IPv6NDTargetAddressCriterion) criterion; + return root.put(CriterionCodec.TARGET_ADDRESS, ipv6NDTargetAddressCriterion.targetAddress().toString()); + } + } + + private static class FormatV6NDTll implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IPv6NDLinkLayerAddressCriterion ipv6NDLinkLayerAddressCriterion + = (IPv6NDLinkLayerAddressCriterion) criterion; + return root.put(CriterionCodec.MAC, ipv6NDLinkLayerAddressCriterion.mac().toString()); + } + } + + private static class FormatMplsLabel implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final MplsCriterion mplsCriterion = + (MplsCriterion) criterion; + return root.put(CriterionCodec.LABEL, mplsCriterion.label().toInt()); + } + } + + private static class FormatIpV6Exthdr implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final IPv6ExthdrFlagsCriterion exthdrCriterion = + (IPv6ExthdrFlagsCriterion) criterion; + return root.put(CriterionCodec.EXT_HDR_FLAGS, exthdrCriterion.exthdrFlags()); + } + } + + private static class FormatOchSigId implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + OchSignal ochSignal = ((OchSignalCriterion) criterion).lambda(); + ObjectNode child = root.putObject(CriterionCodec.OCH_SIGNAL_ID); + + child.put(CriterionCodec.GRID_TYPE, ochSignal.gridType().name()); + child.put(CriterionCodec.CHANNEL_SPACING, ochSignal.channelSpacing().name()); + child.put(CriterionCodec.SPACING_MULIPLIER, ochSignal.spacingMultiplier()); + child.put(CriterionCodec.SLOT_GRANULARITY, ochSignal.slotGranularity()); + + return root; + } + } + + private static class FormatOchSigType implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final OchSignalTypeCriterion ochSignalTypeCriterion = + (OchSignalTypeCriterion) criterion; + return root.put("ochSignalType", ochSignalTypeCriterion.signalType().name()); + } + } + + private static class FormatTunnelId implements CriterionTypeFormatter { + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + final TunnelIdCriterion tunnelIdCriterion = + (TunnelIdCriterion) criterion; + return root.put(CriterionCodec.TUNNEL_ID, tunnelIdCriterion.tunnelId()); + } + } + + private class FormatDummyType implements CriterionTypeFormatter { + + @Override + public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) { + checkNotNull(criterion, "Criterion cannot be null"); + + return root.put(CriterionCodec.TYPE, criterion.type().toString()); + + } + } + + /** + * Encodes a criterion into a JSON node. + * + * @return encoded JSON object for the given criterion + */ + public ObjectNode encode() { + final ObjectNode result = context.mapper().createObjectNode() + .put(CriterionCodec.TYPE, criterion.type().toString()); + + CriterionTypeFormatter formatter = + checkNotNull( + formatMap.get(criterion.type()), + "No formatter found for criterion type " + + criterion.type().toString()); + + return formatter.encodeCriterion(result, criterion); + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java new file mode 100644 index 00000000..d61cf38b --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java @@ -0,0 +1,243 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.net.OchSignal; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction; +import org.onosproject.net.flow.instructions.L4ModificationInstruction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * JSON encoding of Instructions. + */ +public final class EncodeInstructionCodecHelper { + protected static final Logger log = LoggerFactory.getLogger(EncodeInstructionCodecHelper.class); + private final Instruction instruction; + private final CodecContext context; + + /** + * Creates an instruction object encoder. + * + * @param instruction instruction to encode + * @param context codec context for the encoding + */ + public EncodeInstructionCodecHelper(Instruction instruction, CodecContext context) { + this.instruction = instruction; + this.context = context; + } + + + /** + * Encode an L0 modification instruction. + * + * @param result json node that the instruction attributes are added to + */ + private void encodeL0(ObjectNode result) { + L0ModificationInstruction instruction = + (L0ModificationInstruction) this.instruction; + result.put(InstructionCodec.SUBTYPE, instruction.subtype().name()); + + switch (instruction.subtype()) { + case LAMBDA: + final L0ModificationInstruction.ModLambdaInstruction modLambdaInstruction = + (L0ModificationInstruction.ModLambdaInstruction) instruction; + result.put(InstructionCodec.LAMBDA, modLambdaInstruction.lambda()); + break; + + case OCH: + L0ModificationInstruction.ModOchSignalInstruction ochSignalInstruction = + (L0ModificationInstruction.ModOchSignalInstruction) instruction; + OchSignal ochSignal = ochSignalInstruction.lambda(); + result.put(InstructionCodec.GRID_TYPE, ochSignal.gridType().name()); + result.put(InstructionCodec.CHANNEL_SPACING, ochSignal.channelSpacing().name()); + result.put(InstructionCodec.SPACING_MULTIPLIER, ochSignal.spacingMultiplier()); + result.put(InstructionCodec.SLOT_GRANULARITY, ochSignal.slotGranularity()); + break; + + default: + log.info("Cannot convert L0 subtype of {}", instruction.subtype()); + } + } + + /** + * Encode an L2 modification instruction. + * + * @param result json node that the instruction attributes are added to + */ + private void encodeL2(ObjectNode result) { + L2ModificationInstruction instruction = + (L2ModificationInstruction) this.instruction; + result.put(InstructionCodec.SUBTYPE, instruction.subtype().name()); + + switch (instruction.subtype()) { + case ETH_SRC: + case ETH_DST: + final L2ModificationInstruction.ModEtherInstruction modEtherInstruction = + (L2ModificationInstruction.ModEtherInstruction) instruction; + result.put(InstructionCodec.MAC, modEtherInstruction.mac().toString()); + break; + + case VLAN_ID: + final L2ModificationInstruction.ModVlanIdInstruction modVlanIdInstruction = + (L2ModificationInstruction.ModVlanIdInstruction) instruction; + result.put(InstructionCodec.VLAN_ID, modVlanIdInstruction.vlanId().toShort()); + break; + + case VLAN_PCP: + final L2ModificationInstruction.ModVlanPcpInstruction modVlanPcpInstruction = + (L2ModificationInstruction.ModVlanPcpInstruction) instruction; + result.put(InstructionCodec.VLAN_PCP, modVlanPcpInstruction.vlanPcp()); + break; + + case MPLS_LABEL: + final L2ModificationInstruction.ModMplsLabelInstruction modMplsLabelInstruction = + (L2ModificationInstruction.ModMplsLabelInstruction) instruction; + result.put(InstructionCodec.MPLS_LABEL, modMplsLabelInstruction.mplsLabel().toInt()); + break; + + case MPLS_PUSH: + final L2ModificationInstruction.PushHeaderInstructions pushHeaderInstructions = + (L2ModificationInstruction.PushHeaderInstructions) instruction; + + result.put(InstructionCodec.ETHERNET_TYPE, + pushHeaderInstructions.ethernetType().toShort()); + break; + + case TUNNEL_ID: + final L2ModificationInstruction.ModTunnelIdInstruction modTunnelIdInstruction = + (L2ModificationInstruction.ModTunnelIdInstruction) instruction; + result.put(InstructionCodec.TUNNEL_ID, modTunnelIdInstruction.tunnelId()); + break; + + default: + log.info("Cannot convert L2 subtype of {}", instruction.subtype()); + break; + } + } + + /** + * Encode an L3 modification instruction. + * + * @param result json node that the instruction attributes are added to + */ + private void encodeL3(ObjectNode result) { + L3ModificationInstruction instruction = + (L3ModificationInstruction) this.instruction; + result.put(InstructionCodec.SUBTYPE, instruction.subtype().name()); + switch (instruction.subtype()) { + case IPV4_SRC: + case IPV4_DST: + case IPV6_SRC: + case IPV6_DST: + final L3ModificationInstruction.ModIPInstruction modIPInstruction = + (L3ModificationInstruction.ModIPInstruction) instruction; + result.put(InstructionCodec.IP, modIPInstruction.ip().toString()); + break; + + case IPV6_FLABEL: + final L3ModificationInstruction.ModIPv6FlowLabelInstruction + modFlowLabelInstruction = + (L3ModificationInstruction.ModIPv6FlowLabelInstruction) instruction; + result.put(InstructionCodec.FLOW_LABEL, modFlowLabelInstruction.flowLabel()); + break; + + default: + log.info("Cannot convert L3 subtype of {}", instruction.subtype()); + break; + } + } + + /** + * Encode a L4 modification instruction. + * + * @param result json node that the instruction attributes are added to + */ + private void encodeL4(ObjectNode result) { + L4ModificationInstruction instruction = + (L4ModificationInstruction) this.instruction; + result.put(InstructionCodec.SUBTYPE, instruction.subtype().name()); + switch (instruction.subtype()) { + case TCP_DST: + case TCP_SRC: + final L4ModificationInstruction.ModTransportPortInstruction modTcpPortInstruction = + (L4ModificationInstruction.ModTransportPortInstruction) instruction; + result.put(InstructionCodec.TCP_PORT, modTcpPortInstruction.port().toInt()); + break; + + case UDP_DST: + case UDP_SRC: + final L4ModificationInstruction.ModTransportPortInstruction modUdpPortInstruction = + (L4ModificationInstruction.ModTransportPortInstruction) instruction; + result.put(InstructionCodec.UDP_PORT, modUdpPortInstruction.port().toInt()); + break; + + default: + log.info("Cannot convert L4 subtype of {}", instruction.subtype()); + break; + } + } + + /** + * Encodes the given instruction into JSON. + * + * @return JSON object node representing the instruction + */ + public ObjectNode encode() { + final ObjectNode result = context.mapper().createObjectNode() + .put(InstructionCodec.TYPE, instruction.type().toString()); + + switch (instruction.type()) { + case OUTPUT: + final Instructions.OutputInstruction outputInstruction = + (Instructions.OutputInstruction) instruction; + result.put(InstructionCodec.PORT, outputInstruction.port().toLong()); + break; + + case DROP: + break; + + case L0MODIFICATION: + encodeL0(result); + break; + + case L2MODIFICATION: + encodeL2(result); + break; + + case L3MODIFICATION: + encodeL3(result); + break; + + case L4MODIFICATION: + encodeL4(result); + break; + + default: + log.info("Cannot convert instruction type of {}", instruction.type()); + break; + } + return result; + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.java new file mode 100644 index 00000000..f56bca46 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 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 org.onlab.packet.Ethernet; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Ethernet codec. + */ +public final class EthernetCodec extends JsonCodec { + + protected static final Logger log = LoggerFactory.getLogger(CriterionCodec.class); + + @Override + public ObjectNode encode(Ethernet ethernet, CodecContext context) { + checkNotNull(ethernet, "Ethernet cannot be null"); + + final ObjectNode result = context.mapper().createObjectNode() + .put("vlanId", ethernet.getVlanID()) + .put("etherType", ethernet.getEtherType()) + .put("priorityCode", ethernet.getPriorityCode()) + .put("pad", ethernet.isPad()); + + if (ethernet.getDestinationMAC() != null) { + result.put("destMac", + ethernet.getDestinationMAC().toString()); + } + + if (ethernet.getSourceMAC() != null) { + result.put("srcMac", + ethernet.getSourceMAC().toString()); + } + + return result; + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java new file mode 100644 index 00000000..923bdf2b --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014-2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.core.CoreService; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Flow entry JSON codec. + */ +public final class FlowEntryCodec extends JsonCodec { + + @Override + public ObjectNode encode(FlowEntry flowEntry, CodecContext context) { + checkNotNull(flowEntry, "Flow entry cannot be null"); + + CoreService service = context.getService(CoreService.class); + + final ObjectNode result = context.mapper().createObjectNode() + .put("id", Long.toString(flowEntry.id().value())) + .put("appId", service.getAppId(flowEntry.appId()).name()) + .put("groupId", flowEntry.groupId().id()) + .put("priority", flowEntry.priority()) + .put("timeout", flowEntry.timeout()) + .put("isPermanent", flowEntry.isPermanent()) + .put("deviceId", flowEntry.deviceId().toString()) + .put("state", flowEntry.state().toString()) + .put("life", flowEntry.life()) + .put("packets", flowEntry.packets()) + .put("bytes", flowEntry.bytes()) + .put("lastSeen", flowEntry.lastSeen()); + + if (flowEntry.treatment() != null) { + final JsonCodec treatmentCodec = + context.codec(TrafficTreatment.class); + result.set("treatment", treatmentCodec.encode(flowEntry.treatment(), context)); + } + + if (flowEntry.selector() != null) { + final JsonCodec selectorCodec = + context.codec(TrafficSelector.class); + result.set("selector", selectorCodec.encode(flowEntry.selector(), context)); + } + + return result; + } + +} + diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java new file mode 100644 index 00000000..6c02841e --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static org.onlab.util.Tools.nullIsIllegal; + +/** + * Flow rule JSON codec. + */ +public final class FlowRuleCodec extends JsonCodec { + + private static final String PRIORITY = "priority"; + private static final String TIMEOUT = "timeout"; + private static final String IS_PERMANENT = "isPermanent"; + private static final String DEVICE_ID = "deviceId"; + private static final String TREATMENT = "treatment"; + private static final String SELECTOR = "selector"; + private static final String MISSING_MEMBER_MESSAGE = + " member is required in FlowRule"; + public static final String REST_APP_ID = "org.onosproject.rest"; + + + @Override + public FlowRule decode(ObjectNode json, CodecContext context) { + if (json == null || !json.isObject()) { + return null; + } + + FlowRule.Builder resultBuilder = new DefaultFlowRule.Builder(); + + CoreService coreService = context.getService(CoreService.class); + resultBuilder.fromApp(coreService + .registerApplication(REST_APP_ID)); + + int priority = nullIsIllegal(json.get(PRIORITY), + PRIORITY + MISSING_MEMBER_MESSAGE).asInt(); + resultBuilder.withPriority(priority); + + boolean isPermanent = nullIsIllegal(json.get(IS_PERMANENT), + IS_PERMANENT + MISSING_MEMBER_MESSAGE).asBoolean(); + if (isPermanent) { + resultBuilder.makePermanent(); + } else { + resultBuilder.makeTemporary(nullIsIllegal(json.get(TIMEOUT), + TIMEOUT + + MISSING_MEMBER_MESSAGE + + " if the flow is temporary").asInt()); + } + + DeviceId deviceId = DeviceId.deviceId(nullIsIllegal(json.get(DEVICE_ID), + DEVICE_ID + MISSING_MEMBER_MESSAGE).asText()); + resultBuilder.forDevice(deviceId); + + ObjectNode treatmentJson = get(json, TREATMENT); + if (treatmentJson != null) { + JsonCodec treatmentCodec = + context.codec(TrafficTreatment.class); + resultBuilder.withTreatment(treatmentCodec.decode(treatmentJson, context)); + } + + ObjectNode selectorJson = get(json, SELECTOR); + if (selectorJson != null) { + JsonCodec selectorCodec = + context.codec(TrafficSelector.class); + resultBuilder.withSelector(selectorCodec.decode(selectorJson, context)); + } + + return resultBuilder.build(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java new file mode 100644 index 00000000..c710514f --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.group.GroupBucket; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Group bucket JSON codec. + */ +public class GroupBucketCodec extends JsonCodec { + + private static final String TYPE = "type"; + private static final String TREATMENT = "treatment"; + private static final String WEIGHT = "weight"; + private static final String WATCH_PORT = "watchPort"; + private static final String WATCH_GROUP = "watchGroup"; + private static final String PACKETS = "packets"; + private static final String BYTES = "bytes"; + + @Override + public ObjectNode encode(GroupBucket bucket, CodecContext context) { + checkNotNull(bucket, "Driver cannot be null"); + + ObjectNode result = context.mapper().createObjectNode() + .put(TYPE, bucket.type().toString()) + .put(WEIGHT, bucket.weight()) + .put(PACKETS, bucket.packets()) + .put(BYTES, bucket.bytes()); + + if (bucket.watchPort() != null) { + result.put(WATCH_PORT, bucket.watchPort().toString()); + } + + if (bucket.watchGroup() != null) { + result.put(WATCH_GROUP, bucket.watchGroup().toString()); + } + + if (bucket.treatment() != null) { + result.set(TREATMENT, context.codec(TrafficTreatment.class).encode(bucket.treatment(), context)); + } + + return result; + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java new file mode 100644 index 00000000..a2f33cee --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Group JSON codec. + */ +public final class GroupCodec extends JsonCodec { + // JSON field names + private static final String ID = "id"; + private static final String STATE = "state"; + private static final String LIFE = "life"; + private static final String PACKETS = "packets"; + private static final String BYTES = "bytes"; + private static final String REFERENCE_COUNT = "referenceCount"; + private static final String TYPE = "type"; + private static final String DEVICE_ID = "deviceId"; + private static final String APP_ID = "appId"; + private static final String APP_COOKIE = "appCookie"; + private static final String GIVEN_GROUP_ID = "givenGroupId"; + private static final String BUCKETS = "buckets"; + + @Override + public ObjectNode encode(Group group, CodecContext context) { + checkNotNull(group, "Group cannot be null"); + ObjectNode result = context.mapper().createObjectNode() + .put(ID, group.id().toString()) + .put(STATE, group.state().toString()) + .put(LIFE, group.life()) + .put(PACKETS, group.packets()) + .put(BYTES, group.bytes()) + .put(REFERENCE_COUNT, group.referenceCount()) + .put(TYPE, group.type().toString()) + .put(DEVICE_ID, group.deviceId().toString()); + + if (group.appId() != null) { + result.put(APP_ID, group.appId().toString()); + } + + if (group.appCookie() != null) { + result.put(APP_COOKIE, group.appCookie().toString()); + } + + if (group.givenGroupId() != null) { + result.put(GIVEN_GROUP_ID, group.givenGroupId()); + } + + ArrayNode buckets = context.mapper().createArrayNode(); + group.buckets().buckets().forEach(bucket -> { + ObjectNode bucketJson = context.codec(GroupBucket.class).encode(bucket, context); + buckets.add(bucketJson); + }); + result.set(BUCKETS, buckets); + return result; + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java new file mode 100644 index 00000000..a2402728 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014-2015 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 org.onlab.packet.IpAddress; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.Host; +import org.onosproject.net.HostLocation; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Host JSON codec. + */ +public final class HostCodec extends AnnotatedCodec { + + @Override + public ObjectNode encode(Host host, CodecContext context) { + checkNotNull(host, "Host cannot be null"); + final JsonCodec locationCodec = + context.codec(HostLocation.class); + final ObjectNode result = context.mapper().createObjectNode() + .put("id", host.id().toString()) + .put("mac", host.mac().toString()) + .put("vlan", host.vlan().toString()); + + final ArrayNode jsonIpAddresses = result.putArray("ipAddresses"); + for (final IpAddress ipAddress : host.ipAddresses()) { + jsonIpAddresses.add(ipAddress.toString()); + } + result.set("ipAddresses", jsonIpAddresses); + result.set("location", locationCodec.encode(host.location(), context)); + + return annotate(result, host, context); + } + +} + diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java new file mode 100644 index 00000000..f8f616d0 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014-2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.HostLocation; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Host JSON codec. + */ +public final class HostLocationCodec extends JsonCodec { + + @Override + public ObjectNode encode(HostLocation hostLocation, CodecContext context) { + checkNotNull(hostLocation, "Host location cannot be null"); + return context.mapper().createObjectNode() + .put("elementId", hostLocation.elementId().toString()) + .put("port", hostLocation.port().toString()); + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.java new file mode 100644 index 00000000..597ab55c --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.HostId; +import org.onosproject.net.intent.ConnectivityIntent; +import org.onosproject.net.intent.HostToHostIntent; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.util.Tools.nullIsIllegal; + +/** + * Host to host intent codec. + */ +public final class HostToHostIntentCodec extends JsonCodec { + + private static final String ONE = "one"; + private static final String TWO = "two"; + + @Override + public ObjectNode encode(HostToHostIntent intent, CodecContext context) { + checkNotNull(intent, "Host to host intent cannot be null"); + + final JsonCodec connectivityIntentCodec = + context.codec(ConnectivityIntent.class); + final ObjectNode result = connectivityIntentCodec.encode(intent, context); + + final String one = intent.one().toString(); + final String two = intent.two().toString(); + result.put(ONE, one); + result.put(TWO, two); + + return result; + } + + @Override + public HostToHostIntent decode(ObjectNode json, CodecContext context) { + HostToHostIntent.Builder builder = HostToHostIntent.builder(); + + IntentCodec.intentAttributes(json, context, builder); + ConnectivityIntentCodec.intentAttributes(json, context, builder); + + String one = nullIsIllegal(json.get(ONE), + ONE + IntentCodec.MISSING_MEMBER_MESSAGE).asText(); + builder.one(HostId.hostId(one)); + + String two = nullIsIllegal(json.get(TWO), + TWO + IntentCodec.MISSING_MEMBER_MESSAGE).asText(); + builder.two(HostId.hostId(two)); + + return builder.build(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java new file mode 100644 index 00000000..f4d5008a --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.flow.instructions.Instruction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Instruction codec. + */ +public final class InstructionCodec extends JsonCodec { + + protected static final Logger log = LoggerFactory.getLogger(InstructionCodec.class); + + protected static final String TYPE = "type"; + protected static final String SUBTYPE = "subtype"; + protected static final String PORT = "port"; + protected static final String MAC = "mac"; + protected static final String VLAN_ID = "vlanId"; + protected static final String VLAN_PCP = "vlanPcp"; + protected static final String MPLS_LABEL = "label"; + protected static final String IP = "ip"; + protected static final String FLOW_LABEL = "flowLabel"; + protected static final String LAMBDA = "lambda"; + protected static final String GRID_TYPE = "gridType"; + protected static final String CHANNEL_SPACING = "channelSpacing"; + protected static final String SPACING_MULTIPLIER = "spacingMultiplier"; + protected static final String SLOT_GRANULARITY = "slotGranularity"; + protected static final String ETHERNET_TYPE = "ethernetType"; + protected static final String TUNNEL_ID = "tunnelId"; + protected static final String TCP_PORT = "tcpPort"; + protected static final String UDP_PORT = "udpPort"; + + protected static final String MISSING_MEMBER_MESSAGE = + " member is required in Instruction"; + + + @Override + public ObjectNode encode(Instruction instruction, CodecContext context) { + checkNotNull(instruction, "Instruction cannot be null"); + + return new EncodeInstructionCodecHelper(instruction, context).encode(); + } + + @Override + public Instruction decode(ObjectNode json, CodecContext context) { + if (json == null || !json.isObject()) { + return null; + } + + return new DecodeInstructionCodecHelper(json).decode(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java new file mode 100644 index 00000000..8613a964 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014-2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.core.CoreService; +import org.onosproject.net.NetworkResource; +import org.onosproject.net.intent.HostToHostIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.IntentState; +import org.onosproject.net.intent.PointToPointIntent; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.net.UrlEscapers; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.util.Tools.nullIsIllegal; + +/** + * Intent JSON codec. + */ +public final class IntentCodec extends JsonCodec { + + protected static final String TYPE = "type"; + protected static final String ID = "id"; + protected static final String APP_ID = "appId"; + protected static final String STATE = "state"; + protected static final String PRIORITY = "priority"; + protected static final String RESOURCES = "resources"; + protected static final String MISSING_MEMBER_MESSAGE = + " member is required in Intent"; + + @Override + public ObjectNode encode(Intent intent, CodecContext context) { + checkNotNull(intent, "Intent cannot be null"); + + final ObjectNode result = context.mapper().createObjectNode() + .put(TYPE, intent.getClass().getSimpleName()) + .put(ID, intent.id().toString()) + .put(APP_ID, UrlEscapers.urlPathSegmentEscaper() + .escape(intent.appId().name())); + + final ArrayNode jsonResources = result.putArray(RESOURCES); + + for (final NetworkResource resource : intent.resources()) { + jsonResources.add(resource.toString()); + } + + IntentService service = context.getService(IntentService.class); + IntentState state = service.getIntentState(intent.key()); + if (state != null) { + result.put(STATE, state.toString()); + } + + return result; + } + + @Override + public Intent decode(ObjectNode json, CodecContext context) { + checkNotNull(json, "JSON cannot be null"); + + String type = nullIsIllegal(json.get(TYPE), + TYPE + MISSING_MEMBER_MESSAGE).asText(); + + if (type.equals(PointToPointIntent.class.getSimpleName())) { + return context.codec(PointToPointIntent.class).decode(json, context); + } else if (type.equals(HostToHostIntent.class.getSimpleName())) { + return context.codec(HostToHostIntent.class).decode(json, context); + } + + throw new IllegalArgumentException("Intent type " + + type + " is not supported"); + } + + /** + * Extracts base intent specific attributes from a JSON object + * and adds them to a builder. + * + * @param json root JSON object + * @param context code context + * @param builder builder to use for storing the attributes + */ + public static void intentAttributes(ObjectNode json, CodecContext context, + Intent.Builder builder) { + String appId = nullIsIllegal(json.get(IntentCodec.APP_ID), + IntentCodec.APP_ID + IntentCodec.MISSING_MEMBER_MESSAGE).asText(); + CoreService service = context.getService(CoreService.class); + builder.appId(service.getAppId(appId)); + + JsonNode priorityJson = json.get(IntentCodec.PRIORITY); + if (priorityJson != null) { + builder.priority(priorityJson.asInt()); + } + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java new file mode 100644 index 00000000..14ee9b7c --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java @@ -0,0 +1,80 @@ +/* + * Copyright 2014-2015 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 com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.Annotations; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.Link; +import org.onosproject.net.Link.Type; +import org.onosproject.net.provider.ProviderId; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Link JSON codec. + */ +public final class LinkCodec extends AnnotatedCodec { + + // JSON field names + private static final String SRC = "src"; + private static final String DST = "dst"; + private static final String TYPE = "type"; + private static final String STATE = "state"; + + @Override + public ObjectNode encode(Link link, CodecContext context) { + checkNotNull(link, "Link cannot be null"); + JsonCodec codec = context.codec(ConnectPoint.class); + ObjectNode result = context.mapper().createObjectNode(); + result.set(SRC, codec.encode(link.src(), context)); + result.set(DST, codec.encode(link.dst(), context)); + result.put(TYPE, link.type().toString()); + if (link.state() != null) { + result.put(STATE, link.state().toString()); + } + return annotate(result, link, context); + } + + + /** + * {@inheritDoc} + * + * Note: ProviderId is not part of JSON representation. + * Returned object will have random ProviderId set. + */ + @Override + public Link decode(ObjectNode json, CodecContext context) { + if (json == null || !json.isObject()) { + return null; + } + + JsonCodec codec = context.codec(ConnectPoint.class); + // TODO: add providerId to JSON if we need to recover them. + ProviderId pid = new ProviderId("json", "LinkCodec"); + + ConnectPoint src = codec.decode(get(json, SRC), context); + ConnectPoint dst = codec.decode(get(json, DST), context); + Type type = Type.valueOf(json.get(TYPE).asText()); + Annotations annotations = extractAnnotations(json, context); + + return new DefaultLink(pid, src, dst, type, annotations); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java new file mode 100644 index 00000000..0e55592d --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.statistic.Load; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Codec for the Load class. + */ +public class LoadCodec extends JsonCodec { + + private static final String RATE = "rate"; + private static final String LATEST = "latest"; + private static final String VALID = "valid"; + private static final String TIME = "time"; + + @Override + public ObjectNode encode(Load load, CodecContext context) { + checkNotNull(load, "Load cannot be null"); + return context.mapper().createObjectNode() + .put(RATE, load.rate()) + .put(LATEST, load.latest()) + .put(VALID, load.isValid()) + .put(TIME, load.time()); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java new file mode 100644 index 00000000..58b48529 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.Link; +import org.onosproject.net.Path; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Path JSON codec. + */ +public final class PathCodec extends AnnotatedCodec { + @Override + public ObjectNode encode(Path path, CodecContext context) { + checkNotNull(path, "Path cannot be null"); + JsonCodec codec = context.codec(Link.class); + ObjectNode result = context.mapper() + .createObjectNode() + .put("cost", path.cost()); + ArrayNode jsonLinks = result.putArray("links"); + + for (Link link : path.links()) { + jsonLinks.add(codec.encode(link, context)); + } + + return annotate(result, path, context); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java new file mode 100644 index 00000000..20df4890 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.intent.ConnectivityIntent; +import org.onosproject.net.intent.PointToPointIntent; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.util.Tools.nullIsIllegal; + +/** + * Point to point intent codec. + */ +public final class PointToPointIntentCodec extends JsonCodec { + + private static final String INGRESS_POINT = "ingressPoint"; + private static final String EGRESS_POINT = "egressPoint"; + + @Override + public ObjectNode encode(PointToPointIntent intent, CodecContext context) { + checkNotNull(intent, "Point to point intent cannot be null"); + + final JsonCodec connectivityIntentCodec = + context.codec(ConnectivityIntent.class); + final ObjectNode result = connectivityIntentCodec.encode(intent, context); + + final JsonCodec connectPointCodec = + context.codec(ConnectPoint.class); + final ObjectNode ingress = + connectPointCodec.encode(intent.ingressPoint(), context); + final ObjectNode egress = + connectPointCodec.encode(intent.egressPoint(), context); + + result.set(INGRESS_POINT, ingress); + result.set(EGRESS_POINT, egress); + + return result; + } + + + @Override + public PointToPointIntent decode(ObjectNode json, CodecContext context) { + PointToPointIntent.Builder builder = PointToPointIntent.builder(); + + IntentCodec.intentAttributes(json, context, builder); + ConnectivityIntentCodec.intentAttributes(json, context, builder); + + ObjectNode ingressJson = nullIsIllegal(get(json, INGRESS_POINT), + INGRESS_POINT + IntentCodec.MISSING_MEMBER_MESSAGE); + ConnectPoint ingress = context.codec(ConnectPoint.class) + .decode(ingressJson, context); + builder.ingressPoint(ingress); + + ObjectNode egressJson = nullIsIllegal(get(json, EGRESS_POINT), + EGRESS_POINT + IntentCodec.MISSING_MEMBER_MESSAGE); + ConnectPoint egress = context.codec(ConnectPoint.class) + .decode(egressJson, context); + builder.egressPoint(egress); + + return builder.build(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java new file mode 100644 index 00000000..c6f2ac76 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java @@ -0,0 +1,160 @@ +/* + * Copyright 2014-2015 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 com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onlab.packet.ChassisId; +import org.onosproject.codec.CodecContext; +import org.onosproject.net.Annotations; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.Port.Type; +import org.onosproject.net.PortNumber; +import org.onosproject.net.provider.ProviderId; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Device port JSON codec. + */ +public final class PortCodec extends AnnotatedCodec { + + // JSON field names + private static final String ELEMENT = "element"; // DeviceId + private static final String PORT_NAME = "port"; + private static final String IS_ENABLED = "isEnabled"; + private static final String TYPE = "type"; + private static final String PORT_SPEED = "portSpeed"; + + // Special port name alias + private static final String PORT_NAME_LOCAL = "local"; + + @Override + public ObjectNode encode(Port port, CodecContext context) { + checkNotNull(port, "Port cannot be null"); + ObjectNode result = context.mapper().createObjectNode() + .put(ELEMENT, port.element().id().toString()) + .put(PORT_NAME, portName(port.number())) + .put(IS_ENABLED, port.isEnabled()) + .put(TYPE, port.type().toString().toLowerCase()) + .put(PORT_SPEED, port.portSpeed()); + return annotate(result, port, context); + } + + private String portName(PortNumber port) { + return port.equals(PortNumber.LOCAL) ? PORT_NAME_LOCAL : port.toString(); + } + + private static PortNumber portNumber(String portName) { + if (portName.equalsIgnoreCase(PORT_NAME_LOCAL)) { + return PortNumber.LOCAL; + } + + return PortNumber.portNumber(portName); + } + + + /** + * {@inheritDoc} + * + * Note: Result of {@link Port#element()} returned Port object, + * is not a full Device object. + * Only it's DeviceId can be used. + */ + @Override + public Port decode(ObjectNode json, CodecContext context) { + if (json == null || !json.isObject()) { + return null; + } + + DeviceId did = DeviceId.deviceId(json.get(ELEMENT).asText()); + Device device = new DummyDevice(did); + PortNumber number = portNumber(json.get(PORT_NAME).asText()); + boolean isEnabled = json.get(IS_ENABLED).asBoolean(); + Type type = Type.valueOf(json.get(TYPE).asText().toUpperCase()); + long portSpeed = json.get(PORT_SPEED).asLong(); + Annotations annotations = extractAnnotations(json, context); + + return new DefaultPort(device, number, isEnabled, type, portSpeed, annotations); + } + + + /** + * Dummy Device which only holds DeviceId. + */ + private static final class DummyDevice implements Device { + + private final DeviceId did; + + /** + * Constructs Dummy Device which only holds DeviceId. + * + * @param did device Id + */ + public DummyDevice(DeviceId did) { + this.did = did; + } + + @Override + public Annotations annotations() { + return DefaultAnnotations.EMPTY; + } + + @Override + public ProviderId providerId() { + return new ProviderId(did.uri().getScheme(), "PortCodec"); + } + + @Override + public DeviceId id() { + return did; + } + + @Override + public Type type() { + return Type.SWITCH; + } + + @Override + public String manufacturer() { + return "dummy"; + } + + @Override + public String hwVersion() { + return "0"; + } + + @Override + public String swVersion() { + return "0"; + } + + @Override + public String serialNumber() { + return "0"; + } + + @Override + public ChassisId chassisId() { + return new ChassisId(); + } + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java new file mode 100644 index 00000000..dc4c79a7 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.topology.TopologyCluster; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Topology cluster JSON codec. + */ +public final class TopologyClusterCodec extends JsonCodec { + + @Override + public ObjectNode encode(TopologyCluster cluster, CodecContext context) { + checkNotNull(cluster, "Cluster cannot be null"); + + return context.mapper().createObjectNode() + .put("id", cluster.id().index()) + .put("deviceCount", cluster.deviceCount()) + .put("linkCount", cluster.linkCount()) + .put("root", cluster.root().toString()); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java new file mode 100644 index 00000000..f6529eb5 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 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 org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.topology.Topology; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Topology JSON codec. + */ +public final class TopologyCodec extends JsonCodec { + + @Override + public ObjectNode encode(Topology topology, CodecContext context) { + checkNotNull(topology, "Topology cannot be null"); + + return context.mapper().createObjectNode() + .put("time", topology.time()) + .put("devices", topology.deviceCount()) + .put("links", topology.linkCount()) + .put("clusters", topology.clusterCount()); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java new file mode 100644 index 00000000..24ebef1a --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014-2015 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.stream.IntStream; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.criteria.Criterion; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Traffic selector codec. + */ +public final class TrafficSelectorCodec extends JsonCodec { + private static final String CRITERIA = "criteria"; + + @Override + public ObjectNode encode(TrafficSelector selector, CodecContext context) { + checkNotNull(selector, "Traffic selector cannot be null"); + + final ObjectNode result = context.mapper().createObjectNode(); + final ArrayNode jsonCriteria = result.putArray(CRITERIA); + + if (selector.criteria() != null) { + final JsonCodec criterionCodec = + context.codec(Criterion.class); + for (final Criterion criterion : selector.criteria()) { + jsonCriteria.add(criterionCodec.encode(criterion, context)); + } + } + + return result; + } + + @Override + public TrafficSelector decode(ObjectNode json, CodecContext context) { + final JsonCodec criterionCodec = + context.codec(Criterion.class); + + JsonNode criteriaJson = json.get(CRITERIA); + TrafficSelector.Builder builder = DefaultTrafficSelector.builder(); + if (criteriaJson != null) { + IntStream.range(0, criteriaJson.size()) + .forEach(i -> builder.add( + criterionCodec.decode(get(criteriaJson, i), + context))); + } + return builder.build(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.java new file mode 100644 index 00000000..0d7fb420 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.java @@ -0,0 +1,76 @@ +/* + * Copyright 2014-2015 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.stream.IntStream; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instruction; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Traffic treatment codec. + */ +public final class TrafficTreatmentCodec extends JsonCodec { + private static final String INSTRUCTIONS = "instructions"; + + @Override + public ObjectNode encode(TrafficTreatment treatment, CodecContext context) { + checkNotNull(treatment, "Traffic treatment cannot be null"); + + final ObjectNode result = context.mapper().createObjectNode(); + final ArrayNode jsonInstructions = result.putArray(INSTRUCTIONS); + + final JsonCodec instructionCodec = + context.codec(Instruction.class); + + for (final Instruction instruction : treatment.immediate()) { + jsonInstructions.add(instructionCodec.encode(instruction, context)); + } + + final ArrayNode jsonDeferred = result.putArray("deferred"); + + for (final Instruction instruction : treatment.deferred()) { + jsonDeferred.add(instructionCodec.encode(instruction, context)); + } + + return result; + } + + @Override + public TrafficTreatment decode(ObjectNode json, CodecContext context) { + final JsonCodec instructionsCodec = + context.codec(Instruction.class); + + JsonNode instructionsJson = json.get(INSTRUCTIONS); + TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); + if (instructionsJson != null) { + IntStream.range(0, instructionsJson.size()) + .forEach(i -> builder.add( + instructionsCodec.decode(get(instructionsJson, i), + context))); + } + return builder.build(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java new file mode 100644 index 00000000..b3113e97 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2015 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. + */ + +/** + * Implementations of the codec broker and built-in entity JSON codecs. + */ +package org.onosproject.codec.impl; diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java new file mode 100644 index 00000000..bdf7d732 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java @@ -0,0 +1,502 @@ +/* + * Copyright 2014-2015 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.common; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.ImmutableSetMultimap.Builder; +import org.onlab.graph.DijkstraGraphSearch; +import org.onlab.graph.GraphPathSearch; +import org.onlab.graph.GraphPathSearch.Result; +import org.onlab.graph.TarjanGraphSearch; +import org.onlab.graph.TarjanGraphSearch.SCCResult; +import org.onosproject.net.AbstractModel; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.ClusterId; +import org.onosproject.net.topology.DefaultTopologyCluster; +import org.onosproject.net.topology.DefaultTopologyVertex; +import org.onosproject.net.topology.GraphDescription; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyCluster; +import org.onosproject.net.topology.TopologyEdge; +import org.onosproject.net.topology.TopologyGraph; +import org.onosproject.net.topology.TopologyVertex; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; +import static org.onlab.graph.GraphPathSearch.ALL_PATHS; +import static org.onlab.util.Tools.isNullOrEmpty; +import static org.onosproject.core.CoreService.CORE_PROVIDER_ID; +import static org.onosproject.net.Link.State.ACTIVE; +import static org.onosproject.net.Link.State.INACTIVE; +import static org.onosproject.net.Link.Type.INDIRECT; + +/** + * Default implementation of the topology descriptor. This carries the backing + * topology data. + */ +public class DefaultTopology extends AbstractModel implements Topology { + + private static final DijkstraGraphSearch DIJKSTRA = new DijkstraGraphSearch<>(); + private static final TarjanGraphSearch TARJAN = new TarjanGraphSearch<>(); + + private final long time; + private final long creationTime; + private final long computeCost; + private final TopologyGraph graph; + + private final LinkWeight weight; + private final Supplier> clusterResults; + private final Supplier> clusters; + private final Supplier> infrastructurePoints; + private final Supplier> broadcastSets; + private final Function broadcastFunction; + private final Supplier clusterIndexes; + + /** + * Creates a topology descriptor attributed to the specified provider. + * + * @param providerId identity of the provider + * @param description data describing the new topology + * @param broadcastFunction broadcast point function + */ + public DefaultTopology(ProviderId providerId, GraphDescription description, + Function broadcastFunction) { + super(providerId); + this.broadcastFunction = broadcastFunction; + this.time = description.timestamp(); + this.creationTime = description.creationTime(); + + // Build the graph + this.graph = new DefaultTopologyGraph(description.vertexes(), + description.edges()); + + this.clusterResults = Suppliers.memoize(() -> searchForClusters()); + this.clusters = Suppliers.memoize(() -> buildTopologyClusters()); + + this.clusterIndexes = Suppliers.memoize(() -> buildIndexes()); + + this.weight = new HopCountLinkWeight(graph.getVertexes().size()); + this.broadcastSets = Suppliers.memoize(() -> buildBroadcastSets()); + this.infrastructurePoints = Suppliers.memoize(() -> findInfrastructurePoints()); + this.computeCost = Math.max(0, System.nanoTime() - time); + } + + /** + * Creates a topology descriptor attributed to the specified provider. + * + * @param providerId identity of the provider + * @param description data describing the new topology + */ + public DefaultTopology(ProviderId providerId, GraphDescription description) { + this(providerId, description, null); + } + + @Override + public long time() { + return time; + } + + @Override + public long creationTime() { + return creationTime; + } + + @Override + public long computeCost() { + return computeCost; + } + + @Override + public int clusterCount() { + return clusters.get().size(); + } + + @Override + public int deviceCount() { + return graph.getVertexes().size(); + } + + @Override + public int linkCount() { + return graph.getEdges().size(); + } + + private ImmutableMap clustersByDevice() { + return clusterIndexes.get().clustersByDevice; + } + + private ImmutableSetMultimap devicesByCluster() { + return clusterIndexes.get().devicesByCluster; + } + + private ImmutableSetMultimap linksByCluster() { + return clusterIndexes.get().linksByCluster; + } + + /** + * Returns the backing topology graph. + * + * @return topology graph + */ + public TopologyGraph getGraph() { + return graph; + } + + /** + * Returns the set of topology clusters. + * + * @return set of clusters + */ + public Set getClusters() { + return ImmutableSet.copyOf(clusters.get().values()); + } + + /** + * Returns the specified topology cluster. + * + * @param clusterId cluster identifier + * @return topology cluster + */ + public TopologyCluster getCluster(ClusterId clusterId) { + return clusters.get().get(clusterId); + } + + /** + * Returns the topology cluster that contains the given device. + * + * @param deviceId device identifier + * @return topology cluster + */ + public TopologyCluster getCluster(DeviceId deviceId) { + return clustersByDevice().get(deviceId); + } + + /** + * Returns the set of cluster devices. + * + * @param cluster topology cluster + * @return cluster devices + */ + public Set getClusterDevices(TopologyCluster cluster) { + return devicesByCluster().get(cluster); + } + + /** + * Returns the set of cluster links. + * + * @param cluster topology cluster + * @return cluster links + */ + public Set getClusterLinks(TopologyCluster cluster) { + return linksByCluster().get(cluster); + } + + /** + * Indicates whether the given point is an infrastructure link end-point. + * + * @param connectPoint connection point + * @return true if infrastructure + */ + public boolean isInfrastructure(ConnectPoint connectPoint) { + return infrastructurePoints.get().contains(connectPoint); + } + + /** + * Indicates whether the given point is part of a broadcast set. + * + * @param connectPoint connection point + * @return true if in broadcast set + */ + public boolean isBroadcastPoint(ConnectPoint connectPoint) { + if (broadcastFunction != null) { + return broadcastFunction.apply(connectPoint); + } + + // Any non-infrastructure, i.e. edge points are assumed to be OK. + if (!isInfrastructure(connectPoint)) { + return true; + } + + // Find the cluster to which the device belongs. + TopologyCluster cluster = clustersByDevice().get(connectPoint.deviceId()); + checkArgument(cluster != null, "No cluster found for device %s", connectPoint.deviceId()); + + // If the broadcast set is null or empty, or if the point explicitly + // belongs to it, return true. + Set points = broadcastSets.get().get(cluster.id()); + return isNullOrEmpty(points) || points.contains(connectPoint); + } + + /** + * Returns the size of the cluster broadcast set. + * + * @param clusterId cluster identifier + * @return size of the cluster broadcast set + */ + public int broadcastSetSize(ClusterId clusterId) { + return broadcastSets.get().get(clusterId).size(); + } + + /** + * Returns the set of the cluster broadcast points. + * + * @param clusterId cluster identifier + * @return set of cluster broadcast points + */ + public Set broadcastPoints(ClusterId clusterId) { + return broadcastSets.get().get(clusterId); + } + + /** + * Returns the set of pre-computed shortest paths between source and + * destination devices. + * + * @param src source device + * @param dst destination device + * @return set of shortest paths + */ + public Set getPaths(DeviceId src, DeviceId dst) { + return getPaths(src, dst, null); + } + + /** + * Computes on-demand the set of shortest paths between source and + * destination devices. + * + * @param src source device + * @param dst destination device + * @param weight link weight function + * @return set of shortest paths + */ + public Set getPaths(DeviceId src, DeviceId dst, LinkWeight weight) { + final DefaultTopologyVertex srcV = new DefaultTopologyVertex(src); + final DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst); + Set vertices = graph.getVertexes(); + if (!vertices.contains(srcV) || !vertices.contains(dstV)) { + // src or dst not part of the current graph + return ImmutableSet.of(); + } + + GraphPathSearch.Result result = + DIJKSTRA.search(graph, srcV, dstV, weight, ALL_PATHS); + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (org.onlab.graph.Path path : result.paths()) { + builder.add(networkPath(path)); + } + return builder.build(); + } + + // Converts graph path to a network path with the same cost. + private Path networkPath(org.onlab.graph.Path path) { + List links = new ArrayList<>(); + for (TopologyEdge edge : path.edges()) { + links.add(edge.link()); + } + return new DefaultPath(CORE_PROVIDER_ID, links, path.cost()); + } + + // Searches for SCC clusters in the network topology graph using Tarjan + // algorithm. + private SCCResult searchForClusters() { + return TARJAN.search(graph, new NoIndirectLinksWeight()); + } + + // Builds the topology clusters and returns the id-cluster bindings. + private ImmutableMap buildTopologyClusters() { + ImmutableMap.Builder clusterBuilder = ImmutableMap.builder(); + SCCResult results = clusterResults.get(); + // Extract both vertexes and edges from the results; the lists form + // pairs along the same index. + List> clusterVertexes = results.clusterVertexes(); + List> clusterEdges = results.clusterEdges(); + + // Scan over the lists and create a cluster from the results. + for (int i = 0, n = results.clusterCount(); i < n; i++) { + Set vertexSet = clusterVertexes.get(i); + Set edgeSet = clusterEdges.get(i); + + ClusterId cid = ClusterId.clusterId(i); + DefaultTopologyCluster cluster = new DefaultTopologyCluster(cid, + vertexSet.size(), + edgeSet.size(), + findRoot(vertexSet)); + clusterBuilder.put(cid, cluster); + } + return clusterBuilder.build(); + } + + // Finds the vertex whose device id is the lexicographical minimum in the + // specified set. + private TopologyVertex findRoot(Set vertexSet) { + TopologyVertex minVertex = null; + for (TopologyVertex vertex : vertexSet) { + if ((minVertex == null) || (minVertex.deviceId() + .toString().compareTo(minVertex.deviceId().toString()) < 0)) { + minVertex = vertex; + } + } + return minVertex; + } + + // Processes a map of broadcast sets for each cluster. + private ImmutableSetMultimap buildBroadcastSets() { + Builder builder = ImmutableSetMultimap + .builder(); + for (TopologyCluster cluster : clusters.get().values()) { + addClusterBroadcastSet(cluster, builder); + } + return builder.build(); + } + + // Finds all broadcast points for the cluster. These are those connection + // points which lie along the shortest paths between the cluster root and + // all other devices within the cluster. + private void addClusterBroadcastSet(TopologyCluster cluster, Builder builder) { + // Use the graph root search results to build the broadcast set. + Result result = DIJKSTRA.search(graph, cluster.root(), null, weight, 1); + for (Map.Entry> entry : result.parents().entrySet()) { + TopologyVertex vertex = entry.getKey(); + + // Ignore any parents that lead outside the cluster. + if (clustersByDevice().get(vertex.deviceId()) != cluster) { + continue; + } + + // Ignore any back-link sets that are empty. + Set parents = entry.getValue(); + if (parents.isEmpty()) { + continue; + } + + // Use the first back-link source and destinations to add to the + // broadcast set. + Link link = parents.iterator().next().link(); + builder.put(cluster.id(), link.src()); + builder.put(cluster.id(), link.dst()); + } + } + + // Collects and returns an set of all infrastructure link end-points. + private ImmutableSet findInfrastructurePoints() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (TopologyEdge edge : graph.getEdges()) { + builder.add(edge.link().src()); + builder.add(edge.link().dst()); + } + return builder.build(); + } + + // Builds cluster-devices, cluster-links and device-cluster indexes. + private ClusterIndexes buildIndexes() { + // Prepare the index builders + ImmutableMap.Builder clusterBuilder = + ImmutableMap.builder(); + ImmutableSetMultimap.Builder devicesBuilder = + ImmutableSetMultimap.builder(); + ImmutableSetMultimap.Builder linksBuilder = + ImmutableSetMultimap.builder(); + + // Now scan through all the clusters + for (TopologyCluster cluster : clusters.get().values()) { + int i = cluster.id().index(); + + // Scan through all the cluster vertexes. + for (TopologyVertex vertex : clusterResults.get().clusterVertexes().get(i)) { + devicesBuilder.put(cluster, vertex.deviceId()); + clusterBuilder.put(vertex.deviceId(), cluster); + } + + // Scan through all the cluster edges. + for (TopologyEdge edge : clusterResults.get().clusterEdges().get(i)) { + linksBuilder.put(cluster, edge.link()); + } + } + + // Finalize all indexes. + return new ClusterIndexes(clusterBuilder.build(), + devicesBuilder.build(), + linksBuilder.build()); + } + + // Link weight for measuring link cost as hop count with indirect links + // being as expensive as traversing the entire graph to assume the worst. + private static class HopCountLinkWeight implements LinkWeight { + private final int indirectLinkCost; + + HopCountLinkWeight(int indirectLinkCost) { + this.indirectLinkCost = indirectLinkCost; + } + + @Override + public double weight(TopologyEdge edge) { + // To force preference to use direct paths first, make indirect + // links as expensive as the linear vertex traversal. + return edge.link().state() == + ACTIVE ? (edge.link().type() == + INDIRECT ? indirectLinkCost : 1) : -1; + } + } + + // Link weight for preventing traversal over indirect links. + private static class NoIndirectLinksWeight implements LinkWeight { + @Override + public double weight(TopologyEdge edge) { + return (edge.link().state() == INACTIVE) + || (edge.link().type() == INDIRECT) ? -1 : 1; + } + } + + static final class ClusterIndexes { + final ImmutableMap clustersByDevice; + final ImmutableSetMultimap devicesByCluster; + final ImmutableSetMultimap linksByCluster; + + public ClusterIndexes(ImmutableMap clustersByDevice, + ImmutableSetMultimap devicesByCluster, + ImmutableSetMultimap linksByCluster) { + this.clustersByDevice = clustersByDevice; + this.devicesByCluster = devicesByCluster; + this.linksByCluster = linksByCluster; + } + } + + @Override + public String toString() { + return toStringHelper(this) + .add("time", time) + .add("creationTime", creationTime) + .add("computeCost", computeCost) + .add("clusters", clusterCount()) + .add("devices", deviceCount()) + .add("links", linkCount()).toString(); + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java new file mode 100644 index 00000000..b06065e7 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 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.common; + +import org.onlab.graph.AdjacencyListsGraph; +import org.onosproject.net.topology.TopologyEdge; +import org.onosproject.net.topology.TopologyGraph; +import org.onosproject.net.topology.TopologyVertex; + +import java.util.Set; + +/** + * Default implementation of an immutable topology graph based on a generic + * implementation of adjacency lists graph. + */ +public class DefaultTopologyGraph + extends AdjacencyListsGraph + implements TopologyGraph { + + /** + * Creates a topology graph comprising of the specified vertexes and edges. + * + * @param vertexes set of graph vertexes + * @param edges set of graph edges + */ + public DefaultTopologyGraph(Set vertexes, Set edges) { + super(vertexes, edges); + } + +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java new file mode 100644 index 00000000..54f0fb89 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java @@ -0,0 +1,432 @@ +/* + * Copyright 2015 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.common.app; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.onlab.util.Tools; +import org.onosproject.app.ApplicationDescription; +import org.onosproject.app.ApplicationEvent; +import org.onosproject.app.ApplicationException; +import org.onosproject.app.ApplicationStoreDelegate; +import org.onosproject.app.DefaultApplicationDescription; +import org.onosproject.core.ApplicationRole; +import org.onosproject.core.Version; +import org.onosproject.security.AppPermission; +import org.onosproject.security.Permission; +import org.onosproject.store.AbstractStore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.NoSuchFileException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.io.ByteStreams.toByteArray; +import static com.google.common.io.Files.createParentDirs; +import static com.google.common.io.Files.write; + +/** + * Facility for reading application archive stream and managing application + * directory structure. + */ +public class ApplicationArchive + extends AbstractStore { + + private static Logger log = LoggerFactory.getLogger(ApplicationArchive.class); + + // Magic strings to search for at the beginning of the archive stream + private static final String XML_MAGIC = " getApplicationNames() { + ImmutableSet.Builder names = ImmutableSet.builder(); + File[] files = appsDir.listFiles(File::isDirectory); + if (files != null) { + for (File file : files) { + names.add(file.getName()); + } + } + return names.build(); + } + + /** + * Returns the timestamp in millis since start of epoch, of when the + * specified application was last modified or changed state. + * + * @param appName application name + * @return number of millis since start of epoch + */ + public long getUpdateTime(String appName) { + return appFile(appName, APP_XML).lastModified(); + } + + /** + * Loads the application descriptor from the specified application archive + * stream and saves the stream in the appropriate application archive + * directory. + * + * @param appName application name + * @return application descriptor + * @throws org.onosproject.app.ApplicationException if unable to read application description + */ + public ApplicationDescription getApplicationDescription(String appName) { + try { + XMLConfiguration cfg = new XMLConfiguration(); + cfg.setAttributeSplittingDisabled(true); + cfg.setDelimiterParsingDisabled(true); + cfg.load(appFile(appName, APP_XML)); + return loadAppDescription(cfg); + } catch (Exception e) { + throw new ApplicationException("Unable to get app description", e); + } + } + + /** + * Loads the application descriptor from the specified application archive + * stream and saves the stream in the appropriate application archive + * directory. + * + * @param stream application archive stream + * @return application descriptor + * @throws org.onosproject.app.ApplicationException if unable to read the + * archive stream or store + * the application archive + */ + public synchronized ApplicationDescription saveApplication(InputStream stream) { + try (InputStream ais = stream) { + byte[] cache = toByteArray(ais); + InputStream bis = new ByteArrayInputStream(cache); + + boolean plainXml = isPlainXml(cache); + ApplicationDescription desc = plainXml ? + parsePlainAppDescription(bis) : parseZippedAppDescription(bis); + checkState(!appFile(desc.name(), APP_XML).exists(), + "Application %s already installed", desc.name()); + + if (plainXml) { + expandPlainApplication(cache, desc); + } else { + bis.reset(); + expandZippedApplication(bis, desc); + + bis.reset(); + saveApplication(bis, desc); + } + + installArtifacts(desc); + return desc; + } catch (IOException e) { + throw new ApplicationException("Unable to save application", e); + } + } + + // Indicates whether the stream encoded in the given bytes is plain XML. + private boolean isPlainXml(byte[] bytes) { + return substring(bytes, XML_MAGIC.length()).equals(XML_MAGIC) || + substring(bytes, APP_MAGIC_DEPTH).contains(APP_MAGIC); + } + + // Returns the substring of maximum possible length from the specified bytes. + private String substring(byte[] bytes, int length) { + return new String(bytes, 0, Math.min(bytes.length, length), Charset.forName("UTF-8")); + } + + /** + * Purges the application archive directory. + * + * @param appName application name + */ + public synchronized void purgeApplication(String appName) { + File appDir = new File(appsDir, appName); + try { + Tools.removeDirectory(appDir); + } catch (IOException e) { + throw new ApplicationException("Unable to purge application " + appName, e); + } + if (appDir.exists()) { + throw new ApplicationException("Unable to purge application " + appName); + } + } + + /** + * Returns application archive stream for the specified application. This + * will be either the application ZIP file or the application XML file. + * + * @param appName application name + * @return application archive stream + */ + public synchronized InputStream getApplicationInputStream(String appName) { + try { + File appFile = appFile(appName, appName + OAR); + return new FileInputStream(appFile.exists() ? appFile : appFile(appName, APP_XML)); + } catch (FileNotFoundException e) { + throw new ApplicationException("Application " + appName + " not found"); + } + } + + // Scans the specified ZIP stream for app.xml entry and parses it producing + // an application descriptor. + private ApplicationDescription parseZippedAppDescription(InputStream stream) + throws IOException { + try (ZipInputStream zis = new ZipInputStream(stream)) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (entry.getName().equals(APP_XML)) { + byte[] data = ByteStreams.toByteArray(zis); + return parsePlainAppDescription(new ByteArrayInputStream(data)); + } + zis.closeEntry(); + } + } + throw new IOException("Unable to locate " + APP_XML); + } + + // Scans the specified XML stream and parses it producing an application descriptor. + private ApplicationDescription parsePlainAppDescription(InputStream stream) + throws IOException { + XMLConfiguration cfg = new XMLConfiguration(); + cfg.setAttributeSplittingDisabled(true); + cfg.setDelimiterParsingDisabled(true); + try { + cfg.load(stream); + return loadAppDescription(cfg); + } catch (ConfigurationException e) { + throw new IOException("Unable to parse " + APP_XML, e); + } + } + + private ApplicationDescription loadAppDescription(XMLConfiguration cfg) { + String name = cfg.getString(NAME); + Version version = Version.version(cfg.getString(VERSION)); + String desc = cfg.getString(DESCRIPTION); + String origin = cfg.getString(ORIGIN); + ApplicationRole role = getRole(cfg.getString(ROLE)); + Set perms = getPermissions(cfg); + String featRepo = cfg.getString(FEATURES_REPO); + URI featuresRepo = featRepo != null ? URI.create(featRepo) : null; + List features = ImmutableList.copyOf(cfg.getString(FEATURES).split(",")); + + return new DefaultApplicationDescription(name, version, desc, origin, role, + perms, featuresRepo, features); + } + + // Expands the specified ZIP stream into app-specific directory. + private void expandZippedApplication(InputStream stream, ApplicationDescription desc) + throws IOException { + ZipInputStream zis = new ZipInputStream(stream); + ZipEntry entry; + File appDir = new File(appsDir, desc.name()); + while ((entry = zis.getNextEntry()) != null) { + if (!entry.isDirectory()) { + byte[] data = ByteStreams.toByteArray(zis); + zis.closeEntry(); + File file = new File(appDir, entry.getName()); + createParentDirs(file); + write(data, file); + } + } + zis.close(); + } + + // Saves the specified XML stream into app-specific directory. + private void expandPlainApplication(byte[] stream, ApplicationDescription desc) + throws IOException { + File file = appFile(desc.name(), APP_XML); + checkState(!file.getParentFile().exists(), "Application already installed"); + createParentDirs(file); + write(stream, file); + } + + + // Saves the specified ZIP stream into a file under app-specific directory. + private void saveApplication(InputStream stream, ApplicationDescription desc) + throws IOException { + Files.write(toByteArray(stream), appFile(desc.name(), desc.name() + OAR)); + } + + // Installs application artifacts into M2 repository. + private void installArtifacts(ApplicationDescription desc) throws IOException { + try { + Tools.copyDirectory(appFile(desc.name(), M2_PREFIX), m2Dir); + } catch (NoSuchFileException e) { + log.debug("Application {} has no M2 artifacts", desc.name()); + } + } + + /** + * Marks the app as active by creating token file in the app directory. + * + * @param appName application name + * @return true if file was created + */ + protected boolean setActive(String appName) { + try { + return appFile(appName, "active").createNewFile() && updateTime(appName); + } catch (IOException e) { + throw new ApplicationException("Unable to mark app as active", e); + } + } + + /** + * Clears the app as active by deleting token file in the app directory. + * + * @param appName application name + * @return true if file was deleted + */ + protected boolean clearActive(String appName) { + return appFile(appName, "active").delete() && updateTime(appName); + } + + /** + * Updates the time-stamp of the app descriptor file. + * + * @param appName application name + * @return true if the app descriptor was updated + */ + protected boolean updateTime(String appName) { + return appFile(appName, APP_XML).setLastModified(System.currentTimeMillis()); + } + + /** + * Indicates whether the app was marked as active by checking for token file. + * + * @param appName application name + * @return true if the app is marked as active + */ + protected boolean isActive(String appName) { + return appFile(appName, "active").exists(); + } + + + // Returns the name of the file located under the specified app directory. + private File appFile(String appName, String fileName) { + return new File(new File(appsDir, appName), fileName); + } + + // Returns the set of Permissions specified in the app.xml file + private ImmutableSet getPermissions(XMLConfiguration cfg) { + List permissionList = new ArrayList(); + + for (Object o : cfg.getList(APP_PERMISSIONS)) { + String name = (String) o; + permissionList.add(new Permission(AppPermission.class.getName(), name)); + } + for (Object o : cfg.getList(NET_PERMISSIONS)) { + //TODO: TO BE FLESHED OUT WHEN NETWORK PERMISSIONS ARE SUPPORTED + break; + } + + List fields = + cfg.configurationsAt(JAVA_PERMISSIONS); + for (HierarchicalConfiguration sub : fields) { + String classname = sub.getString("classname"); + String name = sub.getString("name"); + String actions = sub.getString("actions"); + + if (classname != null && name != null) { + permissionList.add(new Permission(classname, name, actions)); + } + } + return ImmutableSet.copyOf(permissionList); + } + + // + // Returns application role type + public ApplicationRole getRole(String value) { + if (value == null) { + return ApplicationRole.UNSPECIFIED; + } else { + try { + return ApplicationRole.valueOf(value.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + log.debug("Unknown role value: %s", value); + return ApplicationRole.UNSPECIFIED; + } + } + } +} diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/package-info.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/package-info.java new file mode 100644 index 00000000..898bad7b --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Common facilities for construction of application management subsystem. + */ +package org.onosproject.common.app; \ No newline at end of file diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java new file mode 100644 index 00000000..8cd9b8c4 --- /dev/null +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015 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. + */ + +/** + * Miscellaneous common facilities used for construction of various core and + * app subsystems. + */ +package org.onosproject.common; \ No newline at end of file diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java new file mode 100644 index 00000000..8d85751e --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 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 org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.onosproject.net.ConnectPoint; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Hamcrest matcher for connect points. + */ + +public final class ConnectPointJsonMatcher extends TypeSafeDiagnosingMatcher { + + private final ConnectPoint connectPoint; + + private ConnectPointJsonMatcher(ConnectPoint connectPointValue) { + connectPoint = connectPointValue; + } + + @Override + public boolean matchesSafely(JsonNode jsonConnectPoint, Description description) { + // check device + final String jsonDevice = jsonConnectPoint.get("device").asText(); + final String device = connectPoint.deviceId().toString(); + if (!jsonDevice.equals(device)) { + description.appendText("device was " + jsonDevice); + return false; + } + + // check port + final String jsonPort = jsonConnectPoint.get("port").asText(); + final String port = connectPoint.port().toString(); + if (!jsonPort.equals(port)) { + description.appendText("port was " + jsonPort); + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText(connectPoint.toString()); + } + + /** + * Factory to allocate an connect point matcher. + * + * @param connectPoint connect point object we are looking for + * @return matcher + */ + public static ConnectPointJsonMatcher matchesConnectPoint(ConnectPoint connectPoint) { + return new ConnectPointJsonMatcher(connectPoint); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java new file mode 100644 index 00000000..2a47d115 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java @@ -0,0 +1,202 @@ +/* + * Copyright 2015 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.io.IOException; +import java.io.InputStream; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.codec.JsonCodec; +import org.onosproject.core.CoreService; +import org.onosproject.net.Link; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.constraint.AnnotationConstraint; +import org.onosproject.net.intent.constraint.AsymmetricPathConstraint; +import org.onosproject.net.intent.constraint.BandwidthConstraint; +import org.onosproject.net.intent.constraint.LambdaConstraint; +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; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.onosproject.net.NetTestTools.APP_ID; +import static org.onosproject.net.NetTestTools.did; + +/** + * Unit tests for Constraint codec. + */ +public class ConstraintCodecTest { + + MockCodecContext context; + JsonCodec constraintCodec; + final CoreService mockCoreService = createMock(CoreService.class); + + /** + * Sets up for each test. Creates a context and fetches the flow rule + * codec. + */ + @Before + public void setUp() { + context = new MockCodecContext(); + constraintCodec = context.codec(Constraint.class); + assertThat(constraintCodec, notNullValue()); + + expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID)) + .andReturn(APP_ID).anyTimes(); + replay(mockCoreService); + context.registerService(CoreService.class, mockCoreService); + } + + /** + * Reads in a constraint from the given resource and decodes it. + * + * @param resourceName resource to use to read the JSON for the constraint + * @return decoded constraint + */ + private Constraint getConstraint(String resourceName) { + InputStream jsonStream = ConstraintCodecTest.class + .getResourceAsStream(resourceName); + try { + JsonNode json = context.mapper().readTree(jsonStream); + assertThat(json, notNullValue()); + Constraint constraint = constraintCodec.decode((ObjectNode) json, context); + assertThat(constraint, notNullValue()); + return checkNotNull(constraint); + } catch (IOException ioe) { + Assert.fail(ioe.getMessage()); + throw new IllegalStateException("cannot happen"); + } + } + + + /** + * Tests link type constraint. + */ + @Test + public void linkTypeConstraint() { + Constraint constraint = getConstraint("LinkTypeConstraint.json"); + assertThat(constraint, instanceOf(LinkTypeConstraint.class)); + + LinkTypeConstraint linkTypeConstraint = (LinkTypeConstraint) constraint; + assertThat(linkTypeConstraint.isInclusive(), is(false)); + assertThat(linkTypeConstraint.types(), hasSize(2)); + assertThat(linkTypeConstraint.types(), hasItem(Link.Type.OPTICAL)); + assertThat(linkTypeConstraint.types(), hasItem(Link.Type.DIRECT)); + } + + /** + * Tests annotation constraint. + */ + @Test + public void annotationConstraint() { + Constraint constraint = getConstraint("AnnotationConstraint.json"); + assertThat(constraint, instanceOf(AnnotationConstraint.class)); + + AnnotationConstraint annotationConstraint = (AnnotationConstraint) constraint; + assertThat(annotationConstraint.key(), is("key")); + assertThat(annotationConstraint.threshold(), is(123.0D)); + } + + /** + * Tests bandwidth constraint. + */ + @Test + public void bandwidthConstraint() { + Constraint constraint = getConstraint("BandwidthConstraint.json"); + assertThat(constraint, instanceOf(BandwidthConstraint.class)); + + BandwidthConstraint bandwidthConstraint = (BandwidthConstraint) constraint; + assertThat(bandwidthConstraint.bandwidth().toDouble(), is(345.678D)); + } + + /** + * Tests lambda constraint. + */ + @Test + public void lambdaConstraint() { + Constraint constraint = getConstraint("LambdaConstraint.json"); + assertThat(constraint, instanceOf(LambdaConstraint.class)); + + LambdaConstraint lambdaConstraint = (LambdaConstraint) constraint; + assertThat(lambdaConstraint.lambda().toInt(), is(444)); + } + + /** + * Tests latency constraint. + */ + @Test + public void latencyConstraint() { + Constraint constraint = getConstraint("LatencyConstraint.json"); + assertThat(constraint, instanceOf(LatencyConstraint.class)); + + LatencyConstraint latencyConstraint = (LatencyConstraint) constraint; + assertThat(latencyConstraint.latency().toMillis(), is(111L)); + } + + /** + * Tests obstacle constraint. + */ + @Test + public void obstacleConstraint() { + Constraint constraint = getConstraint("ObstacleConstraint.json"); + assertThat(constraint, instanceOf(ObstacleConstraint.class)); + + ObstacleConstraint obstacleConstraint = (ObstacleConstraint) constraint; + + assertThat(obstacleConstraint.obstacles(), hasItem(did("dev1"))); + assertThat(obstacleConstraint.obstacles(), hasItem(did("dev2"))); + assertThat(obstacleConstraint.obstacles(), hasItem(did("dev3"))); + } + + /** + * Tests waypoint constaint. + */ + @Test + public void waypointConstraint() { + Constraint constraint = getConstraint("WaypointConstraint.json"); + assertThat(constraint, instanceOf(WaypointConstraint.class)); + + WaypointConstraint waypointConstraint = (WaypointConstraint) constraint; + + assertThat(waypointConstraint.waypoints(), hasItem(did("devA"))); + assertThat(waypointConstraint.waypoints(), hasItem(did("devB"))); + assertThat(waypointConstraint.waypoints(), hasItem(did("devC"))); + } + + /** + * Tests asymmetric path constraint. + */ + @Test + public void asymmetricPathConstraint() { + Constraint constraint = getConstraint("AsymmetricPathConstraint.json"); + assertThat(constraint, instanceOf(AsymmetricPathConstraint.class)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java new file mode 100644 index 00000000..6bf46803 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java @@ -0,0 +1,445 @@ +/* + * Copyright 2015 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.EnumMap; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.Ip6Address; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.TpPort; +import org.onlab.packet.VlanId; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.GridType; +import org.onosproject.net.Lambda; +import org.onosproject.net.OchSignalType; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.criteria.Criterion; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.onlab.junit.TestUtils.getField; +import static org.onlab.junit.TestUtils.setField; +import static org.onosproject.codec.impl.CriterionJsonMatcher.matchesCriterion; + +/** + * Unit tests for criterion codec. + */ +public class CriterionCodecTest { + + CodecContext context; + JsonCodec criterionCodec; + final PortNumber port = PortNumber.portNumber(1); + final IpPrefix ipPrefix4 = IpPrefix.valueOf("10.1.1.0/24"); + final IpPrefix ipPrefix6 = IpPrefix.valueOf("fe80::/64"); + final MacAddress mac1 = MacAddress.valueOf("00:00:11:00:00:01"); + final TpPort tpPort = TpPort.tpPort(40000); + + /** + * Sets up for each test. Creates a context and fetches the criterion + * codec. + */ + @Before + public void setUp() { + context = new MockCodecContext(); + criterionCodec = context.codec(Criterion.class); + assertThat(criterionCodec, notNullValue()); + } + + + /** + * Checks that all criterion types are covered by the codec. + */ + @Test + public void checkCriterionTypes() throws Exception { + EncodeCriterionCodecHelper encoder = new EncodeCriterionCodecHelper( + Criteria.dummy(), context); + EnumMap formatMap = + getField(encoder, "formatMap"); + assertThat(formatMap, notNullValue()); + + for (Criterion.Type type : Criterion.Type.values()) { + assertThat("Entry not found for " + type.toString(), + formatMap.get(type), notNullValue()); + } + } + + /** + * Tests in port criterion. + */ + @Test + public void matchInPortTest() { + Criterion criterion = Criteria.matchInPort(port); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests in physical port criterion. + */ + @Test + public void matchInPhyPortTest() { + Criterion criterion = Criteria.matchInPhyPort(port); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests metadata criterion. + */ + @Test + public void matchMetadataTest() { + Criterion criterion = Criteria.matchMetadata(0xabcdL); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests ethernet destination criterion. + */ + @Test + public void matchEthDstTest() { + Criterion criterion = Criteria.matchEthDst(mac1); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests ethernet source criterion. + */ + @Test + public void matchEthSrcTest() { + Criterion criterion = Criteria.matchEthSrc(mac1); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests ethernet type criterion. + */ + @Test + public void matchEthTypeTest() { + Criterion criterion = Criteria.matchEthType((short) 0x8844); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests VLAN Id criterion. + */ + @Test + public void matchVlanIdTest() { + Criterion criterion = Criteria.matchVlanId(VlanId.ANY); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests VLAN PCP criterion. + */ + @Test + public void matchVlanPcpTest() { + Criterion criterion = Criteria.matchVlanPcp((byte) 7); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IP DSCP criterion. + */ + @Test + public void matchIPDscpTest() { + Criterion criterion = Criteria.matchIPDscp((byte) 63); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IP ECN criterion. + */ + @Test + public void matchIPEcnTest() { + Criterion criterion = Criteria.matchIPEcn((byte) 3); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IP protocol criterion. + */ + @Test + public void matchIPProtocolTest() { + Criterion criterion = Criteria.matchIPProtocol((byte) 250); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IP source criterion. + */ + @Test + public void matchIPSrcTest() { + Criterion criterion = Criteria.matchIPSrc(ipPrefix4); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IP destination criterion. + */ + @Test + public void matchIPDstTest() { + Criterion criterion = Criteria.matchIPDst(ipPrefix4); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests source TCP port criterion. + */ + @Test + public void matchTcpSrcTest() { + Criterion criterion = Criteria.matchTcpSrc(tpPort); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests destination TCP port criterion. + */ + @Test + public void matchTcpDstTest() { + Criterion criterion = Criteria.matchTcpDst(tpPort); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests source UDP port criterion. + */ + @Test + public void matchUdpSrcTest() { + Criterion criterion = Criteria.matchUdpSrc(tpPort); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests destination UDP criterion. + */ + @Test + public void matchUdpDstTest() { + Criterion criterion = Criteria.matchUdpDst(tpPort); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests source SCTP criterion. + */ + @Test + public void matchSctpSrcTest() { + Criterion criterion = Criteria.matchSctpSrc(tpPort); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests destination SCTP criterion. + */ + @Test + public void matchSctpDstTest() { + Criterion criterion = Criteria.matchSctpDst(tpPort); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests ICMP type criterion. + */ + @Test + public void matchIcmpTypeTest() { + Criterion criterion = Criteria.matchIcmpType((byte) 250); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests ICMP code criterion. + */ + @Test + public void matchIcmpCodeTest() { + Criterion criterion = Criteria.matchIcmpCode((byte) 250); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IPv6 source criterion. + */ + @Test + public void matchIPv6SrcTest() { + Criterion criterion = Criteria.matchIPv6Src(ipPrefix6); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IPv6 destination criterion. + */ + @Test + public void matchIPv6DstTest() { + Criterion criterion = Criteria.matchIPv6Dst(ipPrefix6); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IPv6 flow label criterion. + */ + @Test + public void matchIPv6FlowLabelTest() { + Criterion criterion = Criteria.matchIPv6FlowLabel(0xffffe); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests ICMP v6 type criterion. + */ + @Test + public void matchIcmpv6TypeTest() { + Criterion criterion = Criteria.matchIcmpv6Type((byte) 250); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests ICMP v6 code criterion. + */ + @Test + public void matchIcmpv6CodeTest() { + Criterion criterion = Criteria.matchIcmpv6Code((byte) 250); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IPV6 target address criterion. + */ + @Test + public void matchIPv6NDTargetAddressTest() { + Criterion criterion = + Criteria.matchIPv6NDTargetAddress( + Ip6Address.valueOf("1111:2222::")); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IPV6 SLL criterion. + */ + @Test + public void matchIPv6NDSourceLinkLayerAddressTest() { + Criterion criterion = Criteria.matchIPv6NDSourceLinkLayerAddress(mac1); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IPV6 TLL criterion. + */ + @Test + public void matchIPv6NDTargetLinkLayerAddressTest() { + Criterion criterion = Criteria.matchIPv6NDTargetLinkLayerAddress(mac1); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests MPLS label criterion. + */ + @Test + public void matchMplsLabelTest() { + Criterion criterion = Criteria.matchMplsLabel(MplsLabel.mplsLabel(0xffffe)); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests IPv6 Extension Header pseudo-field flags criterion. + */ + @Test + public void matchIPv6ExthdrFlagsTest() { + int exthdrFlags = + Criterion.IPv6ExthdrFlags.NONEXT.getValue() | + Criterion.IPv6ExthdrFlags.ESP.getValue() | + Criterion.IPv6ExthdrFlags.AUTH.getValue() | + Criterion.IPv6ExthdrFlags.DEST.getValue() | + Criterion.IPv6ExthdrFlags.FRAG.getValue() | + Criterion.IPv6ExthdrFlags.ROUTER.getValue() | + Criterion.IPv6ExthdrFlags.HOP.getValue() | + Criterion.IPv6ExthdrFlags.UNREP.getValue() | + Criterion.IPv6ExthdrFlags.UNSEQ.getValue(); + Criterion criterion = Criteria.matchIPv6ExthdrFlags(exthdrFlags); + ObjectNode result = criterionCodec.encode(criterion, context); + + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests lambda criterion. + */ + @Test + public void matchOchSignal() { + Lambda ochSignal = Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8); + Criterion criterion = Criteria.matchLambda(ochSignal); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests Och signal type criterion. + */ + @Test + public void matchOchSignalTypeTest() { + Criterion criterion = Criteria.matchOchSignalType(OchSignalType.FIXED_GRID); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result, matchesCriterion(criterion)); + } + + /** + * Tests that an unimplemented criterion type only returns the type and + * no other data. + */ + @Test + public void matchUnknownTypeTest() throws Exception { + Criterion criterion = Criteria.matchOpticalSignalType((byte) 250); + setField(criterion, "type", Criterion.Type.UNASSIGNED_40); + ObjectNode result = criterionCodec.encode(criterion, context); + assertThat(result.get("type").textValue(), is(criterion.type().toString())); + assertThat(result.size(), is(1)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java new file mode 100644 index 00000000..bb3acad5 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java @@ -0,0 +1,609 @@ +/* + * Copyright 2015 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 com.google.common.base.Joiner; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.onosproject.net.OchSignal; +import org.onosproject.net.flow.criteria.Criterion; + +import com.fasterxml.jackson.databind.JsonNode; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.IPDscpCriterion; +import org.onosproject.net.flow.criteria.IPEcnCriterion; +import org.onosproject.net.flow.criteria.IPProtocolCriterion; +import org.onosproject.net.flow.criteria.IPv6ExthdrFlagsCriterion; +import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion; +import org.onosproject.net.flow.criteria.IPv6NDLinkLayerAddressCriterion; +import org.onosproject.net.flow.criteria.IPv6NDTargetAddressCriterion; +import org.onosproject.net.flow.criteria.IcmpCodeCriterion; +import org.onosproject.net.flow.criteria.IcmpTypeCriterion; +import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion; +import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion; +import org.onosproject.net.flow.criteria.MetadataCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; +import org.onosproject.net.flow.criteria.OchSignalCriterion; +import org.onosproject.net.flow.criteria.OchSignalTypeCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.SctpPortCriterion; +import org.onosproject.net.flow.criteria.TcpPortCriterion; +import org.onosproject.net.flow.criteria.UdpPortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.criteria.VlanPcpCriterion; + +import java.util.Objects; + +/** + * Hamcrest matcher for criterion objects. + */ +public final class CriterionJsonMatcher extends + TypeSafeDiagnosingMatcher { + + final Criterion criterion; + Description description; + JsonNode jsonCriterion; + + /** + * Constructs a matcher object. + * + * @param criterionValue criterion to match + */ + private CriterionJsonMatcher(Criterion criterionValue) { + criterion = criterionValue; + } + + /** + * Factory to allocate an criterion matcher. + * + * @param criterion criterion object we are looking for + * @return matcher + */ + public static CriterionJsonMatcher matchesCriterion(Criterion criterion) { + return new CriterionJsonMatcher(criterion); + } + + /** + * Matches a port criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(PortCriterion criterion) { + final long port = criterion.port().toLong(); + final long jsonPort = jsonCriterion.get("port").asLong(); + if (port != jsonPort) { + description.appendText("port was " + Long.toString(jsonPort)); + return false; + } + return true; + } + + /** + * Matches a metadata criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(MetadataCriterion criterion) { + final long metadata = criterion.metadata(); + final long jsonMetadata = jsonCriterion.get("metadata").asLong(); + if (metadata != jsonMetadata) { + description.appendText("metadata was " + + Long.toString(jsonMetadata)); + return false; + } + return true; + } + + /** + * Matches an eth criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(EthCriterion criterion) { + final String mac = criterion.mac().toString(); + final String jsonMac = jsonCriterion.get("mac").textValue(); + if (!mac.equals(jsonMac)) { + description.appendText("mac was " + jsonMac); + return false; + } + return true; + } + + /** + * Matches an eth type criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(EthTypeCriterion criterion) { + final int ethType = criterion.ethType().toShort(); + final int jsonEthType = jsonCriterion.get("ethType").intValue(); + if (ethType != jsonEthType) { + description.appendText("ethType was " + + Integer.toString(jsonEthType)); + return false; + } + return true; + } + + /** + * Matches a VLAN ID criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(VlanIdCriterion criterion) { + final short vlanId = criterion.vlanId().toShort(); + final short jsonVlanId = jsonCriterion.get("vlanId").shortValue(); + if (vlanId != jsonVlanId) { + description.appendText("vlanId was " + Short.toString(jsonVlanId)); + return false; + } + return true; + } + + /** + * Matches a VLAN PCP criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(VlanPcpCriterion criterion) { + final byte priority = criterion.priority(); + final byte jsonPriority = + (byte) jsonCriterion.get("priority").shortValue(); + if (priority != jsonPriority) { + description.appendText("priority was " + Byte.toString(jsonPriority)); + return false; + } + return true; + } + + /** + * Matches an IP DSCP criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IPDscpCriterion criterion) { + final byte ipDscp = criterion.ipDscp(); + final byte jsonIpDscp = (byte) jsonCriterion.get("ipDscp").shortValue(); + if (ipDscp != jsonIpDscp) { + description.appendText("IP DSCP was " + Byte.toString(jsonIpDscp)); + return false; + } + return true; + } + + /** + * Matches an IP ECN criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IPEcnCriterion criterion) { + final byte ipEcn = criterion.ipEcn(); + final byte jsonIpEcn = (byte) jsonCriterion.get("ipEcn").shortValue(); + if (ipEcn != jsonIpEcn) { + description.appendText("IP ECN was " + Byte.toString(jsonIpEcn)); + return false; + } + return true; + } + + /** + * Matches an IP protocol criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IPProtocolCriterion criterion) { + final short protocol = criterion.protocol(); + final short jsonProtocol = jsonCriterion.get("protocol").shortValue(); + if (protocol != jsonProtocol) { + description.appendText("protocol was " + + Short.toString(jsonProtocol)); + return false; + } + return true; + } + + /** + * Matches an IP address criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IPCriterion criterion) { + final String ip = criterion.ip().toString(); + final String jsonIp = jsonCriterion.get("ip").textValue(); + if (!ip.equals(jsonIp)) { + description.appendText("ip was " + jsonIp); + return false; + } + return true; + } + + /** + * Matches a TCP port criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(TcpPortCriterion criterion) { + final int tcpPort = criterion.tcpPort().toInt(); + final int jsonTcpPort = jsonCriterion.get("tcpPort").intValue(); + if (tcpPort != jsonTcpPort) { + description.appendText("tcp port was " + + Integer.toString(jsonTcpPort)); + return false; + } + return true; + } + + /** + * Matches a UDP port criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(UdpPortCriterion criterion) { + final int udpPort = criterion.udpPort().toInt(); + final int jsonUdpPort = jsonCriterion.get("udpPort").intValue(); + if (udpPort != jsonUdpPort) { + description.appendText("udp port was " + + Integer.toString(jsonUdpPort)); + return false; + } + return true; + } + + /** + * Matches an SCTP port criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(SctpPortCriterion criterion) { + final int sctpPort = criterion.sctpPort().toInt(); + final int jsonSctpPort = jsonCriterion.get("sctpPort").intValue(); + if (sctpPort != jsonSctpPort) { + description.appendText("sctp port was " + + Integer.toString(jsonSctpPort)); + return false; + } + return true; + } + + /** + * Matches an ICMP type criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IcmpTypeCriterion criterion) { + final short icmpType = criterion.icmpType(); + final short jsonIcmpType = jsonCriterion.get("icmpType").shortValue(); + if (icmpType != jsonIcmpType) { + description.appendText("icmp type was " + + Short.toString(jsonIcmpType)); + return false; + } + return true; + } + + /** + * Matches an ICMP code criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IcmpCodeCriterion criterion) { + final short icmpCode = criterion.icmpCode(); + final short jsonIcmpCode = jsonCriterion.get("icmpCode").shortValue(); + if (icmpCode != jsonIcmpCode) { + description.appendText("icmp code was " + + Short.toString(jsonIcmpCode)); + return false; + } + return true; + } + + /** + * Matches an IPV6 flow label criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IPv6FlowLabelCriterion criterion) { + final int flowLabel = criterion.flowLabel(); + final int jsonFlowLabel = jsonCriterion.get("flowLabel").intValue(); + if (flowLabel != jsonFlowLabel) { + description.appendText("IPv6 flow label was " + + Integer.toString(jsonFlowLabel)); + return false; + } + return true; + } + + /** + * Matches an ICMP V6 type criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(Icmpv6TypeCriterion criterion) { + final short icmpv6Type = criterion.icmpv6Type(); + final short jsonIcmpv6Type = + jsonCriterion.get("icmpv6Type").shortValue(); + if (icmpv6Type != jsonIcmpv6Type) { + description.appendText("icmpv6 type was " + + Short.toString(jsonIcmpv6Type)); + return false; + } + return true; + } + + /** + * Matches an IPV6 code criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(Icmpv6CodeCriterion criterion) { + final short icmpv6Code = criterion.icmpv6Code(); + final short jsonIcmpv6Code = + jsonCriterion.get("icmpv6Code").shortValue(); + if (icmpv6Code != jsonIcmpv6Code) { + description.appendText("icmpv6 code was " + + Short.toString(jsonIcmpv6Code)); + return false; + } + return true; + } + + /** + * Matches an IPV6 ND target criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IPv6NDTargetAddressCriterion criterion) { + final String targetAddress = + criterion.targetAddress().toString(); + final String jsonTargetAddress = + jsonCriterion.get("targetAddress").textValue(); + if (!targetAddress.equals(jsonTargetAddress)) { + description.appendText("target address was " + + jsonTargetAddress); + return false; + } + return true; + } + + /** + * Matches an IPV6 ND link layer criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IPv6NDLinkLayerAddressCriterion criterion) { + final String llAddress = + criterion.mac().toString(); + final String jsonLlAddress = + jsonCriterion.get("mac").textValue(); + if (!llAddress.equals(jsonLlAddress)) { + description.appendText("mac was " + jsonLlAddress); + return false; + } + return true; + } + + /** + * Matches an MPLS label criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(MplsCriterion criterion) { + final int label = criterion.label().toInt(); + final int jsonLabel = jsonCriterion.get("label").intValue(); + if (label != jsonLabel) { + description.appendText("label was " + Integer.toString(jsonLabel)); + return false; + } + return true; + } + + /** + * Matches an IPV6 exthdr criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(IPv6ExthdrFlagsCriterion criterion) { + final int exthdrFlags = criterion.exthdrFlags(); + final int jsonExthdrFlags = + jsonCriterion.get("exthdrFlags").intValue(); + if (exthdrFlags != jsonExthdrFlags) { + description.appendText("exthdrFlags was " + + Long.toHexString(jsonExthdrFlags)); + return false; + } + return true; + } + + /** + * Matches an Och signal criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(OchSignalCriterion criterion) { + final OchSignal ochSignal = criterion.lambda(); + final JsonNode jsonOchSignal = jsonCriterion.get("ochSignalId"); + String jsonGridType = jsonOchSignal.get("gridType").textValue(); + String jsonChannelSpacing = jsonOchSignal.get("channelSpacing").textValue(); + int jsonSpacingMultiplier = jsonOchSignal.get("spacingMultiplier").intValue(); + int jsonSlotGranularity = jsonOchSignal.get("slotGranularity").intValue(); + + boolean equality = Objects.equals(ochSignal.gridType().name(), jsonGridType) + && Objects.equals(ochSignal.channelSpacing().name(), jsonChannelSpacing) + && Objects.equals(ochSignal.spacingMultiplier(), jsonSpacingMultiplier) + && Objects.equals(ochSignal.slotGranularity(), jsonSlotGranularity); + + if (!equality) { + String joined = Joiner.on(", ") + .join(jsonGridType, jsonChannelSpacing, jsonSpacingMultiplier, jsonSlotGranularity); + + description.appendText("och signal id was " + joined); + return false; + } + return true; + } + + /** + * Matches an Och signal type criterion object. + * + * @param criterion criterion to match + * @return true if the JSON matches the criterion, false otherwise. + */ + private boolean matchCriterion(OchSignalTypeCriterion criterion) { + final String signalType = criterion.signalType().name(); + final String jsonSignalType = jsonCriterion.get("ochSignalType").textValue(); + if (!signalType.equals(jsonSignalType)) { + description.appendText("signal type was " + jsonSignalType); + return false; + } + return true; + } + + @Override + public boolean matchesSafely(JsonNode jsonCriterion, + Description description) { + this.description = description; + this.jsonCriterion = jsonCriterion; + final String type = criterion.type().name(); + final String jsonType = jsonCriterion.get("type").asText(); + if (!type.equals(jsonType)) { + description.appendText("type was " + type); + return false; + } + + switch (criterion.type()) { + + case IN_PORT: + case IN_PHY_PORT: + return matchCriterion((PortCriterion) criterion); + + case METADATA: + return matchCriterion((MetadataCriterion) criterion); + + case ETH_DST: + case ETH_SRC: + return matchCriterion((EthCriterion) criterion); + + case ETH_TYPE: + return matchCriterion((EthTypeCriterion) criterion); + + case VLAN_VID: + return matchCriterion((VlanIdCriterion) criterion); + + case VLAN_PCP: + return matchCriterion((VlanPcpCriterion) criterion); + + case IP_DSCP: + return matchCriterion((IPDscpCriterion) criterion); + + case IP_ECN: + return matchCriterion((IPEcnCriterion) criterion); + + case IP_PROTO: + return matchCriterion((IPProtocolCriterion) criterion); + + case IPV4_SRC: + case IPV4_DST: + case IPV6_SRC: + case IPV6_DST: + return matchCriterion((IPCriterion) criterion); + + case TCP_SRC: + case TCP_DST: + return matchCriterion((TcpPortCriterion) criterion); + + case UDP_SRC: + case UDP_DST: + return matchCriterion((UdpPortCriterion) criterion); + + case SCTP_SRC: + case SCTP_DST: + return matchCriterion((SctpPortCriterion) criterion); + + case ICMPV4_TYPE: + return matchCriterion((IcmpTypeCriterion) criterion); + + case ICMPV4_CODE: + return matchCriterion((IcmpCodeCriterion) criterion); + + case IPV6_FLABEL: + return matchCriterion((IPv6FlowLabelCriterion) criterion); + + case ICMPV6_TYPE: + return matchCriterion((Icmpv6TypeCriterion) criterion); + + case ICMPV6_CODE: + return matchCriterion((Icmpv6CodeCriterion) criterion); + + case IPV6_ND_TARGET: + return matchCriterion( + (IPv6NDTargetAddressCriterion) criterion); + + case IPV6_ND_SLL: + case IPV6_ND_TLL: + return matchCriterion( + (IPv6NDLinkLayerAddressCriterion) criterion); + + case MPLS_LABEL: + return matchCriterion((MplsCriterion) criterion); + + case IPV6_EXTHDR: + return matchCriterion( + (IPv6ExthdrFlagsCriterion) criterion); + + case OCH_SIGID: + return matchCriterion((OchSignalCriterion) criterion); + + case OCH_SIGTYPE: + return matchCriterion((OchSignalTypeCriterion) criterion); + + default: + // Don't know how to format this type + description.appendText("unknown criterion type " + + criterion.type()); + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText(criterion.toString()); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java new file mode 100644 index 00000000..c7196e8b --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015 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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.is; +import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable; + +import org.junit.Test; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.Device; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.device.DeviceServiceAdapter; + +/** + * Unit test for DeviceCodec. + */ +public class DeviceCodecTest { + + private Device device = new DefaultDevice(JsonCodecUtils.PID, + JsonCodecUtils.DID1, + Device.Type.SWITCH, + JsonCodecUtils.MFR, + JsonCodecUtils.HW, + JsonCodecUtils.SW1, + JsonCodecUtils.SN, + JsonCodecUtils.CID, + JsonCodecUtils.A1); + + + + @Test + public void deviceCodecTest() { + final MockCodecContext context = new MockCodecContext(); + context.registerService(DeviceService.class, new DeviceServiceAdapter()); + final JsonCodec codec = context.codec(Device.class); + assertThat(codec, is(notNullValue())); + final Device pojoIn = device; + + assertJsonEncodable(context, codec, pojoIn); + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java new file mode 100644 index 00000000..a1c95176 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015 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.Map; + +import org.junit.Test; +import org.onosproject.net.driver.Behaviour; +import org.onosproject.net.driver.DefaultDriver; +import org.onosproject.net.driver.Driver; +import org.onosproject.net.driver.TestBehaviour; +import org.onosproject.net.driver.TestBehaviourImpl; +import org.onosproject.net.driver.TestBehaviourTwo; +import org.onosproject.net.driver.TestBehaviourTwoImpl; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.onosproject.codec.impl.DriverJsonMatcher.matchesDriver; + +/** + * Unit tests for the driver codec. + */ +public class DriverCodecTest { + + @Test + public void codecTest() { + Map, Class> behaviours = + ImmutableMap.of(TestBehaviour.class, + TestBehaviourImpl.class, + TestBehaviourTwo.class, + TestBehaviourTwoImpl.class); + Map properties = + ImmutableMap.of("key1", "value1", "key2", "value2"); + + DefaultDriver parent = new DefaultDriver("parent", null, "Acme", + "HW1.2.3", "SW1.2.3", + behaviours, + properties); + DefaultDriver child = new DefaultDriver("child", parent, "Acme", + "HW1.2.3.1", "SW1.2.3.1", + behaviours, + properties); + + MockCodecContext context = new MockCodecContext(); + ObjectNode driverJson = context.codec(Driver.class).encode(child, context); + + assertThat(driverJson, matchesDriver(child)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java new file mode 100644 index 00000000..6f0070e5 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015 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.Map; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.onosproject.net.driver.Driver; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Hamcrest matcher for drivers. + */ +public final class DriverJsonMatcher extends TypeSafeDiagnosingMatcher { + private final Driver driver; + + private DriverJsonMatcher(Driver driver) { + this.driver = driver; + } + + @Override + public boolean matchesSafely(JsonNode jsonDriver, Description description) { + // check id + String jsonDriverName = jsonDriver.get("name").asText(); + String driverName = driver.name(); + if (!jsonDriverName.equals(driverName)) { + description.appendText("name was " + jsonDriverName); + return false; + } + + + // check parent + String jsonParent = jsonDriver.get("parent").asText(); + String parent = driver.parent().name(); + if (!jsonParent.equals(parent)) { + description.appendText("parent was " + jsonParent); + return false; + } + + // check manufacturer + String jsonManufacturer = jsonDriver.get("manufacturer").asText(); + String manufacturer = driver.manufacturer(); + if (!jsonManufacturer.equals(manufacturer)) { + description.appendText("manufacturer was " + jsonManufacturer); + return false; + } + + // check HW version + String jsonHWVersion = jsonDriver.get("hwVersion").asText(); + String hwVersion = driver.hwVersion(); + if (!jsonHWVersion.equals(hwVersion)) { + description.appendText("HW version was " + jsonHWVersion); + return false; + } + + // check SW version + String jsonSWVersion = jsonDriver.get("swVersion").asText(); + String swVersion = driver.swVersion(); + if (!jsonSWVersion.equals(swVersion)) { + description.appendText("SW version was " + jsonSWVersion); + return false; + } + + // Check properties + JsonNode jsonProperties = jsonDriver.get("properties"); + if (driver.properties().size() != jsonProperties.size()) { + description.appendText("properties map size was was " + jsonProperties.size()); + return false; + } + for (Map.Entry entry : driver.properties().entrySet()) { + boolean propertyFound = false; + for (int propertyIndex = 0; propertyIndex < jsonProperties.size(); propertyIndex++) { + String jsonName = jsonProperties.get(propertyIndex).get("name").asText(); + String jsonValue = jsonProperties.get(propertyIndex).get("value").asText(); + if (!jsonName.equals(entry.getKey()) || + !jsonValue.equals(entry.getValue())) { + propertyFound = true; + break; + } + } + if (!propertyFound) { + description.appendText("property not found " + entry.getKey()); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText(driver.toString()); + } + + /** + * Factory to allocate a driver matcher. + * + * @param driver driver object we are looking for + * @return matcher + */ + public static DriverJsonMatcher matchesDriver(Driver driver) { + return new DriverJsonMatcher(driver); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java new file mode 100644 index 00000000..847b0d09 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 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 org.junit.Test; +import org.onlab.packet.Ethernet; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; +import static org.onosproject.codec.impl.EthernetJsonMatcher.matchesEthernet; + +/** + * Unit test for Ethernet class codec. + */ +public class EthernetCodecTest { + + /** + * Unit test for the ethernet object codec. + */ + @Test + public void ethernetCodecTest() { + final CodecContext context = new MockCodecContext(); + final JsonCodec ethernetCodec = context.codec(Ethernet.class); + assertThat(ethernetCodec, notNullValue()); + + final Ethernet eth1 = new Ethernet(); + eth1.setSourceMACAddress("11:22:33:44:55:01"); + eth1.setDestinationMACAddress("11:22:33:44:55:02"); + eth1.setPad(true); + eth1.setEtherType(Ethernet.TYPE_ARP); + eth1.setPriorityCode((byte) 7); + eth1.setVlanID((short) 33); + + final ObjectNode eth1Json = ethernetCodec.encode(eth1, context); + assertThat(eth1Json, notNullValue()); + assertThat(eth1Json, matchesEthernet(eth1)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java new file mode 100644 index 00000000..c5827b91 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015 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 org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.onlab.packet.Ethernet; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Hamcrest matcher for ethernet objects. + */ +public final class EthernetJsonMatcher extends TypeSafeMatcher { + + private final Ethernet ethernet; + private String reason = ""; + + private EthernetJsonMatcher(Ethernet ethernetValue) { + ethernet = ethernetValue; + } + + @Override + public boolean matchesSafely(JsonNode jsonEthernet) { + + // check source MAC + final JsonNode jsonSourceMacNode = jsonEthernet.get("srcMac"); + if (ethernet.getSourceMAC() != null) { + final String jsonSourceMac = jsonSourceMacNode.textValue(); + final String sourceMac = ethernet.getSourceMAC().toString(); + if (!jsonSourceMac.equals(sourceMac)) { + reason = "source MAC " + ethernet.getSourceMAC().toString(); + return false; + } + } else { + // source MAC not specified, JSON representation must be empty + if (jsonSourceMacNode != null) { + reason = "source mac should be null "; + return false; + } + } + + // check destination MAC + final JsonNode jsonDestinationMacNode = jsonEthernet.get("destMac"); + if (ethernet.getDestinationMAC() != null) { + final String jsonDestinationMac = jsonDestinationMacNode.textValue(); + final String destinationMac = ethernet.getDestinationMAC().toString(); + if (!jsonDestinationMac.equals(destinationMac)) { + reason = "destination MAC " + ethernet.getDestinationMAC().toString(); + return false; + } + } else { + // destination MAC not specified, JSON representation must be empty + if (jsonDestinationMacNode != null) { + reason = "destination mac should be null "; + return false; + } + } + + // check priority code + final short jsonPriorityCode = jsonEthernet.get("priorityCode").shortValue(); + final short priorityCode = ethernet.getPriorityCode(); + if (jsonPriorityCode != priorityCode) { + reason = "priority code " + Short.toString(ethernet.getPriorityCode()); + return false; + } + + // check vlanId + final short jsonVlanId = jsonEthernet.get("vlanId").shortValue(); + final short vlanId = ethernet.getVlanID(); + if (jsonVlanId != vlanId) { + reason = "vlan id " + Short.toString(ethernet.getVlanID()); + return false; + } + + // check etherType + final short jsonEtherType = jsonEthernet.get("etherType").shortValue(); + final short etherType = ethernet.getEtherType(); + if (jsonEtherType != etherType) { + reason = "etherType " + Short.toString(ethernet.getEtherType()); + return false; + } + + // check pad + final boolean jsonPad = jsonEthernet.get("pad").asBoolean(); + final boolean pad = ethernet.isPad(); + if (jsonPad != pad) { + reason = "pad " + Boolean.toString(ethernet.isPad()); + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText(reason); + } + + /** + * Factory to allocate a ethernet matcher. + * + * @param ethernet ethernet object we are looking for + * @return matcher + */ + public static EthernetJsonMatcher matchesEthernet(Ethernet ethernet) { + return new EthernetJsonMatcher(ethernet); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java new file mode 100644 index 00000000..6c88ac1e --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java @@ -0,0 +1,546 @@ +/* + * Copyright 2015 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.io.IOException; +import java.io.InputStream; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.EthType; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.VlanId; +import org.onosproject.codec.JsonCodec; +import org.onosproject.core.CoreService; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.GridType; +import org.onosproject.net.Lambda; +import org.onosproject.net.OchSignal; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.IPDscpCriterion; +import org.onosproject.net.flow.criteria.IPEcnCriterion; +import org.onosproject.net.flow.criteria.IPProtocolCriterion; +import org.onosproject.net.flow.criteria.IPv6ExthdrFlagsCriterion; +import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion; +import org.onosproject.net.flow.criteria.IPv6NDLinkLayerAddressCriterion; +import org.onosproject.net.flow.criteria.IPv6NDTargetAddressCriterion; +import org.onosproject.net.flow.criteria.IcmpCodeCriterion; +import org.onosproject.net.flow.criteria.IcmpTypeCriterion; +import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion; +import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion; +import org.onosproject.net.flow.criteria.IndexedLambdaCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; +import org.onosproject.net.flow.criteria.OchSignalCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.SctpPortCriterion; +import org.onosproject.net.flow.criteria.TcpPortCriterion; +import org.onosproject.net.flow.criteria.TunnelIdCriterion; +import org.onosproject.net.flow.criteria.UdpPortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.criteria.VlanPcpCriterion; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction; +import org.onosproject.net.flow.instructions.L4ModificationInstruction; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.onosproject.net.NetTestTools.APP_ID; + +/** + * Flow rule codec unit tests. + */ +public class FlowRuleCodecTest { + + MockCodecContext context; + JsonCodec flowRuleCodec; + final CoreService mockCoreService = createMock(CoreService.class); + + /** + * Sets up for each test. Creates a context and fetches the flow rule + * codec. + */ + @Before + public void setUp() { + context = new MockCodecContext(); + flowRuleCodec = context.codec(FlowRule.class); + assertThat(flowRuleCodec, notNullValue()); + + expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID)) + .andReturn(APP_ID).anyTimes(); + replay(mockCoreService); + context.registerService(CoreService.class, mockCoreService); + } + + /** + * Reads in a rule from the given resource and decodes it. + * + * @param resourceName resource to use to read the JSON for the rule + * @return decoded flow rule + * @throws IOException if processing the resource fails + */ + private FlowRule getRule(String resourceName) throws IOException { + InputStream jsonStream = FlowRuleCodecTest.class + .getResourceAsStream(resourceName); + JsonNode json = context.mapper().readTree(jsonStream); + assertThat(json, notNullValue()); + FlowRule rule = flowRuleCodec.decode((ObjectNode) json, context); + assertThat(rule, notNullValue()); + return rule; + } + + /** + * Checks that the data shared by all the resources is correct for a + * given rule. + * + * @param rule rule to check + */ + private void checkCommonData(FlowRule rule) { + assertThat(rule.appId(), is(APP_ID.id())); + assertThat(rule.isPermanent(), is(false)); + assertThat(rule.timeout(), is(1)); + assertThat(rule.priority(), is(1)); + assertThat(rule.deviceId().toString(), is("of:0000000000000001")); + } + + /** + * Checks that a simple rule decodes properly. + * + * @throws IOException if the resource cannot be processed + */ + @Test + public void codecSimpleFlowTest() throws IOException { + FlowRule rule = getRule("simple-flow.json"); + + checkCommonData(rule); + + assertThat(rule.selector().criteria().size(), is(1)); + Criterion criterion1 = rule.selector().criteria().iterator().next(); + assertThat(criterion1.type(), is(Criterion.Type.ETH_TYPE)); + assertThat(((EthTypeCriterion) criterion1).ethType(), is(new EthType(2054))); + + assertThat(rule.treatment().allInstructions().size(), is(1)); + Instruction instruction1 = rule.treatment().allInstructions().get(0); + assertThat(instruction1.type(), is(Instruction.Type.OUTPUT)); + assertThat(((Instructions.OutputInstruction) instruction1).port(), is(PortNumber.CONTROLLER)); + } + + SortedMap instructions = new TreeMap<>(); + + /** + * Looks up an instruction in the instruction map based on type and subtype. + * + * @param type type string + * @param subType subtype string + * @return instruction that matches + */ + private Instruction getInstruction(Instruction.Type type, String subType) { + Instruction instruction = instructions.get(type.name() + "/" + subType); + assertThat(instruction, notNullValue()); + assertThat(instruction.type(), is(type)); + return instruction; + } + + /** + * Checks that a rule with one of each instruction type decodes properly. + * + * @throws IOException if the resource cannot be processed + */ + @Test + public void decodeInstructionsFlowTest() throws Exception { + FlowRule rule = getRule("instructions-flow.json"); + + checkCommonData(rule); + + rule.treatment().allInstructions() + .stream() + .forEach(instruction -> + { + String subType; + if (instruction.type() == Instruction.Type.L0MODIFICATION) { + subType = ((L0ModificationInstruction) instruction) + .subtype().name(); + } else if (instruction.type() == Instruction.Type.L2MODIFICATION) { + subType = ((L2ModificationInstruction) instruction) + .subtype().name(); + } else if (instruction.type() == Instruction.Type.L3MODIFICATION) { + subType = ((L3ModificationInstruction) instruction) + .subtype().name(); + } else if (instruction.type() == Instruction.Type.L4MODIFICATION) { + subType = ((L4ModificationInstruction) instruction) + .subtype().name(); + } else { + subType = ""; + } + instructions.put( + instruction.type().name() + "/" + subType, instruction); + }); + + assertThat(rule.treatment().allInstructions().size(), is(24)); + + Instruction instruction; + + instruction = getInstruction(Instruction.Type.OUTPUT, ""); + assertThat(instruction.type(), is(Instruction.Type.OUTPUT)); + assertThat(((Instructions.OutputInstruction) instruction).port(), is(PortNumber.CONTROLLER)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.ETH_SRC.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(((L2ModificationInstruction.ModEtherInstruction) instruction).mac(), + is(MacAddress.valueOf("12:34:56:78:90:12"))); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.ETH_DST.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(((L2ModificationInstruction.ModEtherInstruction) instruction).mac(), + is(MacAddress.valueOf("98:76:54:32:01:00"))); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.VLAN_ID.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(((L2ModificationInstruction.ModVlanIdInstruction) instruction).vlanId().toShort(), + is((short) 22)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.VLAN_PCP.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(((L2ModificationInstruction.ModVlanPcpInstruction) instruction).vlanPcp(), + is((byte) 1)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.MPLS_LABEL.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(((L2ModificationInstruction.ModMplsLabelInstruction) instruction) + .mplsLabel().toInt(), + is(MplsLabel.MAX_MPLS)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.MPLS_PUSH.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(((L2ModificationInstruction.PushHeaderInstructions) instruction) + .ethernetType().toShort(), + is(Ethernet.MPLS_UNICAST)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.MPLS_POP.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(((L2ModificationInstruction.PushHeaderInstructions) instruction) + .ethernetType().toShort(), + is(Ethernet.MPLS_UNICAST)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.DEC_MPLS_TTL.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(instruction, instanceOf(L2ModificationInstruction.ModMplsTtlInstruction.class)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.VLAN_POP.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(instruction, instanceOf(L2ModificationInstruction.PopVlanInstruction.class)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.VLAN_PUSH.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(instruction, instanceOf(L2ModificationInstruction.PushHeaderInstructions.class)); + + instruction = getInstruction(Instruction.Type.L2MODIFICATION, + L2ModificationInstruction.L2SubType.TUNNEL_ID.name()); + assertThat(instruction.type(), is(Instruction.Type.L2MODIFICATION)); + assertThat(((L2ModificationInstruction.ModTunnelIdInstruction) instruction) + .tunnelId(), is(100L)); + + instruction = getInstruction(Instruction.Type.L3MODIFICATION, + L3ModificationInstruction.L3SubType.IPV4_SRC.name()); + assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION)); + assertThat(((L3ModificationInstruction.ModIPInstruction) instruction).ip(), + is(IpAddress.valueOf("1.2.3.4"))); + + instruction = getInstruction(Instruction.Type.L3MODIFICATION, + L3ModificationInstruction.L3SubType.IPV4_DST.name()); + assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION)); + assertThat(((L3ModificationInstruction.ModIPInstruction) instruction).ip(), + is(IpAddress.valueOf("1.2.3.3"))); + + instruction = getInstruction(Instruction.Type.L3MODIFICATION, + L3ModificationInstruction.L3SubType.IPV6_SRC.name()); + assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION)); + assertThat(((L3ModificationInstruction.ModIPInstruction) instruction).ip(), + is(IpAddress.valueOf("1.2.3.2"))); + + instruction = getInstruction(Instruction.Type.L3MODIFICATION, + L3ModificationInstruction.L3SubType.IPV6_DST.name()); + assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION)); + assertThat(((L3ModificationInstruction.ModIPInstruction) instruction).ip(), + is(IpAddress.valueOf("1.2.3.1"))); + + instruction = getInstruction(Instruction.Type.L3MODIFICATION, + L3ModificationInstruction.L3SubType.IPV6_FLABEL.name()); + assertThat(instruction.type(), is(Instruction.Type.L3MODIFICATION)); + assertThat(((L3ModificationInstruction.ModIPv6FlowLabelInstruction) instruction) + .flowLabel(), + is(8)); + + instruction = getInstruction(Instruction.Type.L0MODIFICATION, + L0ModificationInstruction.L0SubType.LAMBDA.name()); + assertThat(instruction.type(), is(Instruction.Type.L0MODIFICATION)); + assertThat(((L0ModificationInstruction.ModLambdaInstruction) instruction) + .lambda(), + is((short) 7)); + + instruction = getInstruction(Instruction.Type.L0MODIFICATION, + L0ModificationInstruction.L0SubType.OCH.name()); + assertThat(instruction.type(), is(Instruction.Type.L0MODIFICATION)); + L0ModificationInstruction.ModOchSignalInstruction och = + (L0ModificationInstruction.ModOchSignalInstruction) instruction; + assertThat(och.lambda().spacingMultiplier(), is(4)); + assertThat(och.lambda().slotGranularity(), is(8)); + assertThat(och.lambda().gridType(), is(GridType.DWDM)); + assertThat(och.lambda().channelSpacing(), is(ChannelSpacing.CHL_100GHZ)); + + instruction = getInstruction(Instruction.Type.L4MODIFICATION, + L4ModificationInstruction.L4SubType.TCP_DST.name()); + assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION)); + assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction) + .port().toInt(), is(40001)); + + instruction = getInstruction(Instruction.Type.L4MODIFICATION, + L4ModificationInstruction.L4SubType.TCP_SRC.name()); + assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION)); + assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction) + .port().toInt(), is(40002)); + + instruction = getInstruction(Instruction.Type.L4MODIFICATION, + L4ModificationInstruction.L4SubType.UDP_DST.name()); + assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION)); + assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction) + .port().toInt(), is(40003)); + + instruction = getInstruction(Instruction.Type.L4MODIFICATION, + L4ModificationInstruction.L4SubType.UDP_SRC.name()); + assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION)); + assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction) + .port().toInt(), is(40004)); + } + + SortedMap criteria = new TreeMap<>(); + + /** + * Looks up a criterion in the instruction map based on type and subtype. + * + * @param type type string + * @return criterion that matches + */ + private Criterion getCriterion(Criterion.Type type) { + Criterion criterion = criteria.get(type.name()); + assertThat(criterion.type(), is(type)); + return criterion; + } + + /** + * Checks that a rule with one of each kind of criterion decodes properly. + * + * @throws IOException if the resource cannot be processed + */ + @Test + public void codecCriteriaFlowTest() throws Exception { + FlowRule rule = getRule("criteria-flow.json"); + + checkCommonData(rule); + + assertThat(rule.selector().criteria().size(), is(33)); + + rule.selector().criteria() + .stream() + .forEach(criterion -> + criteria.put(criterion.type().name(), criterion)); + + Criterion criterion; + + criterion = getCriterion(Criterion.Type.ETH_TYPE); + assertThat(((EthTypeCriterion) criterion).ethType(), is(new EthType(2054))); + + criterion = getCriterion(Criterion.Type.ETH_DST); + assertThat(((EthCriterion) criterion).mac(), + is(MacAddress.valueOf("00:11:22:33:44:55"))); + + criterion = getCriterion(Criterion.Type.ETH_SRC); + assertThat(((EthCriterion) criterion).mac(), + is(MacAddress.valueOf("00:11:22:33:44:55"))); + + criterion = getCriterion(Criterion.Type.IN_PORT); + assertThat(((PortCriterion) criterion).port(), + is(PortNumber.portNumber(23))); + + criterion = getCriterion(Criterion.Type.IN_PHY_PORT); + assertThat(((PortCriterion) criterion).port(), + is(PortNumber.portNumber(44))); + + criterion = getCriterion(Criterion.Type.VLAN_VID); + assertThat(((VlanIdCriterion) criterion).vlanId(), + is(VlanId.vlanId((short) 777))); + + criterion = getCriterion(Criterion.Type.VLAN_PCP); + assertThat(((VlanPcpCriterion) criterion).priority(), + is(((byte) 3))); + + criterion = getCriterion(Criterion.Type.IP_DSCP); + assertThat(((IPDscpCriterion) criterion).ipDscp(), + is(((byte) 2))); + + criterion = getCriterion(Criterion.Type.IP_ECN); + assertThat(((IPEcnCriterion) criterion).ipEcn(), + is(((byte) 1))); + + criterion = getCriterion(Criterion.Type.IP_PROTO); + assertThat(((IPProtocolCriterion) criterion).protocol(), + is(((short) 4))); + + criterion = getCriterion(Criterion.Type.IPV4_SRC); + assertThat(((IPCriterion) criterion).ip(), + is((IpPrefix.valueOf("1.2.0.0/32")))); + + criterion = getCriterion(Criterion.Type.IPV4_DST); + assertThat(((IPCriterion) criterion).ip(), + is((IpPrefix.valueOf("2.2.0.0/32")))); + + criterion = getCriterion(Criterion.Type.IPV6_SRC); + assertThat(((IPCriterion) criterion).ip(), + is((IpPrefix.valueOf("3.2.0.0/32")))); + + criterion = getCriterion(Criterion.Type.IPV6_DST); + assertThat(((IPCriterion) criterion).ip(), + is((IpPrefix.valueOf("4.2.0.0/32")))); + + criterion = getCriterion(Criterion.Type.TCP_SRC); + assertThat(((TcpPortCriterion) criterion).tcpPort().toInt(), + is(80)); + + criterion = getCriterion(Criterion.Type.TCP_DST); + assertThat(((TcpPortCriterion) criterion).tcpPort().toInt(), + is(443)); + + criterion = getCriterion(Criterion.Type.UDP_SRC); + assertThat(((UdpPortCriterion) criterion).udpPort().toInt(), + is(180)); + + criterion = getCriterion(Criterion.Type.UDP_DST); + assertThat(((UdpPortCriterion) criterion).udpPort().toInt(), + is(1443)); + + criterion = getCriterion(Criterion.Type.SCTP_SRC); + assertThat(((SctpPortCriterion) criterion).sctpPort().toInt(), + is(280)); + + criterion = getCriterion(Criterion.Type.SCTP_DST); + assertThat(((SctpPortCriterion) criterion).sctpPort().toInt(), + is(2443)); + + criterion = getCriterion(Criterion.Type.ICMPV4_TYPE); + assertThat(((IcmpTypeCriterion) criterion).icmpType(), + is((short) 24)); + + criterion = getCriterion(Criterion.Type.ICMPV4_CODE); + assertThat(((IcmpCodeCriterion) criterion).icmpCode(), + is((short) 16)); + + criterion = getCriterion(Criterion.Type.ICMPV6_TYPE); + assertThat(((Icmpv6TypeCriterion) criterion).icmpv6Type(), + is((short) 14)); + + criterion = getCriterion(Criterion.Type.ICMPV6_CODE); + assertThat(((Icmpv6CodeCriterion) criterion).icmpv6Code(), + is((short) 6)); + + criterion = getCriterion(Criterion.Type.IPV6_FLABEL); + assertThat(((IPv6FlowLabelCriterion) criterion).flowLabel(), + is(8)); + + criterion = getCriterion(Criterion.Type.IPV6_ND_TARGET); + assertThat(((IPv6NDTargetAddressCriterion) criterion) + .targetAddress().toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + criterion = getCriterion(Criterion.Type.IPV6_ND_SLL); + assertThat(((IPv6NDLinkLayerAddressCriterion) criterion).mac(), + is(MacAddress.valueOf("00:11:22:33:44:56"))); + + criterion = getCriterion(Criterion.Type.IPV6_ND_TLL); + assertThat(((IPv6NDLinkLayerAddressCriterion) criterion).mac(), + is(MacAddress.valueOf("00:11:22:33:44:57"))); + + criterion = getCriterion(Criterion.Type.MPLS_LABEL); + assertThat(((MplsCriterion) criterion).label(), + is(MplsLabel.mplsLabel(123))); + + criterion = getCriterion(Criterion.Type.IPV6_EXTHDR); + assertThat(((IPv6ExthdrFlagsCriterion) criterion).exthdrFlags(), + is(99)); + + criterion = getCriterion(Criterion.Type.OCH_SIGID); + assertThat(((IndexedLambdaCriterion) criterion).lambda(), + is(Lambda.indexedLambda(122))); + + criterion = getCriterion(Criterion.Type.TUNNEL_ID); + assertThat(((TunnelIdCriterion) criterion).tunnelId(), + is(100L)); + } + + /** + * Checks that a rule with a SigId criterion decodes properly. + * + * @throws IOException if the resource cannot be processed + */ + @Test + public void codecSigIdCriteriaFlowTest() throws Exception { + FlowRule rule = getRule("sigid-flow.json"); + + checkCommonData(rule); + + assertThat(rule.selector().criteria().size(), is(1)); + Criterion criterion = rule.selector().criteria().iterator().next(); + assertThat(criterion.type(), is(Criterion.Type.OCH_SIGID)); + Lambda lambda = ((OchSignalCriterion) criterion).lambda(); + assertThat(lambda, instanceOf(OchSignal.class)); + OchSignal ochSignal = (OchSignal) lambda; + assertThat(ochSignal.spacingMultiplier(), is(3)); + assertThat(ochSignal.slotGranularity(), is(4)); + assertThat(ochSignal.gridType(), is(GridType.CWDM)); + assertThat(ochSignal.channelSpacing(), is(ChannelSpacing.CHL_25GHZ)); + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java new file mode 100644 index 00000000..b3056216 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015 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 org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.onosproject.net.group.GroupBucket; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Hamcrest matcher for instructions. + */ +public final class GroupBucketJsonMatcher extends TypeSafeDiagnosingMatcher { + + private final GroupBucket bucket; + + private GroupBucketJsonMatcher(GroupBucket bucket) { + this.bucket = bucket; + } + + /** + * Matches the contents of a group bucket. + * + * @param bucketJson JSON representation of bucket to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + @Override + public boolean matchesSafely(JsonNode bucketJson, Description description) { + + // check type + final String jsonType = bucketJson.get("type").textValue(); + if (!bucket.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final long jsonWeight = bucketJson.get("weight").longValue(); + if (bucket.weight() != jsonWeight) { + description.appendText("weight was " + jsonWeight); + return false; + } + + final long packetsJson = bucketJson.get("packets").asLong(); + if (bucket.packets() != packetsJson) { + description.appendText("packets was " + packetsJson); + return false; + } + + final long bytesJson = bucketJson.get("bytes").asLong(); + if (bucket.bytes() != bytesJson) { + description.appendText("bytes was " + packetsJson); + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText(bucket.toString()); + } + + /** + * Factory to allocate an bucket matcher. + * + * @param bucket bucket object we are looking for + * @return matcher + */ + public static GroupBucketJsonMatcher matchesGroupBucket(GroupBucket bucket) { + return new GroupBucketJsonMatcher(bucket); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java new file mode 100644 index 00000000..409f8eb3 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 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 org.junit.Test; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.net.NetTestTools; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.group.DefaultGroup; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableList; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.onosproject.codec.impl.GroupJsonMatcher.matchesGroup; + +/** + * Group codec unit tests. + */ + +public class GroupCodecTest { + + @Test + public void codecTest() { + GroupBucket bucket1 = DefaultGroupBucket + .createSelectGroupBucket(DefaultTrafficTreatment.emptyTreatment()); + GroupBucket bucket2 = DefaultGroupBucket + .createIndirectGroupBucket(DefaultTrafficTreatment.emptyTreatment()); + GroupBuckets buckets = new GroupBuckets(ImmutableList.of(bucket1, bucket2)); + + + DefaultGroup group = new DefaultGroup( + new DefaultGroupId(1), + NetTestTools.did("d1"), + GroupDescription.Type.INDIRECT, + buckets); + + MockCodecContext context = new MockCodecContext(); + GroupCodec codec = new GroupCodec(); + ObjectNode groupJson = codec.encode(group, context); + + assertThat(groupJson, matchesGroup(group)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java new file mode 100644 index 00000000..0e62c305 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java @@ -0,0 +1,120 @@ +/* + * Copyright 2015 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 org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Hamcrest matcher for groups. + */ + +public final class GroupJsonMatcher extends TypeSafeDiagnosingMatcher { + + private final Group group; + + private GroupJsonMatcher(Group group) { + this.group = group; + } + + @Override + public boolean matchesSafely(JsonNode jsonGroup, Description description) { + // check id + String jsonGroupId = jsonGroup.get("id").asText(); + String groupId = group.id().toString(); + if (!jsonGroupId.equals(groupId)) { + description.appendText("group id was " + jsonGroupId); + return false; + } + + // check state + String jsonState = jsonGroup.get("state").asText(); + String state = group.state().toString(); + if (!jsonState.equals(state)) { + description.appendText("state was " + jsonState); + return false; + } + + // check life + long jsonLife = jsonGroup.get("life").asLong(); + long life = group.life(); + if (life != jsonLife) { + description.appendText("life was " + jsonLife); + return false; + } + + // check bytes + long jsonBytes = jsonGroup.get("bytes").asLong(); + long bytes = group.bytes(); + if (bytes != jsonBytes) { + description.appendText("bytes was " + jsonBytes); + return false; + } + + // check packets + long jsonPackets = jsonGroup.get("packets").asLong(); + long packets = group.packets(); + if (packets != jsonPackets) { + description.appendText("packets was " + jsonPackets); + return false; + } + + // check size of bucket array + JsonNode jsonBuckets = jsonGroup.get("buckets"); + if (jsonBuckets.size() != group.buckets().buckets().size()) { + description.appendText("buckets size was " + jsonBuckets.size()); + return false; + } + + // Check buckets + for (GroupBucket bucket : group.buckets().buckets()) { + boolean bucketFound = false; + for (int bucketIndex = 0; bucketIndex < jsonBuckets.size(); bucketIndex++) { + GroupBucketJsonMatcher bucketMatcher = + GroupBucketJsonMatcher.matchesGroupBucket(bucket); + if (bucketMatcher.matches(jsonBuckets.get(bucketIndex))) { + bucketFound = true; + break; + } + } + if (!bucketFound) { + description.appendText("bucket not found " + bucket.toString()); + return false; + } + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText(group.toString()); + } + + /** + * Factory to allocate a group matcher. + * + * @param group group object we are looking for + * @return matcher + */ + public static GroupJsonMatcher matchesGroup(Group group) { + return new GroupJsonMatcher(group); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java new file mode 100644 index 00000000..2081fa58 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015 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 org.junit.Test; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass; + +/** + * Tests to assure that the codec classes follow the contract of having + * no local context. + */ +public class ImmutableCodecsTest { + + /** + * Checks that the codec classes adhere to the contract that there cannot + * be any local context in a codec. + */ + @Test + public void checkImmutability() { + assertThatClassIsImmutableBaseClass(AnnotatedCodec.class); + assertThatClassIsImmutable(AnnotationsCodec.class); + assertThatClassIsImmutable(ApplicationCodec.class); + assertThatClassIsImmutable(ConnectivityIntentCodec.class); + assertThatClassIsImmutable(ConnectPointCodec.class); + assertThatClassIsImmutable(ConstraintCodec.class); + assertThatClassIsImmutable(EncodeConstraintCodecHelper.class); + assertThatClassIsImmutable(DecodeConstraintCodecHelper.class); + assertThatClassIsImmutable(CriterionCodec.class); + assertThatClassIsImmutable(EncodeCriterionCodecHelper.class); + assertThatClassIsImmutable(DecodeCriterionCodecHelper.class); + assertThatClassIsImmutable(DeviceCodec.class); + assertThatClassIsImmutable(EthernetCodec.class); + assertThatClassIsImmutable(FlowEntryCodec.class); + assertThatClassIsImmutable(HostCodec.class); + assertThatClassIsImmutable(HostLocationCodec.class); + assertThatClassIsImmutable(HostToHostIntentCodec.class); + assertThatClassIsImmutable(InstructionCodec.class); + assertThatClassIsImmutable(EncodeInstructionCodecHelper.class); + assertThatClassIsImmutable(DecodeInstructionCodecHelper.class); + assertThatClassIsImmutable(IntentCodec.class); + assertThatClassIsImmutable(LinkCodec.class); + assertThatClassIsImmutable(PathCodec.class); + assertThatClassIsImmutable(PointToPointIntentCodec.class); + assertThatClassIsImmutable(PortCodec.class); + assertThatClassIsImmutable(TopologyClusterCodec.class); + assertThatClassIsImmutable(TopologyCodec.class); + assertThatClassIsImmutable(TrafficSelectorCodec.class); + assertThatClassIsImmutable(TrafficTreatmentCodec.class); + assertThatClassIsImmutable(FlowRuleCodec.class); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java new file mode 100644 index 00000000..bafbc0f1 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java @@ -0,0 +1,247 @@ +/* + * Copyright 2015 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 org.junit.Before; +import org.junit.Test; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip6Address; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.VlanId; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.GridType; +import org.onosproject.net.IndexedLambda; +import org.onosproject.net.Lambda; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; +import static org.onosproject.codec.impl.InstructionJsonMatcher.matchesInstruction; + +/** + * Unit tests for Instruction codec. + */ +public class InstructionCodecTest { + CodecContext context; + JsonCodec instructionCodec; + + /** + * Sets up for each test. Creates a context and fetches the instruction + * codec. + */ + @Before + public void setUp() { + context = new MockCodecContext(); + instructionCodec = context.codec(Instruction.class); + assertThat(instructionCodec, notNullValue()); + } + + /** + * Tests the encoding of push header instructions. + */ + @Test + public void pushHeaderInstructionsTest() { + final L2ModificationInstruction.PushHeaderInstructions instruction = + (L2ModificationInstruction.PushHeaderInstructions) Instructions.pushMpls(); + final ObjectNode instructionJson = instructionCodec.encode(instruction, context); + + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of drop instructions. + */ + @Test + public void dropInstructionTest() { + final Instructions.DropInstruction instruction = + Instructions.createDrop(); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of output instructions. + */ + @Test + public void outputInstructionTest() { + final Instructions.OutputInstruction instruction = + Instructions.createOutput(PortNumber.portNumber(22)); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod lambda instructions. + */ + @Test + public void modLambdaInstructionTest() { + final L0ModificationInstruction.ModLambdaInstruction instruction = + (L0ModificationInstruction.ModLambdaInstruction) + Instructions.modL0Lambda(new IndexedLambda(55)); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod OCh signal instructions. + */ + @Test + public void modOchSignalInstructionTest() { + L0ModificationInstruction.ModOchSignalInstruction instruction = + (L0ModificationInstruction.ModOchSignalInstruction) + Instructions.modL0Lambda(Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8)); + ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod ether instructions. + */ + @Test + public void modEtherInstructionTest() { + final L2ModificationInstruction.ModEtherInstruction instruction = + (L2ModificationInstruction.ModEtherInstruction) + Instructions.modL2Src(MacAddress.valueOf("11:22:33:44:55:66")); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod vlan id instructions. + */ + @Test + public void modVlanIdInstructionTest() { + final L2ModificationInstruction.ModVlanIdInstruction instruction = + (L2ModificationInstruction.ModVlanIdInstruction) + Instructions.modVlanId(VlanId.vlanId((short) 12)); + + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod vlan pcp instructions. + */ + @Test + public void modVlanPcpInstructionTest() { + final L2ModificationInstruction.ModVlanPcpInstruction instruction = + (L2ModificationInstruction.ModVlanPcpInstruction) + Instructions.modVlanPcp((byte) 9); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod IPv4 src instructions. + */ + @Test + public void modIPSrcInstructionTest() { + final Ip4Address ip = Ip4Address.valueOf("1.2.3.4"); + final L3ModificationInstruction.ModIPInstruction instruction = + (L3ModificationInstruction.ModIPInstruction) + Instructions.modL3Src(ip); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod IPv4 dst instructions. + */ + @Test + public void modIPDstInstructionTest() { + final Ip4Address ip = Ip4Address.valueOf("1.2.3.4"); + final L3ModificationInstruction.ModIPInstruction instruction = + (L3ModificationInstruction.ModIPInstruction) + Instructions.modL3Dst(ip); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod IPv6 src instructions. + */ + @Test + public void modIPv6SrcInstructionTest() { + final Ip6Address ip = Ip6Address.valueOf("1111::2222"); + final L3ModificationInstruction.ModIPInstruction instruction = + (L3ModificationInstruction.ModIPInstruction) + Instructions.modL3IPv6Src(ip); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod IPv6 dst instructions. + */ + @Test + public void modIPv6DstInstructionTest() { + final Ip6Address ip = Ip6Address.valueOf("1111::2222"); + final L3ModificationInstruction.ModIPInstruction instruction = + (L3ModificationInstruction.ModIPInstruction) + Instructions.modL3IPv6Dst(ip); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod IPv6 flow label instructions. + */ + @Test + public void modIPv6FlowLabelInstructionTest() { + final int flowLabel = 0xfffff; + final L3ModificationInstruction.ModIPv6FlowLabelInstruction instruction = + (L3ModificationInstruction.ModIPv6FlowLabelInstruction) + Instructions.modL3IPv6FlowLabel(flowLabel); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + + /** + * Tests the encoding of mod MPLS label instructions. + */ + @Test + public void modMplsLabelInstructionTest() { + final L2ModificationInstruction.ModMplsLabelInstruction instruction = + (L2ModificationInstruction.ModMplsLabelInstruction) + Instructions.modMplsLabel(MplsLabel.mplsLabel(99)); + final ObjectNode instructionJson = + instructionCodec.encode(instruction, context); + assertThat(instructionJson, matchesInstruction(instruction)); + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java new file mode 100644 index 00000000..72081e6c --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java @@ -0,0 +1,438 @@ +/* + * Copyright 2015 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 org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.onosproject.net.flow.instructions.Instruction; + +import com.fasterxml.jackson.databind.JsonNode; + +import static org.onosproject.net.flow.instructions.Instructions.*; +import static org.onosproject.net.flow.instructions.L0ModificationInstruction.*; +import static org.onosproject.net.flow.instructions.L2ModificationInstruction.*; +import static org.onosproject.net.flow.instructions.L3ModificationInstruction.*; + +/** + * Hamcrest matcher for instructions. + */ +public final class InstructionJsonMatcher extends TypeSafeDiagnosingMatcher { + + private final Instruction instruction; + + private InstructionJsonMatcher(Instruction instructionValue) { + instruction = instructionValue; + } + + /** + * Matches the contents of a push header instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchPushHeaderInstruction(JsonNode instructionJson, + Description description) { + PushHeaderInstructions instructionToMatch = + (PushHeaderInstructions) instruction; + final String jsonSubtype = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubtype)) { + description.appendText("subtype was " + jsonSubtype); + return false; + } + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final JsonNode ethJson = instructionJson.get("ethernetType"); + if (ethJson == null) { + description.appendText("ethernetType was not null"); + return false; + } + + if (instructionToMatch.ethernetType().toShort() != ethJson.asInt()) { + description.appendText("ethernetType was " + ethJson); + return false; + } + + return true; + } + + /** + * Matches the contents of an output instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchOutputInstruction(JsonNode instructionJson, + Description description) { + OutputInstruction instructionToMatch = (OutputInstruction) instruction; + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final long jsonPort = instructionJson.get("port").asLong(); + if (instructionToMatch.port().toLong() != jsonPort) { + description.appendText("port was " + jsonPort); + return false; + } + + return true; + } + + /** + * Matches the contents of a mod lambda instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchModLambdaInstruction(JsonNode instructionJson, + Description description) { + ModLambdaInstruction instructionToMatch = + (ModLambdaInstruction) instruction; + final String jsonSubtype = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubtype)) { + description.appendText("subtype was " + jsonSubtype); + return false; + } + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final long jsonLambda = instructionJson.get("lambda").shortValue(); + if (instructionToMatch.lambda() != jsonLambda) { + description.appendText("lambda was " + jsonLambda); + return false; + } + + return true; + } + + /** + * Matches teh contents of a mod OCh singal instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents matches, false otherwise + */ + private boolean matchModOchSingalInstruction(JsonNode instructionJson, + Description description) { + ModOchSignalInstruction instructionToMatch = + (ModOchSignalInstruction) instruction; + + String jsonSubType = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubType)) { + description.appendText("subtype was " + jsonSubType); + return false; + } + + String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + String jsonGridType = instructionJson.get("gridType").textValue(); + if (!instructionToMatch.lambda().gridType().name().equals(jsonGridType)) { + description.appendText("gridType was " + jsonGridType); + return false; + } + + String jsonChannelSpacing = instructionJson.get("channelSpacing").textValue(); + if (!instructionToMatch.lambda().channelSpacing().name().equals(jsonChannelSpacing)) { + description.appendText("channelSpacing was " + jsonChannelSpacing); + return false; + } + + int jsonSpacingMultiplier = instructionJson.get("spacingMultiplier").intValue(); + if (instructionToMatch.lambda().spacingMultiplier() != jsonSpacingMultiplier) { + description.appendText("spacingMultiplier was " + jsonSpacingMultiplier); + return false; + } + + int jsonSlotGranularity = instructionJson.get("slotGranularity").intValue(); + if (instructionToMatch.lambda().slotGranularity() != jsonSlotGranularity) { + description.appendText("slotGranularity was " + jsonSlotGranularity); + return false; + } + + return true; + } + + /** + * Matches the contents of a mod Ethernet instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchModEtherInstruction(JsonNode instructionJson, + Description description) { + ModEtherInstruction instructionToMatch = + (ModEtherInstruction) instruction; + final String jsonSubtype = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubtype)) { + description.appendText("subtype was " + jsonSubtype); + return false; + } + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final String jsonMac = instructionJson.get("mac").textValue(); + final String mac = instructionToMatch.mac().toString(); + if (!mac.equals(jsonMac)) { + description.appendText("mac was " + jsonMac); + return false; + } + + return true; + } + + /** + * Matches the contents of a mod vlan id instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchModVlanIdInstruction(JsonNode instructionJson, + Description description) { + ModVlanIdInstruction instructionToMatch = + (ModVlanIdInstruction) instruction; + final String jsonSubtype = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubtype)) { + description.appendText("subtype was " + jsonSubtype); + return false; + } + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final short jsonVlanId = instructionJson.get("vlanId").shortValue(); + final short vlanId = instructionToMatch.vlanId().toShort(); + if (jsonVlanId != vlanId) { + description.appendText("vlan id was " + jsonVlanId); + return false; + } + + return true; + } + + /** + * Matches the contents of a mod vlan pcp instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchModVlanPcpInstruction(JsonNode instructionJson, + Description description) { + ModVlanPcpInstruction instructionToMatch = + (ModVlanPcpInstruction) instruction; + final String jsonSubtype = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubtype)) { + description.appendText("subtype was " + jsonSubtype); + return false; + } + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final short jsonVlanPcp = instructionJson.get("vlanPcp").shortValue(); + final short vlanId = instructionToMatch.vlanPcp(); + if (jsonVlanPcp != vlanId) { + description.appendText("vlan pcp was " + jsonVlanPcp); + return false; + } + + return true; + } + + /** + * Matches the contents of a mod ip instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchModIpInstruction(JsonNode instructionJson, + Description description) { + ModIPInstruction instructionToMatch = + (ModIPInstruction) instruction; + final String jsonSubtype = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubtype)) { + description.appendText("subtype was " + jsonSubtype); + return false; + } + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final String jsonIp = instructionJson.get("ip").textValue(); + final String ip = instructionToMatch.ip().toString(); + if (!ip.equals(jsonIp)) { + description.appendText("ip was " + jsonIp); + return false; + } + + return true; + } + + /** + * Matches the contents of a mod IPv6 Flow Label instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchModIPv6FlowLabelInstruction(JsonNode instructionJson, + Description description) { + ModIPv6FlowLabelInstruction instructionToMatch = + (ModIPv6FlowLabelInstruction) instruction; + final String jsonSubtype = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubtype)) { + description.appendText("subtype was " + jsonSubtype); + return false; + } + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final int jsonFlowLabel = instructionJson.get("flowLabel").intValue(); + final int flowLabel = instructionToMatch.flowLabel(); + if (flowLabel != jsonFlowLabel) { + description.appendText("IPv6 flow label was " + jsonFlowLabel); + return false; + } + + return true; + } + + /** + * Matches the contents of a mod MPLS label instruction. + * + * @param instructionJson JSON instruction to match + * @param description Description object used for recording errors + * @return true if contents match, false otherwise + */ + private boolean matchModMplsLabelInstruction(JsonNode instructionJson, + Description description) { + ModMplsLabelInstruction instructionToMatch = + (ModMplsLabelInstruction) instruction; + final String jsonSubtype = instructionJson.get("subtype").textValue(); + if (!instructionToMatch.subtype().name().equals(jsonSubtype)) { + description.appendText("subtype was " + jsonSubtype); + return false; + } + + final String jsonType = instructionJson.get("type").textValue(); + if (!instructionToMatch.type().name().equals(jsonType)) { + description.appendText("type was " + jsonType); + return false; + } + + final int jsonLabel = instructionJson.get("label").intValue(); + final int label = instructionToMatch.mplsLabel().toInt(); + if (label != jsonLabel) { + description.appendText("MPLS label was " + jsonLabel); + return false; + } + + return true; + } + + @Override + public boolean matchesSafely(JsonNode jsonInstruction, Description description) { + + // check type + final JsonNode jsonTypeNode = jsonInstruction.get("type"); + final String jsonType = jsonTypeNode.textValue(); + final String type = instruction.type().name(); + if (!jsonType.equals(type)) { + description.appendText("type was " + type); + return false; + } + + if (instruction instanceof PushHeaderInstructions) { + return matchPushHeaderInstruction(jsonInstruction, description); + } else if (instruction instanceof DropInstruction) { + return true; + } else if (instruction instanceof OutputInstruction) { + return matchOutputInstruction(jsonInstruction, description); + } else if (instruction instanceof ModLambdaInstruction) { + return matchModLambdaInstruction(jsonInstruction, description); + } else if (instruction instanceof ModOchSignalInstruction) { + return matchModOchSingalInstruction(jsonInstruction, description); + } else if (instruction instanceof ModEtherInstruction) { + return matchModEtherInstruction(jsonInstruction, description); + } else if (instruction instanceof ModVlanIdInstruction) { + return matchModVlanIdInstruction(jsonInstruction, description); + } else if (instruction instanceof ModVlanPcpInstruction) { + return matchModVlanPcpInstruction(jsonInstruction, description); + } else if (instruction instanceof ModIPInstruction) { + return matchModIpInstruction(jsonInstruction, description); + } else if (instruction instanceof ModIPv6FlowLabelInstruction) { + return matchModIPv6FlowLabelInstruction(jsonInstruction, + description); + } else if (instruction instanceof ModMplsLabelInstruction) { + return matchModMplsLabelInstruction(jsonInstruction, description); + } + + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText(instruction.toString()); + } + + /** + * Factory to allocate an instruction matcher. + * + * @param instruction instruction object we are looking for + * @return matcher + */ + public static InstructionJsonMatcher matchesInstruction(Instruction instruction) { + return new InstructionJsonMatcher(instruction); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java new file mode 100644 index 00000000..7cbce4d1 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java @@ -0,0 +1,288 @@ +/* + * Copyright 2015 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.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.util.Bandwidth; +import org.onosproject.codec.JsonCodec; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.GridType; +import org.onosproject.net.HostId; +import org.onosproject.net.IndexedLambda; +import org.onosproject.net.Lambda; +import org.onosproject.net.NetTestTools; +import org.onosproject.net.OchSignalType; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.intent.AbstractIntentTest; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.HostToHostIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.IntentServiceAdapter; +import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.net.intent.constraint.AnnotationConstraint; +import org.onosproject.net.intent.constraint.AsymmetricPathConstraint; +import org.onosproject.net.intent.constraint.BandwidthConstraint; +import org.onosproject.net.intent.constraint.LambdaConstraint; +import org.onosproject.net.intent.constraint.LatencyConstraint; +import org.onosproject.net.intent.constraint.ObstacleConstraint; +import org.onosproject.net.intent.constraint.WaypointConstraint; +import org.onosproject.net.resource.link.BandwidthResource; +import org.onosproject.net.resource.link.LambdaResource; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableList; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.onosproject.codec.impl.IntentJsonMatcher.matchesIntent; +import static org.onosproject.net.NetTestTools.did; +import static org.onosproject.net.NetTestTools.hid; +import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction; + +/** + * Unit tests for the host to host intent class codec. + */ +public class IntentCodecTest extends AbstractIntentTest { + + private final HostId id1 = hid("12:34:56:78:91:ab/1"); + private final HostId id2 = hid("12:34:56:78:92:ab/1"); + private final ApplicationId appId = new DefaultApplicationId(3, "test"); + final TrafficSelector emptySelector = + DefaultTrafficSelector.emptySelector(); + final TrafficTreatment emptyTreatment = + DefaultTrafficTreatment.emptyTreatment(); + private final MockCodecContext context = new MockCodecContext(); + final CoreService mockCoreService = createMock(CoreService.class); + + @Before + public void setUpIntentService() { + final IntentService mockIntentService = new IntentServiceAdapter(); + context.registerService(IntentService.class, mockIntentService); + context.registerService(CoreService.class, mockCoreService); + expect(mockCoreService.getAppId(appId.name())) + .andReturn(appId); + replay(mockCoreService); + } + + /** + * Tests the encoding of a host to host intent. + */ + @Test + public void hostToHostIntent() { + final HostToHostIntent intent = + HostToHostIntent.builder() + .appId(appId) + .one(id1) + .two(id2) + .build(); + + final JsonCodec intentCodec = + context.codec(HostToHostIntent.class); + assertThat(intentCodec, notNullValue()); + + final ObjectNode intentJson = intentCodec.encode(intent, context); + assertThat(intentJson, matchesIntent(intent)); + } + + /** + * Tests the encoding of a point to point intent. + */ + @Test + public void pointToPointIntent() { + ConnectPoint ingress = NetTestTools.connectPoint("ingress", 1); + ConnectPoint egress = NetTestTools.connectPoint("egress", 2); + + final PointToPointIntent intent = + PointToPointIntent.builder() + .appId(appId) + .selector(emptySelector) + .treatment(emptyTreatment) + .ingressPoint(ingress) + .egressPoint(egress).build(); + + final JsonCodec intentCodec = + context.codec(PointToPointIntent.class); + assertThat(intentCodec, notNullValue()); + + final ObjectNode intentJson = intentCodec.encode(intent, context); + assertThat(intentJson, matchesIntent(intent)); + } + + /** + * Tests the encoding of an intent with treatment, selector and constraints + * specified. + */ + @Test + public void intentWithTreatmentSelectorAndConstraints() { + ConnectPoint ingress = NetTestTools.connectPoint("ingress", 1); + ConnectPoint egress = NetTestTools.connectPoint("egress", 2); + DeviceId did1 = did("device1"); + DeviceId did2 = did("device2"); + DeviceId did3 = did("device3"); + Lambda ochSignal = Lambda.ochSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, 4, 8); + final TrafficSelector selector = DefaultTrafficSelector.builder() + .matchIPProtocol((byte) 3) + .matchMplsLabel(MplsLabel.mplsLabel(4)) + .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID)) + .add(Criteria.matchLambda(ochSignal)) + .matchEthDst(MacAddress.BROADCAST) + .matchIPDst(IpPrefix.valueOf("1.2.3.4/24")) + .build(); + final TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .add(Instructions.modL0Lambda(new IndexedLambda(33))) + .setMpls(MplsLabel.mplsLabel(44)) + .setOutput(PortNumber.CONTROLLER) + .setEthDst(MacAddress.BROADCAST) + .build(); + + final List constraints = + ImmutableList.of( + new BandwidthConstraint(new BandwidthResource(Bandwidth.bps(1.0))), + new LambdaConstraint(LambdaResource.valueOf(3)), + new AnnotationConstraint("key", 33.0), + new AsymmetricPathConstraint(), + new LatencyConstraint(Duration.ofSeconds(2)), + new ObstacleConstraint(did1, did2), + new WaypointConstraint(did3)); + + final PointToPointIntent intent = + PointToPointIntent.builder() + .appId(appId) + .selector(selector) + .treatment(treatment) + .ingressPoint(ingress) + .egressPoint(egress) + .constraints(constraints) + .build(); + + + final JsonCodec intentCodec = + context.codec(PointToPointIntent.class); + assertThat(intentCodec, notNullValue()); + + final ObjectNode intentJson = intentCodec.encode(intent, context); + assertThat(intentJson, matchesIntent(intent)); + + } + + /** + * Reads in a rule from the given resource and decodes it. + * + * @param resourceName resource to use to read the JSON for the rule + * @return decoded flow rule + * @throws IOException if processing the resource fails + */ + private Intent getIntent(String resourceName, JsonCodec intentCodec) throws IOException { + InputStream jsonStream = FlowRuleCodecTest.class + .getResourceAsStream(resourceName); + JsonNode json = context.mapper().readTree(jsonStream); + assertThat(json, notNullValue()); + Intent intent = (Intent) intentCodec.decode((ObjectNode) json, context); + assertThat(intent, notNullValue()); + return intent; + } + + /** + * Tests the point to point intent JSON codec. + * + * @throws IOException if JSON processing fails + */ + @Test + public void decodePointToPointIntent() throws IOException { + JsonCodec intentCodec = context.codec(Intent.class); + assertThat(intentCodec, notNullValue()); + + Intent intent = getIntent("PointToPointIntent.json", intentCodec); + assertThat(intent, notNullValue()); + assertThat(intent, instanceOf(PointToPointIntent.class)); + + PointToPointIntent pointIntent = (PointToPointIntent) intent; + assertThat(pointIntent.priority(), is(55)); + assertThat(pointIntent.ingressPoint().deviceId(), is(did("0000000000000001"))); + assertThat(pointIntent.ingressPoint().port(), is(PortNumber.portNumber(1))); + assertThat(pointIntent.egressPoint().deviceId(), is(did("0000000000000007"))); + assertThat(pointIntent.egressPoint().port(), is(PortNumber.portNumber(2))); + + assertThat(pointIntent.constraints(), hasSize(1)); + + assertThat(pointIntent.selector(), notNullValue()); + assertThat(pointIntent.selector().criteria(), hasSize(1)); + Criterion criterion1 = pointIntent.selector().criteria().iterator().next(); + assertThat(criterion1, instanceOf(EthCriterion.class)); + EthCriterion ethCriterion = (EthCriterion) criterion1; + assertThat(ethCriterion.mac().toString(), is("11:22:33:44:55:66")); + assertThat(ethCriterion.type().name(), is("ETH_DST")); + + assertThat(pointIntent.treatment(), notNullValue()); + assertThat(pointIntent.treatment().allInstructions(), hasSize(1)); + Instruction instruction1 = pointIntent.treatment().allInstructions().iterator().next(); + assertThat(instruction1, instanceOf(ModEtherInstruction.class)); + ModEtherInstruction ethInstruction = (ModEtherInstruction) instruction1; + assertThat(ethInstruction.mac().toString(), is("22:33:44:55:66:77")); + assertThat(ethInstruction.type().toString(), is("L2MODIFICATION")); + assertThat(ethInstruction.subtype().toString(), is("ETH_SRC")); + } + + /** + * Tests the host to host intent JSON codec. + * + * @throws IOException + */ + @Test + public void decodeHostToHostIntent() throws IOException { + JsonCodec intentCodec = context.codec(Intent.class); + assertThat(intentCodec, notNullValue()); + + Intent intent = getIntent("HostToHostIntent.json", intentCodec); + assertThat(intent, notNullValue()); + assertThat(intent, instanceOf(HostToHostIntent.class)); + + HostToHostIntent hostIntent = (HostToHostIntent) intent; + assertThat(hostIntent.priority(), is(7)); + assertThat(hostIntent.constraints(), hasSize(1)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java new file mode 100644 index 00000000..e485a5fa --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java @@ -0,0 +1,512 @@ +/* + * Copyright 2015 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.LambdaConstraint; +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 { + + 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().toDouble(); + } + + /** + * Matches a lamdba constraint against a JSON representation of the + * constraint. + * + * @param lambdaConstraint constraint object to match + * @param constraintJson JSON representation of the constraint + * @return true if the constraint and JSON match, false otherwise. + */ + private boolean matchLambdaConstraint(LambdaConstraint lambdaConstraint, + JsonNode constraintJson) { + final JsonNode lambdaJson = constraintJson.get("lambda"); + return lambdaJson != null + && constraintJson.get("lambda").asInt() + == lambdaConstraint.lambda().toInt(); + } + + /** + * 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 LambdaConstraint) { + return matchLambdaConstraint((LambdaConstraint) 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 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 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); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java new file mode 100644 index 00000000..67c2f47f --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015 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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.onosproject.net.DeviceId.deviceId; + +import org.onlab.packet.ChassisId; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.provider.ProviderId; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * JsonCodec test utilities. + */ +public abstract class JsonCodecUtils { + + /** + * Checks if given Object can be encoded to JSON and back. + * + * @param context CodecContext + * @param codec JsonCodec + * @param pojoIn Java Object to encode. + * Object is expected to have #equals implemented. + */ + public static void assertJsonEncodable(final CodecContext context, + final JsonCodec codec, + final T pojoIn) { + final ObjectNode json = codec.encode(pojoIn, context); + + assertThat(json, is(notNullValue())); + + final T pojoOut = codec.decode(json, context); + assertThat(pojoOut, is(notNullValue())); + + assertEquals(pojoIn, pojoOut); + } + + static final ProviderId PID = new ProviderId("of", "foo"); + static final ProviderId PIDA = new ProviderId("of", "bar", true); + static final DeviceId DID1 = deviceId("of:foo"); + static final DeviceId DID2 = deviceId("of:bar"); + static final String MFR = "whitebox"; + static final String HW = "1.1.x"; + static final String SW1 = "3.8.1"; + static final String SW2 = "3.9.5"; + static final String SN = "43311-12345"; + static final ChassisId CID = new ChassisId(); + static final PortNumber P1 = PortNumber.portNumber(1); + static final PortNumber P2 = PortNumber.portNumber(2); + static final PortNumber P3 = PortNumber.portNumber(3); + static final SparseAnnotations A1 = DefaultAnnotations.builder() + .set("A1", "a1") + .set("B1", "b1") + .build(); + static final ConnectPoint CP1 = new ConnectPoint(DID1, P1); + static final ConnectPoint CP2 = new ConnectPoint(DID2, P2); + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java new file mode 100644 index 00000000..c44b0eb1 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable; + +import org.junit.Test; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.Link; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.device.DeviceServiceAdapter; + +/** + * Unit test for LinkCodec. + */ +public class LinkCodecTest { + + private final Link link = new DefaultLink(JsonCodecUtils.PID, + JsonCodecUtils.CP1, + JsonCodecUtils.CP2, + Link.Type.DIRECT, + Link.State.ACTIVE, + false, + JsonCodecUtils.A1); + + @Test + public void linkCodecTest() { + final MockCodecContext context = new MockCodecContext(); + context.registerService(DeviceService.class, new DeviceServiceAdapter()); + final JsonCodec codec = context.codec(Link.class); + assertThat(codec, is(notNullValue())); + final Link pojoIn = link; + + assertJsonEncodable(context, codec, pojoIn); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java new file mode 100644 index 00000000..4cb2916e --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 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 org.junit.Test; +import org.onosproject.net.statistic.DefaultLoad; +import org.onosproject.net.statistic.Load; + +import com.fasterxml.jackson.databind.JsonNode; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; + +/** + * Unit tests for Load codec. + */ +public class LoadCodecTest { + + /** + * Tests encoding of a Load object. + */ + @Test + public void testLoadEncode() { + final long startTime = System.currentTimeMillis(); + final Load load = new DefaultLoad(20, 10, 1); + final JsonNode node = new LoadCodec() + .encode(load, new MockCodecContext()); + assertThat(node.get("valid").asBoolean(), is(true)); + assertThat(node.get("latest").asLong(), is(20L)); + assertThat(node.get("rate").asLong(), is(10L)); + assertThat(node.get("time").asLong(), greaterThanOrEqualTo(startTime)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java new file mode 100644 index 00000000..6a9b6708 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015 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.HashMap; +import java.util.Map; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Mock codec context for use in codec unit tests. + */ +public class MockCodecContext implements CodecContext { + + private final ObjectMapper mapper = new ObjectMapper(); + private final CodecManager manager = new CodecManager(); + private final Map, Object> services = new HashMap<>(); + + /** + * Constructs a new mock codec context. + */ + public MockCodecContext() { + manager.activate(); + } + + @Override + public ObjectMapper mapper() { + return mapper; + } + + @Override + @SuppressWarnings("unchecked") + public JsonCodec codec(Class entityClass) { + return manager.getCodec(entityClass); + } + + @SuppressWarnings("unchecked") + @Override + public T getService(Class serviceClass) { + return (T) services.get(serviceClass); + } + + // for registering mock services + public void registerService(Class serviceClass, T impl) { + services.put(serviceClass, impl); + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java new file mode 100644 index 00000000..f3f7d920 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015 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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable; + +import org.junit.Test; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.Port; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.device.DeviceServiceAdapter; + +/** + * Unit test for PortCodec. + */ +public class PortCodecTest { + + + + private final Device device = new DefaultDevice(JsonCodecUtils.PID, + JsonCodecUtils.DID1, + Device.Type.SWITCH, + JsonCodecUtils.MFR, + JsonCodecUtils.HW, + JsonCodecUtils.SW1, + JsonCodecUtils.SN, + JsonCodecUtils.CID, + JsonCodecUtils.A1); + + private final Port port = new DefaultPort(device, + JsonCodecUtils.P1, + true, + JsonCodecUtils.A1); + + @Test + public void portCodecTest() { + final MockCodecContext context = new MockCodecContext(); + context.registerService(DeviceService.class, new DeviceServiceAdapter()); + final JsonCodec codec = context.codec(Port.class); + assertThat(codec, is(notNullValue())); + final Port pojoIn = port; + + assertJsonEncodable(context, codec, pojoIn); + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java new file mode 100644 index 00000000..4d435cfe --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2014-2015 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.common; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.PortNumber; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.ClusterId; +import org.onosproject.net.topology.DefaultGraphDescription; +import org.onosproject.net.topology.GraphDescription; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.TopologyCluster; + +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.Assert.*; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.PortNumber.portNumber; + +/** + * Test of the default topology implementation. + */ +public class DefaultTopologyTest { + + public static final ProviderId PID = new ProviderId("of", "foo.bar"); + + public static final DeviceId D1 = deviceId("of:1"); + public static final DeviceId D2 = deviceId("of:2"); + public static final DeviceId D3 = deviceId("of:3"); + public static final DeviceId D4 = deviceId("of:4"); + public static final DeviceId D5 = deviceId("of:5"); + + public static final PortNumber P1 = portNumber(1); + public static final PortNumber P2 = portNumber(2); + + public static final LinkWeight WEIGHT = edge -> + edge.src().deviceId().equals(D4) || edge.dst().deviceId().equals(D4) + ? 2.0 : 1.0; + + private DefaultTopology dt; + + @Before + public void setUp() { + long now = System.currentTimeMillis(); + Set devices = of(device("1"), device("2"), + device("3"), device("4"), + device("5")); + Set links = of(link("1", 1, "2", 1), link("2", 1, "1", 1), + link("3", 2, "2", 2), link("2", 2, "3", 2), + link("1", 3, "4", 3), link("4", 3, "1", 3), + link("3", 4, "4", 4), link("4", 4, "3", 4)); + GraphDescription graphDescription = + new DefaultGraphDescription(now, devices, links); + + dt = new DefaultTopology(PID, graphDescription); + assertEquals("incorrect supplier", PID, dt.providerId()); + assertEquals("incorrect time", now, dt.time()); + assertEquals("incorrect device count", 5, dt.deviceCount()); + assertEquals("incorrect link count", 8, dt.linkCount()); + assertEquals("incorrect cluster count", 2, dt.clusterCount()); + assertEquals("incorrect broadcast set size", 6, + dt.broadcastSetSize(ClusterId.clusterId(0))); + } + + @Test + public void pathRelated() { + Set paths = dt.getPaths(D1, D2); + assertEquals("incorrect path count", 1, paths.size()); + + paths = dt.getPaths(D1, D3); + assertEquals("incorrect path count", 2, paths.size()); + + paths = dt.getPaths(D1, D5); + assertTrue("no paths expected", paths.isEmpty()); + + paths = dt.getPaths(D1, D3, WEIGHT); + assertEquals("incorrect path count", 1, paths.size()); + } + + @Test + public void pointRelated() { + assertTrue("should be infrastructure point", + dt.isInfrastructure(new ConnectPoint(D1, P1))); + assertFalse("should not be infrastructure point", + dt.isInfrastructure(new ConnectPoint(D1, P2))); + } + + @Test + public void clusterRelated() { + Set clusters = dt.getClusters(); + assertEquals("incorrect cluster count", 2, clusters.size()); + + TopologyCluster c = dt.getCluster(D1); + Set devs = dt.getClusterDevices(c); + assertEquals("incorrect cluster device count", 4, devs.size()); + assertTrue("cluster should contain D2", devs.contains(D2)); + assertFalse("cluster should not contain D5", devs.contains(D5)); + } + + // Short-hand for creating a link. + public static Link link(String src, int sp, String dst, int dp) { + return new DefaultLink(PID, new ConnectPoint(did(src), portNumber(sp)), + new ConnectPoint(did(dst), portNumber(dp)), + Link.Type.DIRECT); + } + + // Crates a new device with the specified id + public static Device device(String id) { + return new DefaultDevice(PID, did(id), Device.Type.SWITCH, + "mfg", "1.0", "1.1", "1234", new ChassisId()); + } + + // Short-hand for producing a device id from a string + public static DeviceId did(String id) { + return deviceId("of:" + id); + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java new file mode 100644 index 00000000..97012c4e --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2015 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.common.app; + +import com.google.common.collect.ImmutableSet; +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.util.Tools; +import org.onosproject.app.ApplicationDescription; +import org.onosproject.app.ApplicationException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.onosproject.app.DefaultApplicationDescriptionTest.*; + +/** + * Suite of tests for the application archive utility. + */ +public class ApplicationArchiveTest { + + static final File STORE = Files.createTempDir(); + + private ApplicationArchive aar = new ApplicationArchive(); + + @Before + public void setUp() { + aar.setRootPath(STORE.getAbsolutePath()); + } + + @After + public void tearDown() throws IOException { + if (STORE.exists()) { + Tools.removeDirectory(STORE); + } + } + + private void validate(ApplicationDescription app) { + assertEquals("incorrect name", APP_NAME, app.name()); + assertEquals("incorrect version", VER, app.version()); + assertEquals("incorrect origin", ORIGIN, app.origin()); + assertEquals("incorrect role", ROLE, app.role()); + + assertEquals("incorrect description", DESC, app.description()); + assertEquals("incorrect features URI", FURL, app.featuresRepo().get()); + assertEquals("incorrect permissions", PERMS, app.permissions()); + assertEquals("incorrect features", FEATURES, app.features()); + } + + @Test + public void saveZippedApp() throws IOException { + InputStream stream = getClass().getResourceAsStream("app.zip"); + ApplicationDescription app = aar.saveApplication(stream); + validate(app); + stream.close(); + } + + @Test + public void savePlainApp() throws IOException { + InputStream stream = getClass().getResourceAsStream("app.xml"); + ApplicationDescription app = aar.saveApplication(stream); + validate(app); + stream.close(); + } + + @Test + public void loadApp() throws IOException { + saveZippedApp(); + ApplicationDescription app = aar.getApplicationDescription(APP_NAME); + validate(app); + } + + @Test + public void getAppNames() throws IOException { + saveZippedApp(); + Set names = aar.getApplicationNames(); + assertEquals("incorrect names", ImmutableSet.of(APP_NAME), names); + } + + @Test + public void purgeApp() throws IOException { + saveZippedApp(); + aar.purgeApplication(APP_NAME); + assertEquals("incorrect names", ImmutableSet.of(), + aar.getApplicationNames()); + } + + @Test + public void getAppZipStream() throws IOException { + saveZippedApp(); + InputStream stream = aar.getApplicationInputStream(APP_NAME); + byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.zip")); + byte[] loaded = ByteStreams.toByteArray(stream); + assertArrayEquals("incorrect stream", orig, loaded); + stream.close(); + } + + @Test + public void getAppXmlStream() throws IOException { + savePlainApp(); + InputStream stream = aar.getApplicationInputStream(APP_NAME); + byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.xml")); + byte[] loaded = ByteStreams.toByteArray(stream); + assertArrayEquals("incorrect stream", orig, loaded); + stream.close(); + } + + @Test + public void active() throws IOException { + savePlainApp(); + assertFalse("should not be active", aar.isActive(APP_NAME)); + aar.setActive(APP_NAME); + assertTrue("should not be active", aar.isActive(APP_NAME)); + aar.clearActive(APP_NAME); + assertFalse("should not be active", aar.isActive(APP_NAME)); + } + + @Test(expected = ApplicationException.class) + public void getBadAppDesc() throws IOException { + aar.getApplicationDescription("org.foo.BAD"); + } + + @Test(expected = ApplicationException.class) + public void getBadAppStream() throws IOException { + aar.getApplicationInputStream("org.foo.BAD"); + } + + @Test(expected = ApplicationException.class) + public void setBadActive() throws IOException { + aar.setActive("org.foo.BAD"); + } + + @Test // (expected = ApplicationException.class) + public void purgeBadApp() throws IOException { + aar.purgeApplication("org.foo.BAD"); + } + +} \ No newline at end of file diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java b/framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java new file mode 100644 index 00000000..4ea371a0 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014-2015 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.common.event.impl; + +import org.onosproject.event.DefaultEventSinkRegistry; +import org.onosproject.event.Event; +import org.onosproject.event.EventDeliveryService; +import org.onosproject.event.EventSink; + +import static com.google.common.base.Preconditions.checkState; + +/** + * Implements event delivery system that delivers events synchronously, or + * in-line with the post method invocation. + */ +public class TestEventDispatcher extends DefaultEventSinkRegistry + implements EventDeliveryService { + + @Override + @SuppressWarnings("unchecked") + public synchronized void post(Event event) { + EventSink sink = getSink(event.getClass()); + checkState(sink != null, "No sink for event %s", event); + sink.process(event); + } + + @Override + public void setDispatchTimeLimit(long millis) { + } + + @Override + public long getDispatchTimeLimit() { + return 0; + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java new file mode 100644 index 00000000..00d6c9d2 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 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.store.trivial; + +import org.onosproject.net.DeviceId; + +import java.util.Objects; + +/** + * Key for filing pre-computed paths between source and destination devices. + */ +class PathKey { + private final DeviceId src; + private final DeviceId dst; + + /** + * Creates a path key from the given source/dest pair. + * @param src source device + * @param dst destination device + */ + PathKey(DeviceId src, DeviceId dst) { + this.src = src; + this.dst = dst; + } + + @Override + public int hashCode() { + return Objects.hash(src, dst); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof PathKey) { + final PathKey other = (PathKey) obj; + return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst); + } + return false; + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java new file mode 100644 index 00000000..6e6b9587 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import com.google.common.collect.ImmutableSet; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.ApplicationIdStore; +import org.onosproject.core.DefaultApplicationId; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Simple implementation of the application ID registry using in-memory + * structures. + */ +@Component(immediate = true) +@Service +public class SimpleApplicationIdStore implements ApplicationIdStore { + + private static final AtomicInteger ID_DISPENSER = new AtomicInteger(1); + + private final Map appIds = new ConcurrentHashMap<>(); + private final Map appIdsByName = new ConcurrentHashMap<>(); + + @Override + public Set getAppIds() { + return ImmutableSet.copyOf(appIds.values()); + } + + @Override + public ApplicationId getAppId(Short id) { + return appIds.get(id); + } + + @Override + public ApplicationId getAppId(String name) { + return appIdsByName.get(name); + } + + @Override + public ApplicationId registerApplication(String name) { + DefaultApplicationId appId = appIdsByName.get(name); + if (appId == null) { + short id = (short) ID_DISPENSER.getAndIncrement(); + appId = new DefaultApplicationId(id, name); + appIds.put(id, appId); + appIdsByName.put(name, appId); + } + return appId; + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java new file mode 100644 index 00000000..ea9a773e --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java @@ -0,0 +1,170 @@ +/* + * Copyright 2015 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.store.trivial; + +import com.google.common.collect.ImmutableSet; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.app.ApplicationDescription; +import org.onosproject.app.ApplicationEvent; +import org.onosproject.app.ApplicationState; +import org.onosproject.app.ApplicationStore; +import org.onosproject.common.app.ApplicationArchive; +import org.onosproject.core.Application; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.ApplicationIdStore; +import org.onosproject.core.DefaultApplication; +import org.onosproject.security.Permission; +import org.slf4j.Logger; + +import java.io.InputStream; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static org.onosproject.app.ApplicationEvent.Type.*; +import static org.onosproject.app.ApplicationState.ACTIVE; +import static org.onosproject.app.ApplicationState.INSTALLED; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Manages inventory of network control applications. + */ +@Component(immediate = true) +@Service +public class SimpleApplicationStore extends ApplicationArchive implements ApplicationStore { + + private final Logger log = getLogger(getClass()); + + // App inventory & states + private final ConcurrentMap apps = new ConcurrentHashMap<>(); + private final ConcurrentMap states = new ConcurrentHashMap<>(); + private final ConcurrentMap> permissions = new ConcurrentHashMap<>(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ApplicationIdStore idStore; + + @Activate + public void activate() { + loadFromDisk(); + log.info("Started"); + } + + private void loadFromDisk() { + for (String name : getApplicationNames()) { + ApplicationId appId = idStore.registerApplication(name); + ApplicationDescription appDesc = getApplicationDescription(name); + DefaultApplication app = + new DefaultApplication(appId, appDesc.version(), + appDesc.description(), appDesc.origin(), + appDesc.role(), appDesc.permissions(), + appDesc.featuresRepo(), appDesc.features()); + apps.put(appId, app); + states.put(appId, isActive(name) ? INSTALLED : ACTIVE); + // load app permissions + } + } + + @Deactivate + public void deactivate() { + apps.clear(); + states.clear(); + permissions.clear(); + log.info("Stopped"); + } + + @Override + public Set getApplications() { + return ImmutableSet.copyOf(apps.values()); + } + + @Override + public ApplicationId getId(String name) { + return idStore.getAppId(name); + } + + @Override + public Application getApplication(ApplicationId appId) { + return apps.get(appId); + } + + @Override + public ApplicationState getState(ApplicationId appId) { + return states.get(appId); + } + + @Override + public Application create(InputStream appDescStream) { + ApplicationDescription appDesc = saveApplication(appDescStream); + ApplicationId appId = idStore.registerApplication(appDesc.name()); + DefaultApplication app = + new DefaultApplication(appId, appDesc.version(), appDesc.description(), + appDesc.origin(), appDesc.role(), appDesc.permissions(), + appDesc.featuresRepo(), appDesc.features()); + apps.put(appId, app); + states.put(appId, INSTALLED); + delegate.notify(new ApplicationEvent(APP_INSTALLED, app)); + return app; + } + + @Override + public void remove(ApplicationId appId) { + Application app = apps.remove(appId); + if (app != null) { + states.remove(appId); + delegate.notify(new ApplicationEvent(APP_UNINSTALLED, app)); + purgeApplication(app.id().name()); + } + } + + @Override + public void activate(ApplicationId appId) { + Application app = apps.get(appId); + if (app != null) { + setActive(appId.name()); + states.put(appId, ACTIVE); + delegate.notify(new ApplicationEvent(APP_ACTIVATED, app)); + } + } + + @Override + public void deactivate(ApplicationId appId) { + Application app = apps.get(appId); + if (app != null) { + clearActive(appId.name()); + states.put(appId, INSTALLED); + delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app)); + } + } + + @Override + public Set getPermissions(ApplicationId appId) { + return permissions.get(appId); + } + + @Override + public void setPermissions(ApplicationId appId, Set permissions) { + Application app = getApplication(appId); + if (app != null) { + this.permissions.put(appId, permissions); + delegate.notify(new ApplicationEvent(APP_PERMISSIONS_CHANGED, app)); + } + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java new file mode 100644 index 00000000..a1c7da37 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2015 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.store.trivial; + +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Files; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.util.Tools; +import org.onosproject.app.ApplicationEvent; +import org.onosproject.app.ApplicationStoreDelegate; +import org.onosproject.common.app.ApplicationArchive; +import org.onosproject.core.Application; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.ApplicationIdStoreAdapter; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.security.AppPermission; +import org.onosproject.security.Permission; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.onosproject.app.ApplicationEvent.Type.*; +import static org.onosproject.app.ApplicationState.ACTIVE; +import static org.onosproject.app.ApplicationState.INSTALLED; + +/** + * Test of the trivial application store implementation. + */ +public class SimpleApplicationStoreTest { + + static final File STORE = Files.createTempDir(); + + private TestApplicationStore store = new TestApplicationStore(); + private TestDelegate delegate = new TestDelegate(); + private static final Object LOCK = new Object(); + + @Before + public void setUp() { + store.idStore = new TestIdStore(); + store.setRootPath(STORE.getAbsolutePath()); + store.setDelegate(delegate); + store.activate(); + } + + @After + public void tearDown() throws IOException { + if (STORE.exists()) { + Tools.removeDirectory(STORE); + } + store.deactivate(); + } + + private Application createTestApp() { + synchronized (LOCK) { + return store.create(ApplicationArchive.class.getResourceAsStream("app.zip")); + } + } + + @Test + public void create() { + Application app = createTestApp(); + assertEquals("incorrect name", "org.foo.app", app.id().name()); + assertEquals("incorrect app count", 1, store.getApplications().size()); + assertEquals("incorrect app", app, store.getApplication(app.id())); + assertEquals("incorrect app state", INSTALLED, store.getState(app.id())); + assertEquals("incorrect event type", APP_INSTALLED, delegate.event.type()); + assertEquals("incorrect event app", app, delegate.event.subject()); + } + + @Test + public void remove() { + Application app = createTestApp(); + store.remove(app.id()); + assertEquals("incorrect app count", 0, store.getApplications().size()); + assertEquals("incorrect event type", APP_UNINSTALLED, delegate.event.type()); + assertEquals("incorrect event app", app, delegate.event.subject()); + } + + @Test + public void activate() { + Application app = createTestApp(); + store.activate(app.id()); + assertEquals("incorrect app count", 1, store.getApplications().size()); + assertEquals("incorrect app state", ACTIVE, store.getState(app.id())); + assertEquals("incorrect event type", APP_ACTIVATED, delegate.event.type()); + assertEquals("incorrect event app", app, delegate.event.subject()); + } + + @Test + public void deactivate() { + Application app = createTestApp(); + store.deactivate(app.id()); + assertEquals("incorrect app count", 1, store.getApplications().size()); + assertEquals("incorrect app state", INSTALLED, store.getState(app.id())); + assertEquals("incorrect event type", APP_DEACTIVATED, delegate.event.type()); + assertEquals("incorrect event app", app, delegate.event.subject()); + } + + @Test + public void permissions() { + Application app = createTestApp(); + ImmutableSet permissions = + ImmutableSet.of(new Permission(AppPermission.class.getName(), "FLOWRULE_WRITE")); + store.setPermissions(app.id(), permissions); + assertEquals("incorrect app perms", 1, store.getPermissions(app.id()).size()); + assertEquals("incorrect app state", INSTALLED, store.getState(app.id())); + assertEquals("incorrect event type", APP_PERMISSIONS_CHANGED, delegate.event.type()); + assertEquals("incorrect event app", app, delegate.event.subject()); + } + + private class TestIdStore extends ApplicationIdStoreAdapter { + @Override + public ApplicationId registerApplication(String name) { + return new DefaultApplicationId(1, name); + } + + @Override + public ApplicationId getAppId(String name) { + return new DefaultApplicationId(1, name); + } + } + + private class TestDelegate implements ApplicationStoreDelegate { + private ApplicationEvent event; + + @Override + public void notify(ApplicationEvent event) { + this.event = event; + } + } + + private class TestApplicationStore extends SimpleApplicationStore { + @Override + public void setRootPath(String root) { + super.setRootPath(root); + } + } +} \ No newline at end of file diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java new file mode 100644 index 00000000..5eea3cc8 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java @@ -0,0 +1,139 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import com.google.common.collect.ImmutableSet; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.joda.time.DateTime; +import org.onlab.packet.IpAddress; +import org.onosproject.cluster.ClusterEvent; +import org.onosproject.cluster.ClusterStore; +import org.onosproject.cluster.ClusterStoreDelegate; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.DefaultControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.event.EventDeliveryService; +import org.onosproject.event.ListenerRegistry; +import org.onosproject.net.intent.Key; +import org.onosproject.net.intent.PartitionEvent; +import org.onosproject.net.intent.PartitionEventListener; +import org.onosproject.net.intent.PartitionService; +import org.onosproject.store.AbstractStore; +import org.slf4j.Logger; + +import java.util.Set; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Manages inventory of infrastructure devices using trivial in-memory + * structures implementation. + */ +@Component(immediate = true) +@Service +public class SimpleClusterStore + extends AbstractStore + implements ClusterStore, PartitionService { + + public static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1"); + + private final Logger log = getLogger(getClass()); + + private ControllerNode instance; + + private final DateTime creationTime = DateTime.now(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected EventDeliveryService eventDispatcher; + + private ListenerRegistry listenerRegistry; + + @Activate + public void activate() { + instance = new DefaultControllerNode(new NodeId("local"), LOCALHOST); + + listenerRegistry = new ListenerRegistry<>(); + eventDispatcher.addSink(PartitionEvent.class, listenerRegistry); + + log.info("Started"); + } + + @Deactivate + public void deactivate() { + eventDispatcher.removeSink(PartitionEvent.class); + log.info("Stopped"); + } + + + @Override + public ControllerNode getLocalNode() { + return instance; + } + + @Override + public Set getNodes() { + return ImmutableSet.of(instance); + } + + @Override + public ControllerNode getNode(NodeId nodeId) { + return instance.id().equals(nodeId) ? instance : null; + } + + @Override + public ControllerNode.State getState(NodeId nodeId) { + return ControllerNode.State.ACTIVE; + } + + @Override + public DateTime getLastUpdated(NodeId nodeId) { + return creationTime; + } + + @Override + public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) { + return null; + } + + @Override + public void removeNode(NodeId nodeId) { + } + + @Override + public boolean isMine(Key intentKey) { + return true; + } + + @Override + public NodeId getLeader(Key intentKey) { + return instance.id(); + } + + @Override + public void addListener(PartitionEventListener listener) { + listenerRegistry.addListener(listener); + } + + @Override + public void removeListener(PartitionEventListener listener) { + listenerRegistry.removeListener(listener); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java new file mode 100644 index 00000000..1d8bcd62 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015 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.store.trivial; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.cfg.ComponentConfigEvent; +import org.onosproject.cfg.ComponentConfigStore; +import org.onosproject.cfg.ComponentConfigStoreDelegate; +import org.onosproject.store.AbstractStore; +import org.slf4j.Logger; + +import static org.onosproject.cfg.ComponentConfigEvent.Type.PROPERTY_SET; +import static org.onosproject.cfg.ComponentConfigEvent.Type.PROPERTY_UNSET; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Manages inventory of component configuration properties. + */ +@Component(immediate = true) +@Service +public class SimpleComponentConfigStore + extends AbstractStore + implements ComponentConfigStore { + + private final Logger log = getLogger(getClass()); + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + @Override + public void setProperty(String componentName, String name, String value) { + delegate.notify(new ComponentConfigEvent(PROPERTY_SET, componentName, name, value)); + } + + @Override + public void unsetProperty(String componentName, String name) { + delegate.notify(new ComponentConfigEvent(PROPERTY_UNSET, componentName, name, null)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java new file mode 100644 index 00000000..fc90dfad --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java @@ -0,0 +1,691 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.AnnotationsUtil; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.Device.Type; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DefaultPortStatistics; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceStore; +import org.onosproject.net.device.DeviceStoreDelegate; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.AbstractStore; +import org.onlab.packet.ChassisId; +import org.onlab.util.NewConcurrentHashMap; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.notNull; +import static com.google.common.base.Verify.verify; +import static org.onosproject.net.device.DeviceEvent.Type.*; +import static org.slf4j.LoggerFactory.getLogger; +import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked; +import static org.onosproject.net.DefaultAnnotations.union; +import static org.onosproject.net.DefaultAnnotations.merge; + +/** + * Manages inventory of infrastructure devices using trivial in-memory + * structures implementation. + */ +@Component(immediate = true) +@Service +public class SimpleDeviceStore + extends AbstractStore + implements DeviceStore { + + private final Logger log = getLogger(getClass()); + + public static final String DEVICE_NOT_FOUND = "Device with ID %s not found"; + + // Collection of Description given from various providers + private final ConcurrentMap> + deviceDescs = Maps.newConcurrentMap(); + + // Cache of Device and Ports generated by compositing descriptions from providers + private final ConcurrentMap devices = Maps.newConcurrentMap(); + private final ConcurrentMap> + devicePorts = Maps.newConcurrentMap(); + private final ConcurrentMap> + devicePortStats = Maps.newConcurrentMap(); + private final ConcurrentMap> + devicePortDeltaStats = Maps.newConcurrentMap(); + + // Available (=UP) devices + private final Set availableDevices = Sets.newConcurrentHashSet(); + + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + deviceDescs.clear(); + devices.clear(); + devicePorts.clear(); + availableDevices.clear(); + log.info("Stopped"); + } + + @Override + public int getDeviceCount() { + return devices.size(); + } + + @Override + public Iterable getDevices() { + return Collections.unmodifiableCollection(devices.values()); + } + + @Override + public Iterable getAvailableDevices() { + return FluentIterable.from(getDevices()) + .filter(new Predicate() { + + @Override + public boolean apply(Device input) { + return isAvailable(input.id()); + } + }); + } + + @Override + public Device getDevice(DeviceId deviceId) { + return devices.get(deviceId); + } + + @Override + public DeviceEvent createOrUpdateDevice(ProviderId providerId, + DeviceId deviceId, + DeviceDescription deviceDescription) { + Map providerDescs + = getOrCreateDeviceDescriptions(deviceId); + + synchronized (providerDescs) { + // locking per device + DeviceDescriptions descs + = getOrCreateProviderDeviceDescriptions(providerDescs, + providerId, + deviceDescription); + + Device oldDevice = devices.get(deviceId); + // update description + descs.putDeviceDesc(deviceDescription); + Device newDevice = composeDevice(deviceId, providerDescs); + + if (oldDevice == null) { + // ADD + return createDevice(providerId, newDevice); + } else { + // UPDATE or ignore (no change or stale) + return updateDevice(providerId, oldDevice, newDevice); + } + } + } + + // Creates the device and returns the appropriate event if necessary. + // Guarded by deviceDescs value (=Device lock) + private DeviceEvent createDevice(ProviderId providerId, Device newDevice) { + // update composed device cache + Device oldDevice = devices.putIfAbsent(newDevice.id(), newDevice); + verify(oldDevice == null, + "Unexpected Device in cache. PID:%s [old=%s, new=%s]", + providerId, oldDevice, newDevice); + + if (!providerId.isAncillary()) { + availableDevices.add(newDevice.id()); + } + + return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, newDevice, null); + } + + // Updates the device and returns the appropriate event if necessary. + // Guarded by deviceDescs value (=Device lock) + private DeviceEvent updateDevice(ProviderId providerId, Device oldDevice, Device newDevice) { + // We allow only certain attributes to trigger update + boolean propertiesChanged = + !Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) || + !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()); + boolean annotationsChanged = + !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations()); + + // Primary providers can respond to all changes, but ancillary ones + // should respond only to annotation changes. + if ((providerId.isAncillary() && annotationsChanged) || + (!providerId.isAncillary() && (propertiesChanged || annotationsChanged))) { + + boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice); + if (!replaced) { + // FIXME: Is the enclosing if required here? + verify(replaced, + "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]", + providerId, oldDevice, devices.get(newDevice.id()) + , newDevice); + } + if (!providerId.isAncillary()) { + availableDevices.add(newDevice.id()); + } + return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null); + } + + // Otherwise merely attempt to change availability if primary provider + if (!providerId.isAncillary()) { + boolean added = availableDevices.add(newDevice.id()); + return !added ? null : + new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null); + } + return null; + } + + @Override + public DeviceEvent markOffline(DeviceId deviceId) { + Map providerDescs + = getOrCreateDeviceDescriptions(deviceId); + + // locking device + synchronized (providerDescs) { + Device device = devices.get(deviceId); + if (device == null) { + return null; + } + boolean removed = availableDevices.remove(deviceId); + if (removed) { + // TODO: broadcast ... DOWN only? + return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null); + } + return null; + } + } + + @Override + public List updatePorts(ProviderId providerId, + DeviceId deviceId, + List portDescriptions) { + Device device = devices.get(deviceId); + if (device == null) { + log.debug("Device {} doesn't exist or hasn't been initialized yet", deviceId); + return Collections.emptyList(); + } + + Map descsMap = deviceDescs.get(deviceId); + checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId); + + List events = new ArrayList<>(); + synchronized (descsMap) { + DeviceDescriptions descs = descsMap.get(providerId); + // every provider must provide DeviceDescription. + checkArgument(descs != null, + "Device description for Device ID %s from Provider %s was not found", + deviceId, providerId); + + Map ports = getPortMap(deviceId); + + // Add new ports + Set processed = new HashSet<>(); + for (PortDescription portDescription : portDescriptions) { + final PortNumber number = portDescription.portNumber(); + processed.add(portDescription.portNumber()); + + final Port oldPort = ports.get(number); + final Port newPort; + +// event suppression hook? + + // update description + descs.putPortDesc(portDescription); + newPort = composePort(device, number, descsMap); + + events.add(oldPort == null ? + createPort(device, newPort, ports) : + updatePort(device, oldPort, newPort, ports)); + } + + events.addAll(pruneOldPorts(device, ports, processed)); + } + return FluentIterable.from(events).filter(notNull()).toList(); + } + + // Creates a new port based on the port description adds it to the map and + // Returns corresponding event. + // Guarded by deviceDescs value (=Device lock) + private DeviceEvent createPort(Device device, Port newPort, + Map ports) { + ports.put(newPort.number(), newPort); + return new DeviceEvent(PORT_ADDED, device, newPort); + } + + // Checks if the specified port requires update and if so, it replaces the + // existing entry in the map and returns corresponding event. + // Guarded by deviceDescs value (=Device lock) + private DeviceEvent updatePort(Device device, Port oldPort, + Port newPort, + Map ports) { + if (oldPort.isEnabled() != newPort.isEnabled() || + oldPort.type() != newPort.type() || + oldPort.portSpeed() != newPort.portSpeed() || + !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) { + ports.put(oldPort.number(), newPort); + return new DeviceEvent(PORT_UPDATED, device, newPort); + } + return null; + } + + // Prunes the specified list of ports based on which ports are in the + // processed list and returns list of corresponding events. + // Guarded by deviceDescs value (=Device lock) + private List pruneOldPorts(Device device, + Map ports, + Set processed) { + List events = new ArrayList<>(); + Iterator> iterator = ports.entrySet().iterator(); + while (iterator.hasNext()) { + Entry e = iterator.next(); + PortNumber portNumber = e.getKey(); + if (!processed.contains(portNumber)) { + events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue())); + iterator.remove(); + } + } + return events; + } + + // Gets the map of ports for the specified device; if one does not already + // exist, it creates and registers a new one. + private ConcurrentMap getPortMap(DeviceId deviceId) { + return createIfAbsentUnchecked(devicePorts, deviceId, + NewConcurrentHashMap.ifNeeded()); + } + + private Map getOrCreateDeviceDescriptions( + DeviceId deviceId) { + Map r; + r = deviceDescs.get(deviceId); + if (r != null) { + return r; + } + r = new HashMap<>(); + final Map concurrentlyAdded; + concurrentlyAdded = deviceDescs.putIfAbsent(deviceId, r); + if (concurrentlyAdded != null) { + return concurrentlyAdded; + } else { + return r; + } + } + + // Guarded by deviceDescs value (=Device lock) + private DeviceDescriptions getOrCreateProviderDeviceDescriptions( + Map device, + ProviderId providerId, DeviceDescription deltaDesc) { + synchronized (device) { + DeviceDescriptions r = device.get(providerId); + if (r == null) { + r = new DeviceDescriptions(deltaDesc); + device.put(providerId, r); + } + return r; + } + } + + @Override + public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId, + PortDescription portDescription) { + Device device = devices.get(deviceId); + checkArgument(device != null, DEVICE_NOT_FOUND, deviceId); + + Map descsMap = deviceDescs.get(deviceId); + checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId); + + synchronized (descsMap) { + DeviceDescriptions descs = descsMap.get(providerId); + // assuming all providers must give DeviceDescription first + checkArgument(descs != null, + "Device description for Device ID %s from Provider %s was not found", + deviceId, providerId); + + ConcurrentMap ports = getPortMap(deviceId); + final PortNumber number = portDescription.portNumber(); + final Port oldPort = ports.get(number); + final Port newPort; + + // update description + descs.putPortDesc(portDescription); + newPort = composePort(device, number, descsMap); + + if (oldPort == null) { + return createPort(device, newPort, ports); + } else { + return updatePort(device, oldPort, newPort, ports); + } + } + } + + @Override + public List getPorts(DeviceId deviceId) { + Map ports = devicePorts.get(deviceId); + if (ports == null) { + return Collections.emptyList(); + } + return ImmutableList.copyOf(ports.values()); + } + + @Override + public DeviceEvent updatePortStatistics(ProviderId providerId, DeviceId deviceId, + Collection newStatsCollection) { + + ConcurrentMap prvStatsMap = devicePortStats.get(deviceId); + ConcurrentMap newStatsMap = Maps.newConcurrentMap(); + ConcurrentMap deltaStatsMap = Maps.newConcurrentMap(); + + if (prvStatsMap != null) { + for (PortStatistics newStats : newStatsCollection) { + PortNumber port = PortNumber.portNumber(newStats.port()); + PortStatistics prvStats = prvStatsMap.get(port); + DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder(); + PortStatistics deltaStats = builder.build(); + if (prvStats != null) { + deltaStats = calcDeltaStats(deviceId, prvStats, newStats); + } + deltaStatsMap.put(port, deltaStats); + newStatsMap.put(port, newStats); + } + } else { + for (PortStatistics newStats : newStatsCollection) { + PortNumber port = PortNumber.portNumber(newStats.port()); + newStatsMap.put(port, newStats); + } + } + devicePortDeltaStats.put(deviceId, deltaStatsMap); + devicePortStats.put(deviceId, newStatsMap); + return new DeviceEvent(PORT_STATS_UPDATED, devices.get(deviceId), null); + } + + public PortStatistics calcDeltaStats(DeviceId deviceId, PortStatistics prvStats, PortStatistics newStats) { + // calculate time difference + long deltaStatsSec, deltaStatsNano; + if (newStats.durationNano() < prvStats.durationNano()) { + deltaStatsNano = newStats.durationNano() - prvStats.durationNano() + TimeUnit.SECONDS.toNanos(1); + deltaStatsSec = newStats.durationSec() - prvStats.durationSec() - 1L; + } else { + deltaStatsNano = newStats.durationNano() - prvStats.durationNano(); + deltaStatsSec = newStats.durationSec() - prvStats.durationSec(); + } + DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder(); + DefaultPortStatistics deltaStats = builder.setDeviceId(deviceId) + .setPort(newStats.port()) + .setPacketsReceived(newStats.packetsReceived() - prvStats.packetsReceived()) + .setPacketsSent(newStats.packetsSent() - prvStats.packetsSent()) + .setBytesReceived(newStats.bytesReceived() - prvStats.bytesReceived()) + .setBytesSent(newStats.bytesSent() - prvStats.bytesSent()) + .setPacketsRxDropped(newStats.packetsRxDropped() - prvStats.packetsRxDropped()) + .setPacketsTxDropped(newStats.packetsTxDropped() - prvStats.packetsTxDropped()) + .setPacketsRxErrors(newStats.packetsRxErrors() - prvStats.packetsRxErrors()) + .setPacketsTxErrors(newStats.packetsTxErrors() - prvStats.packetsTxErrors()) + .setDurationSec(deltaStatsSec) + .setDurationNano(deltaStatsNano) + .build(); + return deltaStats; + } + + @Override + public Port getPort(DeviceId deviceId, PortNumber portNumber) { + Map ports = devicePorts.get(deviceId); + return ports == null ? null : ports.get(portNumber); + } + + @Override + public List getPortStatistics(DeviceId deviceId) { + Map portStats = devicePortStats.get(deviceId); + if (portStats == null) { + return Collections.emptyList(); + } + return ImmutableList.copyOf(portStats.values()); + } + + @Override + public List getPortDeltaStatistics(DeviceId deviceId) { + Map portStats = devicePortDeltaStats.get(deviceId); + if (portStats == null) { + return Collections.emptyList(); + } + return ImmutableList.copyOf(portStats.values()); + } + + @Override + public boolean isAvailable(DeviceId deviceId) { + return availableDevices.contains(deviceId); + } + + @Override + public DeviceEvent removeDevice(DeviceId deviceId) { + Map descs = getOrCreateDeviceDescriptions(deviceId); + synchronized (descs) { + Device device = devices.remove(deviceId); + // should DEVICE_REMOVED carry removed ports? + Map ports = devicePorts.get(deviceId); + if (ports != null) { + ports.clear(); + } + availableDevices.remove(deviceId); + descs.clear(); + return device == null ? null : + new DeviceEvent(DEVICE_REMOVED, device, null); + } + } + + /** + * Returns a Device, merging description given from multiple Providers. + * + * @param deviceId device identifier + * @param providerDescs Collection of Descriptions from multiple providers + * @return Device instance + */ + private Device composeDevice(DeviceId deviceId, + Map providerDescs) { + + checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied"); + + ProviderId primary = pickPrimaryPID(providerDescs); + + DeviceDescriptions desc = providerDescs.get(primary); + + final DeviceDescription base = desc.getDeviceDesc(); + Type type = base.type(); + String manufacturer = base.manufacturer(); + String hwVersion = base.hwVersion(); + String swVersion = base.swVersion(); + String serialNumber = base.serialNumber(); + ChassisId chassisId = base.chassisId(); + DefaultAnnotations annotations = DefaultAnnotations.builder().build(); + annotations = merge(annotations, base.annotations()); + + for (Entry e : providerDescs.entrySet()) { + if (e.getKey().equals(primary)) { + continue; + } + // TODO: should keep track of Description timestamp + // and only merge conflicting keys when timestamp is newer + // Currently assuming there will never be a key conflict between + // providers + + // annotation merging. not so efficient, should revisit later + annotations = merge(annotations, e.getValue().getDeviceDesc().annotations()); + } + + return new DefaultDevice(primary, deviceId, type, manufacturer, + hwVersion, swVersion, serialNumber, + chassisId, annotations); + } + + /** + * Returns a Port, merging description given from multiple Providers. + * + * @param device device the port is on + * @param number port number + * @param descsMap Collection of Descriptions from multiple providers + * @return Port instance + */ + private Port composePort(Device device, PortNumber number, + Map descsMap) { + + ProviderId primary = pickPrimaryPID(descsMap); + DeviceDescriptions primDescs = descsMap.get(primary); + // if no primary, assume not enabled + // TODO: revisit this default port enabled/disabled behavior + boolean isEnabled = false; + DefaultAnnotations annotations = DefaultAnnotations.builder().build(); + + final PortDescription portDesc = primDescs.getPortDesc(number); + if (portDesc != null) { + isEnabled = portDesc.isEnabled(); + annotations = merge(annotations, portDesc.annotations()); + } + + for (Entry e : descsMap.entrySet()) { + if (e.getKey().equals(primary)) { + continue; + } + // TODO: should keep track of Description timestamp + // and only merge conflicting keys when timestamp is newer + // Currently assuming there will never be a key conflict between + // providers + + // annotation merging. not so efficient, should revisit later + final PortDescription otherPortDesc = e.getValue().getPortDesc(number); + if (otherPortDesc != null) { + annotations = merge(annotations, otherPortDesc.annotations()); + } + } + + return portDesc == null ? + new DefaultPort(device, number, false, annotations) : + new DefaultPort(device, number, isEnabled, portDesc.type(), + portDesc.portSpeed(), annotations); + } + + /** + * @return primary ProviderID, or randomly chosen one if none exists + */ + private ProviderId pickPrimaryPID(Map descsMap) { + ProviderId fallBackPrimary = null; + for (Entry e : descsMap.entrySet()) { + if (!e.getKey().isAncillary()) { + return e.getKey(); + } else if (fallBackPrimary == null) { + // pick randomly as a fallback in case there is no primary + fallBackPrimary = e.getKey(); + } + } + return fallBackPrimary; + } + + /** + * Collection of Description of a Device and it's Ports given from a Provider. + */ + private static class DeviceDescriptions { + + private final AtomicReference deviceDesc; + private final ConcurrentMap portDescs; + + public DeviceDescriptions(DeviceDescription desc) { + this.deviceDesc = new AtomicReference<>(checkNotNull(desc)); + this.portDescs = new ConcurrentHashMap<>(); + } + + public DeviceDescription getDeviceDesc() { + return deviceDesc.get(); + } + + public PortDescription getPortDesc(PortNumber number) { + return portDescs.get(number); + } + + /** + * Puts DeviceDescription, merging annotations as necessary. + * + * @param newDesc new DeviceDescription + * @return previous DeviceDescription + */ + public synchronized DeviceDescription putDeviceDesc(DeviceDescription newDesc) { + DeviceDescription oldOne = deviceDesc.get(); + DeviceDescription newOne = newDesc; + if (oldOne != null) { + SparseAnnotations merged = union(oldOne.annotations(), + newDesc.annotations()); + newOne = new DefaultDeviceDescription(newOne, merged); + } + return deviceDesc.getAndSet(newOne); + } + + /** + * Puts PortDescription, merging annotations as necessary. + * + * @param newDesc new PortDescription + * @return previous PortDescription + */ + public synchronized PortDescription putPortDesc(PortDescription newDesc) { + PortDescription oldOne = portDescs.get(newDesc.portNumber()); + PortDescription newOne = newDesc; + if (oldOne != null) { + SparseAnnotations merged = union(oldOne.annotations(), + newDesc.annotations()); + newOne = new DefaultPortDescription(newOne, merged); + } + return portDescs.put(newOne.portNumber(), newOne); + } + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java new file mode 100644 index 00000000..562e6f3c --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java @@ -0,0 +1,530 @@ +/* + * Copyright 2014 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.store.trivial; + +import static org.junit.Assert.*; +import static org.onosproject.net.Device.Type.SWITCH; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.device.DeviceEvent.Type.*; +import static org.onosproject.net.NetTestTools.assertAnnotationsEquals; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceStore; +import org.onosproject.net.device.DeviceStoreDelegate; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.provider.ProviderId; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + +import org.onlab.packet.ChassisId; + +/** + * Test of the simple DeviceStore implementation. + */ +public class SimpleDeviceStoreTest { + + private static final ProviderId PID = new ProviderId("of", "foo"); + private static final ProviderId PIDA = new ProviderId("of", "bar", true); + private static final DeviceId DID1 = deviceId("of:foo"); + private static final DeviceId DID2 = deviceId("of:bar"); + private static final String MFR = "whitebox"; + private static final String HW = "1.1.x"; + private static final String SW1 = "3.8.1"; + private static final String SW2 = "3.9.5"; + private static final String SN = "43311-12345"; + private static final ChassisId CID = new ChassisId(); + + private static final PortNumber P1 = PortNumber.portNumber(1); + private static final PortNumber P2 = PortNumber.portNumber(2); + private static final PortNumber P3 = PortNumber.portNumber(3); + + private static final SparseAnnotations A1 = DefaultAnnotations.builder() + .set("A1", "a1") + .set("B1", "b1") + .build(); + private static final SparseAnnotations A1_2 = DefaultAnnotations.builder() + .remove("A1") + .set("B3", "b3") + .build(); + private static final SparseAnnotations A2 = DefaultAnnotations.builder() + .set("A2", "a2") + .set("B2", "b2") + .build(); + private static final SparseAnnotations A2_2 = DefaultAnnotations.builder() + .remove("A2") + .set("B4", "b4") + .build(); + + private SimpleDeviceStore simpleDeviceStore; + private DeviceStore deviceStore; + + + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + + @Before + public void setUp() throws Exception { + simpleDeviceStore = new SimpleDeviceStore(); + simpleDeviceStore.activate(); + deviceStore = simpleDeviceStore; + } + + @After + public void tearDown() throws Exception { + simpleDeviceStore.deactivate(); + } + + private void putDevice(DeviceId deviceId, String swVersion, + SparseAnnotations... annotations) { + DeviceDescription description = + new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR, + HW, swVersion, SN, CID, annotations); + deviceStore.createOrUpdateDevice(PID, deviceId, description); + } + + private void putDeviceAncillary(DeviceId deviceId, String swVersion, + SparseAnnotations... annotations) { + DeviceDescription description = + new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR, + HW, swVersion, SN, CID, annotations); + deviceStore.createOrUpdateDevice(PIDA, deviceId, description); + } + + private static void assertDevice(DeviceId id, String swVersion, Device device) { + assertNotNull(device); + assertEquals(id, device.id()); + assertEquals(MFR, device.manufacturer()); + assertEquals(HW, device.hwVersion()); + assertEquals(swVersion, device.swVersion()); + assertEquals(SN, device.serialNumber()); + } + + @Test + public final void testGetDeviceCount() { + assertEquals("initialy empty", 0, deviceStore.getDeviceCount()); + + putDevice(DID1, SW1); + putDevice(DID2, SW2); + putDevice(DID1, SW1); + + assertEquals("expect 2 uniq devices", 2, deviceStore.getDeviceCount()); + } + + @Test + public final void testGetDevices() { + assertEquals("initialy empty", 0, Iterables.size(deviceStore.getDevices())); + + putDevice(DID1, SW1); + putDevice(DID2, SW2); + putDevice(DID1, SW1); + + assertEquals("expect 2 uniq devices", + 2, Iterables.size(deviceStore.getDevices())); + + Map devices = new HashMap<>(); + for (Device device : deviceStore.getDevices()) { + devices.put(device.id(), device); + } + + assertDevice(DID1, SW1, devices.get(DID1)); + assertDevice(DID2, SW2, devices.get(DID2)); + + // add case for new node? + } + + @Test + public final void testGetDevice() { + + putDevice(DID1, SW1); + + assertDevice(DID1, SW1, deviceStore.getDevice(DID1)); + assertNull("DID2 shouldn't be there", deviceStore.getDevice(DID2)); + } + + @Test + public final void testCreateOrUpdateDevice() { + DeviceDescription description = + new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, + HW, SW1, SN, CID); + DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description); + assertEquals(DEVICE_ADDED, event.type()); + assertDevice(DID1, SW1, event.subject()); + + DeviceDescription description2 = + new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, + HW, SW2, SN, CID); + DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2); + assertEquals(DEVICE_UPDATED, event2.type()); + assertDevice(DID1, SW2, event2.subject()); + + assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2)); + } + + @Test + public final void testCreateOrUpdateDeviceAncillary() { + DeviceDescription description = + new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, + HW, SW1, SN, CID, A2); + DeviceEvent event = deviceStore.createOrUpdateDevice(PIDA, DID1, description); + assertEquals(DEVICE_ADDED, event.type()); + assertDevice(DID1, SW1, event.subject()); + assertEquals(PIDA, event.subject().providerId()); + assertAnnotationsEquals(event.subject().annotations(), A2); + assertFalse("Ancillary will not bring device up", deviceStore.isAvailable(DID1)); + + DeviceDescription description2 = + new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, + HW, SW2, SN, CID, A1); + DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2); + assertEquals(DEVICE_UPDATED, event2.type()); + assertDevice(DID1, SW2, event2.subject()); + assertEquals(PID, event2.subject().providerId()); + assertAnnotationsEquals(event2.subject().annotations(), A1, A2); + assertTrue(deviceStore.isAvailable(DID1)); + + assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2)); + + // For now, Ancillary is ignored once primary appears + assertNull("No change expected", deviceStore.createOrUpdateDevice(PIDA, DID1, description)); + + // But, Ancillary annotations will be in effect + DeviceDescription description3 = + new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, + HW, SW1, SN, CID, A2_2); + DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3); + assertEquals(DEVICE_UPDATED, event3.type()); + // basic information will be the one from Primary + assertDevice(DID1, SW2, event3.subject()); + assertEquals(PID, event3.subject().providerId()); + // but annotation from Ancillary will be merged + assertAnnotationsEquals(event3.subject().annotations(), A1, A2, A2_2); + assertTrue(deviceStore.isAvailable(DID1)); + } + + + @Test + public final void testMarkOffline() { + + putDevice(DID1, SW1); + assertTrue(deviceStore.isAvailable(DID1)); + + DeviceEvent event = deviceStore.markOffline(DID1); + assertEquals(DEVICE_AVAILABILITY_CHANGED, event.type()); + assertDevice(DID1, SW1, event.subject()); + assertFalse(deviceStore.isAvailable(DID1)); + + DeviceEvent event2 = deviceStore.markOffline(DID1); + assertNull("No change, no event", event2); +} + + @Test + public final void testUpdatePorts() { + putDevice(DID1, SW1); + List pds = Arrays.asList( + new DefaultPortDescription(P1, true), + new DefaultPortDescription(P2, true) + ); + + List events = deviceStore.updatePorts(PID, DID1, pds); + + Set expectedPorts = Sets.newHashSet(P1, P2); + for (DeviceEvent event : events) { + assertEquals(PORT_ADDED, event.type()); + assertDevice(DID1, SW1, event.subject()); + assertTrue("PortNumber is one of expected", + expectedPorts.remove(event.port().number())); + assertTrue("Port is enabled", event.port().isEnabled()); + } + assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty()); + + + List pds2 = Arrays.asList( + new DefaultPortDescription(P1, false), + new DefaultPortDescription(P2, true), + new DefaultPortDescription(P3, true) + ); + + events = deviceStore.updatePorts(PID, DID1, pds2); + assertFalse("event should be triggered", events.isEmpty()); + for (DeviceEvent event : events) { + PortNumber num = event.port().number(); + if (P1.equals(num)) { + assertEquals(PORT_UPDATED, event.type()); + assertDevice(DID1, SW1, event.subject()); + assertFalse("Port is disabled", event.port().isEnabled()); + } else if (P2.equals(num)) { + fail("P2 event not expected."); + } else if (P3.equals(num)) { + assertEquals(PORT_ADDED, event.type()); + assertDevice(DID1, SW1, event.subject()); + assertTrue("Port is enabled", event.port().isEnabled()); + } else { + fail("Unknown port number encountered: " + num); + } + } + + List pds3 = Arrays.asList( + new DefaultPortDescription(P1, false), + new DefaultPortDescription(P2, true) + ); + events = deviceStore.updatePorts(PID, DID1, pds3); + assertFalse("event should be triggered", events.isEmpty()); + for (DeviceEvent event : events) { + PortNumber num = event.port().number(); + if (P1.equals(num)) { + fail("P1 event not expected."); + } else if (P2.equals(num)) { + fail("P2 event not expected."); + } else if (P3.equals(num)) { + assertEquals(PORT_REMOVED, event.type()); + assertDevice(DID1, SW1, event.subject()); + assertTrue("Port was enabled", event.port().isEnabled()); + } else { + fail("Unknown port number encountered: " + num); + } + } + + } + + @Test + public final void testUpdatePortStatus() { + putDevice(DID1, SW1); + List pds = Arrays.asList( + new DefaultPortDescription(P1, true) + ); + deviceStore.updatePorts(PID, DID1, pds); + + DeviceEvent event = deviceStore.updatePortStatus(PID, DID1, + new DefaultPortDescription(P1, false)); + assertEquals(PORT_UPDATED, event.type()); + assertDevice(DID1, SW1, event.subject()); + assertEquals(P1, event.port().number()); + assertFalse("Port is disabled", event.port().isEnabled()); + + } + + @Test + public final void testUpdatePortStatusAncillary() { + putDeviceAncillary(DID1, SW1); + putDevice(DID1, SW1); + List pds = Arrays.asList( + new DefaultPortDescription(P1, true, A1) + ); + deviceStore.updatePorts(PID, DID1, pds); + + DeviceEvent event = deviceStore.updatePortStatus(PID, DID1, + new DefaultPortDescription(P1, false, A1_2)); + assertEquals(PORT_UPDATED, event.type()); + assertDevice(DID1, SW1, event.subject()); + assertEquals(P1, event.port().number()); + assertAnnotationsEquals(event.port().annotations(), A1, A1_2); + assertFalse("Port is disabled", event.port().isEnabled()); + + DeviceEvent event2 = deviceStore.updatePortStatus(PIDA, DID1, + new DefaultPortDescription(P1, true)); + assertNull("Ancillary is ignored if primary exists", event2); + + // but, Ancillary annotation update will be notified + DeviceEvent event3 = deviceStore.updatePortStatus(PIDA, DID1, + new DefaultPortDescription(P1, true, A2)); + assertEquals(PORT_UPDATED, event3.type()); + assertDevice(DID1, SW1, event3.subject()); + assertEquals(P1, event3.port().number()); + assertAnnotationsEquals(event3.port().annotations(), A1, A1_2, A2); + assertFalse("Port is disabled", event3.port().isEnabled()); + + // port only reported from Ancillary will be notified as down + DeviceEvent event4 = deviceStore.updatePortStatus(PIDA, DID1, + new DefaultPortDescription(P2, true)); + assertEquals(PORT_ADDED, event4.type()); + assertDevice(DID1, SW1, event4.subject()); + assertEquals(P2, event4.port().number()); + assertAnnotationsEquals(event4.port().annotations()); + assertFalse("Port is disabled if not given from primary provider", + event4.port().isEnabled()); + } + + @Test + public final void testGetPorts() { + putDevice(DID1, SW1); + putDevice(DID2, SW1); + List pds = Arrays.asList( + new DefaultPortDescription(P1, true), + new DefaultPortDescription(P2, true) + ); + deviceStore.updatePorts(PID, DID1, pds); + + Set expectedPorts = Sets.newHashSet(P1, P2); + List ports = deviceStore.getPorts(DID1); + for (Port port : ports) { + assertTrue("Port is enabled", port.isEnabled()); + assertTrue("PortNumber is one of expected", + expectedPorts.remove(port.number())); + } + assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty()); + + + assertTrue("DID2 has no ports", deviceStore.getPorts(DID2).isEmpty()); + } + + @Test + public final void testGetPort() { + putDevice(DID1, SW1); + putDevice(DID2, SW1); + List pds = Arrays.asList( + new DefaultPortDescription(P1, true), + new DefaultPortDescription(P2, false) + ); + deviceStore.updatePorts(PID, DID1, pds); + + Port port1 = deviceStore.getPort(DID1, P1); + assertEquals(P1, port1.number()); + assertTrue("Port is enabled", port1.isEnabled()); + + Port port2 = deviceStore.getPort(DID1, P2); + assertEquals(P2, port2.number()); + assertFalse("Port is disabled", port2.isEnabled()); + + Port port3 = deviceStore.getPort(DID1, P3); + assertNull("P3 not expected", port3); + } + + @Test + public final void testRemoveDevice() { + putDevice(DID1, SW1, A1); + List pds = Arrays.asList( + new DefaultPortDescription(P1, true, A2) + ); + deviceStore.updatePorts(PID, DID1, pds); + putDevice(DID2, SW1); + + assertEquals(2, deviceStore.getDeviceCount()); + assertEquals(1, deviceStore.getPorts(DID1).size()); + assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations(), A1); + assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations(), A2); + + DeviceEvent event = deviceStore.removeDevice(DID1); + assertEquals(DEVICE_REMOVED, event.type()); + assertDevice(DID1, SW1, event.subject()); + + assertEquals(1, deviceStore.getDeviceCount()); + assertEquals(0, deviceStore.getPorts(DID1).size()); + + // putBack Device, Port w/o annotation + putDevice(DID1, SW1); + List pds2 = Arrays.asList( + new DefaultPortDescription(P1, true) + ); + deviceStore.updatePorts(PID, DID1, pds2); + + // annotations should not survive + assertEquals(2, deviceStore.getDeviceCount()); + assertEquals(1, deviceStore.getPorts(DID1).size()); + assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations()); + assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations()); + } + + // If Delegates should be called only on remote events, + // then Simple* should never call them, thus not test required. + // TODO add test for Port events when we have them + @Ignore("Ignore until Delegate spec. is clear.") + @Test + public final void testEvents() throws InterruptedException { + final CountDownLatch addLatch = new CountDownLatch(1); + DeviceStoreDelegate checkAdd = new DeviceStoreDelegate() { + @Override + public void notify(DeviceEvent event) { + assertEquals(DEVICE_ADDED, event.type()); + assertDevice(DID1, SW1, event.subject()); + addLatch.countDown(); + } + }; + final CountDownLatch updateLatch = new CountDownLatch(1); + DeviceStoreDelegate checkUpdate = new DeviceStoreDelegate() { + @Override + public void notify(DeviceEvent event) { + assertEquals(DEVICE_UPDATED, event.type()); + assertDevice(DID1, SW2, event.subject()); + updateLatch.countDown(); + } + }; + final CountDownLatch removeLatch = new CountDownLatch(1); + DeviceStoreDelegate checkRemove = new DeviceStoreDelegate() { + @Override + public void notify(DeviceEvent event) { + assertEquals(DEVICE_REMOVED, event.type()); + assertDevice(DID1, SW2, event.subject()); + removeLatch.countDown(); + } + }; + + DeviceDescription description = + new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, + HW, SW1, SN, CID); + deviceStore.setDelegate(checkAdd); + deviceStore.createOrUpdateDevice(PID, DID1, description); + assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS)); + + + DeviceDescription description2 = + new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, + HW, SW2, SN, CID); + deviceStore.unsetDelegate(checkAdd); + deviceStore.setDelegate(checkUpdate); + deviceStore.createOrUpdateDevice(PID, DID1, description2); + assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS)); + + deviceStore.unsetDelegate(checkUpdate); + deviceStore.setDelegate(checkRemove); + deviceStore.removeDevice(DID1); + assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java new file mode 100644 index 00000000..3b8f1d35 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java @@ -0,0 +1,327 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import com.google.common.base.Function; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.SettableFuture; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onlab.util.NewConcurrentHashMap; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.CompletedBatchOperation; +import org.onosproject.net.flow.DefaultFlowEntry; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowEntry.FlowEntryState; +import org.onosproject.net.flow.FlowId; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleBatchEntry; +import org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation; +import org.onosproject.net.flow.FlowRuleBatchEvent; +import org.onosproject.net.flow.FlowRuleBatchOperation; +import org.onosproject.net.flow.FlowRuleBatchRequest; +import org.onosproject.net.flow.FlowRuleEvent; +import org.onosproject.net.flow.FlowRuleEvent.Type; +import org.onosproject.net.flow.FlowRuleStore; +import org.onosproject.net.flow.FlowRuleStoreDelegate; +import org.onosproject.net.flow.StoredFlowEntry; +import org.onosproject.store.AbstractStore; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked; +import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Manages inventory of flow rules using trivial in-memory implementation. + */ +@Component(immediate = true) +@Service +public class SimpleFlowRuleStore + extends AbstractStore + implements FlowRuleStore { + + private final Logger log = getLogger(getClass()); + + + // inner Map is Device flow table + // inner Map value (FlowId synonym list) must be synchronized before modifying + private final ConcurrentMap>> + flowEntries = new ConcurrentHashMap<>(); + + private final AtomicInteger localBatchIdGen = new AtomicInteger(); + + // TODO: make this configurable + private int pendingFutureTimeoutMinutes = 5; + + private Cache> pendingFutures = + CacheBuilder.newBuilder() + .expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES) + .removalListener(new TimeoutFuture()) + .build(); + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + flowEntries.clear(); + log.info("Stopped"); + } + + + @Override + public int getFlowRuleCount() { + int sum = 0; + for (ConcurrentMap> ft : flowEntries.values()) { + for (List fes : ft.values()) { + sum += fes.size(); + } + } + return sum; + } + + private static NewConcurrentHashMap> lazyEmptyFlowTable() { + return NewConcurrentHashMap.>ifNeeded(); + } + + /** + * Returns the flow table for specified device. + * + * @param deviceId identifier of the device + * @return Map representing Flow Table of given device. + */ + private ConcurrentMap> getFlowTable(DeviceId deviceId) { + return createIfAbsentUnchecked(flowEntries, + deviceId, lazyEmptyFlowTable()); + } + + private List getFlowEntries(DeviceId deviceId, FlowId flowId) { + final ConcurrentMap> flowTable = getFlowTable(deviceId); + List r = flowTable.get(flowId); + if (r == null) { + final List concurrentlyAdded; + r = new CopyOnWriteArrayList<>(); + concurrentlyAdded = flowTable.putIfAbsent(flowId, r); + if (concurrentlyAdded != null) { + return concurrentlyAdded; + } + } + return r; + } + + private FlowEntry getFlowEntryInternal(DeviceId deviceId, FlowRule rule) { + List fes = getFlowEntries(deviceId, rule.id()); + for (StoredFlowEntry fe : fes) { + if (fe.equals(rule)) { + return fe; + } + } + return null; + } + + @Override + public FlowEntry getFlowEntry(FlowRule rule) { + return getFlowEntryInternal(rule.deviceId(), rule); + } + + @Override + public Iterable getFlowEntries(DeviceId deviceId) { + // flatten and make iterator unmodifiable + return FluentIterable.from(getFlowTable(deviceId).values()) + .transformAndConcat( + new Function, Iterable>() { + + @Override + public Iterable apply( + List input) { + return Collections.unmodifiableList(input); + } + }); + } + + @Override + public void storeFlowRule(FlowRule rule) { + storeFlowRuleInternal(rule); + } + + private void storeFlowRuleInternal(FlowRule rule) { + StoredFlowEntry f = new DefaultFlowEntry(rule); + final DeviceId did = f.deviceId(); + final FlowId fid = f.id(); + List existing = getFlowEntries(did, fid); + synchronized (existing) { + for (StoredFlowEntry fe : existing) { + if (fe.equals(rule)) { + // was already there? ignore + return; + } + } + // new flow rule added + existing.add(f); + } + } + + @Override + public void deleteFlowRule(FlowRule rule) { + + List entries = getFlowEntries(rule.deviceId(), rule.id()); + + synchronized (entries) { + for (StoredFlowEntry entry : entries) { + if (entry.equals(rule)) { + synchronized (entry) { + entry.setState(FlowEntryState.PENDING_REMOVE); + } + } + } + } + + + //log.warn("Cannot find rule {}", rule); + } + + @Override + public FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) { + // check if this new rule is an update to an existing entry + List entries = getFlowEntries(rule.deviceId(), rule.id()); + synchronized (entries) { + for (StoredFlowEntry stored : entries) { + if (stored.equals(rule)) { + synchronized (stored) { + stored.setBytes(rule.bytes()); + stored.setLife(rule.life()); + stored.setPackets(rule.packets()); + if (stored.state() == FlowEntryState.PENDING_ADD) { + stored.setState(FlowEntryState.ADDED); + // TODO: Do we need to change `rule` state? + return new FlowRuleEvent(Type.RULE_ADDED, rule); + } + return new FlowRuleEvent(Type.RULE_UPDATED, rule); + } + } + } + } + + // should not reach here + // storeFlowRule was expected to be called + log.error("FlowRule was not found in store {} to update", rule); + + //flowEntries.put(did, rule); + return null; + } + + @Override + public FlowRuleEvent removeFlowRule(FlowEntry rule) { + // This is where one could mark a rule as removed and still keep it in the store. + final DeviceId did = rule.deviceId(); + + List entries = getFlowEntries(did, rule.id()); + synchronized (entries) { + if (entries.remove(rule)) { + return new FlowRuleEvent(RULE_REMOVED, rule); + } + } + return null; + } + + @Override + public void storeBatch( + FlowRuleBatchOperation operation) { + List toAdd = new ArrayList<>(); + List toRemove = new ArrayList<>(); + + for (FlowRuleBatchEntry entry : operation.getOperations()) { + final FlowRule flowRule = entry.target(); + if (entry.operator().equals(FlowRuleOperation.ADD)) { + if (!getFlowEntries(flowRule.deviceId(), flowRule.id()).contains(flowRule)) { + storeFlowRule(flowRule); + toAdd.add(entry); + } + } else if (entry.operator().equals(FlowRuleOperation.REMOVE)) { + if (getFlowEntries(flowRule.deviceId(), flowRule.id()).contains(flowRule)) { + deleteFlowRule(flowRule); + toRemove.add(entry); + } + } else { + throw new UnsupportedOperationException("Unsupported operation type"); + } + } + + if (toAdd.isEmpty() && toRemove.isEmpty()) { + notifyDelegate(FlowRuleBatchEvent.completed( + new FlowRuleBatchRequest(operation.id(), Collections.emptySet()), + new CompletedBatchOperation(true, Collections.emptySet(), + operation.deviceId()))); + return; + } + + SettableFuture r = SettableFuture.create(); + final int batchId = localBatchIdGen.incrementAndGet(); + + pendingFutures.put(batchId, r); + + toAdd.addAll(toRemove); + notifyDelegate(FlowRuleBatchEvent.requested( + new FlowRuleBatchRequest(batchId, Sets.newHashSet(toAdd)), operation.deviceId())); + + } + + @Override + public void batchOperationComplete(FlowRuleBatchEvent event) { + final Long batchId = event.subject().batchId(); + SettableFuture future + = pendingFutures.getIfPresent(batchId); + if (future != null) { + future.set(event.result()); + pendingFutures.invalidate(batchId); + } + notifyDelegate(event); + } + + private static final class TimeoutFuture + implements RemovalListener> { + @Override + public void onRemoval(RemovalNotification> notification) { + // wrapping in ExecutionException to support Future.get + if (notification.wasEvicted()) { + notification.getValue() + .setException(new ExecutionException("Timed out", + new TimeoutException())); + } + } + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java new file mode 100644 index 00000000..71de3e13 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java @@ -0,0 +1,717 @@ +/* + * Copyright 2015 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.store.trivial; + +import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onlab.util.NewConcurrentHashMap; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.core.GroupId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.group.DefaultGroup; +import org.onosproject.net.group.DefaultGroupDescription; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.Group.GroupState; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupEvent.Type; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupOperation; +import org.onosproject.net.group.GroupStore; +import org.onosproject.net.group.GroupStoreDelegate; +import org.onosproject.net.group.StoredGroupBucketEntry; +import org.onosproject.net.group.StoredGroupEntry; +import org.onosproject.store.AbstractStore; +import org.slf4j.Logger; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Sets; + +/** + * Manages inventory of group entries using trivial in-memory implementation. + */ +@Component(immediate = true) +@Service +public class SimpleGroupStore + extends AbstractStore + implements GroupStore { + + private final Logger log = getLogger(getClass()); + + private final int dummyId = 0xffffffff; + private final GroupId dummyGroupId = new DefaultGroupId(dummyId); + + // inner Map is per device group table + private final ConcurrentMap> + groupEntriesByKey = new ConcurrentHashMap<>(); + private final ConcurrentMap> + groupEntriesById = new ConcurrentHashMap<>(); + private final ConcurrentMap> + pendingGroupEntriesByKey = new ConcurrentHashMap<>(); + private final ConcurrentMap> + extraneousGroupEntriesById = new ConcurrentHashMap<>(); + + private final HashMap deviceAuditStatus = + new HashMap(); + + private final AtomicInteger groupIdGen = new AtomicInteger(); + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + groupEntriesByKey.clear(); + groupEntriesById.clear(); + log.info("Stopped"); + } + + private static NewConcurrentHashMap + lazyEmptyGroupKeyTable() { + return NewConcurrentHashMap.ifNeeded(); + } + + private static NewConcurrentHashMap + lazyEmptyGroupIdTable() { + return NewConcurrentHashMap.ifNeeded(); + } + + private static NewConcurrentHashMap + lazyEmptyPendingGroupKeyTable() { + return NewConcurrentHashMap.ifNeeded(); + } + + private static NewConcurrentHashMap + lazyEmptyExtraneousGroupIdTable() { + return NewConcurrentHashMap.ifNeeded(); + } + + /** + * Returns the group key table for specified device. + * + * @param deviceId identifier of the device + * @return Map representing group key table of given device. + */ + private ConcurrentMap getGroupKeyTable(DeviceId deviceId) { + return createIfAbsentUnchecked(groupEntriesByKey, + deviceId, lazyEmptyGroupKeyTable()); + } + + /** + * Returns the group id table for specified device. + * + * @param deviceId identifier of the device + * @return Map representing group key table of given device. + */ + private ConcurrentMap getGroupIdTable(DeviceId deviceId) { + return createIfAbsentUnchecked(groupEntriesById, + deviceId, lazyEmptyGroupIdTable()); + } + + /** + * Returns the pending group key table for specified device. + * + * @param deviceId identifier of the device + * @return Map representing group key table of given device. + */ + private ConcurrentMap + getPendingGroupKeyTable(DeviceId deviceId) { + return createIfAbsentUnchecked(pendingGroupEntriesByKey, + deviceId, lazyEmptyPendingGroupKeyTable()); + } + + /** + * Returns the extraneous group id table for specified device. + * + * @param deviceId identifier of the device + * @return Map representing group key table of given device. + */ + private ConcurrentMap + getExtraneousGroupIdTable(DeviceId deviceId) { + return createIfAbsentUnchecked(extraneousGroupEntriesById, + deviceId, + lazyEmptyExtraneousGroupIdTable()); + } + + /** + * Returns the number of groups for the specified device in the store. + * + * @return number of groups for the specified device + */ + @Override + public int getGroupCount(DeviceId deviceId) { + return (groupEntriesByKey.get(deviceId) != null) ? + groupEntriesByKey.get(deviceId).size() : 0; + } + + /** + * Returns the groups associated with a device. + * + * @param deviceId the device ID + * + * @return the group entries + */ + @Override + public Iterable getGroups(DeviceId deviceId) { + // flatten and make iterator unmodifiable + return FluentIterable.from(getGroupKeyTable(deviceId).values()) + .transform( + new Function() { + + @Override + public Group apply( + StoredGroupEntry input) { + return input; + } + }); + } + + /** + * Returns the stored group entry. + * + * @param deviceId the device ID + * @param appCookie the group key + * + * @return a group associated with the key + */ + @Override + public Group getGroup(DeviceId deviceId, GroupKey appCookie) { + return (groupEntriesByKey.get(deviceId) != null) ? + groupEntriesByKey.get(deviceId).get(appCookie) : + null; + } + + @Override + public Group getGroup(DeviceId deviceId, GroupId groupId) { + return (groupEntriesById.get(deviceId) != null) ? + groupEntriesById.get(deviceId).get(groupId) : + null; + } + + private int getFreeGroupIdValue(DeviceId deviceId) { + int freeId = groupIdGen.incrementAndGet(); + + while (true) { + Group existing = ( + groupEntriesById.get(deviceId) != null) ? + groupEntriesById.get(deviceId).get(new DefaultGroupId(freeId)) : + null; + if (existing == null) { + existing = ( + extraneousGroupEntriesById.get(deviceId) != null) ? + extraneousGroupEntriesById.get(deviceId). + get(new DefaultGroupId(freeId)) : + null; + } + if (existing != null) { + freeId = groupIdGen.incrementAndGet(); + } else { + break; + } + } + return freeId; + } + + /** + * Stores a new group entry using the information from group description. + * + * @param groupDesc group description to be used to create group entry + */ + @Override + public void storeGroupDescription(GroupDescription groupDesc) { + // Check if a group is existing with the same key + if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) { + return; + } + + if (deviceAuditStatus.get(groupDesc.deviceId()) == null) { + // Device group audit has not completed yet + // Add this group description to pending group key table + // Create a group entry object with Dummy Group ID + StoredGroupEntry group = new DefaultGroup(dummyGroupId, groupDesc); + group.setState(GroupState.WAITING_AUDIT_COMPLETE); + ConcurrentMap pendingKeyTable = + getPendingGroupKeyTable(groupDesc.deviceId()); + pendingKeyTable.put(groupDesc.appCookie(), group); + return; + } + + storeGroupDescriptionInternal(groupDesc); + } + + private void storeGroupDescriptionInternal(GroupDescription groupDesc) { + // Check if a group is existing with the same key + if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) { + return; + } + + GroupId id = null; + if (groupDesc.givenGroupId() == null) { + // Get a new group identifier + id = new DefaultGroupId(getFreeGroupIdValue(groupDesc.deviceId())); + } else { + id = new DefaultGroupId(groupDesc.givenGroupId()); + } + // Create a group entry object + StoredGroupEntry group = new DefaultGroup(id, groupDesc); + // Insert the newly created group entry into concurrent key and id maps + ConcurrentMap keyTable = + getGroupKeyTable(groupDesc.deviceId()); + keyTable.put(groupDesc.appCookie(), group); + ConcurrentMap idTable = + getGroupIdTable(groupDesc.deviceId()); + idTable.put(id, group); + notifyDelegate(new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED, + group)); + } + + /** + * Updates the existing group entry with the information + * from group description. + * + * @param deviceId the device ID + * @param oldAppCookie the current group key + * @param type update type + * @param newBuckets group buckets for updates + * @param newAppCookie optional new group key + */ + @Override + public void updateGroupDescription(DeviceId deviceId, + GroupKey oldAppCookie, + UpdateType type, + GroupBuckets newBuckets, + GroupKey newAppCookie) { + // Check if a group is existing with the provided key + Group oldGroup = getGroup(deviceId, oldAppCookie); + if (oldGroup == null) { + return; + } + + List newBucketList = getUpdatedBucketList(oldGroup, + type, + newBuckets); + if (newBucketList != null) { + // Create a new group object from the old group + GroupBuckets updatedBuckets = new GroupBuckets(newBucketList); + GroupKey newCookie = (newAppCookie != null) ? newAppCookie : oldAppCookie; + GroupDescription updatedGroupDesc = new DefaultGroupDescription( + oldGroup.deviceId(), + oldGroup.type(), + updatedBuckets, + newCookie, + oldGroup.givenGroupId(), + oldGroup.appId()); + StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(), + updatedGroupDesc); + newGroup.setState(GroupState.PENDING_UPDATE); + newGroup.setLife(oldGroup.life()); + newGroup.setPackets(oldGroup.packets()); + newGroup.setBytes(oldGroup.bytes()); + // Remove the old entry from maps and add new entry using new key + ConcurrentMap keyTable = + getGroupKeyTable(oldGroup.deviceId()); + ConcurrentMap idTable = + getGroupIdTable(oldGroup.deviceId()); + keyTable.remove(oldGroup.appCookie()); + idTable.remove(oldGroup.id()); + keyTable.put(newGroup.appCookie(), newGroup); + idTable.put(newGroup.id(), newGroup); + notifyDelegate(new GroupEvent(Type.GROUP_UPDATE_REQUESTED, newGroup)); + } + } + + private List getUpdatedBucketList(Group oldGroup, + UpdateType type, + GroupBuckets buckets) { + GroupBuckets oldBuckets = oldGroup.buckets(); + List newBucketList = new ArrayList( + oldBuckets.buckets()); + boolean groupDescUpdated = false; + + if (type == UpdateType.ADD) { + // Check if the any of the new buckets are part of + // the old bucket list + for (GroupBucket addBucket:buckets.buckets()) { + if (!newBucketList.contains(addBucket)) { + newBucketList.add(addBucket); + groupDescUpdated = true; + } + } + } else if (type == UpdateType.REMOVE) { + // Check if the to be removed buckets are part of the + // old bucket list + for (GroupBucket removeBucket:buckets.buckets()) { + if (newBucketList.contains(removeBucket)) { + newBucketList.remove(removeBucket); + groupDescUpdated = true; + } + } + } + + if (groupDescUpdated) { + return newBucketList; + } else { + return null; + } + } + + /** + * Triggers deleting the existing group entry. + * + * @param deviceId the device ID + * @param appCookie the group key + */ + @Override + public void deleteGroupDescription(DeviceId deviceId, + GroupKey appCookie) { + // Check if a group is existing with the provided key + StoredGroupEntry existing = (groupEntriesByKey.get(deviceId) != null) ? + groupEntriesByKey.get(deviceId).get(appCookie) : + null; + if (existing == null) { + return; + } + + synchronized (existing) { + existing.setState(GroupState.PENDING_DELETE); + } + notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_REQUESTED, existing)); + } + + /** + * Stores a new group entry, or updates an existing entry. + * + * @param group group entry + */ + @Override + public void addOrUpdateGroupEntry(Group group) { + // check if this new entry is an update to an existing entry + StoredGroupEntry existing = (groupEntriesById.get( + group.deviceId()) != null) ? + groupEntriesById.get(group.deviceId()).get(group.id()) : + null; + GroupEvent event = null; + + if (existing != null) { + synchronized (existing) { + for (GroupBucket bucket:group.buckets().buckets()) { + Optional matchingBucket = + existing.buckets().buckets() + .stream() + .filter((existingBucket)->(existingBucket.equals(bucket))) + .findFirst(); + if (matchingBucket.isPresent()) { + ((StoredGroupBucketEntry) matchingBucket. + get()).setPackets(bucket.packets()); + ((StoredGroupBucketEntry) matchingBucket. + get()).setBytes(bucket.bytes()); + } else { + log.warn("addOrUpdateGroupEntry: No matching " + + "buckets to update stats"); + } + } + existing.setLife(group.life()); + existing.setPackets(group.packets()); + existing.setBytes(group.bytes()); + if (existing.state() == GroupState.PENDING_ADD) { + existing.setState(GroupState.ADDED); + event = new GroupEvent(Type.GROUP_ADDED, existing); + } else { + if (existing.state() == GroupState.PENDING_UPDATE) { + existing.setState(GroupState.ADDED); + } + event = new GroupEvent(Type.GROUP_UPDATED, existing); + } + } + } + + if (event != null) { + notifyDelegate(event); + } + } + + /** + * Removes the group entry from store. + * + * @param group group entry + */ + @Override + public void removeGroupEntry(Group group) { + StoredGroupEntry existing = (groupEntriesById.get( + group.deviceId()) != null) ? + groupEntriesById.get(group.deviceId()).get(group.id()) : + null; + + if (existing != null) { + ConcurrentMap keyTable = + getGroupKeyTable(existing.deviceId()); + ConcurrentMap idTable = + getGroupIdTable(existing.deviceId()); + idTable.remove(existing.id()); + keyTable.remove(existing.appCookie()); + notifyDelegate(new GroupEvent(Type.GROUP_REMOVED, existing)); + } + } + + @Override + public void deviceInitialAuditCompleted(DeviceId deviceId, + boolean completed) { + synchronized (deviceAuditStatus) { + if (completed) { + log.debug("deviceInitialAuditCompleted: AUDIT " + + "completed for device {}", deviceId); + deviceAuditStatus.put(deviceId, true); + // Execute all pending group requests + ConcurrentMap pendingGroupRequests = + getPendingGroupKeyTable(deviceId); + for (Group group:pendingGroupRequests.values()) { + GroupDescription tmp = new DefaultGroupDescription( + group.deviceId(), + group.type(), + group.buckets(), + group.appCookie(), + group.givenGroupId(), + group.appId()); + storeGroupDescriptionInternal(tmp); + } + getPendingGroupKeyTable(deviceId).clear(); + } else { + if (deviceAuditStatus.get(deviceId)) { + log.debug("deviceInitialAuditCompleted: Clearing AUDIT " + + "status for device {}", deviceId); + deviceAuditStatus.put(deviceId, false); + } + } + } + } + + @Override + public boolean deviceInitialAuditStatus(DeviceId deviceId) { + synchronized (deviceAuditStatus) { + return (deviceAuditStatus.get(deviceId) != null) + ? deviceAuditStatus.get(deviceId) : false; + } + } + + @Override + public void groupOperationFailed(DeviceId deviceId, GroupOperation operation) { + + StoredGroupEntry existing = (groupEntriesById.get( + deviceId) != null) ? + groupEntriesById.get(deviceId).get(operation.groupId()) : + null; + + if (existing == null) { + log.warn("No group entry with ID {} found ", operation.groupId()); + return; + } + + switch (operation.opType()) { + case ADD: + notifyDelegate(new GroupEvent(Type.GROUP_ADD_FAILED, existing)); + break; + case MODIFY: + notifyDelegate(new GroupEvent(Type.GROUP_UPDATE_FAILED, existing)); + break; + case DELETE: + notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_FAILED, existing)); + break; + default: + log.warn("Unknown group operation type {}", operation.opType()); + } + + ConcurrentMap keyTable = + getGroupKeyTable(existing.deviceId()); + ConcurrentMap idTable = + getGroupIdTable(existing.deviceId()); + idTable.remove(existing.id()); + keyTable.remove(existing.appCookie()); + } + + @Override + public void addOrUpdateExtraneousGroupEntry(Group group) { + ConcurrentMap extraneousIdTable = + getExtraneousGroupIdTable(group.deviceId()); + extraneousIdTable.put(group.id(), group); + // Check the reference counter + if (group.referenceCount() == 0) { + notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_REQUESTED, group)); + } + } + + @Override + public void removeExtraneousGroupEntry(Group group) { + ConcurrentMap extraneousIdTable = + getExtraneousGroupIdTable(group.deviceId()); + extraneousIdTable.remove(group.id()); + } + + @Override + public Iterable getExtraneousGroups(DeviceId deviceId) { + // flatten and make iterator unmodifiable + return FluentIterable.from( + getExtraneousGroupIdTable(deviceId).values()); + } + + @Override + public void pushGroupMetrics(DeviceId deviceId, + Collection groupEntries) { + boolean deviceInitialAuditStatus = + deviceInitialAuditStatus(deviceId); + Set southboundGroupEntries = + Sets.newHashSet(groupEntries); + Set storedGroupEntries = + Sets.newHashSet(getGroups(deviceId)); + Set extraneousStoredEntries = + Sets.newHashSet(getExtraneousGroups(deviceId)); + + log.trace("pushGroupMetrics: Displaying all ({}) " + + "southboundGroupEntries for device {}", + southboundGroupEntries.size(), + deviceId); + for (Iterator it = southboundGroupEntries.iterator(); it.hasNext();) { + Group group = it.next(); + log.trace("Group {} in device {}", group, deviceId); + } + + log.trace("Displaying all ({}) stored group entries for device {}", + storedGroupEntries.size(), + deviceId); + for (Iterator it1 = storedGroupEntries.iterator(); + it1.hasNext();) { + Group group = it1.next(); + log.trace("Stored Group {} for device {}", group, deviceId); + } + + for (Iterator it2 = southboundGroupEntries.iterator(); it2.hasNext();) { + Group group = it2.next(); + if (storedGroupEntries.remove(group)) { + // we both have the group, let's update some info then. + log.trace("Group AUDIT: group {} exists " + + "in both planes for device {}", + group.id(), deviceId); + groupAdded(group); + it2.remove(); + } + } + for (Group group : southboundGroupEntries) { + if (getGroup(group.deviceId(), group.id()) != null) { + // There is a group existing with the same id + // It is possible that group update is + // in progress while we got a stale info from switch + if (!storedGroupEntries.remove(getGroup( + group.deviceId(), group.id()))) { + log.warn("Group AUDIT: Inconsistent state:" + + "Group exists in ID based table while " + + "not present in key based table"); + } + } else { + // there are groups in the switch that aren't in the store + log.trace("Group AUDIT: extraneous group {} exists " + + "in data plane for device {}", + group.id(), deviceId); + extraneousStoredEntries.remove(group); + extraneousGroup(group); + } + } + for (Group group : storedGroupEntries) { + // there are groups in the store that aren't in the switch + log.trace("Group AUDIT: group {} missing " + + "in data plane for device {}", + group.id(), deviceId); + groupMissing(group); + } + for (Group group : extraneousStoredEntries) { + // there are groups in the extraneous store that + // aren't in the switch + log.trace("Group AUDIT: clearing extransoeus group {} " + + "from store for device {}", + group.id(), deviceId); + removeExtraneousGroupEntry(group); + } + + if (!deviceInitialAuditStatus) { + log.debug("Group AUDIT: Setting device {} initial " + + "AUDIT completed", deviceId); + deviceInitialAuditCompleted(deviceId, true); + } + } + + private void groupMissing(Group group) { + switch (group.state()) { + case PENDING_DELETE: + log.debug("Group {} delete confirmation from device {}", + group, group.deviceId()); + removeGroupEntry(group); + break; + case ADDED: + case PENDING_ADD: + case PENDING_UPDATE: + log.debug("Group {} is in store but not on device {}", + group, group.deviceId()); + StoredGroupEntry existing = (groupEntriesById.get( + group.deviceId()) != null) ? + groupEntriesById.get(group.deviceId()).get(group.id()) : + null; + log.trace("groupMissing: group " + + "entry {} in device {} moving " + + "from {} to PENDING_ADD", + existing.id(), + existing.deviceId(), + existing.state()); + existing.setState(Group.GroupState.PENDING_ADD); + notifyDelegate(new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED, + group)); + break; + default: + log.debug("Group {} has not been installed.", group); + break; + } + } + + private void extraneousGroup(Group group) { + log.debug("Group {} is on device {} but not in store.", + group, group.deviceId()); + addOrUpdateExtraneousGroupEntry(group); + } + + private void groupAdded(Group group) { + log.trace("Group {} Added or Updated in device {}", + group, group.deviceId()); + addOrUpdateGroupEntry(group); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java new file mode 100644 index 00000000..dd6c8a58 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java @@ -0,0 +1,482 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import static org.junit.Assert.assertEquals; +import static org.onosproject.net.DeviceId.deviceId; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.core.GroupId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.group.DefaultGroup; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.DefaultGroupDescription; +import org.onosproject.net.group.DefaultGroupKey; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupOperation; +import org.onosproject.net.group.GroupStore.UpdateType; +import org.onosproject.net.group.GroupStoreDelegate; +import org.onosproject.net.group.StoredGroupBucketEntry; +import org.onosproject.net.group.StoredGroupEntry; + +import com.google.common.collect.Iterables; + +/** + * Test of the simple DeviceStore implementation. + */ +public class SimpleGroupStoreTest { + + private SimpleGroupStore simpleGroupStore; + private final ApplicationId appId = + new DefaultApplicationId(2, "org.groupstore.test"); + + public static final DeviceId D1 = deviceId("of:1"); + + @Before + public void setUp() throws Exception { + simpleGroupStore = new SimpleGroupStore(); + simpleGroupStore.activate(); + } + + @After + public void tearDown() throws Exception { + simpleGroupStore.deactivate(); + } + + private class InternalGroupStoreDelegate + implements GroupStoreDelegate { + private GroupId createdGroupId = null; + private GroupKey createdGroupKey; + private GroupBuckets createdBuckets; + private GroupEvent.Type expectedEvent; + + public InternalGroupStoreDelegate(GroupKey key, + GroupBuckets buckets, + GroupEvent.Type expectedEvent) { + this.createdBuckets = buckets; + this.createdGroupKey = key; + this.expectedEvent = expectedEvent; + } + @Override + public void notify(GroupEvent event) { + assertEquals(expectedEvent, event.type()); + assertEquals(Group.Type.SELECT, event.subject().type()); + assertEquals(D1, event.subject().deviceId()); + assertEquals(createdGroupKey, event.subject().appCookie()); + assertEquals(createdBuckets.buckets(), event.subject().buckets().buckets()); + if (expectedEvent == GroupEvent.Type.GROUP_ADD_REQUESTED) { + createdGroupId = event.subject().id(); + assertEquals(Group.GroupState.PENDING_ADD, + event.subject().state()); + } else if (expectedEvent == GroupEvent.Type.GROUP_ADDED) { + createdGroupId = event.subject().id(); + assertEquals(Group.GroupState.ADDED, + event.subject().state()); + } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATED) { + createdGroupId = event.subject().id(); + assertEquals(true, + event.subject().buckets(). + buckets().containsAll(createdBuckets.buckets())); + assertEquals(true, + createdBuckets.buckets(). + containsAll(event.subject().buckets().buckets())); + for (GroupBucket bucket:event.subject().buckets().buckets()) { + Optional matched = createdBuckets.buckets() + .stream() + .filter((expected) -> expected.equals(bucket)) + .findFirst(); + assertEquals(matched.get().packets(), + bucket.packets()); + assertEquals(matched.get().bytes(), + bucket.bytes()); + } + assertEquals(Group.GroupState.ADDED, + event.subject().state()); + } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATE_REQUESTED) { + assertEquals(Group.GroupState.PENDING_UPDATE, + event.subject().state()); + } else if (expectedEvent == GroupEvent.Type.GROUP_REMOVE_REQUESTED) { + assertEquals(Group.GroupState.PENDING_DELETE, + event.subject().state()); + } else if (expectedEvent == GroupEvent.Type.GROUP_REMOVED) { + createdGroupId = event.subject().id(); + assertEquals(Group.GroupState.PENDING_DELETE, + event.subject().state()); + } else if (expectedEvent == GroupEvent.Type.GROUP_ADD_FAILED) { + createdGroupId = event.subject().id(); + assertEquals(Group.GroupState.PENDING_ADD, + event.subject().state()); + } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATE_FAILED) { + createdGroupId = event.subject().id(); + assertEquals(Group.GroupState.PENDING_UPDATE, + event.subject().state()); + } else if (expectedEvent == GroupEvent.Type.GROUP_REMOVE_FAILED) { + createdGroupId = event.subject().id(); + assertEquals(Group.GroupState.PENDING_DELETE, + event.subject().state()); + } + } + + public void verifyGroupId(GroupId id) { + assertEquals(createdGroupId, id); + } + } + + /** + * Tests group store operations. The following operations are tested: + * a)Tests device group audit completion status change + * b)Tests storeGroup operation + * c)Tests getGroupCount operation + * d)Tests getGroup operation + * e)Tests getGroups operation + * f)Tests addOrUpdateGroupEntry operation from southbound + * g)Tests updateGroupDescription for ADD operation from northbound + * h)Tests updateGroupDescription for REMOVE operation from northbound + * i)Tests deleteGroupDescription operation from northbound + * j)Tests removeGroupEntry operation from southbound + */ + @Test + public void testGroupStoreOperations() { + // Set the Device AUDIT completed in the store + simpleGroupStore.deviceInitialAuditCompleted(D1, true); + + // Testing storeGroup operation + GroupKey newKey = new DefaultGroupKey("group1".getBytes()); + testStoreAndGetGroup(newKey); + + // Testing addOrUpdateGroupEntry operation from southbound + GroupKey currKey = newKey; + testAddGroupEntryFromSB(currKey); + + // Testing updateGroupDescription for ADD operation from northbound + newKey = new DefaultGroupKey("group1AddBuckets".getBytes()); + testAddBuckets(currKey, newKey); + + // Testing updateGroupDescription for REMOVE operation from northbound + currKey = newKey; + newKey = new DefaultGroupKey("group1RemoveBuckets".getBytes()); + testRemoveBuckets(currKey, newKey); + + // Testing addOrUpdateGroupEntry operation from southbound + currKey = newKey; + testUpdateGroupEntryFromSB(currKey); + + // Testing deleteGroupDescription operation from northbound + testDeleteGroup(currKey); + + // Testing removeGroupEntry operation from southbound + testRemoveGroupFromSB(currKey); + } + + // Testing storeGroup operation + private void testStoreAndGetGroup(GroupKey key) { + PortNumber[] ports = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + List outPorts = new ArrayList(); + outPorts.addAll(Arrays.asList(ports)); + + List buckets = new ArrayList(); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(106)); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupBuckets = new GroupBuckets(buckets); + GroupDescription groupDesc = new DefaultGroupDescription( + D1, + Group.Type.SELECT, + groupBuckets, + key, + null, + appId); + InternalGroupStoreDelegate checkStoreGroupDelegate = + new InternalGroupStoreDelegate(key, + groupBuckets, + GroupEvent.Type.GROUP_ADD_REQUESTED); + simpleGroupStore.setDelegate(checkStoreGroupDelegate); + // Testing storeGroup operation + simpleGroupStore.storeGroupDescription(groupDesc); + + // Testing getGroupCount operation + assertEquals(1, simpleGroupStore.getGroupCount(D1)); + + // Testing getGroup operation + Group createdGroup = simpleGroupStore.getGroup(D1, key); + checkStoreGroupDelegate.verifyGroupId(createdGroup.id()); + + // Testing getGroups operation + Iterable createdGroups = simpleGroupStore.getGroups(D1); + int groupCount = 0; + for (Group group:createdGroups) { + checkStoreGroupDelegate.verifyGroupId(group.id()); + groupCount++; + } + assertEquals(1, groupCount); + simpleGroupStore.unsetDelegate(checkStoreGroupDelegate); + } + + // Testing addOrUpdateGroupEntry operation from southbound + private void testAddGroupEntryFromSB(GroupKey currKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + + InternalGroupStoreDelegate addGroupEntryDelegate = + new InternalGroupStoreDelegate(currKey, + existingGroup.buckets(), + GroupEvent.Type.GROUP_ADDED); + simpleGroupStore.setDelegate(addGroupEntryDelegate); + simpleGroupStore.addOrUpdateGroupEntry(existingGroup); + simpleGroupStore.unsetDelegate(addGroupEntryDelegate); + } + + // Testing addOrUpdateGroupEntry operation from southbound + private void testUpdateGroupEntryFromSB(GroupKey currKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + int totalPkts = 0; + int totalBytes = 0; + List newBucketList = new ArrayList(); + for (GroupBucket bucket:existingGroup.buckets().buckets()) { + StoredGroupBucketEntry newBucket = + (StoredGroupBucketEntry) + DefaultGroupBucket.createSelectGroupBucket(bucket.treatment()); + newBucket.setPackets(10); + newBucket.setBytes(10 * 256 * 8); + totalPkts += 10; + totalBytes += 10 * 256 * 8; + newBucketList.add(newBucket); + } + GroupBuckets updatedBuckets = new GroupBuckets(newBucketList); + Group updatedGroup = new DefaultGroup(existingGroup.id(), + existingGroup.deviceId(), + existingGroup.type(), + updatedBuckets); + ((StoredGroupEntry) updatedGroup).setPackets(totalPkts); + ((StoredGroupEntry) updatedGroup).setBytes(totalBytes); + + InternalGroupStoreDelegate updateGroupEntryDelegate = + new InternalGroupStoreDelegate(currKey, + updatedBuckets, + GroupEvent.Type.GROUP_UPDATED); + simpleGroupStore.setDelegate(updateGroupEntryDelegate); + simpleGroupStore.addOrUpdateGroupEntry(updatedGroup); + simpleGroupStore.unsetDelegate(updateGroupEntryDelegate); + } + + // Testing updateGroupDescription for ADD operation from northbound + private void testAddBuckets(GroupKey currKey, GroupKey addKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + List buckets = new ArrayList(); + buckets.addAll(existingGroup.buckets().buckets()); + + PortNumber[] newNeighborPorts = {PortNumber.portNumber(41), + PortNumber.portNumber(42)}; + List newOutPorts = new ArrayList(); + newOutPorts.addAll(Collections.singletonList(newNeighborPorts[0])); + + List toAddBuckets = new ArrayList(); + for (PortNumber portNumber: newOutPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:03")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(106)); + toAddBuckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets toAddGroupBuckets = new GroupBuckets(toAddBuckets); + buckets.addAll(toAddBuckets); + GroupBuckets updatedGroupBuckets = new GroupBuckets(buckets); + InternalGroupStoreDelegate updateGroupDescDelegate = + new InternalGroupStoreDelegate(addKey, + updatedGroupBuckets, + GroupEvent.Type.GROUP_UPDATE_REQUESTED); + simpleGroupStore.setDelegate(updateGroupDescDelegate); + simpleGroupStore.updateGroupDescription(D1, + currKey, + UpdateType.ADD, + toAddGroupBuckets, + addKey); + simpleGroupStore.unsetDelegate(updateGroupDescDelegate); + } + + // Testing updateGroupDescription for REMOVE operation from northbound + private void testRemoveBuckets(GroupKey currKey, GroupKey removeKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + List buckets = new ArrayList(); + buckets.addAll(existingGroup.buckets().buckets()); + + List toRemoveBuckets = new ArrayList(); + + // There should be 4 buckets in the current group + toRemoveBuckets.add(buckets.remove(0)); + toRemoveBuckets.add(buckets.remove(1)); + GroupBuckets toRemoveGroupBuckets = new GroupBuckets(toRemoveBuckets); + + GroupBuckets remainingGroupBuckets = new GroupBuckets(buckets); + InternalGroupStoreDelegate removeGroupDescDelegate = + new InternalGroupStoreDelegate(removeKey, + remainingGroupBuckets, + GroupEvent.Type.GROUP_UPDATE_REQUESTED); + simpleGroupStore.setDelegate(removeGroupDescDelegate); + simpleGroupStore.updateGroupDescription(D1, + currKey, + UpdateType.REMOVE, + toRemoveGroupBuckets, + removeKey); + simpleGroupStore.unsetDelegate(removeGroupDescDelegate); + } + + // Testing deleteGroupDescription operation from northbound + private void testDeleteGroup(GroupKey currKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + InternalGroupStoreDelegate deleteGroupDescDelegate = + new InternalGroupStoreDelegate(currKey, + existingGroup.buckets(), + GroupEvent.Type.GROUP_REMOVE_REQUESTED); + simpleGroupStore.setDelegate(deleteGroupDescDelegate); + simpleGroupStore.deleteGroupDescription(D1, currKey); + simpleGroupStore.unsetDelegate(deleteGroupDescDelegate); + } + + // Testing removeGroupEntry operation from southbound + private void testRemoveGroupFromSB(GroupKey currKey) { + Group existingGroup = simpleGroupStore.getGroup(D1, currKey); + InternalGroupStoreDelegate removeGroupEntryDelegate = + new InternalGroupStoreDelegate(currKey, + existingGroup.buckets(), + GroupEvent.Type.GROUP_REMOVED); + simpleGroupStore.setDelegate(removeGroupEntryDelegate); + simpleGroupStore.removeGroupEntry(existingGroup); + + // Testing getGroup operation + existingGroup = simpleGroupStore.getGroup(D1, currKey); + assertEquals(null, existingGroup); + assertEquals(0, Iterables.size(simpleGroupStore.getGroups(D1))); + assertEquals(0, simpleGroupStore.getGroupCount(D1)); + + simpleGroupStore.unsetDelegate(removeGroupEntryDelegate); + } + + @Test + public void testGroupOperationFailure() { + + simpleGroupStore.deviceInitialAuditCompleted(D1, true); + + ApplicationId appId = + new DefaultApplicationId(2, "org.groupstore.test"); + GroupKey key = new DefaultGroupKey("group1".getBytes()); + PortNumber[] ports = {PortNumber.portNumber(31), + PortNumber.portNumber(32)}; + List outPorts = new ArrayList(); + outPorts.add(ports[0]); + outPorts.add(ports[1]); + + List buckets = new ArrayList(); + for (PortNumber portNumber: outPorts) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(portNumber) + .setEthDst(MacAddress.valueOf("00:00:00:00:00:02")) + .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01")) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(106)); + buckets.add(DefaultGroupBucket.createSelectGroupBucket( + tBuilder.build())); + } + GroupBuckets groupBuckets = new GroupBuckets(buckets); + GroupDescription groupDesc = new DefaultGroupDescription( + D1, + Group.Type.SELECT, + groupBuckets, + key, + null, + appId); + InternalGroupStoreDelegate checkStoreGroupDelegate = + new InternalGroupStoreDelegate(key, + groupBuckets, + GroupEvent.Type.GROUP_ADD_REQUESTED); + simpleGroupStore.setDelegate(checkStoreGroupDelegate); + // Testing storeGroup operation + simpleGroupStore.storeGroupDescription(groupDesc); + simpleGroupStore.unsetDelegate(checkStoreGroupDelegate); + + // Testing Group add operation failure + Group createdGroup = simpleGroupStore.getGroup(D1, key); + checkStoreGroupDelegate.verifyGroupId(createdGroup.id()); + + GroupOperation groupAddOp = GroupOperation. + createAddGroupOperation(createdGroup.id(), + createdGroup.type(), + createdGroup.buckets()); + InternalGroupStoreDelegate checkGroupAddFailureDelegate = + new InternalGroupStoreDelegate(key, + groupBuckets, + GroupEvent.Type.GROUP_ADD_FAILED); + simpleGroupStore.setDelegate(checkGroupAddFailureDelegate); + simpleGroupStore.groupOperationFailed(D1, groupAddOp); + + + // Testing Group modify operation failure + simpleGroupStore.unsetDelegate(checkGroupAddFailureDelegate); + GroupOperation groupModOp = GroupOperation. + createModifyGroupOperation(createdGroup.id(), + createdGroup.type(), + createdGroup.buckets()); + InternalGroupStoreDelegate checkGroupModFailureDelegate = + new InternalGroupStoreDelegate(key, + groupBuckets, + GroupEvent.Type.GROUP_UPDATE_FAILED); + simpleGroupStore.setDelegate(checkGroupModFailureDelegate); + simpleGroupStore.groupOperationFailed(D1, groupModOp); + + // Testing Group modify operation failure + simpleGroupStore.unsetDelegate(checkGroupModFailureDelegate); + GroupOperation groupDelOp = GroupOperation. + createDeleteGroupOperation(createdGroup.id(), + createdGroup.type()); + InternalGroupStoreDelegate checkGroupDelFailureDelegate = + new InternalGroupStoreDelegate(key, + groupBuckets, + GroupEvent.Type.GROUP_REMOVE_FAILED); + simpleGroupStore.setDelegate(checkGroupDelFailureDelegate); + simpleGroupStore.groupOperationFailed(D1, groupDelOp); + } +} + diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java new file mode 100644 index 00000000..f5604f68 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java @@ -0,0 +1,293 @@ +/* + * Copyright 2014 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.store.trivial; + +import static org.onosproject.net.DefaultAnnotations.merge; +import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED; +import static org.onosproject.net.host.HostEvent.Type.HOST_MOVED; +import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED; +import static org.onosproject.net.host.HostEvent.Type.HOST_UPDATED; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.Annotations; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultHost; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.HostLocation; +import org.onosproject.net.host.HostDescription; +import org.onosproject.net.host.HostEvent; +import org.onosproject.net.host.HostStore; +import org.onosproject.net.host.HostStoreDelegate; +import org.onosproject.net.host.PortAddresses; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.AbstractStore; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.slf4j.Logger; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.collect.SetMultimap; + +// TODO: multi-provider, annotation not supported. +/** + * Manages inventory of end-station hosts using trivial in-memory + * implementation. + */ +@Component(immediate = true) +@Service +public class SimpleHostStore + extends AbstractStore + implements HostStore { + + private final Logger log = getLogger(getClass()); + + // Host inventory + private final Map hosts = new ConcurrentHashMap<>(2000000, 0.75f, 16); + + // Hosts tracked by their location + private final Multimap locations = HashMultimap.create(); + + private final SetMultimap portAddresses = + Multimaps.synchronizedSetMultimap( + HashMultimap.create()); + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + @Override + public HostEvent createOrUpdateHost(ProviderId providerId, HostId hostId, + HostDescription hostDescription) { + StoredHost host = hosts.get(hostId); + if (host == null) { + return createHost(providerId, hostId, hostDescription); + } + return updateHost(providerId, host, hostDescription); + } + + // creates a new host and sends HOST_ADDED + private HostEvent createHost(ProviderId providerId, HostId hostId, + HostDescription descr) { + StoredHost newhost = new StoredHost(providerId, hostId, + descr.hwAddress(), + descr.vlan(), + descr.location(), + ImmutableSet.copyOf(descr.ipAddress()), + descr.annotations()); + synchronized (this) { + hosts.put(hostId, newhost); + locations.put(descr.location(), newhost); + } + return new HostEvent(HOST_ADDED, newhost); + } + + // checks for type of update to host, sends appropriate event + private HostEvent updateHost(ProviderId providerId, StoredHost host, + HostDescription descr) { + HostEvent event; + if (!host.location().equals(descr.location())) { + host.setLocation(descr.location()); + return new HostEvent(HOST_MOVED, host); + } + + if (host.ipAddresses().containsAll(descr.ipAddress()) && + descr.annotations().keys().isEmpty()) { + return null; + } + + Set addresses = new HashSet<>(host.ipAddresses()); + addresses.addAll(descr.ipAddress()); + Annotations annotations = merge((DefaultAnnotations) host.annotations(), + descr.annotations()); + StoredHost updated = new StoredHost(providerId, host.id(), + host.mac(), host.vlan(), + descr.location(), addresses, + annotations); + event = new HostEvent(HOST_UPDATED, updated); + synchronized (this) { + hosts.put(host.id(), updated); + locations.remove(host.location(), host); + locations.put(updated.location(), updated); + } + return event; + } + + @Override + public HostEvent removeHost(HostId hostId) { + synchronized (this) { + Host host = hosts.remove(hostId); + if (host != null) { + locations.remove((host.location()), host); + return new HostEvent(HOST_REMOVED, host); + } + return null; + } + } + + @Override + public int getHostCount() { + return hosts.size(); + } + + @Override + public Iterable getHosts() { + return ImmutableSet.copyOf(hosts.values()); + } + + @Override + public Host getHost(HostId hostId) { + return hosts.get(hostId); + } + + @Override + public Set getHosts(VlanId vlanId) { + Set vlanset = new HashSet<>(); + for (Host h : hosts.values()) { + if (h.vlan().equals(vlanId)) { + vlanset.add(h); + } + } + return vlanset; + } + + @Override + public Set getHosts(MacAddress mac) { + Set macset = new HashSet<>(); + for (Host h : hosts.values()) { + if (h.mac().equals(mac)) { + macset.add(h); + } + } + return macset; + } + + @Override + public Set getHosts(IpAddress ip) { + Set ipset = new HashSet<>(); + for (Host h : hosts.values()) { + if (h.ipAddresses().contains(ip)) { + ipset.add(h); + } + } + return ipset; + } + + @Override + public Set getConnectedHosts(ConnectPoint connectPoint) { + return ImmutableSet.copyOf(locations.get(connectPoint)); + } + + @Override + public Set getConnectedHosts(DeviceId deviceId) { + Set hostset = new HashSet<>(); + for (ConnectPoint p : locations.keySet()) { + if (p.deviceId().equals(deviceId)) { + hostset.addAll(locations.get(p)); + } + } + return hostset; + } + + @Override + public void updateAddressBindings(PortAddresses addresses) { + portAddresses.put(addresses.connectPoint(), addresses); + } + + @Override + public void removeAddressBindings(PortAddresses addresses) { + portAddresses.remove(addresses.connectPoint(), addresses); + } + + @Override + public void clearAddressBindings(ConnectPoint connectPoint) { + portAddresses.removeAll(connectPoint); + } + + @Override + public Set getAddressBindings() { + synchronized (portAddresses) { + return ImmutableSet.copyOf(portAddresses.values()); + } + } + + @Override + public Set getAddressBindingsForPort(ConnectPoint connectPoint) { + synchronized (portAddresses) { + Set addresses = portAddresses.get(connectPoint); + + if (addresses == null) { + return Collections.emptySet(); + } else { + return ImmutableSet.copyOf(addresses); + } + } + } + + // Auxiliary extension to allow location to mutate. + private static final class StoredHost extends DefaultHost { + private HostLocation location; + + /** + * Creates an end-station host using the supplied information. + * + * @param providerId provider identity + * @param id host identifier + * @param mac host MAC address + * @param vlan host VLAN identifier + * @param location host location + * @param ips host IP addresses + * @param annotations optional key/value annotations + */ + public StoredHost(ProviderId providerId, HostId id, + MacAddress mac, VlanId vlan, HostLocation location, + Set ips, Annotations... annotations) { + super(providerId, id, mac, vlan, location, ips, annotations); + this.location = location; + } + + void setLocation(HostLocation location) { + this.location = location; + } + + @Override + public HostLocation location() { + return location; + } + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.java new file mode 100644 index 00000000..3f7e563a --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014 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.store.trivial; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.core.IdBlock; +import org.onosproject.core.IdBlockStore; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Simple implementation of id block store. + */ +@Component(immediate = true) +@Service +public class SimpleIdBlockStore implements IdBlockStore { + + private static final long DEFAULT_BLOCK_SIZE = 0x1000L; + + private final Map topicBlocks = new ConcurrentHashMap<>(); + + @Override + public synchronized IdBlock getIdBlock(String topic) { + AtomicLong blockGenerator = topicBlocks.get(topic); + if (blockGenerator == null) { + blockGenerator = new AtomicLong(0); + topicBlocks.put(topic, blockGenerator); + } + Long blockBase = blockGenerator.getAndAdd(DEFAULT_BLOCK_SIZE); + return new IdBlock(blockBase, DEFAULT_BLOCK_SIZE); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java new file mode 100644 index 00000000..9f959663 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java @@ -0,0 +1,212 @@ +/* + * Copyright 2015 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.store.trivial; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentData; +import org.onosproject.net.intent.IntentEvent; +import org.onosproject.net.intent.IntentState; +import org.onosproject.net.intent.IntentStore; +import org.onosproject.net.intent.IntentStoreDelegate; +import org.onosproject.net.intent.Key; +import org.onosproject.store.AbstractStore; +import org.slf4j.Logger; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.intent.IntentState.PURGE_REQ; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Simple single-instance implementation of the intent store. + */ +@Component(immediate = true) +@Service +public class SimpleIntentStore + extends AbstractStore + implements IntentStore { + + private final Logger log = getLogger(getClass()); + + private final Map current = Maps.newConcurrentMap(); + private final Map pending = Maps.newConcurrentMap(); + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + @Override + public long getIntentCount() { + return current.size(); + } + + @Override + public Iterable getIntents() { + return current.values().stream() + .map(IntentData::intent) + .collect(Collectors.toList()); + } + + @Override + public Iterable getIntentData(boolean localOnly, long olderThan) { + if (localOnly || olderThan > 0) { + long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns + final SystemClockTimestamp time = new SystemClockTimestamp(older); + return current.values().stream() + .filter(data -> data.version().isOlderThan(time) && + (!localOnly || isMaster(data.key()))) + .collect(Collectors.toList()); + } + return Lists.newArrayList(current.values()); + } + + @Override + public IntentState getIntentState(Key intentKey) { + IntentData data = current.get(intentKey); + return (data != null) ? data.state() : null; + } + + @Override + public List getInstallableIntents(Key intentKey) { + IntentData data = current.get(intentKey); + if (data != null) { + return data.installables(); + } + return null; + } + + @Override + public void write(IntentData newData) { + checkNotNull(newData); + + synchronized (this) { + // TODO this could be refactored/cleaned up + IntentData currentData = current.get(newData.key()); + IntentData pendingData = pending.get(newData.key()); + + if (IntentData.isUpdateAcceptable(currentData, newData)) { + if (pendingData != null) { + if (pendingData.state() == PURGE_REQ) { + current.remove(newData.key(), newData); + } else { + current.put(newData.key(), new IntentData(newData)); + } + + if (pendingData.version().compareTo(newData.version()) <= 0) { + // pendingData version is less than or equal to newData's + // Note: a new update for this key could be pending (it's version will be greater) + pending.remove(newData.key()); + } + } + notifyDelegateIfNotNull(IntentEvent.getEvent(newData)); + } + } + } + + private void notifyDelegateIfNotNull(IntentEvent event) { + if (event != null) { + notifyDelegate(event); + } + } + + @Override + public void batchWrite(Iterable updates) { + for (IntentData data : updates) { + write(data); + } + } + + @Override + public Intent getIntent(Key key) { + IntentData data = current.get(key); + return (data != null) ? data.intent() : null; + } + + @Override + public IntentData getIntentData(Key key) { + IntentData currentData = current.get(key); + if (currentData == null) { + return null; + } + return new IntentData(currentData); + } + + @Override + public void addPending(IntentData data) { + if (data.version() == null) { // recompiled intents will already have a version + data.setVersion(new SystemClockTimestamp()); + } + synchronized (this) { + IntentData existingData = pending.get(data.key()); + if (existingData == null || + // existing version is strictly less than data's version + // Note: if they are equal, we already have the update + // TODO maybe we should still make this <= to be safe? + existingData.version().compareTo(data.version()) < 0) { + pending.put(data.key(), data); + checkNotNull(delegate, "Store delegate is not set") + .process(new IntentData(data)); + notifyDelegateIfNotNull(IntentEvent.getEvent(data)); + } else { + log.debug("IntentData {} is older than existing: {}", + data, existingData); + } + //TODO consider also checking the current map at this point + } + } + + @Override + public boolean isMaster(Key intentKey) { + return true; + } + + @Override + public Iterable getPending() { + return pending.values().stream() + .map(IntentData::intent) + .collect(Collectors.toList()); + } + + @Override + public Iterable getPendingData() { + return Lists.newArrayList(pending.values()); + } + + @Override + public Iterable getPendingData(boolean localOnly, long olderThan) { + long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns + final SystemClockTimestamp time = new SystemClockTimestamp(older); + return pending.values().stream() + .filter(data -> data.version().isOlderThan(time) && + (!localOnly || isMaster(data.key()))) + .collect(Collectors.toList()); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java new file mode 100644 index 00000000..194ffec1 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java @@ -0,0 +1,135 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.stream.Collectors; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.Leadership; +import org.onosproject.cluster.LeadershipEvent; +import org.onosproject.cluster.LeadershipEvent.Type; +import org.onosproject.cluster.LeadershipEventListener; +import org.onosproject.cluster.LeadershipService; +import org.onosproject.cluster.NodeId; + +/** + * A trivial implementation of the leadership service. + *

+ * The service is not distributed, so it can assume there's a single leadership + * contender. This contender is always granted leadership whenever it asks. + */ +@Component(immediate = true) +@Service +public class SimpleLeadershipManager implements LeadershipService { + + private Set listeners = new CopyOnWriteArraySet<>(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private ClusterService clusterService; + + private Map elections = new ConcurrentHashMap<>(); + + @Override + public NodeId getLeader(String path) { + return elections.get(path) ? clusterService.getLocalNode().id() : null; + } + + @Override + public Leadership getLeadership(String path) { + checkArgument(path != null); + return elections.get(path) ? new Leadership(path, clusterService.getLocalNode().id(), 0, 0) : null; + } + + @Override + public Set ownedTopics(NodeId nodeId) { + checkArgument(nodeId != null); + return elections.entrySet() + .stream() + .filter(Entry::getValue) + .map(Entry::getKey) + .collect(Collectors.toSet()); + } + + @Override + public CompletableFuture runForLeadership(String path) { + elections.put(path, true); + for (LeadershipEventListener listener : listeners) { + listener.event(new LeadershipEvent(Type.LEADER_ELECTED, + new Leadership(path, clusterService.getLocalNode().id(), 0, 0))); + } + return CompletableFuture.completedFuture(new Leadership(path, clusterService.getLocalNode().id(), 0, 0)); + } + + @Override + public CompletableFuture withdraw(String path) { + elections.remove(path); + for (LeadershipEventListener listener : listeners) { + listener.event(new LeadershipEvent(Type.LEADER_BOOTED, + new Leadership(path, clusterService.getLocalNode().id(), 0, 0))); + } + return CompletableFuture.completedFuture(null); + } + + @Override + public Map getLeaderBoard() { + //FIXME + throw new UnsupportedOperationException("I don't know what to do." + + " I wish you luck."); + } + + @Override + public void addListener(LeadershipEventListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(LeadershipEventListener listener) { + listeners.remove(listener); + } + + @Override + public Map> getCandidates() { + return null; + } + + @Override + public List getCandidates(String path) { + return null; + } + + @Override + public boolean stepdown(String path) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean makeTopCandidate(String path, NodeId nodeId) { + throw new UnsupportedOperationException(); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java new file mode 100644 index 00000000..58b446cf --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java @@ -0,0 +1,286 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onlab.util.Bandwidth; +import org.onlab.util.PositionalParameterStringFormatter; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.Annotations; +import org.onosproject.net.Link; +import org.onosproject.net.intent.IntentId; +import org.onosproject.net.resource.link.BandwidthResource; +import org.onosproject.net.resource.link.BandwidthResourceAllocation; +import org.onosproject.net.resource.link.LambdaResource; +import org.onosproject.net.resource.link.LambdaResourceAllocation; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.onosproject.net.resource.link.LinkResourceEvent; +import org.onosproject.net.resource.link.LinkResourceStore; +import org.onosproject.net.resource.ResourceAllocation; +import org.onosproject.net.resource.ResourceAllocationException; +import org.onosproject.net.resource.ResourceType; +import org.slf4j.Logger; + +import com.google.common.collect.ImmutableList; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Manages link resources using trivial in-memory structures implementation. + */ +@Component(immediate = true) +@Service +public class SimpleLinkResourceStore implements LinkResourceStore { + private static final BandwidthResource DEFAULT_BANDWIDTH = new BandwidthResource(Bandwidth.mbps(1_000)); + private final Logger log = getLogger(getClass()); + + private Map linkResourceAllocationsMap; + private Map> allocatedResources; + private Map> freeResources; + + @Activate + public void activate() { + linkResourceAllocationsMap = new HashMap<>(); + allocatedResources = new HashMap<>(); + freeResources = new HashMap<>(); + + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + /** + * Returns free resources for a given link obtaining from topology + * information. + * + * @param link the target link + * @return free resources + */ + private synchronized Set readOriginalFreeResources(Link link) { + Annotations annotations = link.annotations(); + Set allocations = new HashSet<>(); + + try { + int waves = Integer.parseInt(annotations.value(AnnotationKeys.OPTICAL_WAVES)); + for (int i = 1; i <= waves; i++) { + allocations.add(new LambdaResourceAllocation(LambdaResource.valueOf(i))); + } + } catch (NumberFormatException e) { + log.debug("No optical.wave annotation on link %s", link); + } + + BandwidthResource bandwidth = DEFAULT_BANDWIDTH; + try { + bandwidth = new BandwidthResource( + Bandwidth.mbps((Double.parseDouble(annotations.value(AnnotationKeys.BANDWIDTH))))); + } catch (NumberFormatException e) { + log.debug("No bandwidth annotation on link %s", link); + } + allocations.add( + new BandwidthResourceAllocation(bandwidth)); + return allocations; + } + + /** + * Finds and returns {@link BandwidthResourceAllocation} object from a given + * set. + * + * @param freeRes a set of ResourceAllocation object. + * @return {@link BandwidthResourceAllocation} object if found, otherwise + * {@link BandwidthResourceAllocation} object with 0 bandwidth + * + */ + private synchronized BandwidthResourceAllocation getBandwidth( + Set freeRes) { + for (ResourceAllocation res : freeRes) { + if (res.type() == ResourceType.BANDWIDTH) { + return (BandwidthResourceAllocation) res; + } + } + return new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.bps(0))); + } + + /** + * Subtracts given resources from free resources for given link. + * + * @param link the target link + * @param allocations the resources to be subtracted + */ + private synchronized void subtractFreeResources(Link link, + LinkResourceAllocations allocations) { + // TODO Use lock or version for updating freeResources. + checkNotNull(link); + Set freeRes = new HashSet<>(getFreeResources(link)); + Set subRes = allocations.getResourceAllocation(link); + for (ResourceAllocation res : subRes) { + switch (res.type()) { + case BANDWIDTH: + BandwidthResourceAllocation ba = getBandwidth(freeRes); + double requestedBandwidth = + ((BandwidthResourceAllocation) res).bandwidth().toDouble(); + double newBandwidth = ba.bandwidth().toDouble() - requestedBandwidth; + if (newBandwidth < 0.0) { + throw new ResourceAllocationException( + PositionalParameterStringFormatter.format( + "Unable to allocate bandwidth for link {} " + + "requested amount is {} current allocation is {}", + link, + requestedBandwidth, + ba)); + } + freeRes.remove(ba); + freeRes.add(new BandwidthResourceAllocation( + new BandwidthResource(Bandwidth.bps(newBandwidth)))); + break; + case LAMBDA: + final boolean lambdaAvailable = freeRes.remove(res); + if (!lambdaAvailable) { + int requestedLambda = + ((LambdaResourceAllocation) res).lambda().toInt(); + throw new ResourceAllocationException( + PositionalParameterStringFormatter.format( + "Unable to allocate lambda for link {} lambda is {}", + link, + requestedLambda)); + } + break; + default: + break; + } + } + freeResources.put(link, freeRes); + + } + + /** + * Adds given resources to free resources for given link. + * + * @param link the target link + * @param allocations the resources to be added + */ + private synchronized void addFreeResources(Link link, + LinkResourceAllocations allocations) { + // TODO Use lock or version for updating freeResources. + Set freeRes = new HashSet<>(getFreeResources(link)); + Set addRes = allocations.getResourceAllocation(link); + for (ResourceAllocation res : addRes) { + switch (res.type()) { + case BANDWIDTH: + BandwidthResourceAllocation ba = getBandwidth(freeRes); + double requestedBandwidth = + ((BandwidthResourceAllocation) res).bandwidth().toDouble(); + double newBandwidth = ba.bandwidth().toDouble() + requestedBandwidth; + freeRes.remove(ba); + freeRes.add(new BandwidthResourceAllocation( + new BandwidthResource(Bandwidth.bps(newBandwidth)))); + break; + case LAMBDA: + checkState(freeRes.add(res)); + break; + default: + break; + } + } + freeResources.put(link, freeRes); + } + + @Override + public synchronized Set getFreeResources(Link link) { + checkNotNull(link); + Set freeRes = freeResources.get(link); + if (freeRes == null) { + freeRes = readOriginalFreeResources(link); + } + + return freeRes; + } + + @Override + public synchronized void allocateResources(LinkResourceAllocations allocations) { + checkNotNull(allocations); + linkResourceAllocationsMap.put(allocations.intentId(), allocations); + for (Link link : allocations.links()) { + subtractFreeResources(link, allocations); + Set linkAllocs = allocatedResources.get(link); + if (linkAllocs == null) { + linkAllocs = new HashSet<>(); + } + linkAllocs.add(allocations); + allocatedResources.put(link, linkAllocs); + } + } + + @Override + public synchronized LinkResourceEvent releaseResources(LinkResourceAllocations allocations) { + checkNotNull(allocations); + linkResourceAllocationsMap.remove(allocations.intentId()); + for (Link link : allocations.links()) { + addFreeResources(link, allocations); + Set linkAllocs = allocatedResources.get(link); + if (linkAllocs == null) { + log.error("Missing resource allocation."); + } else { + linkAllocs.remove(allocations); + } + allocatedResources.put(link, linkAllocs); + } + + final List releasedResources = + ImmutableList.of(allocations); + + return new LinkResourceEvent( + LinkResourceEvent.Type.ADDITIONAL_RESOURCES_AVAILABLE, + releasedResources); + } + + @Override + public synchronized LinkResourceAllocations getAllocations(IntentId intentId) { + checkNotNull(intentId); + return linkResourceAllocationsMap.get(intentId); + } + + @Override + public synchronized Iterable getAllocations(Link link) { + checkNotNull(link); + Set result = allocatedResources.get(link); + if (result == null) { + result = Collections.emptySet(); + } + return Collections.unmodifiableSet(result); + } + + @Override + public synchronized Iterable getAllocations() { + return Collections.unmodifiableCollection(linkResourceAllocationsMap.values()); + } + + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java new file mode 100644 index 00000000..238e75d0 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java @@ -0,0 +1,307 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.util.Bandwidth; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.Annotations; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.Link; +import org.onosproject.net.intent.IntentId; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.resource.link.BandwidthResource; +import org.onosproject.net.resource.link.BandwidthResourceAllocation; +import org.onosproject.net.resource.link.LambdaResource; +import org.onosproject.net.resource.link.LambdaResourceAllocation; +import org.onosproject.net.resource.link.LinkResourceAllocations; +import org.onosproject.net.resource.link.LinkResourceStore; +import org.onosproject.net.resource.ResourceAllocation; +import org.onosproject.net.resource.ResourceAllocationException; +import org.onosproject.net.resource.ResourceRequest; +import org.onosproject.net.resource.ResourceType; + +import com.google.common.collect.ImmutableSet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.PortNumber.portNumber; + +/** + * Test of the simple LinkResourceStore implementation. + */ +public class SimpleLinkResourceStoreTest { + + private LinkResourceStore store; + private SimpleLinkResourceStore simpleStore; + private Link link1; + private Link link2; + private Link link3; + + /** + * Returns {@link Link} object. + * + * @param dev1 source device + * @param port1 source port + * @param dev2 destination device + * @param port2 destination port + * @return created {@link Link} object + */ + private static Link newLink(String dev1, int port1, String dev2, int port2) { + Annotations annotations = DefaultAnnotations.builder() + .set(AnnotationKeys.OPTICAL_WAVES, "80") + .set(AnnotationKeys.BANDWIDTH, "1000") + .build(); + return new DefaultLink( + new ProviderId("of", "foo"), + new ConnectPoint(deviceId(dev1), portNumber(port1)), + new ConnectPoint(deviceId(dev2), portNumber(port2)), + DIRECT, annotations); + } + + @Before + public void setUp() throws Exception { + simpleStore = new SimpleLinkResourceStore(); + simpleStore.activate(); + store = simpleStore; + + link1 = newLink("of:1", 1, "of:2", 2); + link2 = newLink("of:2", 1, "of:3", 2); + link3 = newLink("of:3", 1, "of:4", 2); + } + + @After + public void tearDown() throws Exception { + simpleStore.deactivate(); + } + + /** + * Tests constructor and activate method. + */ + @Test + public void testConstructorAndActivate() { + final Iterable allAllocations = store.getAllocations(); + assertNotNull(allAllocations); + assertFalse(allAllocations.iterator().hasNext()); + + final Iterable linkAllocations = + store.getAllocations(link1); + assertNotNull(linkAllocations); + assertFalse(linkAllocations.iterator().hasNext()); + + final Set res = store.getFreeResources(link2); + assertNotNull(res); + } + + /** + * Picks up and returns one of bandwidth allocations from a given set. + * + * @param resources the set of {@link ResourceAllocation}s + * @return {@link BandwidthResourceAllocation} object if found, null + * otherwise + */ + private BandwidthResourceAllocation getBandwidthObj(Set resources) { + for (ResourceAllocation res : resources) { + if (res.type() == ResourceType.BANDWIDTH) { + return ((BandwidthResourceAllocation) res); + } + } + return null; + } + + /** + * Returns all lambda allocations from a given set. + * + * @param resources the set of {@link ResourceAllocation}s + * @return a set of {@link LambdaResourceAllocation} objects + */ + private Set getLambdaObjs(Set resources) { + Set lambdaResources = new HashSet<>(); + for (ResourceAllocation res : resources) { + if (res.type() == ResourceType.LAMBDA) { + lambdaResources.add((LambdaResourceAllocation) res); + } + } + return lambdaResources; + } + + /** + * Tests initial free bandwidth for a link. + */ + @Test + public void testInitialBandwidth() { + final Set freeRes = store.getFreeResources(link1); + assertNotNull(freeRes); + + final BandwidthResourceAllocation alloc = getBandwidthObj(freeRes); + assertNotNull(alloc); + + assertEquals(new BandwidthResource(Bandwidth.mbps(1000.0)), alloc.bandwidth()); + } + + /** + * Tests initial free lambda for a link. + */ + @Test + public void testInitialLambdas() { + final Set freeRes = store.getFreeResources(link3); + assertNotNull(freeRes); + + final Set res = getLambdaObjs(freeRes); + assertNotNull(res); + assertEquals(80, res.size()); + } + + public static class MockLinkResourceBandwidthAllocations implements LinkResourceAllocations { + final double allocationAmount; + + MockLinkResourceBandwidthAllocations(Double allocationAmount) { + this.allocationAmount = allocationAmount; + } + @Override + public Set getResourceAllocation(Link link) { + final ResourceAllocation allocation = + new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.bps(allocationAmount))); + final Set allocations = new HashSet<>(); + allocations.add(allocation); + return allocations; + } + + @Override + public IntentId intentId() { + return null; + } + + @Override + public Collection links() { + return ImmutableSet.of(newLink("of:1", 1, "of:2", 2)); + } + + @Override + public Set resources() { + return null; + } + + @Override + public ResourceType type() { + return null; + } + } + + public static class MockLinkResourceLambdaAllocations implements LinkResourceAllocations { + final int allocatedLambda; + + MockLinkResourceLambdaAllocations(int allocatedLambda) { + this.allocatedLambda = allocatedLambda; + } + @Override + public Set getResourceAllocation(Link link) { + final ResourceAllocation allocation = + new LambdaResourceAllocation(LambdaResource.valueOf(allocatedLambda)); + final Set allocations = new HashSet<>(); + allocations.add(allocation); + return allocations; + } + + @Override + public IntentId intentId() { + return null; + } + + @Override + public Collection links() { + return ImmutableSet.of(newLink("of:1", 1, "of:2", 2)); + } + + @Override + public Set resources() { + return null; + } + + @Override + public ResourceType type() { + return null; + } + } + + /** + * Tests a successful bandwidth allocation. + */ + @Test + public void testSuccessfulBandwidthAllocation() { + final LinkResourceAllocations allocations = + new MockLinkResourceBandwidthAllocations(900.0); + store.allocateResources(allocations); + } + + /** + * Tests an unsuccessful bandwidth allocation. + */ + @Test + public void testUnsuccessfulBandwidthAllocation() { + final LinkResourceAllocations allocations = + new MockLinkResourceBandwidthAllocations(2000000000.0); + boolean gotException = false; + try { + store.allocateResources(allocations); + } catch (ResourceAllocationException rae) { + assertEquals(true, rae.getMessage().contains("Unable to allocate bandwidth for link")); + gotException = true; + } + assertEquals(true, gotException); + } + + /** + * Tests a successful lambda allocation. + */ + @Test + public void testSuccessfulLambdaAllocation() { + final LinkResourceAllocations allocations = + new MockLinkResourceLambdaAllocations(1); + store.allocateResources(allocations); + } + + /** + * Tests an unsuccessful lambda allocation. + */ + @Test + public void testUnsuccessfulLambdaAllocation() { + final LinkResourceAllocations allocations = + new MockLinkResourceLambdaAllocations(1); + store.allocateResources(allocations); + + boolean gotException = false; + + try { + store.allocateResources(allocations); + } catch (ResourceAllocationException rae) { + assertEquals(true, rae.getMessage().contains("Unable to allocate lambda for link")); + gotException = true; + } + assertEquals(true, gotException); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java new file mode 100644 index 00000000..d0be2b1f --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java @@ -0,0 +1,366 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Multimaps; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.AnnotationsUtil; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Link.Type; +import org.onosproject.net.LinkKey; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkDescription; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.LinkStore; +import org.onosproject.net.link.LinkStoreDelegate; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.store.AbstractStore; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static com.google.common.base.Predicates.notNull; +import static com.google.common.base.Verify.verifyNotNull; +import static com.google.common.collect.Multimaps.synchronizedSetMultimap; +import static org.onosproject.net.DefaultAnnotations.merge; +import static org.onosproject.net.DefaultAnnotations.union; +import static org.onosproject.net.Link.State.ACTIVE; +import static org.onosproject.net.Link.State.INACTIVE; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.Link.Type.INDIRECT; +import static org.onosproject.net.LinkKey.linkKey; +import static org.onosproject.net.link.LinkEvent.Type.*; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Manages inventory of infrastructure links using trivial in-memory structures + * implementation. + */ +@Component(immediate = true) +@Service +public class SimpleLinkStore + extends AbstractStore + implements LinkStore { + + private final Logger log = getLogger(getClass()); + + // Link inventory + private final ConcurrentMap> + linkDescs = new ConcurrentHashMap<>(); + + // Link instance cache + private final ConcurrentMap links = new ConcurrentHashMap<>(); + + // Egress and ingress link sets + private final SetMultimap srcLinks = createSynchronizedHashMultiMap(); + private final SetMultimap dstLinks = createSynchronizedHashMultiMap(); + + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + linkDescs.clear(); + links.clear(); + srcLinks.clear(); + dstLinks.clear(); + log.info("Stopped"); + } + + @Override + public int getLinkCount() { + return links.size(); + } + + @Override + public Iterable getLinks() { + return Collections.unmodifiableCollection(links.values()); + } + + @Override + public Set getDeviceEgressLinks(DeviceId deviceId) { + // lock for iteration + synchronized (srcLinks) { + return FluentIterable.from(srcLinks.get(deviceId)) + .transform(lookupLink()) + .filter(notNull()) + .toSet(); + } + } + + @Override + public Set getDeviceIngressLinks(DeviceId deviceId) { + // lock for iteration + synchronized (dstLinks) { + return FluentIterable.from(dstLinks.get(deviceId)) + .transform(lookupLink()) + .filter(notNull()) + .toSet(); + } + } + + @Override + public Link getLink(ConnectPoint src, ConnectPoint dst) { + return links.get(linkKey(src, dst)); + } + + @Override + public Set getEgressLinks(ConnectPoint src) { + Set egress = new HashSet<>(); + synchronized (srcLinks) { + for (LinkKey linkKey : srcLinks.get(src.deviceId())) { + if (linkKey.src().equals(src)) { + egress.add(links.get(linkKey)); + } + } + } + return egress; + } + + @Override + public Set getIngressLinks(ConnectPoint dst) { + Set ingress = new HashSet<>(); + synchronized (dstLinks) { + for (LinkKey linkKey : dstLinks.get(dst.deviceId())) { + if (linkKey.dst().equals(dst)) { + ingress.add(links.get(linkKey)); + } + } + } + return ingress; + } + + @Override + public LinkEvent createOrUpdateLink(ProviderId providerId, + LinkDescription linkDescription) { + LinkKey key = linkKey(linkDescription.src(), linkDescription.dst()); + + Map descs = getOrCreateLinkDescriptions(key); + synchronized (descs) { + final Link oldLink = links.get(key); + // update description + createOrUpdateLinkDescription(descs, providerId, linkDescription); + final Link newLink = composeLink(descs); + if (oldLink == null) { + return createLink(key, newLink); + } + return updateLink(key, oldLink, newLink); + } + } + + @Override + public LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst) { + Link link = getLink(src, dst); + if (link == null) { + return null; + } + + if (link.isDurable()) { + return link.state() == INACTIVE ? null : + updateLink(linkKey(link.src(), link.dst()), link, + new DefaultLink(link.providerId(), + link.src(), link.dst(), + link.type(), INACTIVE, + link.isDurable(), + link.annotations())); + } + return removeLink(src, dst); + } + + // Guarded by linkDescs value (=locking each Link) + private LinkDescription createOrUpdateLinkDescription( + Map descs, + ProviderId providerId, + LinkDescription linkDescription) { + + // merge existing attributes and merge + LinkDescription oldDesc = descs.get(providerId); + LinkDescription newDesc = linkDescription; + if (oldDesc != null) { + // we only allow transition from INDIRECT -> DIRECT + final Type newType; + if (oldDesc.type() == DIRECT) { + newType = DIRECT; + } else { + newType = linkDescription.type(); + } + SparseAnnotations merged = union(oldDesc.annotations(), + linkDescription.annotations()); + newDesc = new DefaultLinkDescription(linkDescription.src(), + linkDescription.dst(), + newType, merged); + } + return descs.put(providerId, newDesc); + } + + // Creates and stores the link and returns the appropriate event. + // Guarded by linkDescs value (=locking each Link) + private LinkEvent createLink(LinkKey key, Link newLink) { + links.put(key, newLink); + srcLinks.put(newLink.src().deviceId(), key); + dstLinks.put(newLink.dst().deviceId(), key); + return new LinkEvent(LINK_ADDED, newLink); + } + + // Updates, if necessary the specified link and returns the appropriate event. + // Guarded by linkDescs value (=locking each Link) + private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) { + if (oldLink.state() != newLink.state() || + (oldLink.type() == INDIRECT && newLink.type() == DIRECT) || + !AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) { + + links.put(key, newLink); + // strictly speaking following can be ommitted + srcLinks.put(oldLink.src().deviceId(), key); + dstLinks.put(oldLink.dst().deviceId(), key); + return new LinkEvent(LINK_UPDATED, newLink); + } + return null; + } + + @Override + public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) { + final LinkKey key = linkKey(src, dst); + Map descs = getOrCreateLinkDescriptions(key); + synchronized (descs) { + Link link = links.remove(key); + descs.clear(); + if (link != null) { + srcLinks.remove(link.src().deviceId(), key); + dstLinks.remove(link.dst().deviceId(), key); + return new LinkEvent(LINK_REMOVED, link); + } + return null; + } + } + + /** + * Creates concurrent readable, synchronized HashMultimap. + * + * @return SetMultimap + */ + private static SetMultimap createSynchronizedHashMultiMap() { + return synchronizedSetMultimap( + Multimaps.newSetMultimap(new ConcurrentHashMap>(), + () -> Sets.newConcurrentHashSet())); + } + + /** + * @return primary ProviderID, or randomly chosen one if none exists + */ + // Guarded by linkDescs value (=locking each Link) + private ProviderId getBaseProviderId(Map providerDescs) { + + ProviderId fallBackPrimary = null; + for (Entry e : providerDescs.entrySet()) { + if (!e.getKey().isAncillary()) { + return e.getKey(); + } else if (fallBackPrimary == null) { + // pick randomly as a fallback in case there is no primary + fallBackPrimary = e.getKey(); + } + } + return fallBackPrimary; + } + + // Guarded by linkDescs value (=locking each Link) + private Link composeLink(Map descs) { + ProviderId primary = getBaseProviderId(descs); + LinkDescription base = descs.get(verifyNotNull(primary)); + + ConnectPoint src = base.src(); + ConnectPoint dst = base.dst(); + Type type = base.type(); + DefaultAnnotations annotations = DefaultAnnotations.builder().build(); + annotations = merge(annotations, base.annotations()); + + for (Entry e : descs.entrySet()) { + if (primary.equals(e.getKey())) { + continue; + } + + // TODO: should keep track of Description timestamp + // and only merge conflicting keys when timestamp is newer + // Currently assuming there will never be a key conflict between + // providers + + // annotation merging. not so efficient, should revisit later + annotations = merge(annotations, e.getValue().annotations()); + } + + boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true"); + return new DefaultLink(primary, src, dst, type, ACTIVE, isDurable, annotations); + } + + private Map getOrCreateLinkDescriptions(LinkKey key) { + Map r; + r = linkDescs.get(key); + if (r != null) { + return r; + } + r = new HashMap<>(); + final Map concurrentlyAdded; + concurrentlyAdded = linkDescs.putIfAbsent(key, r); + if (concurrentlyAdded == null) { + return r; + } else { + return concurrentlyAdded; + } + } + + private final Function lookupLink = new LookupLink(); + + private Function lookupLink() { + return lookupLink; + } + + private final class LookupLink implements Function { + @Override + public Link apply(LinkKey input) { + if (input == null) { + return null; + } else { + return links.get(input); + } + } + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java new file mode 100644 index 00000000..2d2b2759 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java @@ -0,0 +1,542 @@ +/* + * Copyright 2014 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.store.trivial; + +import com.google.common.collect.Iterables; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Link.Type; +import org.onosproject.net.LinkKey; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.LinkStore; +import org.onosproject.net.link.LinkStoreDelegate; +import org.onosproject.net.provider.ProviderId; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.Link.Type.*; +import static org.onosproject.net.link.LinkEvent.Type.*; +import static org.onosproject.net.NetTestTools.assertAnnotationsEquals; + +/** + * Test of the simple LinkStore implementation. + */ +public class SimpleLinkStoreTest { + + private static final ProviderId PID = new ProviderId("of", "foo"); + private static final ProviderId PIDA = new ProviderId("of", "bar", true); + private static final DeviceId DID1 = deviceId("of:foo"); + private static final DeviceId DID2 = deviceId("of:bar"); + + private static final PortNumber P1 = PortNumber.portNumber(1); + private static final PortNumber P2 = PortNumber.portNumber(2); + private static final PortNumber P3 = PortNumber.portNumber(3); + + private static final SparseAnnotations A1 = DefaultAnnotations.builder() + .set("A1", "a1") + .set("B1", "b1") + .build(); + private static final SparseAnnotations A1_2 = DefaultAnnotations.builder() + .remove("A1") + .set("B3", "b3") + .build(); + private static final SparseAnnotations A2 = DefaultAnnotations.builder() + .set("A2", "a2") + .set("B2", "b2") + .build(); + private static final SparseAnnotations A2_2 = DefaultAnnotations.builder() + .remove("A2") + .set("B4", "b4") + .build(); + + private static final SparseAnnotations DA1 = DefaultAnnotations.builder() + .set("A1", "a1") + .set("B1", "b1") + .set(AnnotationKeys.DURABLE, "true") + .build(); + private static final SparseAnnotations DA2 = DefaultAnnotations.builder() + .set("A2", "a2") + .set("B2", "b2") + .set(AnnotationKeys.DURABLE, "true") + .build(); + private static final SparseAnnotations NDA1 = DefaultAnnotations.builder() + .set("A1", "a1") + .set("B1", "b1") + .remove(AnnotationKeys.DURABLE) + .build(); + + + + private SimpleLinkStore simpleLinkStore; + private LinkStore linkStore; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + simpleLinkStore = new SimpleLinkStore(); + simpleLinkStore.activate(); + linkStore = simpleLinkStore; + } + + @After + public void tearDown() throws Exception { + simpleLinkStore.deactivate(); + } + + private void putLink(DeviceId srcId, PortNumber srcNum, + DeviceId dstId, PortNumber dstNum, + Type type, boolean isDurable, + SparseAnnotations... annotations) { + ConnectPoint src = new ConnectPoint(srcId, srcNum); + ConnectPoint dst = new ConnectPoint(dstId, dstNum); + linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type, + annotations)); + } + + private void putLink(LinkKey key, Type type, SparseAnnotations... annotations) { + putLink(key.src().deviceId(), key.src().port(), + key.dst().deviceId(), key.dst().port(), + type, false, annotations); + } + + private static void assertLink(DeviceId srcId, PortNumber srcNum, + DeviceId dstId, PortNumber dstNum, Type type, + Link link) { + assertEquals(srcId, link.src().deviceId()); + assertEquals(srcNum, link.src().port()); + assertEquals(dstId, link.dst().deviceId()); + assertEquals(dstNum, link.dst().port()); + assertEquals(type, link.type()); + } + + private static void assertLink(LinkKey key, Type type, Link link) { + assertLink(key.src().deviceId(), key.src().port(), + key.dst().deviceId(), key.dst().port(), + type, link); + } + + @Test + public final void testGetLinkCount() { + assertEquals("initialy empty", 0, linkStore.getLinkCount()); + + putLink(DID1, P1, DID2, P2, DIRECT, false); + putLink(DID2, P2, DID1, P1, DIRECT, false); + putLink(DID1, P1, DID2, P2, DIRECT, false); + + assertEquals("expecting 2 unique link", 2, linkStore.getLinkCount()); + } + + @Test + public final void testGetLinks() { + assertEquals("initialy empty", 0, + Iterables.size(linkStore.getLinks())); + + LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2)); + LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1)); + + putLink(linkId1, DIRECT); + putLink(linkId2, DIRECT); + putLink(linkId1, DIRECT); + + assertEquals("expecting 2 unique link", 2, + Iterables.size(linkStore.getLinks())); + + Map links = new HashMap<>(); + for (Link link : linkStore.getLinks()) { + links.put(LinkKey.linkKey(link), link); + } + + assertLink(linkId1, DIRECT, links.get(linkId1)); + assertLink(linkId2, DIRECT, links.get(linkId2)); + } + + @Test + public final void testGetDeviceEgressLinks() { + LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2)); + LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1)); + LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3)); + + putLink(linkId1, DIRECT); + putLink(linkId2, DIRECT); + putLink(linkId3, DIRECT); + + // DID1,P1 => DID2,P2 + // DID2,P2 => DID1,P1 + // DID1,P2 => DID2,P3 + + Set links1 = linkStore.getDeviceEgressLinks(DID1); + assertEquals(2, links1.size()); + // check + + Set links2 = linkStore.getDeviceEgressLinks(DID2); + assertEquals(1, links2.size()); + assertLink(linkId2, DIRECT, links2.iterator().next()); + } + + @Test + public final void testGetDeviceIngressLinks() { + LinkKey linkId1 = LinkKey.linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2)); + LinkKey linkId2 = LinkKey.linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1)); + LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3)); + + putLink(linkId1, DIRECT); + putLink(linkId2, DIRECT); + putLink(linkId3, DIRECT); + + // DID1,P1 => DID2,P2 + // DID2,P2 => DID1,P1 + // DID1,P2 => DID2,P3 + + Set links1 = linkStore.getDeviceIngressLinks(DID2); + assertEquals(2, links1.size()); + // check + + Set links2 = linkStore.getDeviceIngressLinks(DID1); + assertEquals(1, links2.size()); + assertLink(linkId2, DIRECT, links2.iterator().next()); + } + + @Test + public final void testGetLink() { + ConnectPoint src = new ConnectPoint(DID1, P1); + ConnectPoint dst = new ConnectPoint(DID2, P2); + LinkKey linkId1 = LinkKey.linkKey(src, dst); + + putLink(linkId1, DIRECT); + + Link link = linkStore.getLink(src, dst); + assertLink(linkId1, DIRECT, link); + + assertNull("There shouldn't be reverese link", + linkStore.getLink(dst, src)); + } + + @Test + public final void testGetEgressLinks() { + final ConnectPoint d1P1 = new ConnectPoint(DID1, P1); + final ConnectPoint d2P2 = new ConnectPoint(DID2, P2); + LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2); + LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1); + LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3)); + + putLink(linkId1, DIRECT); + putLink(linkId2, DIRECT); + putLink(linkId3, DIRECT); + + // DID1,P1 => DID2,P2 + // DID2,P2 => DID1,P1 + // DID1,P2 => DID2,P3 + + Set links1 = linkStore.getEgressLinks(d1P1); + assertEquals(1, links1.size()); + assertLink(linkId1, DIRECT, links1.iterator().next()); + + Set links2 = linkStore.getEgressLinks(d2P2); + assertEquals(1, links2.size()); + assertLink(linkId2, DIRECT, links2.iterator().next()); + } + + @Test + public final void testGetIngressLinks() { + final ConnectPoint d1P1 = new ConnectPoint(DID1, P1); + final ConnectPoint d2P2 = new ConnectPoint(DID2, P2); + LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2); + LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1); + LinkKey linkId3 = LinkKey.linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3)); + + putLink(linkId1, DIRECT); + putLink(linkId2, DIRECT); + putLink(linkId3, DIRECT); + + // DID1,P1 => DID2,P2 + // DID2,P2 => DID1,P1 + // DID1,P2 => DID2,P3 + + Set links1 = linkStore.getIngressLinks(d2P2); + assertEquals(1, links1.size()); + assertLink(linkId1, DIRECT, links1.iterator().next()); + + Set links2 = linkStore.getIngressLinks(d1P1); + assertEquals(1, links2.size()); + assertLink(linkId2, DIRECT, links2.iterator().next()); + } + + @Test + public final void testCreateOrUpdateLink() { + ConnectPoint src = new ConnectPoint(DID1, P1); + ConnectPoint dst = new ConnectPoint(DID2, P2); + + // add link + LinkEvent event = linkStore.createOrUpdateLink(PID, + new DefaultLinkDescription(src, dst, INDIRECT)); + + assertLink(DID1, P1, DID2, P2, INDIRECT, event.subject()); + assertEquals(LINK_ADDED, event.type()); + + // update link type + LinkEvent event2 = linkStore.createOrUpdateLink(PID, + new DefaultLinkDescription(src, dst, DIRECT)); + + assertLink(DID1, P1, DID2, P2, DIRECT, event2.subject()); + assertEquals(LINK_UPDATED, event2.type()); + + // no change + LinkEvent event3 = linkStore.createOrUpdateLink(PID, + new DefaultLinkDescription(src, dst, DIRECT)); + + assertNull("No change event expected", event3); + } + + @Test + public final void testCreateOrUpdateLinkAncillary() { + ConnectPoint src = new ConnectPoint(DID1, P1); + ConnectPoint dst = new ConnectPoint(DID2, P2); + + // add Ancillary link + LinkEvent event = linkStore.createOrUpdateLink(PIDA, + new DefaultLinkDescription(src, dst, INDIRECT, A1)); + + assertNotNull("Ancillary only link is ignored", event); + + // add Primary link + LinkEvent event2 = linkStore.createOrUpdateLink(PID, + new DefaultLinkDescription(src, dst, INDIRECT, A2)); + + assertLink(DID1, P1, DID2, P2, INDIRECT, event2.subject()); + assertAnnotationsEquals(event2.subject().annotations(), A2, A1); + assertEquals(LINK_UPDATED, event2.type()); + + // update link type + LinkEvent event3 = linkStore.createOrUpdateLink(PID, + new DefaultLinkDescription(src, dst, DIRECT, A2)); + assertLink(DID1, P1, DID2, P2, DIRECT, event3.subject()); + assertAnnotationsEquals(event3.subject().annotations(), A2, A1); + assertEquals(LINK_UPDATED, event3.type()); + + + // no change + LinkEvent event4 = linkStore.createOrUpdateLink(PID, + new DefaultLinkDescription(src, dst, DIRECT)); + assertNull("No change event expected", event4); + + // update link annotation (Primary) + LinkEvent event5 = linkStore.createOrUpdateLink(PID, + new DefaultLinkDescription(src, dst, DIRECT, A2_2)); + assertLink(DID1, P1, DID2, P2, DIRECT, event5.subject()); + assertAnnotationsEquals(event5.subject().annotations(), A2, A2_2, A1); + assertEquals(LINK_UPDATED, event5.type()); + + // update link annotation (Ancillary) + LinkEvent event6 = linkStore.createOrUpdateLink(PIDA, + new DefaultLinkDescription(src, dst, DIRECT, A1_2)); + assertLink(DID1, P1, DID2, P2, DIRECT, event6.subject()); + assertAnnotationsEquals(event6.subject().annotations(), A2, A2_2, A1, A1_2); + assertEquals(LINK_UPDATED, event6.type()); + + // update link type (Ancillary) : ignored + LinkEvent event7 = linkStore.createOrUpdateLink(PIDA, + new DefaultLinkDescription(src, dst, EDGE)); + assertNull("Ancillary change other than annotation is ignored", event7); + } + + + @Test + public final void testRemoveOrDownLink() { + removeOrDownLink(false); + } + + @Test + public final void testRemoveOrDownLinkDurable() { + removeOrDownLink(true); + } + + private void removeOrDownLink(boolean isDurable) { + final ConnectPoint d1P1 = new ConnectPoint(DID1, P1); + final ConnectPoint d2P2 = new ConnectPoint(DID2, P2); + LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2); + LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1); + + putLink(linkId1, DIRECT, isDurable ? DA1 : A1); + putLink(linkId2, DIRECT, isDurable ? DA2 : A2); + + // DID1,P1 => DID2,P2 + // DID2,P2 => DID1,P1 + // DID1,P2 => DID2,P3 + + LinkEvent event = linkStore.removeOrDownLink(d1P1, d2P2); + assertEquals(isDurable ? LINK_UPDATED : LINK_REMOVED, event.type()); + assertAnnotationsEquals(event.subject().annotations(), isDurable ? DA1 : A1); + LinkEvent event2 = linkStore.removeOrDownLink(d1P1, d2P2); + assertNull(event2); + + assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1)); + assertAnnotationsEquals(linkStore.getLink(d2P2, d1P1).annotations(), + isDurable ? DA2 : A2); + + // annotations, etc. should not survive remove + if (!isDurable) { + putLink(linkId1, DIRECT); + assertLink(linkId1, DIRECT, linkStore.getLink(d1P1, d2P2)); + assertAnnotationsEquals(linkStore.getLink(d1P1, d2P2).annotations()); + } + } + + @Test + public final void testRemoveLink() { + final ConnectPoint d1P1 = new ConnectPoint(DID1, P1); + final ConnectPoint d2P2 = new ConnectPoint(DID2, P2); + LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2); + LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1); + + putLink(linkId1, DIRECT, A1); + putLink(linkId2, DIRECT, A2); + + // DID1,P1 => DID2,P2 + // DID2,P2 => DID1,P1 + // DID1,P2 => DID2,P3 + + LinkEvent event = linkStore.removeLink(d1P1, d2P2); + assertEquals(LINK_REMOVED, event.type()); + assertAnnotationsEquals(event.subject().annotations(), A1); + LinkEvent event2 = linkStore.removeLink(d1P1, d2P2); + assertNull(event2); + + assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1)); + assertAnnotationsEquals(linkStore.getLink(d2P2, d1P1).annotations(), A2); + + // annotations, etc. should not survive remove + putLink(linkId1, DIRECT); + assertLink(linkId1, DIRECT, linkStore.getLink(d1P1, d2P2)); + assertAnnotationsEquals(linkStore.getLink(d1P1, d2P2).annotations()); + } + + @Test + public final void testAncillaryVisible() { + ConnectPoint src = new ConnectPoint(DID1, P1); + ConnectPoint dst = new ConnectPoint(DID2, P2); + + // add Ancillary link + linkStore.createOrUpdateLink(PIDA, + new DefaultLinkDescription(src, dst, INDIRECT, A1)); + + // Ancillary only link should not be visible + assertEquals(1, linkStore.getLinkCount()); + assertNotNull(linkStore.getLink(src, dst)); + } + + @Test + public void testDurableToNonDurable() { + final ConnectPoint d1P1 = new ConnectPoint(DID1, P1); + final ConnectPoint d2P2 = new ConnectPoint(DID2, P2); + LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2); + + putLink(linkId1, DIRECT, DA1); + assertTrue("should be be durable", linkStore.getLink(d1P1, d2P2).isDurable()); + putLink(linkId1, DIRECT, NDA1); + assertFalse("should not be durable", linkStore.getLink(d1P1, d2P2).isDurable()); + } + + @Test + public void testNonDurableToDurable() { + final ConnectPoint d1P1 = new ConnectPoint(DID1, P1); + final ConnectPoint d2P2 = new ConnectPoint(DID2, P2); + LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2); + + putLink(linkId1, DIRECT, A1); + assertFalse("should not be durable", linkStore.getLink(d1P1, d2P2).isDurable()); + putLink(linkId1, DIRECT, DA1); + assertTrue("should be durable", linkStore.getLink(d1P1, d2P2).isDurable()); + } + + // If Delegates should be called only on remote events, + // then Simple* should never call them, thus not test required. + @Ignore("Ignore until Delegate spec. is clear.") + @Test + public final void testEvents() throws InterruptedException { + + final ConnectPoint d1P1 = new ConnectPoint(DID1, P1); + final ConnectPoint d2P2 = new ConnectPoint(DID2, P2); + final LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2); + + final CountDownLatch addLatch = new CountDownLatch(1); + LinkStoreDelegate checkAdd = new LinkStoreDelegate() { + @Override + public void notify(LinkEvent event) { + assertEquals(LINK_ADDED, event.type()); + assertLink(linkId1, INDIRECT, event.subject()); + addLatch.countDown(); + } + }; + final CountDownLatch updateLatch = new CountDownLatch(1); + LinkStoreDelegate checkUpdate = new LinkStoreDelegate() { + @Override + public void notify(LinkEvent event) { + assertEquals(LINK_UPDATED, event.type()); + assertLink(linkId1, DIRECT, event.subject()); + updateLatch.countDown(); + } + }; + final CountDownLatch removeLatch = new CountDownLatch(1); + LinkStoreDelegate checkRemove = new LinkStoreDelegate() { + @Override + public void notify(LinkEvent event) { + assertEquals(LINK_REMOVED, event.type()); + assertLink(linkId1, DIRECT, event.subject()); + removeLatch.countDown(); + } + }; + + linkStore.setDelegate(checkAdd); + putLink(linkId1, INDIRECT); + assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS)); + + linkStore.unsetDelegate(checkAdd); + linkStore.setDelegate(checkUpdate); + putLink(linkId1, DIRECT); + assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS)); + + linkStore.unsetDelegate(checkUpdate); + linkStore.setDelegate(checkRemove); + linkStore.removeOrDownLink(d1P1, d2P2); + assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS)); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java new file mode 100644 index 00000000..ef92ded2 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java @@ -0,0 +1,388 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED; +import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.joda.time.DateTime; +import org.onlab.packet.IpAddress; +import org.onosproject.cluster.ClusterEventListener; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.ControllerNode.State; +import org.onosproject.cluster.DefaultControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.cluster.RoleInfo; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipStore; +import org.onosproject.mastership.MastershipStoreDelegate; +import org.onosproject.mastership.MastershipTerm; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.store.AbstractStore; +import org.slf4j.Logger; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +/** + * Manages inventory of controller mastership over devices using + * trivial, non-distributed in-memory structures implementation. + */ +@Component(immediate = true) +@Service +public class SimpleMastershipStore + extends AbstractStore + implements MastershipStore { + + private final Logger log = getLogger(getClass()); + + private static final int NOTHING = 0; + private static final int INIT = 1; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + //devices mapped to their masters, to emulate multiple nodes + protected final Map masterMap = new HashMap<>(); + //emulate backups with pile of nodes + protected final Map> backups = new HashMap<>(); + //terms + protected final Map termMap = new HashMap<>(); + + @Activate + public void activate() { + if (clusterService == null) { + // just for ease of unit test + final ControllerNode instance = + new DefaultControllerNode(new NodeId("local"), + IpAddress.valueOf("127.0.0.1")); + + clusterService = new ClusterService() { + + private final DateTime creationTime = DateTime.now(); + + @Override + public ControllerNode getLocalNode() { + return instance; + } + + @Override + public Set getNodes() { + return ImmutableSet.of(instance); + } + + @Override + public ControllerNode getNode(NodeId nodeId) { + if (instance.id().equals(nodeId)) { + return instance; + } + return null; + } + + @Override + public State getState(NodeId nodeId) { + if (instance.id().equals(nodeId)) { + return State.ACTIVE; + } else { + return State.INACTIVE; + } + } + + @Override + public DateTime getLastUpdated(NodeId nodeId) { + return creationTime; + } + + @Override + public void addListener(ClusterEventListener listener) { + } + + @Override + public void removeListener(ClusterEventListener listener) { + } + }; + } + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + @Override + public synchronized CompletableFuture setMaster(NodeId nodeId, DeviceId deviceId) { + + MastershipRole role = getRole(nodeId, deviceId); + switch (role) { + case MASTER: + // no-op + return CompletableFuture.completedFuture(null); + case STANDBY: + case NONE: + NodeId prevMaster = masterMap.put(deviceId, nodeId); + incrementTerm(deviceId); + removeFromBackups(deviceId, nodeId); + addToBackup(deviceId, prevMaster); + break; + default: + log.warn("unknown Mastership Role {}", role); + return null; + } + + return CompletableFuture.completedFuture( + new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId))); + } + + @Override + public NodeId getMaster(DeviceId deviceId) { + return masterMap.get(deviceId); + } + + // synchronized for atomic read + @Override + public synchronized RoleInfo getNodes(DeviceId deviceId) { + return new RoleInfo(masterMap.get(deviceId), + backups.getOrDefault(deviceId, ImmutableList.of())); + } + + @Override + public Set getDevices(NodeId nodeId) { + Set ids = new HashSet<>(); + for (Map.Entry d : masterMap.entrySet()) { + if (Objects.equals(d.getValue(), nodeId)) { + ids.add(d.getKey()); + } + } + return ids; + } + + @Override + public synchronized CompletableFuture requestRole(DeviceId deviceId) { + //query+possible reelection + NodeId node = clusterService.getLocalNode().id(); + MastershipRole role = getRole(node, deviceId); + + switch (role) { + case MASTER: + return CompletableFuture.completedFuture(MastershipRole.MASTER); + case STANDBY: + if (getMaster(deviceId) == null) { + // no master => become master + masterMap.put(deviceId, node); + incrementTerm(deviceId); + // remove from backup list + removeFromBackups(deviceId, node); + notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, + getNodes(deviceId))); + return CompletableFuture.completedFuture(MastershipRole.MASTER); + } + return CompletableFuture.completedFuture(MastershipRole.STANDBY); + case NONE: + if (getMaster(deviceId) == null) { + // no master => become master + masterMap.put(deviceId, node); + incrementTerm(deviceId); + notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, + getNodes(deviceId))); + return CompletableFuture.completedFuture(MastershipRole.MASTER); + } + // add to backup list + if (addToBackup(deviceId, node)) { + notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId, + getNodes(deviceId))); + } + return CompletableFuture.completedFuture(MastershipRole.STANDBY); + default: + log.warn("unknown Mastership Role {}", role); + } + return CompletableFuture.completedFuture(role); + } + + // add to backup if not there already, silently ignores null node + private synchronized boolean addToBackup(DeviceId deviceId, NodeId nodeId) { + boolean modified = false; + List stbys = backups.getOrDefault(deviceId, new ArrayList<>()); + if (nodeId != null && !stbys.contains(nodeId)) { + stbys.add(nodeId); + modified = true; + } + backups.put(deviceId, stbys); + return modified; + } + + private synchronized boolean removeFromBackups(DeviceId deviceId, NodeId node) { + List stbys = backups.getOrDefault(deviceId, new ArrayList<>()); + boolean modified = stbys.remove(node); + backups.put(deviceId, stbys); + return modified; + } + + private synchronized void incrementTerm(DeviceId deviceId) { + AtomicInteger term = termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING)); + term.incrementAndGet(); + termMap.put(deviceId, term); + } + + @Override + public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) { + //just query + NodeId current = masterMap.get(deviceId); + MastershipRole role; + + if (current != null && current.equals(nodeId)) { + return MastershipRole.MASTER; + } + + if (backups.getOrDefault(deviceId, Collections.emptyList()).contains(nodeId)) { + role = MastershipRole.STANDBY; + } else { + role = MastershipRole.NONE; + } + return role; + } + + // synchronized for atomic read + @Override + public synchronized MastershipTerm getTermFor(DeviceId deviceId) { + if ((termMap.get(deviceId) == null)) { + return MastershipTerm.of(masterMap.get(deviceId), NOTHING); + } + return MastershipTerm.of( + masterMap.get(deviceId), termMap.get(deviceId).get()); + } + + @Override + public synchronized CompletableFuture setStandby(NodeId nodeId, DeviceId deviceId) { + MastershipRole role = getRole(nodeId, deviceId); + switch (role) { + case MASTER: + NodeId backup = reelect(deviceId, nodeId); + if (backup == null) { + // no master alternative + masterMap.remove(deviceId); + // TODO: Should there be new event type for no MASTER? + return CompletableFuture.completedFuture( + new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId))); + } else { + NodeId prevMaster = masterMap.put(deviceId, backup); + incrementTerm(deviceId); + addToBackup(deviceId, prevMaster); + return CompletableFuture.completedFuture( + new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId))); + } + + case STANDBY: + case NONE: + boolean modified = addToBackup(deviceId, nodeId); + if (modified) { + return CompletableFuture.completedFuture( + new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId))); + } + break; + + default: + log.warn("unknown Mastership Role {}", role); + } + return null; + } + + //dumbly selects next-available node that's not the current one + //emulate leader election + private synchronized NodeId reelect(DeviceId did, NodeId nodeId) { + List stbys = backups.getOrDefault(did, Collections.emptyList()); + NodeId backup = null; + for (NodeId n : stbys) { + if (!n.equals(nodeId)) { + backup = n; + break; + } + } + stbys.remove(backup); + return backup; + } + + @Override + public synchronized CompletableFuture relinquishRole(NodeId nodeId, DeviceId deviceId) { + MastershipRole role = getRole(nodeId, deviceId); + switch (role) { + case MASTER: + NodeId backup = reelect(deviceId, nodeId); + masterMap.put(deviceId, backup); + incrementTerm(deviceId); + return CompletableFuture.completedFuture( + new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId))); + + case STANDBY: + if (removeFromBackups(deviceId, nodeId)) { + return CompletableFuture.completedFuture( + new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId))); + } + break; + + case NONE: + break; + + default: + log.warn("unknown Mastership Role {}", role); + } + return CompletableFuture.completedFuture(null); + } + + @Override + public synchronized void relinquishAllRole(NodeId nodeId) { + List> eventFutures = new ArrayList<>(); + Set toRelinquish = new HashSet<>(); + + masterMap.entrySet().stream() + .filter(entry -> nodeId.equals(entry.getValue())) + .forEach(entry -> toRelinquish.add(entry.getKey())); + + backups.entrySet().stream() + .filter(entry -> entry.getValue().contains(nodeId)) + .forEach(entry -> toRelinquish.add(entry.getKey())); + + toRelinquish.forEach(deviceId -> { + eventFutures.add(relinquishRole(nodeId, deviceId)); + }); + + eventFutures.forEach(future -> { + future.whenComplete((event, error) -> { + notifyDelegate(event); + }); + }); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java new file mode 100644 index 00000000..672fc503 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java @@ -0,0 +1,184 @@ +/* + * Copyright 2014 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.store.trivial; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.cluster.NodeId; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipTerm; +import org.onosproject.net.DeviceId; + +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Futures; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.onosproject.mastership.MastershipEvent.Type.*; +import static org.onosproject.net.MastershipRole.*; + +/** + * Test for the simple MastershipStore implementation. + */ +public class SimpleMastershipStoreTest { + + private static final DeviceId DID1 = DeviceId.deviceId("of:01"); + private static final DeviceId DID2 = DeviceId.deviceId("of:02"); + private static final DeviceId DID3 = DeviceId.deviceId("of:03"); + private static final DeviceId DID4 = DeviceId.deviceId("of:04"); + + private static final NodeId N1 = new NodeId("local"); + private static final NodeId N2 = new NodeId("other"); + + private SimpleMastershipStore sms; + + @Before + public void setUp() throws Exception { + sms = new SimpleMastershipStore(); + sms.activate(); + } + + @After + public void tearDown() throws Exception { + sms.deactivate(); + } + + @Test + public void getRole() { + //special case, no backup or master + put(DID1, N1, false, false); + assertEquals("wrong role", NONE, sms.getRole(N1, DID1)); + + //backup exists but we aren't mapped + put(DID2, N1, false, true); + assertEquals("wrong role", STANDBY, sms.getRole(N1, DID2)); + + //N2 is master + put(DID3, N2, true, true); + assertEquals("wrong role", MASTER, sms.getRole(N2, DID3)); + + //N2 is master but N1 is only in backups set + put(DID4, N1, false, true); + put(DID4, N2, true, false); + assertEquals("wrong role", STANDBY, sms.getRole(N1, DID4)); + } + + @Test + public void getMaster() { + put(DID3, N2, true, true); + assertEquals("wrong role", MASTER, sms.getRole(N2, DID3)); + assertEquals("wrong device", N2, sms.getMaster(DID3)); + } + + @Test + public void setMaster() { + put(DID1, N1, false, false); + assertEquals("wrong event", MASTER_CHANGED, Futures.getUnchecked(sms.setMaster(N1, DID1)).type()); + assertEquals("wrong role", MASTER, sms.getRole(N1, DID1)); + //set node that's already master - should be ignored + assertNull("wrong event", Futures.getUnchecked(sms.setMaster(N1, DID1))); + + //set STANDBY to MASTER + put(DID2, N1, false, true); + assertEquals("wrong role", STANDBY, sms.getRole(N1, DID2)); + assertEquals("wrong event", MASTER_CHANGED, Futures.getUnchecked(sms.setMaster(N1, DID2)).type()); + assertEquals("wrong role", MASTER, sms.getRole(N1, DID2)); + } + + @Test + public void getDevices() { + Set d = Sets.newHashSet(DID1, DID2); + + put(DID1, N2, true, true); + put(DID2, N2, true, true); + put(DID3, N1, true, true); + assertTrue("wrong devices", d.equals(sms.getDevices(N2))); + } + + @Test + public void getTermFor() { + put(DID1, N1, true, true); + assertEquals("wrong term", MastershipTerm.of(N1, 0), sms.getTermFor(DID1)); + + //switch to N2 and back - 2 term switches + sms.setMaster(N2, DID1); + sms.setMaster(N1, DID1); + assertEquals("wrong term", MastershipTerm.of(N1, 2), sms.getTermFor(DID1)); + } + + @Test + public void requestRole() { + //NONE - become MASTER + put(DID1, N1, false, false); + assertEquals("wrong role", MASTER, Futures.getUnchecked(sms.requestRole(DID1))); + + //was STANDBY - become MASTER + put(DID2, N1, false, true); + assertEquals("wrong role", MASTER, Futures.getUnchecked(sms.requestRole(DID2))); + + //other MASTER - stay STANDBY + put(DID3, N2, true, false); + assertEquals("wrong role", STANDBY, Futures.getUnchecked(sms.requestRole(DID3))); + + //local (N1) is MASTER - stay MASTER + put(DID4, N1, true, true); + assertEquals("wrong role", MASTER, Futures.getUnchecked(sms.requestRole(DID4))); + } + + @Test + public void unsetMaster() { + //NONE - record backup but take no other action + put(DID1, N1, false, false); + sms.setStandby(N1, DID1); + assertTrue("not backed up", sms.backups.get(DID1).contains(N1)); + int prev = sms.termMap.get(DID1).get(); + sms.setStandby(N1, DID1); + assertEquals("term should not change", prev, sms.termMap.get(DID1).get()); + + //no backup, MASTER + put(DID1, N1, true, false); + assertNull("expect no MASTER event", Futures.getUnchecked(sms.setStandby(N1, DID1)).roleInfo().master()); + assertNull("wrong node", sms.masterMap.get(DID1)); + + //backup, switch + sms.masterMap.clear(); + put(DID1, N1, true, true); + put(DID1, N2, false, true); + put(DID2, N2, true, true); + MastershipEvent event = Futures.getUnchecked(sms.setStandby(N1, DID1)); + assertEquals("wrong event", MASTER_CHANGED, event.type()); + assertEquals("wrong master", N2, event.roleInfo().master()); + } + + //helper to populate master/backup structures + private void put(DeviceId dev, NodeId node, boolean master, boolean backup) { + if (master) { + sms.masterMap.put(dev, node); + } else if (backup) { + List stbys = sms.backups.getOrDefault(dev, new ArrayList<>()); + stbys.add(node); + sms.backups.put(dev, stbys); + } + sms.termMap.put(dev, new AtomicInteger()); + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java new file mode 100644 index 00000000..4345abaf --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketEvent; +import org.onosproject.net.packet.PacketEvent.Type; +import org.onosproject.net.packet.PacketRequest; +import org.onosproject.net.packet.PacketStore; +import org.onosproject.net.packet.PacketStoreDelegate; +import org.onosproject.store.AbstractStore; + + +import java.util.Collections; +import java.util.Set; + +/** + * Simple single instance implementation of the packet store. + */ +@Component(immediate = true) +@Service +public class SimplePacketStore + extends AbstractStore + implements PacketStore { + + private Set requests = Sets.newConcurrentHashSet(); + + @Override + public void emit(OutboundPacket packet) { + notifyDelegate(new PacketEvent(Type.EMIT, packet)); + } + + @Override + public boolean requestPackets(PacketRequest request) { + return requests.add(request); + } + + @Override + public boolean cancelPackets(PacketRequest request) { + return requests.remove(request); + } + + @Override + public Set existingRequests() { + return Collections.unmodifiableSet(requests); + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java new file mode 100644 index 00000000..370686f9 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java @@ -0,0 +1,211 @@ +/* + * Copyright 2014-2015 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.store.trivial; + +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.statistic.StatisticStore; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Maintains statistics using RPC calls to collect stats from remote instances + * on demand. + */ +@Component(immediate = true) +@Service +public class SimpleStatisticStore implements StatisticStore { + + private final Logger log = getLogger(getClass()); + + private Map + representations = new ConcurrentHashMap<>(); + + private Map> previous = new ConcurrentHashMap<>(); + private Map> current = new ConcurrentHashMap<>(); + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + @Override + public void prepareForStatistics(FlowRule rule) { + ConnectPoint cp = buildConnectPoint(rule); + if (cp == null) { + return; + } + InternalStatisticRepresentation rep; + synchronized (representations) { + rep = getOrCreateRepresentation(cp); + } + rep.prepare(); + } + + @Override + public synchronized void removeFromStatistics(FlowRule rule) { + ConnectPoint cp = buildConnectPoint(rule); + if (cp == null) { + return; + } + InternalStatisticRepresentation rep = representations.get(cp); + if (rep != null && rep.remove(rule)) { + updatePublishedStats(cp, Collections.emptySet()); + } + Set values = current.get(cp); + if (values != null) { + values.remove(rule); + } + values = previous.get(cp); + if (values != null) { + values.remove(rule); + } + + } + + @Override + public void addOrUpdateStatistic(FlowEntry rule) { + ConnectPoint cp = buildConnectPoint(rule); + if (cp == null) { + return; + } + InternalStatisticRepresentation rep = representations.get(cp); + if (rep != null && rep.submit(rule)) { + updatePublishedStats(cp, rep.get()); + } + } + + private synchronized void updatePublishedStats(ConnectPoint cp, + Set flowEntries) { + Set curr = current.get(cp); + if (curr == null) { + curr = new HashSet<>(); + } + previous.put(cp, curr); + current.put(cp, flowEntries); + + } + + @Override + public Set getCurrentStatistic(ConnectPoint connectPoint) { + return getCurrentStatisticInternal(connectPoint); + } + + private synchronized Set getCurrentStatisticInternal(ConnectPoint connectPoint) { + return current.get(connectPoint); + } + + @Override + public Set getPreviousStatistic(ConnectPoint connectPoint) { + return getPreviousStatisticInternal(connectPoint); + } + + private synchronized Set getPreviousStatisticInternal(ConnectPoint connectPoint) { + return previous.get(connectPoint); + } + + private InternalStatisticRepresentation getOrCreateRepresentation(ConnectPoint cp) { + + if (representations.containsKey(cp)) { + return representations.get(cp); + } else { + InternalStatisticRepresentation rep = new InternalStatisticRepresentation(); + representations.put(cp, rep); + return rep; + } + + } + + private ConnectPoint buildConnectPoint(FlowRule rule) { + PortNumber port = getOutput(rule); + + if (port == null) { + return null; + } + ConnectPoint cp = new ConnectPoint(rule.deviceId(), port); + return cp; + } + + private PortNumber getOutput(FlowRule rule) { + for (Instruction i : rule.treatment().immediate()) { + if (i.type() == Instruction.Type.OUTPUT) { + Instructions.OutputInstruction out = (Instructions.OutputInstruction) i; + return out.port(); + } + if (i.type() == Instruction.Type.DROP) { + return PortNumber.P0; + } + } + return null; + } + + private class InternalStatisticRepresentation { + + private final AtomicInteger counter = new AtomicInteger(0); + private final Set rules = new HashSet<>(); + + public void prepare() { + counter.incrementAndGet(); + } + + public synchronized boolean remove(FlowRule rule) { + rules.remove(rule); + return counter.decrementAndGet() == 0; + } + + public synchronized boolean submit(FlowEntry rule) { + if (rules.contains(rule)) { + rules.remove(rule); + } + rules.add(rule); + if (counter.get() == 0) { + return true; + } else { + return counter.decrementAndGet() == 0; + } + } + + public synchronized Set get() { + counter.set(rules.size()); + return Sets.newHashSet(rules); + } + + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java new file mode 100644 index 00000000..6a89c019 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java @@ -0,0 +1,157 @@ +/* + * Copyright 2014 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.store.trivial; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.common.DefaultTopology; +import org.onosproject.event.Event; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.ClusterId; +import org.onosproject.net.topology.GraphDescription; +import org.onosproject.net.topology.LinkWeight; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyCluster; +import org.onosproject.net.topology.TopologyEvent; +import org.onosproject.net.topology.TopologyGraph; +import org.onosproject.net.topology.TopologyStore; +import org.onosproject.net.topology.TopologyStoreDelegate; +import org.onosproject.store.AbstractStore; +import org.slf4j.Logger; + +import java.util.List; +import java.util.Set; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Manages inventory of topology snapshots using trivial in-memory + * structures implementation. + */ +@Component(immediate = true) +@Service +public class SimpleTopologyStore + extends AbstractStore + implements TopologyStore { + + private final Logger log = getLogger(getClass()); + + private volatile DefaultTopology current; + + @Activate + public void activate() { + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + @Override + public Topology currentTopology() { + return current; + } + + @Override + public boolean isLatest(Topology topology) { + // Topology is current only if it is the same as our current topology + return topology == current; + } + + @Override + public TopologyGraph getGraph(Topology topology) { + return defaultTopology(topology).getGraph(); + } + + @Override + public Set getClusters(Topology topology) { + return defaultTopology(topology).getClusters(); + } + + @Override + public TopologyCluster getCluster(Topology topology, ClusterId clusterId) { + return defaultTopology(topology).getCluster(clusterId); + } + + @Override + public Set getClusterDevices(Topology topology, TopologyCluster cluster) { + return defaultTopology(topology).getClusterDevices(cluster); + } + + @Override + public Set getClusterLinks(Topology topology, TopologyCluster cluster) { + return defaultTopology(topology).getClusterLinks(cluster); + } + + @Override + public Set getPaths(Topology topology, DeviceId src, DeviceId dst) { + return defaultTopology(topology).getPaths(src, dst); + } + + @Override + public Set getPaths(Topology topology, DeviceId src, DeviceId dst, + LinkWeight weight) { + return defaultTopology(topology).getPaths(src, dst, weight); + } + + @Override + public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) { + return defaultTopology(topology).isInfrastructure(connectPoint); + } + + @Override + public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) { + return defaultTopology(topology).isBroadcastPoint(connectPoint); + } + + @Override + public TopologyEvent updateTopology(ProviderId providerId, + GraphDescription graphDescription, + List reasons) { + // First off, make sure that what we're given is indeed newer than + // what we already have. + if (current != null && graphDescription.timestamp() < current.time()) { + return null; + } + + // Have the default topology construct self from the description data. + DefaultTopology newTopology = + new DefaultTopology(providerId, graphDescription); + + // Promote the new topology to current and return a ready-to-send event. + synchronized (this) { + current = newTopology; + return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED, + current, reasons); + } + } + + // Validates the specified topology and returns it as a default + private DefaultTopology defaultTopology(Topology topology) { + if (topology instanceof DefaultTopology) { + return (DefaultTopology) topology; + } + throw new IllegalArgumentException("Topology class " + topology.getClass() + + " not supported"); + } + +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java new file mode 100644 index 00000000..2ee41945 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015 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.store.trivial; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ComparisonChain; +import org.onosproject.store.Timestamp; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * A Timestamp that derives its value from the system clock time (in ns) + * on the controller where it is generated. + */ +public class SystemClockTimestamp implements Timestamp { + + private final long nanoTimestamp; + + public SystemClockTimestamp() { + nanoTimestamp = System.nanoTime(); + } + + public SystemClockTimestamp(long timestamp) { + nanoTimestamp = timestamp; + } + + @Override + public int compareTo(Timestamp o) { + checkArgument(o instanceof SystemClockTimestamp, + "Must be SystemClockTimestamp", o); + SystemClockTimestamp that = (SystemClockTimestamp) o; + + return ComparisonChain.start() + .compare(this.nanoTimestamp, that.nanoTimestamp) + .result(); + } + @Override + public int hashCode() { + return Objects.hash(nanoTimestamp); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SystemClockTimestamp)) { + return false; + } + SystemClockTimestamp that = (SystemClockTimestamp) obj; + return Objects.equals(this.nanoTimestamp, that.nanoTimestamp); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("nanoTimestamp", nanoTimestamp) + .toString(); + } + + public long nanoTimestamp() { + return nanoTimestamp; + } + + public long systemTimestamp() { + return nanoTimestamp / 1_000_000; // convert ns to ms + } +} diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/package-info.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/package-info.java new file mode 100644 index 00000000..2b0c36b8 --- /dev/null +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 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. + */ + +/** + * Implementations of in-memory stores suitable for unit testing and + * experimentation; not for production use. + */ +package org.onosproject.store.trivial; diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json new file mode 100644 index 00000000..aaa72a72 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json @@ -0,0 +1,5 @@ +{ + "type":"AnnotationConstraint", + "key":"key", + "threshold":123.0 +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json new file mode 100644 index 00000000..340bdbac --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json @@ -0,0 +1,3 @@ +{ + "type":"AsymmetricPathConstraint" +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json new file mode 100644 index 00000000..34750061 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json @@ -0,0 +1,4 @@ +{ + "type":"BandwidthConstraint", + "bandwidth":345.678 +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json new file mode 100644 index 00000000..fedea251 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json @@ -0,0 +1,19 @@ +{ + "type": "HostToHostIntent", + "appId": "test", + "selector": {"criteria": []}, + "treatment": { + "instructions": [], + "deferred": [] + }, + "priority": 7, + "constraints": [ + { + "inclusive": false, + "types": ["OPTICAL"], + "type": "LinkTypeConstraint" + } + ], + "one": "00:00:00:00:00:02/-1", + "two": "00:00:00:00:00:05/-1" +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json new file mode 100644 index 00000000..4ac37631 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json @@ -0,0 +1,4 @@ +{ + "type":"LambdaConstraint", + "lambda":444 +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json new file mode 100644 index 00000000..1c46e5e8 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json @@ -0,0 +1,4 @@ +{ + "type":"LatencyConstraint", + "latencyMillis":111 +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json new file mode 100644 index 00000000..8b766da0 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json @@ -0,0 +1,5 @@ +{ + "inclusive":false, + "types":["DIRECT","OPTICAL"], + "type":"LinkTypeConstraint" +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json new file mode 100644 index 00000000..35dcb0fe --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json @@ -0,0 +1,4 @@ +{ + "type":"ObstacleConstraint", + "obstacles":["of:dev1","of:dev2","of:dev3"] +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json new file mode 100644 index 00000000..b941bef8 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json @@ -0,0 +1,38 @@ +{ + "type": "PointToPointIntent", + "appId": "test", + "selector": { + "criteria": [ + { + "type": "ETH_DST", + "mac": "11:22:33:44:55:66" + } + ] + }, + "treatment": { + "instructions": [ + { + "type": "L2MODIFICATION", + "subtype": "ETH_SRC", + "mac": "22:33:44:55:66:77" + } + ], + "deferred": [] + }, + "priority": 55, + "constraints": [ + { + "inclusive": false, + "types": ["OPTICAL"], + "type": "LinkTypeConstraint" + } + ], + "ingressPoint": { + "port": "1", + "device": "of:0000000000000001" + }, + "egressPoint": { + "port": "2", + "device": "of:0000000000000007" + } +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json new file mode 100644 index 00000000..7009cf98 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json @@ -0,0 +1,4 @@ +{ + "type":"WaypointConstraint", + "waypoints":["of:devA","of:devB","of:devC"] +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json new file mode 100644 index 00000000..1a96e92f --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json @@ -0,0 +1,44 @@ +{ + "priority":1, + "isPermanent":"false", + "timeout":1, + "deviceId":"of:0000000000000001", + "selector": + {"criteria": + [ + {"type":"IN_PORT", "port":23}, + {"type":"IN_PHY_PORT", "port":44}, + {"type":"METADATA", "metadata":123456}, + {"type":"ETH_TYPE","ethType":2054}, + {"type":"ETH_SRC","mac":"00:11:22:33:44:55"}, + {"type":"ETH_DST","mac":"00:11:22:33:44:55"}, + {"type":"VLAN_VID","vlanId":777}, + {"type":"VLAN_PCP","priority":3}, + {"type":"IP_DSCP","ipDscp":2}, + {"type":"IP_ECN","ipEcn":1}, + {"type":"IP_PROTO","protocol":4}, + {"type":"IPV4_SRC", "ip":"1.2.0.0/32"}, + {"type":"IPV4_DST", "ip":"2.2.0.0/32"}, + {"type":"IPV6_SRC", "ip":"3.2.0.0/32"}, + {"type":"IPV6_DST", "ip":"4.2.0.0/32"}, + {"type":"TCP_SRC", "tcpPort":80}, + {"type":"TCP_DST", "tcpPort":443}, + {"type":"UDP_SRC", "udpPort":180}, + {"type":"UDP_DST", "udpPort":1443}, + {"type":"SCTP_SRC", "sctpPort":280}, + {"type":"SCTP_DST", "sctpPort":2443}, + {"type":"ICMPV4_TYPE", "icmpType":24}, + {"type":"ICMPV4_CODE", "icmpCode":16}, + {"type":"ICMPV6_TYPE", "icmpv6Type":14}, + {"type":"ICMPV6_CODE", "icmpv6Code":6}, + {"type":"IPV6_FLABEL", "flowLabel":8}, + {"type":"IPV6_ND_TARGET", "targetAddress":"1111:2222:3333:4444:5555:6666:7777:8888"}, + {"type":"IPV6_ND_SLL", "mac":"00:11:22:33:44:56"}, + {"type":"IPV6_ND_TLL", "mac":"00:11:22:33:44:57"}, + {"type":"MPLS_LABEL", "label":123}, + {"type":"IPV6_EXTHDR", "exthdrFlags":99}, + {"type":"OCH_SIGID", "lambda":122}, + {"type":"TUNNEL_ID", "tunnelId":100} + ] + } +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json new file mode 100644 index 00000000..74b9546a --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json @@ -0,0 +1,39 @@ +{ + "priority":1, + "isPermanent":"false", + "timeout":1, + "deviceId":"of:0000000000000001", + "treatment": + { + "instructions": + [ + {"type":"OUTPUT","port":-3}, + {"type":"DROP"}, + {"type":"L2MODIFICATION","subtype":"ETH_SRC","mac":"12:34:56:78:90:12"}, + {"type":"L2MODIFICATION","subtype":"ETH_DST","mac":"98:76:54:32:01:00"}, + {"type":"L2MODIFICATION","subtype":"VLAN_ID","vlanId":22}, + {"type":"L2MODIFICATION","subtype":"VLAN_PCP","vlanPcp":1}, + {"type":"L2MODIFICATION","subtype":"MPLS_LABEL","label":1048575}, + {"type":"L2MODIFICATION","subtype":"MPLS_PUSH"}, + {"type":"L2MODIFICATION","subtype":"MPLS_POP"}, + {"type":"L2MODIFICATION","subtype":"DEC_MPLS_TTL"}, + {"type":"L2MODIFICATION","subtype":"VLAN_POP"}, + {"type":"L2MODIFICATION","subtype":"VLAN_PUSH"}, + {"type":"L2MODIFICATION","subtype":"TUNNEL_ID", "tunnelId":100}, + {"type":"L3MODIFICATION","subtype":"IPV4_SRC", "ip":"1.2.3.4"}, + {"type":"L3MODIFICATION","subtype":"IPV4_DST", "ip":"1.2.3.3"}, + {"type":"L3MODIFICATION","subtype":"IPV6_SRC", "ip":"1.2.3.2"}, + {"type":"L3MODIFICATION","subtype":"IPV6_DST", "ip":"1.2.3.1"}, + {"type":"L3MODIFICATION","subtype":"IPV6_FLABEL", "flowLabel":8}, + {"type":"L0MODIFICATION","subtype":"LAMBDA","lambda":7}, + {"type":"L0MODIFICATION","subtype":"OCH","gridType":"DWDM", + "channelSpacing":"CHL_100GHZ","spacingMultiplier":4,"slotGranularity":8}, + {"type":"L4MODIFICATION","subtype":"TCP_DST","tcpPort":40001}, + {"type":"L4MODIFICATION","subtype":"TCP_SRC","tcpPort":40002}, + {"type":"L4MODIFICATION","subtype":"UDP_DST","udpPort":40003}, + {"type":"L4MODIFICATION","subtype":"UDP_SRC","udpPort":40004} + ], + "deferred":[] + }, + "selector": {"criteria":[{"type":"ETH_TYPE","ethType":2054}]} +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json new file mode 100644 index 00000000..49d6b1ce --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json @@ -0,0 +1,20 @@ +{ + "priority":1, + "isPermanent":"false", + "timeout":1, + "deviceId":"of:0000000000000001", + "selector": + {"criteria": + [ + {"type":"OCH_SIGID", + "ochSignalId": + { + "gridType":"CWDM", + "channelSpacing":"CHL_25GHZ", + "spacingMultiplier":3, + "slotGranularity":4 + } + } + ] + } +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json new file mode 100644 index 00000000..dc241f55 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json @@ -0,0 +1,12 @@ +{ + "priority":1, + "isPermanent":"false", + "timeout":1, + "deviceId":"of:0000000000000001", + "treatment": + {"instructions": + [{"type":"OUTPUT","port":-3}],"deferred":[]}, + "selector": + {"criteria": + [{"type":"ETH_TYPE","ethType":2054}]} +} diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml new file mode 100644 index 00000000..0d91a735 --- /dev/null +++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml @@ -0,0 +1,29 @@ + + + Awesome application from Circus, Inc. + + ADMIN + + FLOWRULE_WRITE + FLOWRULE_READ + + + + + diff --git a/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zip b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zip new file mode 100644 index 00000000..436cd755 Binary files /dev/null and b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zip differ -- cgit 1.2.3-korg