aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/core/common
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/core/common
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/core/common')
-rw-r--r--framework/src/onos/core/common/pom.xml58
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java64
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java49
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java49
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java132
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java74
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConnectivityIntentCodec.java118
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ConstraintCodec.java63
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java57
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java78
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeConstraintCodecHelper.java225
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java449
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java235
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java93
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/DriverCodec.java78
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeConstraintCodecHelper.java201
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java396
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java243
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/EthernetCodec.java58
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java70
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java94
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java64
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java79
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostCodec.java55
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostLocationCodec.java39
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/HostToHostIntentCodec.java70
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java73
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/IntentCodec.java112
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java80
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/LoadCodec.java45
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PathCodec.java47
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PointToPointIntentCodec.java80
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java160
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyClusterCodec.java41
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TopologyCodec.java41
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficSelectorCodec.java71
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/TrafficTreatmentCodec.java76
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/package-info.java20
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopology.java502
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/DefaultTopologyGraph.java43
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java432
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/app/package-info.java20
-rw-r--r--framework/src/onos/core/common/src/main/java/org/onosproject/common/package-info.java21
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConnectPointJsonMatcher.java71
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ConstraintCodecTest.java202
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java445
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java609
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java59
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverCodecTest.java65
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/DriverJsonMatcher.java118
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java55
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java122
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java546
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupBucketJsonMatcher.java87
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupCodecTest.java61
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/GroupJsonMatcher.java120
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/ImmutableCodecsTest.java65
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java247
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java438
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java288
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/IntentJsonMatcher.java512
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java83
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java54
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/LoadCodecTest.java47
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java64
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java66
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/common/DefaultTopologyTest.java141
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java157
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/common/event/impl/TestEventDispatcher.java48
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/PathKey.java55
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationIdStore.java70
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java170
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStoreTest.java154
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java139
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleComponentConfigStore.java62
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStore.java691
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleDeviceStoreTest.java530
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java327
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java717
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java482
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java293
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIdBlockStore.java48
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleIntentStore.java212
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLeadershipManager.java135
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStore.java286
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkResourceStoreTest.java307
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStore.java366
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleLinkStoreTest.java542
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java388
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStoreTest.java184
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimplePacketStore.java64
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleStatisticStore.java211
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleTopologyStore.java157
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SystemClockTimestamp.java83
-rw-r--r--framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/package-info.java21
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AnnotationConstraint.json5
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/AsymmetricPathConstraint.json3
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/BandwidthConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/HostToHostIntent.json19
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LambdaConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LatencyConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/LinkTypeConstraint.json5
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/ObstacleConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/PointToPointIntent.json38
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/WaypointConstraint.json4
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json44
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json39
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json20
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json12
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.xml29
-rw-r--r--framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zipbin0 -> 1450 bytes
111 files changed, 16753 insertions, 0 deletions
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-core-common</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>ONOS utilities common to the core modules</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
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<T extends Annotated> extends JsonCodec<T> {
+
+ /**
+ * 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<Annotations> 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<Annotations> 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<Annotations> {
+
+ @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<Application> {
+
+ @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<Class<?>, 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<Class<?>> getCodecs() {
+ return ImmutableSet.copyOf(codecs.keySet());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> JsonCodec<T> getCodec(Class<T> entityClass) {
+ return codecs.get(entityClass);
+ }
+
+ @Override
+ public <T> void registerCodec(Class<T> entityClass, JsonCodec<T> 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<ConnectPoint> {
+
+ // 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<ConnectivityIntent> {
+
+ 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<Intent> intentCodec = context.codec(Intent.class);
+ final ObjectNode result = intentCodec.encode(intent, context);
+
+ if (intent.selector() != null) {
+ final JsonCodec<TrafficSelector> selectorCodec =
+ context.codec(TrafficSelector.class);
+ result.set(SELECTOR, selectorCodec.encode(intent.selector(), context));
+ }
+
+ if (intent.treatment() != null) {
+ final JsonCodec<TrafficTreatment> 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<Constraint> 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<Constraint> constraintsCodec = context.codec(Constraint.class);
+ ArrayList<Constraint> 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<TrafficSelector> selectorCodec = context.codec(TrafficSelector.class);
+ TrafficSelector selector = selectorCodec.decode(selectorJson, context);
+ builder.selector(selector);
+ }
+
+ ObjectNode treatmentJson = get(json, TREATMENT);
+ if (treatmentJson != null) {
+ JsonCodec<TrafficTreatment> 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<Constraint> {
+
+ 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<ControllerNode> {
+
+ @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<Criterion> {
+
+ 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<Link.Type> 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<DeviceId> 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<DeviceId> 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<String, CriterionDecoder> 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<Device> {
+
+ // 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<Driver> {
+ 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<Criterion.Type, CriterionTypeFormatter> 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<Ethernet> {
+
+ 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<FlowEntry> {
+
+ @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<TrafficTreatment> treatmentCodec =
+ context.codec(TrafficTreatment.class);
+ result.set("treatment", treatmentCodec.encode(flowEntry.treatment(), context));
+ }
+
+ if (flowEntry.selector() != null) {
+ final JsonCodec<TrafficSelector> 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<FlowRule> {
+
+ 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<TrafficTreatment> treatmentCodec =
+ context.codec(TrafficTreatment.class);
+ resultBuilder.withTreatment(treatmentCodec.decode(treatmentJson, context));
+ }
+
+ ObjectNode selectorJson = get(json, SELECTOR);
+ if (selectorJson != null) {
+ JsonCodec<TrafficSelector> 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<GroupBucket> {
+
+ 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<Group> {
+ // 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<Host> {
+
+ @Override
+ public ObjectNode encode(Host host, CodecContext context) {
+ checkNotNull(host, "Host cannot be null");
+ final JsonCodec<HostLocation> 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<HostLocation> {
+
+ @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<HostToHostIntent> {
+
+ 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<ConnectivityIntent> 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<Instruction> {
+
+ 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<Intent> {
+
+ 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<Link> {
+
+ // 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<ConnectPoint> 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<ConnectPoint> 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<Load> {
+
+ 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<Path> {
+ @Override
+ public ObjectNode encode(Path path, CodecContext context) {
+ checkNotNull(path, "Path cannot be null");
+ JsonCodec<Link> 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<PointToPointIntent> {
+
+ 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<ConnectivityIntent> connectivityIntentCodec =
+ context.codec(ConnectivityIntent.class);
+ final ObjectNode result = connectivityIntentCodec.encode(intent, context);
+
+ final JsonCodec<ConnectPoint> 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<Port> {
+
+ // 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<TopologyCluster> {
+
+ @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<Topology> {
+
+ @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<TrafficSelector> {
+ 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<Criterion> 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<Criterion> 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<TrafficTreatment> {
+ 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<Instruction> 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<Instruction> 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<TopologyVertex, TopologyEdge> DIJKSTRA = new DijkstraGraphSearch<>();
+ private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> 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<SCCResult<TopologyVertex, TopologyEdge>> clusterResults;
+ private final Supplier<ImmutableMap<ClusterId, TopologyCluster>> clusters;
+ private final Supplier<ImmutableSet<ConnectPoint>> infrastructurePoints;
+ private final Supplier<ImmutableSetMultimap<ClusterId, ConnectPoint>> broadcastSets;
+ private final Function<ConnectPoint, Boolean> broadcastFunction;
+ private final Supplier<ClusterIndexes> 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<ConnectPoint, Boolean> 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<DeviceId, TopologyCluster> clustersByDevice() {
+ return clusterIndexes.get().clustersByDevice;
+ }
+
+ private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster() {
+ return clusterIndexes.get().devicesByCluster;
+ }
+
+ private ImmutableSetMultimap<TopologyCluster, Link> 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<TopologyCluster> 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<DeviceId> getClusterDevices(TopologyCluster cluster) {
+ return devicesByCluster().get(cluster);
+ }
+
+ /**
+ * Returns the set of cluster links.
+ *
+ * @param cluster topology cluster
+ * @return cluster links
+ */
+ public Set<Link> 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<ConnectPoint> 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<ConnectPoint> 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<Path> 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<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
+ final DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
+ final DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
+ Set<TopologyVertex> vertices = graph.getVertexes();
+ if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
+ // src or dst not part of the current graph
+ return ImmutableSet.of();
+ }
+
+ GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
+ DIJKSTRA.search(graph, srcV, dstV, weight, ALL_PATHS);
+ ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
+ for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> 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<TopologyVertex, TopologyEdge> path) {
+ List<Link> 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<TopologyVertex, TopologyEdge> searchForClusters() {
+ return TARJAN.search(graph, new NoIndirectLinksWeight());
+ }
+
+ // Builds the topology clusters and returns the id-cluster bindings.
+ private ImmutableMap<ClusterId, TopologyCluster> buildTopologyClusters() {
+ ImmutableMap.Builder<ClusterId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
+ SCCResult<TopologyVertex, TopologyEdge> results = clusterResults.get();
+ // Extract both vertexes and edges from the results; the lists form
+ // pairs along the same index.
+ List<Set<TopologyVertex>> clusterVertexes = results.clusterVertexes();
+ List<Set<TopologyEdge>> 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<TopologyVertex> vertexSet = clusterVertexes.get(i);
+ Set<TopologyEdge> 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<TopologyVertex> 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<ClusterId, ConnectPoint> buildBroadcastSets() {
+ Builder<ClusterId, ConnectPoint> 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<ClusterId, ConnectPoint> builder) {
+ // Use the graph root search results to build the broadcast set.
+ Result<TopologyVertex, TopologyEdge> result = DIJKSTRA.search(graph, cluster.root(), null, weight, 1);
+ for (Map.Entry<TopologyVertex, Set<TopologyEdge>> 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<TopologyEdge> 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<ConnectPoint> findInfrastructurePoints() {
+ ImmutableSet.Builder<ConnectPoint> 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<DeviceId, TopologyCluster> clusterBuilder =
+ ImmutableMap.builder();
+ ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder =
+ ImmutableSetMultimap.builder();
+ ImmutableSetMultimap.Builder<TopologyCluster, Link> 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<DeviceId, TopologyCluster> clustersByDevice;
+ final ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
+ final ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
+
+ public ClusterIndexes(ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
+ ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
+ ImmutableSetMultimap<TopologyCluster, Link> 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<TopologyVertex, TopologyEdge>
+ 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<TopologyVertex> vertexes, Set<TopologyEdge> 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<ApplicationEvent, ApplicationStoreDelegate> {
+
+ 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 = "<?xml ";
+
+ // Magic strings to search for and how deep to search it into the archive stream
+ private static final String APP_MAGIC = "<app ";
+ private static final int APP_MAGIC_DEPTH = 1024;
+
+ private static final String NAME = "[@name]";
+ private static final String ORIGIN = "[@origin]";
+ private static final String VERSION = "[@version]";
+ private static final String FEATURES_REPO = "[@featuresRepo]";
+ private static final String FEATURES = "[@features]";
+ private static final String DESCRIPTION = "description";
+
+ private static final String ROLE = "security.role";
+ private static final String APP_PERMISSIONS = "security.permissions.app-perm";
+ private static final String NET_PERMISSIONS = "security.permissions.net-perm";
+ private static final String JAVA_PERMISSIONS = "security.permissions.java-perm";
+
+ private static final String OAR = ".oar";
+ private static final String APP_XML = "app.xml";
+ private static final String M2_PREFIX = "m2";
+
+ private static final String ROOT = "../";
+ private static final String M2_ROOT = "system/";
+ private static final String APPS_ROOT = "apps/";
+
+ private File root = new File(ROOT);
+ private File appsDir = new File(root, APPS_ROOT);
+ private File m2Dir = new File(M2_ROOT);
+
+ /**
+ * Sets the root directory where apps directory is contained.
+ *
+ * @param root top-level directory path
+ */
+ protected void setRootPath(String root) {
+ this.root = new File(root);
+ this.appsDir = new File(this.root, APPS_ROOT);
+ this.m2Dir = new File(M2_ROOT);
+ }
+
+ /**
+ * Returns the root directory where apps directory is contained.
+ *
+ * @return top-level directory path
+ */
+ public String getRootPath() {
+ return root.getPath();
+ }
+
+ /**
+ * Returns the set of installed application names.
+ *
+ * @return installed application names
+ */
+ public Set<String> getApplicationNames() {
+ ImmutableSet.Builder<String> 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<Permission> perms = getPermissions(cfg);
+ String featRepo = cfg.getString(FEATURES_REPO);
+ URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
+ List<String> 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<Permission> getPermissions(XMLConfiguration cfg) {
+ List<Permission> 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<HierarchicalConfiguration> 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<JsonNode> {
+
+ 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<Constraint> 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<Criterion> 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<Criterion.Type, Object> 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<JsonNode> {
+
+ 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<Device> 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<? extends Behaviour>, Class<? extends Behaviour>> behaviours =
+ ImmutableMap.of(TestBehaviour.class,
+ TestBehaviourImpl.class,
+ TestBehaviourTwo.class,
+ TestBehaviourTwoImpl.class);
+ Map<String, String> 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<JsonNode> {
+ 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<String, String> 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<Ethernet> 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<JsonNode> {
+
+ 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<FlowRule> 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<String, Instruction> 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<String, Criterion> 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<JsonNode> {
+
+ 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<JsonNode> {
+
+ 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<Instruction> 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<JsonNode> {
+
+ 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<HostToHostIntent> 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<PointToPointIntent> 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<Constraint> 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<PointToPointIntent> 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<Intent> 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<Intent> 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<JsonNode> {
+
+ private final Intent intent;
+
+ /**
+ * Constructor is private, use factory method.
+ *
+ * @param intentValue the intent object to compare against
+ */
+ private IntentJsonMatcher(Intent intentValue) {
+ intent = intentValue;
+ }
+
+ /**
+ * Matches the JSON representation of a host to host intent.
+ *
+ * @param jsonIntent JSON representation of the intent
+ * @param description Description object used for recording errors
+ * @return true if the JSON matches the intent, false otherwise
+ */
+ private boolean matchHostToHostIntent(JsonNode jsonIntent, Description description) {
+ final HostToHostIntent hostToHostIntent = (HostToHostIntent) intent;
+
+ // check host one
+ final String host1 = hostToHostIntent.one().toString();
+ final String jsonHost1 = jsonIntent.get("one").asText();
+ if (!host1.equals(jsonHost1)) {
+ description.appendText("host one was " + jsonHost1);
+ return false;
+ }
+
+ // check host 2
+ final String host2 = hostToHostIntent.two().toString();
+ final String jsonHost2 = jsonIntent.get("two").asText();
+ if (!host2.equals(jsonHost2)) {
+ description.appendText("host two was " + jsonHost2);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches the JSON representation of a point to point intent.
+ *
+ * @param jsonIntent JSON representation of the intent
+ * @param description Description object used for recording errors
+ * @return true if the JSON matches the intent, false otherwise
+ */
+ private boolean matchPointToPointIntent(JsonNode jsonIntent, Description description) {
+ final PointToPointIntent pointToPointIntent = (PointToPointIntent) intent;
+
+ // check ingress connection
+ final ConnectPoint ingress = pointToPointIntent.ingressPoint();
+ final ConnectPointJsonMatcher ingressMatcher =
+ ConnectPointJsonMatcher.matchesConnectPoint(ingress);
+ final JsonNode jsonIngress = jsonIntent.get("ingressPoint");
+ final boolean ingressMatches =
+ ingressMatcher.matchesSafely(jsonIngress, description);
+
+ if (!ingressMatches) {
+ description.appendText("ingress was " + jsonIngress);
+ return false;
+ }
+
+ // check egress connection
+ final ConnectPoint egress = pointToPointIntent.egressPoint();
+ final ConnectPointJsonMatcher egressMatcher =
+ ConnectPointJsonMatcher.matchesConnectPoint(egress);
+ final JsonNode jsonEgress = jsonIntent.get("egressPoint");
+ final boolean egressMatches =
+ egressMatcher.matchesSafely(jsonEgress, description);
+
+ if (!egressMatches) {
+ description.appendText("egress was " + jsonEgress);
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Matches a bandwidth constraint against a JSON representation of the
+ * constraint.
+ *
+ * @param bandwidthConstraint constraint object to match
+ * @param constraintJson JSON representation of the constraint
+ * @return true if the constraint and JSON match, false otherwise.
+ */
+ private boolean matchBandwidthConstraint(BandwidthConstraint bandwidthConstraint,
+ JsonNode constraintJson) {
+ final JsonNode bandwidthJson = constraintJson.get("bandwidth");
+ return bandwidthJson != null
+ && constraintJson.get("bandwidth").asDouble()
+ == bandwidthConstraint.bandwidth().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<Criterion> criteria = selector.criteria();
+ final JsonNode jsonCriteria = jsonSelector.get("criteria");
+ if (jsonCriteria.size() != criteria.size()) {
+ description.appendText("size of criteria array is "
+ + Integer.toString(jsonCriteria.size()));
+ return false;
+ }
+
+ for (Criterion criterion : criteria) {
+ boolean criterionFound = false;
+ for (int criterionIndex = 0; criterionIndex < jsonCriteria.size(); criterionIndex++) {
+ final CriterionJsonMatcher criterionMatcher =
+ CriterionJsonMatcher.matchesCriterion(criterion);
+ if (criterionMatcher.matches(jsonCriteria.get(criterionIndex))) {
+ criterionFound = true;
+ break;
+ }
+ }
+ if (!criterionFound) {
+ description.appendText("criterion not found " + criterion.toString());
+ return false;
+ }
+ }
+
+ // check treatment
+ final JsonNode jsonTreatment = jsonIntent.get("treatment");
+ final TrafficTreatment treatment = connectivityIntent.treatment();
+ final List<Instruction> instructions = treatment.immediate();
+ final JsonNode jsonInstructions = jsonTreatment.get("instructions");
+ if (jsonInstructions.size() != instructions.size()) {
+ description.appendText("size of instructions array is "
+ + Integer.toString(jsonInstructions.size()));
+ return false;
+ }
+
+ for (Instruction instruction : instructions) {
+ boolean instructionFound = false;
+ for (int instructionIndex = 0; instructionIndex < jsonInstructions.size(); instructionIndex++) {
+ final InstructionJsonMatcher instructionMatcher =
+ InstructionJsonMatcher.matchesInstruction(instruction);
+ if (instructionMatcher.matches(jsonInstructions.get(instructionIndex))) {
+ instructionFound = true;
+ break;
+ }
+ }
+ if (!instructionFound) {
+ description.appendText("instruction not found " + instruction.toString());
+ return false;
+ }
+ }
+
+ // Check constraints
+ final JsonNode jsonConstraints = jsonIntent.get("constraints");
+ if (connectivityIntent.constraints() != null) {
+ if (connectivityIntent.constraints().size() != jsonConstraints.size()) {
+ description.appendText("constraints array size was "
+ + Integer.toString(jsonConstraints.size()));
+ return false;
+ }
+ for (final Constraint constraint : connectivityIntent.constraints()) {
+ boolean constraintFound = false;
+ for (int constraintIndex = 0; constraintIndex < jsonConstraints.size();
+ constraintIndex++) {
+ final JsonNode value = jsonConstraints.get(constraintIndex);
+ if (matchConstraint(constraint, value)) {
+ constraintFound = true;
+ }
+ }
+ if (!constraintFound) {
+ final String constraintString = constraint.toString();
+ description.appendText("constraint missing " + constraintString);
+ return false;
+ }
+ }
+ } else if (jsonConstraints.size() != 0) {
+ description.appendText("constraint array not empty");
+ return false;
+ }
+
+ if (connectivityIntent instanceof HostToHostIntent) {
+ return matchHostToHostIntent(jsonIntent, description);
+ } else if (connectivityIntent instanceof PointToPointIntent) {
+ return matchPointToPointIntent(jsonIntent, description);
+ } else {
+ description.appendText("class of connectivity intent is unknown");
+ return false;
+ }
+ }
+
+ @Override
+ public boolean matchesSafely(JsonNode jsonIntent, Description description) {
+ // check id
+ final String jsonId = jsonIntent.get("id").asText();
+ final String id = intent.id().toString();
+ if (!jsonId.equals(id)) {
+ description.appendText("id was " + jsonId);
+ return false;
+ }
+
+ // check application id
+ final JsonNode jsonAppIdNode = jsonIntent.get("appId");
+
+ final String jsonAppId = jsonAppIdNode.asText();
+ final String appId = intent.appId().name();
+ if (!jsonAppId.equals(appId)) {
+ description.appendText("appId was " + jsonAppId);
+ return false;
+ }
+
+ // check intent type
+ final String jsonType = jsonIntent.get("type").asText();
+ final String type = intent.getClass().getSimpleName();
+ if (!jsonType.equals(type)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ // check resources array
+ final JsonNode jsonResources = jsonIntent.get("resources");
+ if (intent.resources() != null) {
+ if (intent.resources().size() != jsonResources.size()) {
+ description.appendText("resources array size was "
+ + Integer.toString(jsonResources.size()));
+ return false;
+ }
+ for (final NetworkResource resource : intent.resources()) {
+ boolean resourceFound = false;
+ final String resourceString = resource.toString();
+ for (int resourceIndex = 0; resourceIndex < jsonResources.size(); resourceIndex++) {
+ final JsonNode value = jsonResources.get(resourceIndex);
+ if (value.asText().equals(resourceString)) {
+ resourceFound = true;
+ }
+ }
+ if (!resourceFound) {
+ description.appendText("resource missing " + resourceString);
+ return false;
+ }
+ }
+ } else if (jsonResources.size() != 0) {
+ description.appendText("resources array empty");
+ return false;
+ }
+
+ if (intent instanceof ConnectivityIntent) {
+ return matchConnectivityIntent(jsonIntent, description);
+ } else {
+ description.appendText("class of intent is unknown");
+ return false;
+ }
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(intent.toString());
+ }
+
+ /**
+ * Factory to allocate an intent matcher.
+ *
+ * @param intent intent object we are looking for
+ * @return matcher
+ */
+ public static IntentJsonMatcher matchesIntent(Intent intent) {
+ return new IntentJsonMatcher(intent);
+ }
+}
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 <T> void assertJsonEncodable(final CodecContext context,
+ final JsonCodec<T> 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<Link> 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<Class<? extends Object>, Object> services = new HashMap<>();
+
+ /**
+ * Constructs a new mock codec context.
+ */
+ public MockCodecContext() {
+ manager.activate();
+ }
+
+ @Override
+ public ObjectMapper mapper() {
+ return mapper;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> JsonCodec<T> codec(Class<T> entityClass) {
+ return manager.getCodec(entityClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T getService(Class<T> serviceClass) {
+ return (T) services.get(serviceClass);
+ }
+
+ // for registering mock services
+ public <T> void registerService(Class<T> 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<Port> 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<Device> devices = of(device("1"), device("2"),
+ device("3"), device("4"),
+ device("5"));
+ Set<Link> 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<Path> 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<TopologyCluster> clusters = dt.getClusters();
+ assertEquals("incorrect cluster count", 2, clusters.size());
+
+ TopologyCluster c = dt.getCluster(D1);
+ Set<DeviceId> 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<String> 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.<String>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<Short, DefaultApplicationId> appIds = new ConcurrentHashMap<>();
+ private final Map<String, DefaultApplicationId> appIdsByName = new ConcurrentHashMap<>();
+
+ @Override
+ public Set<ApplicationId> getAppIds() {
+ return ImmutableSet.<ApplicationId>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<ApplicationId, DefaultApplication> apps = new ConcurrentHashMap<>();
+ private final ConcurrentMap<ApplicationId, ApplicationState> states = new ConcurrentHashMap<>();
+ private final ConcurrentMap<ApplicationId, Set<Permission>> 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<Application> 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<Permission> getPermissions(ApplicationId appId) {
+ return permissions.get(appId);
+ }
+
+ @Override
+ public void setPermissions(ApplicationId appId, Set<Permission> 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<Permission> 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<ClusterEvent, ClusterStoreDelegate>
+ 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<PartitionEvent, PartitionEventListener> 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<ControllerNode> 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<ComponentConfigEvent, ComponentConfigStoreDelegate>
+ 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<DeviceEvent, DeviceStoreDelegate>
+ 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<DeviceId, Map<ProviderId, DeviceDescriptions>>
+ deviceDescs = Maps.newConcurrentMap();
+
+ // Cache of Device and Ports generated by compositing descriptions from providers
+ private final ConcurrentMap<DeviceId, Device> devices = Maps.newConcurrentMap();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>>
+ devicePorts = Maps.newConcurrentMap();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, PortStatistics>>
+ devicePortStats = Maps.newConcurrentMap();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, PortStatistics>>
+ devicePortDeltaStats = Maps.newConcurrentMap();
+
+ // Available (=UP) devices
+ private final Set<DeviceId> 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<Device> getDevices() {
+ return Collections.unmodifiableCollection(devices.values());
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices() {
+ return FluentIterable.from(getDevices())
+ .filter(new Predicate<Device>() {
+
+ @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<ProviderId, DeviceDescriptions> 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<ProviderId, DeviceDescriptions> 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<DeviceEvent> updatePorts(ProviderId providerId,
+ DeviceId deviceId,
+ List<PortDescription> 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<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
+ checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
+
+ List<DeviceEvent> 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<PortNumber, Port> ports = getPortMap(deviceId);
+
+ // Add new ports
+ Set<PortNumber> 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<PortNumber, Port> 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<PortNumber, Port> 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<DeviceEvent> pruneOldPorts(Device device,
+ Map<PortNumber, Port> ports,
+ Set<PortNumber> processed) {
+ List<DeviceEvent> events = new ArrayList<>();
+ Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<PortNumber, Port> 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<PortNumber, Port> getPortMap(DeviceId deviceId) {
+ return createIfAbsentUnchecked(devicePorts, deviceId,
+ NewConcurrentHashMap.<PortNumber, Port>ifNeeded());
+ }
+
+ private Map<ProviderId, DeviceDescriptions> getOrCreateDeviceDescriptions(
+ DeviceId deviceId) {
+ Map<ProviderId, DeviceDescriptions> r;
+ r = deviceDescs.get(deviceId);
+ if (r != null) {
+ return r;
+ }
+ r = new HashMap<>();
+ final Map<ProviderId, DeviceDescriptions> concurrentlyAdded;
+ concurrentlyAdded = deviceDescs.putIfAbsent(deviceId, r);
+ if (concurrentlyAdded != null) {
+ return concurrentlyAdded;
+ } else {
+ return r;
+ }
+ }
+
+ // Guarded by deviceDescs value (=Device lock)
+ private DeviceDescriptions getOrCreateProviderDeviceDescriptions(
+ Map<ProviderId, DeviceDescriptions> 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<ProviderId, DeviceDescriptions> 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<PortNumber, Port> 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<Port> getPorts(DeviceId deviceId) {
+ Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+ if (ports == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(ports.values());
+ }
+
+ @Override
+ public DeviceEvent updatePortStatistics(ProviderId providerId, DeviceId deviceId,
+ Collection<PortStatistics> newStatsCollection) {
+
+ ConcurrentMap<PortNumber, PortStatistics> prvStatsMap = devicePortStats.get(deviceId);
+ ConcurrentMap<PortNumber, PortStatistics> newStatsMap = Maps.newConcurrentMap();
+ ConcurrentMap<PortNumber, PortStatistics> 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<PortNumber, Port> ports = devicePorts.get(deviceId);
+ return ports == null ? null : ports.get(portNumber);
+ }
+
+ @Override
+ public List<PortStatistics> getPortStatistics(DeviceId deviceId) {
+ Map<PortNumber, PortStatistics> portStats = devicePortStats.get(deviceId);
+ if (portStats == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(portStats.values());
+ }
+
+ @Override
+ public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) {
+ Map<PortNumber, PortStatistics> 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<ProviderId, DeviceDescriptions> descs = getOrCreateDeviceDescriptions(deviceId);
+ synchronized (descs) {
+ Device device = devices.remove(deviceId);
+ // should DEVICE_REMOVED carry removed ports?
+ Map<PortNumber, Port> 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<ProviderId, DeviceDescriptions> 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<ProviderId, DeviceDescriptions> 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<ProviderId, DeviceDescriptions> 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<ProviderId, DeviceDescriptions> 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<ProviderId, DeviceDescriptions> descsMap) {
+ ProviderId fallBackPrimary = null;
+ for (Entry<ProviderId, DeviceDescriptions> 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<DeviceDescription> deviceDesc;
+ private final ConcurrentMap<PortNumber, PortDescription> 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<DeviceId, Device> 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<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true),
+ new DefaultPortDescription(P2, true)
+ );
+
+ List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
+
+ Set<PortNumber> 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<PortDescription> pds2 = Arrays.<PortDescription>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<PortDescription> pds3 = Arrays.<PortDescription>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<PortDescription> pds = Arrays.<PortDescription>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<PortDescription> pds = Arrays.<PortDescription>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<PortDescription> pds = Arrays.<PortDescription>asList(
+ new DefaultPortDescription(P1, true),
+ new DefaultPortDescription(P2, true)
+ );
+ deviceStore.updatePorts(PID, DID1, pds);
+
+ Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+ List<Port> 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<PortDescription> pds = Arrays.<PortDescription>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<PortDescription> pds = Arrays.<PortDescription>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<PortDescription> pds2 = Arrays.<PortDescription>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<FlowRuleBatchEvent, FlowRuleStoreDelegate>
+ 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<DeviceId, ConcurrentMap<FlowId, List<StoredFlowEntry>>>
+ flowEntries = new ConcurrentHashMap<>();
+
+ private final AtomicInteger localBatchIdGen = new AtomicInteger();
+
+ // TODO: make this configurable
+ private int pendingFutureTimeoutMinutes = 5;
+
+ private Cache<Integer, SettableFuture<CompletedBatchOperation>> 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<FlowId, List<StoredFlowEntry>> ft : flowEntries.values()) {
+ for (List<StoredFlowEntry> fes : ft.values()) {
+ sum += fes.size();
+ }
+ }
+ return sum;
+ }
+
+ private static NewConcurrentHashMap<FlowId, List<StoredFlowEntry>> lazyEmptyFlowTable() {
+ return NewConcurrentHashMap.<FlowId, List<StoredFlowEntry>>ifNeeded();
+ }
+
+ /**
+ * Returns the flow table for specified device.
+ *
+ * @param deviceId identifier of the device
+ * @return Map representing Flow Table of given device.
+ */
+ private ConcurrentMap<FlowId, List<StoredFlowEntry>> getFlowTable(DeviceId deviceId) {
+ return createIfAbsentUnchecked(flowEntries,
+ deviceId, lazyEmptyFlowTable());
+ }
+
+ private List<StoredFlowEntry> getFlowEntries(DeviceId deviceId, FlowId flowId) {
+ final ConcurrentMap<FlowId, List<StoredFlowEntry>> flowTable = getFlowTable(deviceId);
+ List<StoredFlowEntry> r = flowTable.get(flowId);
+ if (r == null) {
+ final List<StoredFlowEntry> concurrentlyAdded;
+ r = new CopyOnWriteArrayList<>();
+ concurrentlyAdded = flowTable.putIfAbsent(flowId, r);
+ if (concurrentlyAdded != null) {
+ return concurrentlyAdded;
+ }
+ }
+ return r;
+ }
+
+ private FlowEntry getFlowEntryInternal(DeviceId deviceId, FlowRule rule) {
+ List<StoredFlowEntry> 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<FlowEntry> getFlowEntries(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(getFlowTable(deviceId).values())
+ .transformAndConcat(
+ new Function<List<StoredFlowEntry>, Iterable<? extends FlowEntry>>() {
+
+ @Override
+ public Iterable<? extends FlowEntry> apply(
+ List<StoredFlowEntry> 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<StoredFlowEntry> 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<StoredFlowEntry> 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<StoredFlowEntry> 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<StoredFlowEntry> 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<FlowRuleBatchEntry> toAdd = new ArrayList<>();
+ List<FlowRuleBatchEntry> 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<CompletedBatchOperation> 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<CompletedBatchOperation> future
+ = pendingFutures.getIfPresent(batchId);
+ if (future != null) {
+ future.set(event.result());
+ pendingFutures.invalidate(batchId);
+ }
+ notifyDelegate(event);
+ }
+
+ private static final class TimeoutFuture
+ implements RemovalListener<Integer, SettableFuture<CompletedBatchOperation>> {
+ @Override
+ public void onRemoval(RemovalNotification<Integer, SettableFuture<CompletedBatchOperation>> 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<GroupEvent, GroupStoreDelegate>
+ 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<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>
+ groupEntriesByKey = new ConcurrentHashMap<>();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>>
+ groupEntriesById = new ConcurrentHashMap<>();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>
+ pendingGroupEntriesByKey = new ConcurrentHashMap<>();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, Group>>
+ extraneousGroupEntriesById = new ConcurrentHashMap<>();
+
+ private final HashMap<DeviceId, Boolean> deviceAuditStatus =
+ new HashMap<DeviceId, Boolean>();
+
+ 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<GroupKey, StoredGroupEntry>
+ lazyEmptyGroupKeyTable() {
+ return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded();
+ }
+
+ private static NewConcurrentHashMap<GroupId, StoredGroupEntry>
+ lazyEmptyGroupIdTable() {
+ return NewConcurrentHashMap.<GroupId, StoredGroupEntry>ifNeeded();
+ }
+
+ private static NewConcurrentHashMap<GroupKey, StoredGroupEntry>
+ lazyEmptyPendingGroupKeyTable() {
+ return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded();
+ }
+
+ private static NewConcurrentHashMap<GroupId, Group>
+ lazyEmptyExtraneousGroupIdTable() {
+ return NewConcurrentHashMap.<GroupId, Group>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<GroupKey, StoredGroupEntry> 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<GroupId, StoredGroupEntry> 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<GroupKey, StoredGroupEntry>
+ 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<GroupId, Group>
+ 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<Group> getGroups(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(getGroupKeyTable(deviceId).values())
+ .transform(
+ new Function<StoredGroupEntry, Group>() {
+
+ @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<GroupKey, StoredGroupEntry> 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<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(groupDesc.deviceId());
+ keyTable.put(groupDesc.appCookie(), group);
+ ConcurrentMap<GroupId, StoredGroupEntry> 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<GroupBucket> 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<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(oldGroup.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> 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<GroupBucket> getUpdatedBucketList(Group oldGroup,
+ UpdateType type,
+ GroupBuckets buckets) {
+ GroupBuckets oldBuckets = oldGroup.buckets();
+ List<GroupBucket> newBucketList = new ArrayList<GroupBucket>(
+ 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<GroupBucket> 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<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(existing.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> 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<GroupKey, StoredGroupEntry> 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<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(existing.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(existing.deviceId());
+ idTable.remove(existing.id());
+ keyTable.remove(existing.appCookie());
+ }
+
+ @Override
+ public void addOrUpdateExtraneousGroupEntry(Group group) {
+ ConcurrentMap<GroupId, Group> 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<GroupId, Group> extraneousIdTable =
+ getExtraneousGroupIdTable(group.deviceId());
+ extraneousIdTable.remove(group.id());
+ }
+
+ @Override
+ public Iterable<Group> getExtraneousGroups(DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(
+ getExtraneousGroupIdTable(deviceId).values());
+ }
+
+ @Override
+ public void pushGroupMetrics(DeviceId deviceId,
+ Collection<Group> groupEntries) {
+ boolean deviceInitialAuditStatus =
+ deviceInitialAuditStatus(deviceId);
+ Set<Group> southboundGroupEntries =
+ Sets.newHashSet(groupEntries);
+ Set<Group> storedGroupEntries =
+ Sets.newHashSet(getGroups(deviceId));
+ Set<Group> extraneousStoredEntries =
+ Sets.newHashSet(getExtraneousGroups(deviceId));
+
+ log.trace("pushGroupMetrics: Displaying all ({}) "
+ + "southboundGroupEntries for device {}",
+ southboundGroupEntries.size(),
+ deviceId);
+ for (Iterator<Group> 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<Group> it1 = storedGroupEntries.iterator();
+ it1.hasNext();) {
+ Group group = it1.next();
+ log.trace("Stored Group {} for device {}", group, deviceId);
+ }
+
+ for (Iterator<Group> 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<GroupBucket> 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<PortNumber> outPorts = new ArrayList<PortNumber>();
+ outPorts.addAll(Arrays.asList(ports));
+
+ List<GroupBucket> buckets = new ArrayList<GroupBucket>();
+ 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<Group> 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<GroupBucket> newBucketList = new ArrayList<GroupBucket>();
+ 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<GroupBucket> buckets = new ArrayList<GroupBucket>();
+ buckets.addAll(existingGroup.buckets().buckets());
+
+ PortNumber[] newNeighborPorts = {PortNumber.portNumber(41),
+ PortNumber.portNumber(42)};
+ List<PortNumber> newOutPorts = new ArrayList<PortNumber>();
+ newOutPorts.addAll(Collections.singletonList(newNeighborPorts[0]));
+
+ List<GroupBucket> toAddBuckets = new ArrayList<GroupBucket>();
+ 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<GroupBucket> buckets = new ArrayList<GroupBucket>();
+ buckets.addAll(existingGroup.buckets().buckets());
+
+ List<GroupBucket> toRemoveBuckets = new ArrayList<GroupBucket>();
+
+ // 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<PortNumber> outPorts = new ArrayList<PortNumber>();
+ outPorts.add(ports[0]);
+ outPorts.add(ports[1]);
+
+ List<GroupBucket> buckets = new ArrayList<GroupBucket>();
+ 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<HostEvent, HostStoreDelegate>
+ implements HostStore {
+
+ private final Logger log = getLogger(getClass());
+
+ // Host inventory
+ private final Map<HostId, StoredHost> hosts = new ConcurrentHashMap<>(2000000, 0.75f, 16);
+
+ // Hosts tracked by their location
+ private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
+
+ private final SetMultimap<ConnectPoint, PortAddresses> portAddresses =
+ Multimaps.synchronizedSetMultimap(
+ HashMultimap.<ConnectPoint, PortAddresses>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<IpAddress> 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<Host> getHosts() {
+ return ImmutableSet.<Host>copyOf(hosts.values());
+ }
+
+ @Override
+ public Host getHost(HostId hostId) {
+ return hosts.get(hostId);
+ }
+
+ @Override
+ public Set<Host> getHosts(VlanId vlanId) {
+ Set<Host> vlanset = new HashSet<>();
+ for (Host h : hosts.values()) {
+ if (h.vlan().equals(vlanId)) {
+ vlanset.add(h);
+ }
+ }
+ return vlanset;
+ }
+
+ @Override
+ public Set<Host> getHosts(MacAddress mac) {
+ Set<Host> macset = new HashSet<>();
+ for (Host h : hosts.values()) {
+ if (h.mac().equals(mac)) {
+ macset.add(h);
+ }
+ }
+ return macset;
+ }
+
+ @Override
+ public Set<Host> getHosts(IpAddress ip) {
+ Set<Host> ipset = new HashSet<>();
+ for (Host h : hosts.values()) {
+ if (h.ipAddresses().contains(ip)) {
+ ipset.add(h);
+ }
+ }
+ return ipset;
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+ return ImmutableSet.copyOf(locations.get(connectPoint));
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(DeviceId deviceId) {
+ Set<Host> 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<PortAddresses> getAddressBindings() {
+ synchronized (portAddresses) {
+ return ImmutableSet.copyOf(portAddresses.values());
+ }
+ }
+
+ @Override
+ public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
+ synchronized (portAddresses) {
+ Set<PortAddresses> 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<IpAddress> 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<String, AtomicLong> 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<IntentEvent, IntentStoreDelegate>
+ implements IntentStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private final Map<Key, IntentData> current = Maps.newConcurrentMap();
+ private final Map<Key, IntentData> 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<Intent> getIntents() {
+ return current.values().stream()
+ .map(IntentData::intent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<IntentData> 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<Intent> 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<IntentData> 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<Intent> getPending() {
+ return pending.values().stream()
+ .map(IntentData::intent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<IntentData> getPendingData() {
+ return Lists.newArrayList(pending.values());
+ }
+
+ @Override
+ public Iterable<IntentData> 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.
+ * <p>
+ * 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<LeadershipEventListener> listeners = new CopyOnWriteArraySet<>();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private ClusterService clusterService;
+
+ private Map<String, Boolean> 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<String> ownedTopics(NodeId nodeId) {
+ checkArgument(nodeId != null);
+ return elections.entrySet()
+ .stream()
+ .filter(Entry::getValue)
+ .map(Entry::getKey)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public CompletableFuture<Leadership> 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<Void> 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<String, Leadership> 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<String, List<NodeId>> getCandidates() {
+ return null;
+ }
+
+ @Override
+ public List<NodeId> 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<IntentId, LinkResourceAllocations> linkResourceAllocationsMap;
+ private Map<Link, Set<LinkResourceAllocations>> allocatedResources;
+ private Map<Link, Set<ResourceAllocation>> 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<ResourceAllocation> readOriginalFreeResources(Link link) {
+ Annotations annotations = link.annotations();
+ Set<ResourceAllocation> 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<ResourceAllocation> 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<ResourceAllocation> freeRes = new HashSet<>(getFreeResources(link));
+ Set<ResourceAllocation> 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<ResourceAllocation> freeRes = new HashSet<>(getFreeResources(link));
+ Set<ResourceAllocation> 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<ResourceAllocation> getFreeResources(Link link) {
+ checkNotNull(link);
+ Set<ResourceAllocation> 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<LinkResourceAllocations> 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<LinkResourceAllocations> linkAllocs = allocatedResources.get(link);
+ if (linkAllocs == null) {
+ log.error("Missing resource allocation.");
+ } else {
+ linkAllocs.remove(allocations);
+ }
+ allocatedResources.put(link, linkAllocs);
+ }
+
+ final List<LinkResourceAllocations> 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<LinkResourceAllocations> getAllocations(Link link) {
+ checkNotNull(link);
+ Set<LinkResourceAllocations> result = allocatedResources.get(link);
+ if (result == null) {
+ result = Collections.emptySet();
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ @Override
+ public synchronized Iterable<LinkResourceAllocations> 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<LinkResourceAllocations> allAllocations = store.getAllocations();
+ assertNotNull(allAllocations);
+ assertFalse(allAllocations.iterator().hasNext());
+
+ final Iterable<LinkResourceAllocations> linkAllocations =
+ store.getAllocations(link1);
+ assertNotNull(linkAllocations);
+ assertFalse(linkAllocations.iterator().hasNext());
+
+ final Set<ResourceAllocation> 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<ResourceAllocation> 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<LambdaResourceAllocation> getLambdaObjs(Set<ResourceAllocation> resources) {
+ Set<LambdaResourceAllocation> 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<ResourceAllocation> 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<ResourceAllocation> freeRes = store.getFreeResources(link3);
+ assertNotNull(freeRes);
+
+ final Set<LambdaResourceAllocation> 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<ResourceAllocation> getResourceAllocation(Link link) {
+ final ResourceAllocation allocation =
+ new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.bps(allocationAmount)));
+ final Set<ResourceAllocation> allocations = new HashSet<>();
+ allocations.add(allocation);
+ return allocations;
+ }
+
+ @Override
+ public IntentId intentId() {
+ return null;
+ }
+
+ @Override
+ public Collection<Link> links() {
+ return ImmutableSet.of(newLink("of:1", 1, "of:2", 2));
+ }
+
+ @Override
+ public Set<ResourceRequest> 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<ResourceAllocation> getResourceAllocation(Link link) {
+ final ResourceAllocation allocation =
+ new LambdaResourceAllocation(LambdaResource.valueOf(allocatedLambda));
+ final Set<ResourceAllocation> allocations = new HashSet<>();
+ allocations.add(allocation);
+ return allocations;
+ }
+
+ @Override
+ public IntentId intentId() {
+ return null;
+ }
+
+ @Override
+ public Collection<Link> links() {
+ return ImmutableSet.of(newLink("of:1", 1, "of:2", 2));
+ }
+
+ @Override
+ public Set<ResourceRequest> 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<LinkEvent, LinkStoreDelegate>
+ implements LinkStore {
+
+ private final Logger log = getLogger(getClass());
+
+ // Link inventory
+ private final ConcurrentMap<LinkKey, Map<ProviderId, LinkDescription>>
+ linkDescs = new ConcurrentHashMap<>();
+
+ // Link instance cache
+ private final ConcurrentMap<LinkKey, Link> links = new ConcurrentHashMap<>();
+
+ // Egress and ingress link sets
+ private final SetMultimap<DeviceId, LinkKey> srcLinks = createSynchronizedHashMultiMap();
+ private final SetMultimap<DeviceId, LinkKey> 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<Link> getLinks() {
+ return Collections.unmodifiableCollection(links.values());
+ }
+
+ @Override
+ public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+ // lock for iteration
+ synchronized (srcLinks) {
+ return FluentIterable.from(srcLinks.get(deviceId))
+ .transform(lookupLink())
+ .filter(notNull())
+ .toSet();
+ }
+ }
+
+ @Override
+ public Set<Link> 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<Link> getEgressLinks(ConnectPoint src) {
+ Set<Link> 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<Link> getIngressLinks(ConnectPoint dst) {
+ Set<Link> 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<ProviderId, LinkDescription> 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<ProviderId, LinkDescription> 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<ProviderId, LinkDescription> 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 <K, V> SetMultimap<K, V> createSynchronizedHashMultiMap() {
+ return synchronizedSetMultimap(
+ Multimaps.newSetMultimap(new ConcurrentHashMap<K, Collection<V>>(),
+ () -> Sets.newConcurrentHashSet()));
+ }
+
+ /**
+ * @return primary ProviderID, or randomly chosen one if none exists
+ */
+ // Guarded by linkDescs value (=locking each Link)
+ private ProviderId getBaseProviderId(Map<ProviderId, LinkDescription> providerDescs) {
+
+ ProviderId fallBackPrimary = null;
+ for (Entry<ProviderId, LinkDescription> 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<ProviderId, LinkDescription> 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<ProviderId, LinkDescription> 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<ProviderId, LinkDescription> getOrCreateLinkDescriptions(LinkKey key) {
+ Map<ProviderId, LinkDescription> r;
+ r = linkDescs.get(key);
+ if (r != null) {
+ return r;
+ }
+ r = new HashMap<>();
+ final Map<ProviderId, LinkDescription> concurrentlyAdded;
+ concurrentlyAdded = linkDescs.putIfAbsent(key, r);
+ if (concurrentlyAdded == null) {
+ return r;
+ } else {
+ return concurrentlyAdded;
+ }
+ }
+
+ private final Function<LinkKey, Link> lookupLink = new LookupLink();
+
+ private Function<LinkKey, Link> lookupLink() {
+ return lookupLink;
+ }
+
+ private final class LookupLink implements Function<LinkKey, Link> {
+ @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<LinkKey, Link> 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<Link> links1 = linkStore.getDeviceEgressLinks(DID1);
+ assertEquals(2, links1.size());
+ // check
+
+ Set<Link> 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<Link> links1 = linkStore.getDeviceIngressLinks(DID2);
+ assertEquals(2, links1.size());
+ // check
+
+ Set<Link> 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<Link> links1 = linkStore.getEgressLinks(d1P1);
+ assertEquals(1, links1.size());
+ assertLink(linkId1, DIRECT, links1.iterator().next());
+
+ Set<Link> 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<Link> links1 = linkStore.getIngressLinks(d2P2);
+ assertEquals(1, links1.size());
+ assertLink(linkId1, DIRECT, links1.iterator().next());
+
+ Set<Link> 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<MastershipEvent, MastershipStoreDelegate>
+ 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<DeviceId, NodeId> masterMap = new HashMap<>();
+ //emulate backups with pile of nodes
+ protected final Map<DeviceId, List<NodeId>> backups = new HashMap<>();
+ //terms
+ protected final Map<DeviceId, AtomicInteger> 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<ControllerNode> 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<MastershipEvent> 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<DeviceId> getDevices(NodeId nodeId) {
+ Set<DeviceId> ids = new HashSet<>();
+ for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
+ if (Objects.equals(d.getValue(), nodeId)) {
+ ids.add(d.getKey());
+ }
+ }
+ return ids;
+ }
+
+ @Override
+ public synchronized CompletableFuture<MastershipRole> 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<NodeId> 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<NodeId> 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<MastershipEvent> 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<NodeId> 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<MastershipEvent> 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<CompletableFuture<MastershipEvent>> eventFutures = new ArrayList<>();
+ Set<DeviceId> 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<DeviceId> 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<NodeId> 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<PacketEvent, PacketStoreDelegate>
+ implements PacketStore {
+
+ private Set<PacketRequest> 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<PacketRequest> 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<ConnectPoint, InternalStatisticRepresentation>
+ representations = new ConcurrentHashMap<>();
+
+ private Map<ConnectPoint, Set<FlowEntry>> previous = new ConcurrentHashMap<>();
+ private Map<ConnectPoint, Set<FlowEntry>> 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<FlowEntry> 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<FlowEntry> flowEntries) {
+ Set<FlowEntry> curr = current.get(cp);
+ if (curr == null) {
+ curr = new HashSet<>();
+ }
+ previous.put(cp, curr);
+ current.put(cp, flowEntries);
+
+ }
+
+ @Override
+ public Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint) {
+ return getCurrentStatisticInternal(connectPoint);
+ }
+
+ private synchronized Set<FlowEntry> getCurrentStatisticInternal(ConnectPoint connectPoint) {
+ return current.get(connectPoint);
+ }
+
+ @Override
+ public Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint) {
+ return getPreviousStatisticInternal(connectPoint);
+ }
+
+ private synchronized Set<FlowEntry> 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<FlowEntry> 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<FlowEntry> 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<TopologyEvent, TopologyStoreDelegate>
+ 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<TopologyCluster> getClusters(Topology topology) {
+ return defaultTopology(topology).getClusters();
+ }
+
+ @Override
+ public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
+ return defaultTopology(topology).getCluster(clusterId);
+ }
+
+ @Override
+ public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
+ return defaultTopology(topology).getClusterDevices(cluster);
+ }
+
+ @Override
+ public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
+ return defaultTopology(topology).getClusterLinks(cluster);
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
+ return defaultTopology(topology).getPaths(src, dst);
+ }
+
+ @Override
+ public Set<Path> 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<Event> 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 @@
+<!--
+ ~ 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.
+ -->
+<app name="org.foo.app" origin="Circus" version="1.2.a"
+ featuresRepo="mvn:org.foo-features/1.2a/xml/features"
+ features="foo,bar">
+ <description>Awesome application from Circus, Inc.</description>
+ <security>
+ <role>ADMIN</role>
+ <permissions>
+ <app-perm>FLOWRULE_WRITE</app-perm>
+ <app-perm>FLOWRULE_READ</app-perm>
+ </permissions>
+
+ </security>
+
+</app>
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
--- /dev/null
+++ b/framework/src/onos/core/common/src/test/resources/org/onosproject/common/app/app.zip
Binary files differ