Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/core/api/src/main/java/org/onlab/onos/net/DefaultAnnotations.java b/core/api/src/main/java/org/onlab/onos/net/DefaultAnnotations.java
index 001518e..0c0f375 100644
--- a/core/api/src/main/java/org/onlab/onos/net/DefaultAnnotations.java
+++ b/core/api/src/main/java/org/onlab/onos/net/DefaultAnnotations.java
@@ -1,5 +1,6 @@
package org.onlab.onos.net;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -71,9 +72,33 @@
return new DefaultAnnotations(merged);
}
+ /**
+ * Convert Annotations to DefaultAnnotations if needed and merges.
+ *
+ * @see #merge(DefaultAnnotations, SparseAnnotations)
+ *
+ * @param annotations base annotations
+ * @param sparseAnnotations additional sparse annotations
+ * @return combined annotations or the original base annotations if there
+ * are not additional annotations
+ */
+ public static DefaultAnnotations merge(Annotations annotations,
+ SparseAnnotations sparseAnnotations) {
+ if (annotations instanceof DefaultAnnotations) {
+ return merge((DefaultAnnotations) annotations, sparseAnnotations);
+ }
+
+ DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
+ for (String key : annotations.keys()) {
+ builder.set(key, annotations.value(key));
+ }
+ return merge(builder.build(), sparseAnnotations);
+ }
+
@Override
public Set<String> keys() {
- return map.keySet();
+ // TODO: unmodifiable to be removed after switching to ImmutableMap;
+ return Collections.unmodifiableSet(map.keySet());
}
@Override
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
index fd04e5c..788d23a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
@@ -45,6 +45,18 @@
this.serialNumber = serialNumber;
}
+ /**
+ * Creates a device description using the supplied information.
+ * @param base DeviceDescription to basic information
+ * @param annotations Annotations to use.
+ */
+ public DefaultDeviceDescription(DeviceDescription base,
+ SparseAnnotations... annotations) {
+ this(base.deviceURI(), base.type(), base.manufacturer(),
+ base.hwVersion(), base.swVersion(), base.serialNumber(),
+ annotations);
+ }
+
@Override
public URI deviceURI() {
return uri;
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
index 1d52ac9..eb75ede 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
@@ -1,20 +1,43 @@
package org.onlab.onos.net.device;
+import org.onlab.onos.net.AbstractDescription;
import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.SparseAnnotations;
/**
* Default implementation of immutable port description.
*/
-public class DefaultPortDescription implements PortDescription {
+public class DefaultPortDescription extends AbstractDescription
+ implements PortDescription {
private final PortNumber number;
private final boolean isEnabled;
- public DefaultPortDescription(PortNumber number, boolean isEnabled) {
+ /**
+ * Creates a port description using the supplied information.
+ *
+ * @param number port number
+ * @param isEnabled port enabled state
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultPortDescription(PortNumber number, boolean isEnabled,
+ SparseAnnotations... annotations) {
+ super(annotations);
this.number = number;
this.isEnabled = isEnabled;
}
+ /**
+ * Creates a port description using the supplied information.
+ *
+ * @param base PortDescription to get basic information from
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultPortDescription(PortDescription base,
+ SparseAnnotations annotations) {
+ this(base.portNumber(), base.isEnabled(), annotations);
+ }
+
@Override
public PortNumber portNumber() {
return number;
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/PortDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/PortDescription.java
index f0dc8ee..f01b49c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/PortDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/PortDescription.java
@@ -1,11 +1,12 @@
package org.onlab.onos.net.device;
+import org.onlab.onos.net.Description;
import org.onlab.onos.net.PortNumber;
/**
* Information about a port.
*/
-public interface PortDescription {
+public interface PortDescription extends Description {
// TODO: possibly relocate this to a common ground so that this can also used by host tracking if required
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/AbstractIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/AbstractIntent.java
new file mode 100644
index 0000000..eefe750
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/AbstractIntent.java
@@ -0,0 +1,49 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * Base intent implementation.
+ */
+public abstract class AbstractIntent implements Intent {
+
+ private final IntentId id;
+
+ /**
+ * Creates a base intent with the specified identifier.
+ *
+ * @param id intent identifier
+ */
+ protected AbstractIntent(IntentId id) {
+ this.id = id;
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected AbstractIntent() {
+ this.id = null;
+ }
+
+ @Override
+ public IntentId getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ AbstractIntent that = (AbstractIntent) o;
+ return id.equals(that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
new file mode 100644
index 0000000..ad34a2c
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
@@ -0,0 +1,99 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A list of BatchOperationEntry.
+ *
+ * @param <T> the enum of operators <br>
+ * This enum must be defined in each sub-classes.
+ *
+ */
+public abstract class BatchOperation<T extends BatchOperationEntry<?, ?>> {
+ private List<T> ops;
+
+ /**
+ * Creates new {@link BatchOperation} object.
+ */
+ public BatchOperation() {
+ ops = new LinkedList<>();
+ }
+
+ /**
+ * Creates {@link BatchOperation} object from a list of batch operation
+ * entries.
+ *
+ * @param batchOperations the list of batch operation entries.
+ */
+ public BatchOperation(List<T> batchOperations) {
+ ops = new LinkedList<>(checkNotNull(batchOperations));
+ }
+
+ /**
+ * Removes all operations maintained in this object.
+ */
+ public void clear() {
+ ops.clear();
+ }
+
+ /**
+ * Returns the number of operations in this object.
+ *
+ * @return the number of operations in this object
+ */
+ public int size() {
+ return ops.size();
+ }
+
+ /**
+ * Returns the operations in this object.
+ *
+ * @return the operations in this object
+ */
+ public List<T> getOperations() {
+ return Collections.unmodifiableList(ops);
+ }
+
+ /**
+ * Adds an operation.
+ *
+ * @param entry the operation to be added
+ * @return this object if succeeded, null otherwise
+ */
+ public BatchOperation<T> addOperation(T entry) {
+ return ops.add(entry) ? this : null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ if (getClass() != o.getClass()) {
+ return false;
+ }
+ BatchOperation<?> other = (BatchOperation<?>) o;
+
+ return this.ops.equals(other.ops);
+ }
+
+ @Override
+ public int hashCode() {
+ return ops.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return ops.toString();
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
new file mode 100644
index 0000000..b5dfa88
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
@@ -0,0 +1,82 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * A super class for batch operation entry classes.
+ * <p>
+ * This is the interface to classes which are maintained by BatchOperation as
+ * its entries.
+ */
+public class BatchOperationEntry<T extends Enum<?>, U extends BatchOperationTarget> {
+ private final T operator;
+ private final U target;
+
+ /**
+ * Default constructor for serializer.
+ */
+ @Deprecated
+ protected BatchOperationEntry() {
+ this.operator = null;
+ this.target = null;
+ }
+
+ /**
+ * Constructs new instance for the entry of the BatchOperation.
+ *
+ * @param operator the operator of this operation
+ * @param target the target object of this operation
+ */
+ public BatchOperationEntry(T operator, U target) {
+ this.operator = operator;
+ this.target = target;
+ }
+
+ /**
+ * Gets the target object of this operation.
+ *
+ * @return the target object of this operation
+ */
+ public U getTarget() {
+ return target;
+ }
+
+ /**
+ * Gets the operator of this operation.
+ *
+ * @return the operator of this operation
+ */
+ public T getOperator() {
+ return operator;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ BatchOperationEntry<?, ?> other = (BatchOperationEntry<?, ?>) o;
+ return (this.operator == other.operator) &&
+ Objects.equals(this.target, other.target);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(operator, target);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("operator", operator)
+ .add("target", target)
+ .toString();
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java
new file mode 100644
index 0000000..c678f31
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java
@@ -0,0 +1,9 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An interface of the class which is assigned to BatchOperation.
+ */
+public interface BatchOperationTarget {
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java
new file mode 100644
index 0000000..629a9d1
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java
@@ -0,0 +1,84 @@
+package org.onlab.onos.net.intent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+
+import com.google.common.base.Objects;
+
+/**
+ * Abstraction of connectivity intent for traffic matching some criteria.
+ */
+public abstract class ConnectivityIntent extends AbstractIntent {
+
+ // TODO: other forms of intents should be considered for this family:
+ // point-to-point with constraints (waypoints/obstacles)
+ // multi-to-single point with constraints (waypoints/obstacles)
+ // single-to-multi point with constraints (waypoints/obstacles)
+ // concrete path (with alternate)
+ // ...
+
+ private final TrafficSelector selector;
+ // TODO: should consider which is better for multiple actions,
+ // defining compound action class or using list of actions.
+ private final TrafficTreatment treatment;
+
+ /**
+ * Creates a connectivity intent that matches on the specified intent
+ * and applies the specified action.
+ *
+ * @param id intent identifier
+ * @param match traffic match
+ * @param action action
+ * @throws NullPointerException if the match or action is null
+ */
+ protected ConnectivityIntent(IntentId id, TrafficSelector match, TrafficTreatment action) {
+ super(id);
+ this.selector = checkNotNull(match);
+ this.treatment = checkNotNull(action);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected ConnectivityIntent() {
+ super();
+ this.selector = null;
+ this.treatment = null;
+ }
+
+ /**
+ * Returns the match specifying the type of traffic.
+ *
+ * @return traffic match
+ */
+ public TrafficSelector getTrafficSelector() {
+ return selector;
+ }
+
+ /**
+ * Returns the action applied to the traffic.
+ *
+ * @return applied action
+ */
+ public TrafficTreatment getTrafficTreatment() {
+ return treatment;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!super.equals(o)) {
+ return false;
+ }
+ ConnectivityIntent that = (ConnectivityIntent) o;
+ return Objects.equal(this.selector, that.selector)
+ && Objects.equal(this.treatment, that.treatment);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(super.hashCode(), selector, treatment);
+ }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IdGenerator.java b/core/api/src/main/java/org/onlab/onos/net/intent/IdGenerator.java
new file mode 100644
index 0000000..0bba622
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IdGenerator.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * A generalized interface for ID generation
+ *
+ * {@link #getNewId()} generates a globally unique ID instance on
+ * each invocation.
+ *
+ * @param <T> the type of ID
+ */
+// TODO: do we need to define a base marker interface for ID,
+// then changed the type parameter to <T extends BaseId> something
+// like that?
+public interface IdGenerator<T> {
+ /**
+ * Returns a globally unique ID instance.
+ *
+ * @return globally unique ID instance
+ */
+ T getNewId();
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/InstallableIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/InstallableIntent.java
new file mode 100644
index 0000000..66bc759
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/InstallableIntent.java
@@ -0,0 +1,8 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * Abstraction of an intent that can be installed into
+ * the underlying system without additional compilation.
+ */
+public interface InstallableIntent extends Intent {
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java b/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java
new file mode 100644
index 0000000..b239ede
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java
@@ -0,0 +1,15 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * Abstraction of an application level intent.
+ *
+ * Make sure that an Intent should be immutable when a new type is defined.
+ */
+public interface Intent extends BatchOperationTarget {
+ /**
+ * Returns the intent identifier.
+ *
+ * @return intent identifier
+ */
+ IntentId getId();
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchOperation.java
new file mode 100644
index 0000000..b450d71
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchOperation.java
@@ -0,0 +1,39 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * A list of intent operations.
+ */
+public class IntentBatchOperation extends
+ BatchOperation<BatchOperationEntry<IntentBatchOperation.Operator, ?>> {
+ /**
+ * The intent operators.
+ */
+ public enum Operator {
+ ADD,
+ REMOVE,
+ }
+
+ /**
+ * Adds an add-intent operation.
+ *
+ * @param intent the intent to be added
+ * @return the IntentBatchOperation object if succeeded, null otherwise
+ */
+ public IntentBatchOperation addAddIntentOperation(Intent intent) {
+ return (null == super.addOperation(
+ new BatchOperationEntry<Operator, Intent>(Operator.ADD, intent)))
+ ? null : this;
+ }
+
+ /**
+ * Adds a remove-intent operation.
+ *
+ * @param id the ID of intent to be removed
+ * @return the IntentBatchOperation object if succeeded, null otherwise
+ */
+ public IntentBatchOperation addRemoveIntentOperation(IntentId id) {
+ return (null == super.addOperation(
+ new BatchOperationEntry<Operator, IntentId>(Operator.REMOVE, id)))
+ ? null : this;
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java
new file mode 100644
index 0000000..dbc3cc4
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java
@@ -0,0 +1,20 @@
+package org.onlab.onos.net.intent;
+
+import java.util.List;
+
+/**
+ * Abstraction of a compiler which is capable of taking an intent
+ * and translating it to other, potentially installable, intents.
+ *
+ * @param <T> the type of intent
+ */
+public interface IntentCompiler<T extends Intent> {
+ /**
+ * Compiles the specified intent into other intents.
+ *
+ * @param intent intent to be compiled
+ * @return list of resulting intents
+ * @throws IntentException if issues are encountered while compiling the intent
+ */
+ List<Intent> compile(T intent);
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java
new file mode 100644
index 0000000..27ae834
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java
@@ -0,0 +1,113 @@
+package org.onlab.onos.net.intent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * A class to represent an intent related event.
+ */
+public class IntentEvent {
+
+ // TODO: determine a suitable parent class; if one does not exist, consider introducing one
+
+ private final long time;
+ private final Intent intent;
+ private final IntentState state;
+ private final IntentState previous;
+
+ /**
+ * Creates an event describing a state change of an intent.
+ *
+ * @param intent subject intent
+ * @param state new intent state
+ * @param previous previous intent state
+ * @param time time the event created in milliseconds since start of epoch
+ * @throws NullPointerException if the intent or state is null
+ */
+ public IntentEvent(Intent intent, IntentState state, IntentState previous, long time) {
+ this.intent = checkNotNull(intent);
+ this.state = checkNotNull(state);
+ this.previous = previous;
+ this.time = time;
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected IntentEvent() {
+ this.intent = null;
+ this.state = null;
+ this.previous = null;
+ this.time = 0;
+ }
+
+ /**
+ * Returns the state of the intent which caused the event.
+ *
+ * @return the state of the intent
+ */
+ public IntentState getState() {
+ return state;
+ }
+
+ /**
+ * Returns the previous state of the intent which caused the event.
+ *
+ * @return the previous state of the intent
+ */
+ public IntentState getPreviousState() {
+ return previous;
+ }
+
+ /**
+ * Returns the intent associated with the event.
+ *
+ * @return the intent
+ */
+ public Intent getIntent() {
+ return intent;
+ }
+
+ /**
+ * Returns the time at which the event was created.
+ *
+ * @return the time in milliseconds since start of epoch
+ */
+ public long getTime() {
+ return time;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ IntentEvent that = (IntentEvent) o;
+ return Objects.equals(this.intent, that.intent)
+ && Objects.equals(this.state, that.state)
+ && Objects.equals(this.previous, that.previous)
+ && Objects.equals(this.time, that.time);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(intent, state, previous, time);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("intent", intent)
+ .add("state", state)
+ .add("previous", previous)
+ .add("time", time)
+ .toString();
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentEventListener.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEventListener.java
new file mode 100644
index 0000000..f59ecfc
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEventListener.java
@@ -0,0 +1,13 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * Listener for {@link IntentEvent intent events}.
+ */
+public interface IntentEventListener {
+ /**
+ * Processes the specified intent event.
+ *
+ * @param event the event to process
+ */
+ void event(IntentEvent event);
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java
new file mode 100644
index 0000000..4148dea
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java
@@ -0,0 +1,35 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * Represents an intent related error.
+ */
+public class IntentException extends RuntimeException {
+
+ private static final long serialVersionUID = 1907263634145241319L;
+
+ /**
+ * Constructs an exception with no message and no underlying cause.
+ */
+ public IntentException() {
+ }
+
+ /**
+ * Constructs an exception with the specified message.
+ *
+ * @param message the message describing the specific nature of the error
+ */
+ public IntentException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an exception with the specified message and the underlying cause.
+ *
+ * @param message the message describing the specific nature of the error
+ * @param cause the underlying cause of this error
+ */
+ public IntentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java
new file mode 100644
index 0000000..c6338a7f
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java
@@ -0,0 +1,57 @@
+package org.onlab.onos.net.intent;
+
+import java.util.Map;
+
+/**
+ * Service for extending the capability of intent framework by
+ * adding additional compilers or/and installers.
+ */
+public interface IntentExtensionService {
+ /**
+ * Registers the specified compiler for the given intent class.
+ *
+ * @param cls intent class
+ * @param compiler intent compiler
+ * @param <T> the type of intent
+ */
+ <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler);
+
+ /**
+ * Unregisters the compiler for the specified intent class.
+ *
+ * @param cls intent class
+ * @param <T> the type of intent
+ */
+ <T extends Intent> void unregisterCompiler(Class<T> cls);
+
+ /**
+ * Returns immutable set of bindings of currently registered intent compilers.
+ *
+ * @return the set of compiler bindings
+ */
+ Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers();
+
+ /**
+ * Registers the specified installer for the given installable intent class.
+ *
+ * @param cls installable intent class
+ * @param installer intent installer
+ * @param <T> the type of installable intent
+ */
+ <T extends InstallableIntent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer);
+
+ /**
+ * Unregisters the installer for the given installable intent class.
+ *
+ * @param cls installable intent class
+ * @param <T> the type of installable intent
+ */
+ <T extends InstallableIntent> void unregisterInstaller(Class<T> cls);
+
+ /**
+ * Returns immutable set of bindings of currently registered intent installers.
+ *
+ * @return the set of installer bindings
+ */
+ Map<Class<? extends InstallableIntent>, IntentInstaller<? extends InstallableIntent>> getInstallers();
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java
new file mode 100644
index 0000000..798e00c
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java
@@ -0,0 +1,68 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * Intent identifier suitable as an external key.
+ *
+ * This class is immutable.
+ */
+public final class IntentId implements BatchOperationTarget {
+
+ private static final int DEC = 10;
+ private static final int HEX = 16;
+
+ private final long id;
+
+ /**
+ * Creates an intent identifier from the specified string representation.
+ *
+ * @param value long value
+ * @return intent identifier
+ */
+ public static IntentId valueOf(String value) {
+ long id = value.toLowerCase().startsWith("0x")
+ ? Long.parseLong(value.substring(2), HEX)
+ : Long.parseLong(value, DEC);
+ return new IntentId(id);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected IntentId() {
+ this.id = 0;
+ }
+
+ /**
+ * Constructs the ID corresponding to a given long value.
+ *
+ * @param id the underlying value of this ID
+ */
+ public IntentId(long id) {
+ this.id = id;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (id ^ (id >>> 32));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof IntentId)) {
+ return false;
+ }
+
+ IntentId that = (IntentId) obj;
+ return this.id == that.id;
+ }
+
+ @Override
+ public String toString() {
+ return "0x" + Long.toHexString(id);
+ }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java
new file mode 100644
index 0000000..738be04
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * Abstraction of entity capable of installing intents to the environment.
+ */
+public interface IntentInstaller<T extends InstallableIntent> {
+ /**
+ * Installs the specified intent to the environment.
+ *
+ * @param intent intent to be installed
+ * @throws IntentException if issues are encountered while installing the intent
+ */
+ void install(T intent);
+
+ /**
+ * Uninstalls the specified intent from the environment.
+ *
+ * @param intent intent to be uninstalled
+ * @throws IntentException if issues are encountered while uninstalling the intent
+ */
+ void uninstall(T intent);
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java
new file mode 100644
index 0000000..470d98b
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java
@@ -0,0 +1,10 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * Abstraction of a batch of intent submit/withdraw operations.
+ */
+public interface IntentOperations {
+
+ // TODO: elaborate once the revised BatchOperation scheme is in place
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
new file mode 100644
index 0000000..2b4fb59
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
@@ -0,0 +1,76 @@
+package org.onlab.onos.net.intent;
+
+import java.util.Set;
+
+/**
+ * Service for application submitting or withdrawing their intents.
+ */
+public interface IntentService {
+ /**
+ * Submits an intent into the system.
+ *
+ * This is an asynchronous request meaning that any compiling
+ * or installation activities may be done at later time.
+ *
+ * @param intent intent to be submitted
+ */
+ void submit(Intent intent);
+
+ /**
+ * Withdraws an intent from the system.
+ *
+ * This is an asynchronous request meaning that the environment
+ * may be affected at later time.
+ *
+ * @param intent intent to be withdrawn
+ */
+ void withdraw(Intent intent);
+
+ /**
+ * Submits a batch of submit & withdraw operations. Such a batch is
+ * assumed to be processed together.
+ *
+ * This is an asynchronous request meaning that the environment
+ * may be affected at later time.
+ *
+ * @param operations batch of intent operations
+ */
+ void execute(IntentOperations operations);
+
+ /**
+ * Returns immutable set of intents currently in the system.
+ *
+ * @return set of intents
+ */
+ Set<Intent> getIntents();
+
+ /**
+ * Retrieves the intent specified by its identifier.
+ *
+ * @param id intent identifier
+ * @return the intent or null if one with the given identifier is not found
+ */
+ Intent getIntent(IntentId id);
+
+ /**
+ * Retrieves the state of an intent by its identifier.
+ *
+ * @param id intent identifier
+ * @return the intent state or null if one with the given identifier is not found
+ */
+ IntentState getIntentState(IntentId id);
+
+ /**
+ * Adds the specified listener for intent events.
+ *
+ * @param listener listener to be added
+ */
+ void addListener(IntentEventListener listener);
+
+ /**
+ * Removes the specified listener for intent events.
+ *
+ * @param listener listener to be removed
+ */
+ void removeListener(IntentEventListener listener);
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java
new file mode 100644
index 0000000..20476e5
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java
@@ -0,0 +1,55 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * This class represents the states of an intent.
+ *
+ * <p>
+ * Note: The state is expressed as enum, but there is possibility
+ * in the future that we define specific class instead of enum to improve
+ * the extensibility of state definition.
+ * </p>
+ */
+public enum IntentState {
+ // FIXME: requires discussion on State vs. EventType and a solid state-transition diagram
+ // TODO: consider the impact of conflict detection
+ // TODO: consider the impact that external events affect an installed intent
+ /**
+ * The beginning state.
+ *
+ * All intent in the runtime take this state first.
+ */
+ SUBMITTED,
+
+ /**
+ * The intent compilation has been completed.
+ *
+ * An intent translation graph (tree) is completely created.
+ * Leaves of the graph are installable intent type.
+ */
+ COMPILED,
+
+ /**
+ * The intent has been successfully installed.
+ */
+ INSTALLED,
+
+ /**
+ * The intent is being withdrawn.
+ *
+ * When {@link IntentService#withdraw(Intent)} is called,
+ * the intent takes this state first.
+ */
+ WITHDRAWING,
+
+ /**
+ * The intent has been successfully withdrawn.
+ */
+ WITHDRAWN,
+
+ /**
+ * The intent has failed to be compiled, installed, or withdrawn.
+ *
+ * When the intent failed to be withdrawn, it is still, at least partially installed.
+ */
+ FAILED,
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
new file mode 100644
index 0000000..1e421ab
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
@@ -0,0 +1,110 @@
+package org.onlab.onos.net.intent;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+import java.util.Set;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Sets;
+
+/**
+ * Abstraction of multiple source to single destination connectivity intent.
+ */
+public class MultiPointToSinglePointIntent extends ConnectivityIntent {
+
+ private final Set<ConnectPoint> ingressPorts;
+ private final ConnectPoint egressPort;
+
+ /**
+ * Creates a new multi-to-single point connectivity intent for the specified
+ * traffic match and action.
+ *
+ * @param id intent identifier
+ * @param match traffic match
+ * @param action action
+ * @param ingressPorts set of ports from which ingress traffic originates
+ * @param egressPort port to which traffic will egress
+ * @throws NullPointerException if {@code ingressPorts} or
+ * {@code egressPort} is null.
+ * @throws IllegalArgumentException if the size of {@code ingressPorts} is
+ * not more than 1
+ */
+ public MultiPointToSinglePointIntent(IntentId id, TrafficSelector match, TrafficTreatment action,
+ Set<ConnectPoint> ingressPorts, ConnectPoint egressPort) {
+ super(id, match, action);
+
+ checkNotNull(ingressPorts);
+ checkArgument(!ingressPorts.isEmpty(),
+ "there should be at least one ingress port");
+
+ this.ingressPorts = Sets.newHashSet(ingressPorts);
+ this.egressPort = checkNotNull(egressPort);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected MultiPointToSinglePointIntent() {
+ super();
+ this.ingressPorts = null;
+ this.egressPort = null;
+ }
+
+ /**
+ * Returns the set of ports on which ingress traffic should be connected to
+ * the egress port.
+ *
+ * @return set of ingress ports
+ */
+ public Set<ConnectPoint> getIngressPorts() {
+ return ingressPorts;
+ }
+
+ /**
+ * Returns the port on which the traffic should egress.
+ *
+ * @return egress port
+ */
+ public ConnectPoint getEgressPort() {
+ return egressPort;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ MultiPointToSinglePointIntent that = (MultiPointToSinglePointIntent) o;
+ return Objects.equals(this.ingressPorts, that.ingressPorts)
+ && Objects.equals(this.egressPort, that.egressPort);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), ingressPorts, egressPort);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", getId())
+ .add("match", getTrafficSelector())
+ .add("action", getTrafficTreatment())
+ .add("ingressPorts", getIngressPorts())
+ .add("egressPort", getEgressPort())
+ .toString();
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java
new file mode 100644
index 0000000..d11dc7c
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java
@@ -0,0 +1,58 @@
+package org.onlab.onos.net.intent;
+
+import org.onlab.onos.net.ConnectPoint;
+
+// TODO: consider if this intent should be sub-class of ConnectivityIntent
+/**
+ * An optical layer Intent for a connectivity from a transponder port to another
+ * transponder port.
+ * <p>
+ * This class doesn't accepts lambda specifier. This class computes path between
+ * ports and assign lambda automatically. The lambda can be specified using
+ * OpticalPathFlow class.
+ */
+public class OpticalConnectivityIntent extends AbstractIntent {
+ protected ConnectPoint srcConnectPoint;
+ protected ConnectPoint dstConnectPoint;
+
+ /**
+ * Constructor.
+ *
+ * @param id ID for this new Intent object.
+ * @param srcConnectPoint The source transponder port.
+ * @param dstConnectPoint The destination transponder port.
+ */
+ public OpticalConnectivityIntent(IntentId id,
+ ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
+ super(id);
+ this.srcConnectPoint = srcConnectPoint;
+ this.dstConnectPoint = dstConnectPoint;
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected OpticalConnectivityIntent() {
+ super();
+ this.srcConnectPoint = null;
+ this.dstConnectPoint = null;
+ }
+
+ /**
+ * Gets source transponder port.
+ *
+ * @return The source transponder port.
+ */
+ public ConnectPoint getSrcConnectPoint() {
+ return srcConnectPoint;
+ }
+
+ /**
+ * Gets destination transponder port.
+ *
+ * @return The source transponder port.
+ */
+ public ConnectPoint getDstConnectPoint() {
+ return dstConnectPoint;
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PacketConnectivityIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PacketConnectivityIntent.java
new file mode 100644
index 0000000..893d70e
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PacketConnectivityIntent.java
@@ -0,0 +1,177 @@
+package org.onlab.onos.net.intent;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.flow.TrafficSelector;
+
+// TODO: consider if this intent should be sub-class of Connectivity intent
+/**
+ * A packet layer Intent for a connectivity from a set of ports to a set of
+ * ports.
+ * <p>
+ * TODO: Design methods to support the ReactiveForwarding and the SDN-IP. <br>
+ * NOTE: Should this class support modifier methods? Should this object a
+ * read-only object?
+ */
+public class PacketConnectivityIntent extends AbstractIntent {
+ protected Set<ConnectPoint> srcConnectPoints;
+ protected TrafficSelector selector;
+ protected Set<ConnectPoint> dstConnectPoints;
+ protected boolean canSetupOpticalFlow;
+ protected int idleTimeoutValue;
+ protected int hardTimeoutValue;
+
+ /**
+ * Creates a connectivity intent for the packet layer.
+ * <p>
+ * When the "canSetupOpticalFlow" option is true, this intent will compute
+ * the packet/optical converged path, decompose it to the OpticalPathFlow
+ * and the PacketPathFlow objects, and execute the operations to add them
+ * considering the dependency between the packet and optical layers.
+ *
+ * @param id ID for this new Intent object.
+ * @param srcConnectPoints The set of source switch ports.
+ * @param match Traffic specifier for this object.
+ * @param dstConnectPoints The set of destination switch ports.
+ * @param canSetupOpticalFlow The flag whether this intent can create
+ * optical flows if needed.
+ */
+ public PacketConnectivityIntent(IntentId id,
+ Collection<ConnectPoint> srcConnectPoints, TrafficSelector match,
+ Collection<ConnectPoint> dstConnectPoints, boolean canSetupOpticalFlow) {
+ super(id);
+ this.srcConnectPoints = new HashSet<ConnectPoint>(srcConnectPoints);
+ this.selector = match;
+ this.dstConnectPoints = new HashSet<ConnectPoint>(dstConnectPoints);
+ this.canSetupOpticalFlow = canSetupOpticalFlow;
+ this.idleTimeoutValue = 0;
+ this.hardTimeoutValue = 0;
+
+ // TODO: check consistency between these parameters.
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected PacketConnectivityIntent() {
+ super();
+ this.srcConnectPoints = null;
+ this.selector = null;
+ this.dstConnectPoints = null;
+ this.canSetupOpticalFlow = false;
+ this.idleTimeoutValue = 0;
+ this.hardTimeoutValue = 0;
+ }
+
+ /**
+ * Gets the set of source switch ports.
+ *
+ * @return the set of source switch ports.
+ */
+ public Collection<ConnectPoint> getSrcConnectPoints() {
+ return Collections.unmodifiableCollection(srcConnectPoints);
+ }
+
+ /**
+ * Gets the traffic specifier.
+ *
+ * @return The traffic specifier.
+ */
+ public TrafficSelector getMatch() {
+ return selector;
+ }
+
+ /**
+ * Gets the set of destination switch ports.
+ *
+ * @return the set of destination switch ports.
+ */
+ public Collection<ConnectPoint> getDstConnectPoints() {
+ return Collections.unmodifiableCollection(dstConnectPoints);
+ }
+
+ /**
+ * Adds the specified port to the set of source ports.
+ *
+ * @param port ConnectPoint object to be added
+ */
+ public void addSrcConnectPoint(ConnectPoint port) {
+ // TODO implement it.
+ }
+
+ /**
+ * Adds the specified port to the set of destination ports.
+ *
+ * @param port ConnectPoint object to be added
+ */
+ public void addDstConnectPoint(ConnectPoint port) {
+ // TODO implement it.
+ }
+
+ /**
+ * Removes the specified port from the set of source ports.
+ *
+ * @param port ConnectPoint object to be removed
+ */
+ public void removeSrcConnectPoint(ConnectPoint port) {
+ // TODO implement it.
+ }
+
+ /**
+ * Removes the specified port from the set of destination ports.
+ *
+ * @param port ConnectPoint object to be removed
+ */
+ public void removeDstConnectPoint(ConnectPoint port) {
+ // TODO implement it.
+ }
+
+ /**
+ * Sets idle-timeout value.
+ *
+ * @param timeout Idle-timeout value (seconds)
+ */
+ public void setIdleTimeout(int timeout) {
+ idleTimeoutValue = timeout;
+ }
+
+ /**
+ * Sets hard-timeout value.
+ *
+ * @param timeout Hard-timeout value (seconds)
+ */
+ public void setHardTimeout(int timeout) {
+ hardTimeoutValue = timeout;
+ }
+
+ /**
+ * Gets idle-timeout value.
+ *
+ * @return Idle-timeout value (seconds)
+ */
+ public int getIdleTimeout() {
+ return idleTimeoutValue;
+ }
+
+ /**
+ * Gets hard-timeout value.
+ *
+ * @return Hard-timeout value (seconds)
+ */
+ public int getHardTimeout() {
+ return hardTimeoutValue;
+ }
+
+ /**
+ * Returns whether this intent can create optical flows if needed.
+ *
+ * @return whether this intent can create optical flows.
+ */
+ public boolean canSetupOpticalFlow() {
+ return canSetupOpticalFlow;
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
new file mode 100644
index 0000000..39ad011
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
@@ -0,0 +1,89 @@
+package org.onlab.onos.net.intent;
+
+import java.util.Objects;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Abstraction of explicitly path specified connectivity intent.
+ */
+public class PathIntent extends PointToPointIntent {
+
+ private final Path path;
+
+ /**
+ * Creates a new point-to-point intent with the supplied ingress/egress
+ * ports and using the specified explicit path.
+ *
+ * @param id intent identifier
+ * @param match traffic match
+ * @param action action
+ * @param ingressPort ingress port
+ * @param egressPort egress port
+ * @param path traversed links
+ * @throws NullPointerException {@code path} is null
+ */
+ public PathIntent(IntentId id, TrafficSelector match, TrafficTreatment action,
+ ConnectPoint ingressPort, ConnectPoint egressPort,
+ Path path) {
+ super(id, match, action, ingressPort, egressPort);
+ this.path = path;
+ }
+
+ protected PathIntent() {
+ super();
+ this.path = null;
+ }
+
+ /**
+ * Returns the links which the traffic goes along.
+ *
+ * @return traversed links
+ */
+ public Path getPath() {
+ return path;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ PathIntent that = (PathIntent) o;
+
+ if (!path.equals(that.path)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), path);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", getId())
+ .add("match", getTrafficSelector())
+ .add("action", getTrafficTreatment())
+ .add("ingressPort", getIngressPort())
+ .add("egressPort", getEgressPort())
+ .add("path", path)
+ .toString();
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java
new file mode 100644
index 0000000..b1d18ee
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java
@@ -0,0 +1,100 @@
+package org.onlab.onos.net.intent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Abstraction of point-to-point connectivity.
+ */
+public class PointToPointIntent extends ConnectivityIntent {
+
+ private final ConnectPoint ingressPort;
+ private final ConnectPoint egressPort;
+
+ /**
+ * Creates a new point-to-point intent with the supplied ingress/egress
+ * ports.
+ *
+ * @param id intent identifier
+ * @param match traffic match
+ * @param action action
+ * @param ingressPort ingress port
+ * @param egressPort egress port
+ * @throws NullPointerException if {@code ingressPort} or {@code egressPort} is null.
+ */
+ public PointToPointIntent(IntentId id, TrafficSelector match, TrafficTreatment action,
+ ConnectPoint ingressPort, ConnectPoint egressPort) {
+ super(id, match, action);
+ this.ingressPort = checkNotNull(ingressPort);
+ this.egressPort = checkNotNull(egressPort);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected PointToPointIntent() {
+ super();
+ this.ingressPort = null;
+ this.egressPort = null;
+ }
+
+ /**
+ * Returns the port on which the ingress traffic should be connected to
+ * the egress.
+ *
+ * @return ingress port
+ */
+ public ConnectPoint getIngressPort() {
+ return ingressPort;
+ }
+
+ /**
+ * Returns the port on which the traffic should egress.
+ *
+ * @return egress port
+ */
+ public ConnectPoint getEgressPort() {
+ return egressPort;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ PointToPointIntent that = (PointToPointIntent) o;
+ return Objects.equals(this.ingressPort, that.ingressPort)
+ && Objects.equals(this.egressPort, that.egressPort);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), ingressPort, egressPort);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", getId())
+ .add("match", getTrafficSelector())
+ .add("action", getTrafficTreatment())
+ .add("ingressPort", ingressPort)
+ .add("egressPort", egressPort)
+ .toString();
+ }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
new file mode 100644
index 0000000..e69a740
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
@@ -0,0 +1,110 @@
+package org.onlab.onos.net.intent;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+import java.util.Set;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Sets;
+
+/**
+ * Abstraction of single source, multiple destination connectivity intent.
+ */
+public class SinglePointToMultiPointIntent extends ConnectivityIntent {
+
+ private final ConnectPoint ingressPort;
+ private final Set<ConnectPoint> egressPorts;
+
+ /**
+ * Creates a new single-to-multi point connectivity intent.
+ *
+ * @param id intent identifier
+ * @param match traffic match
+ * @param action action
+ * @param ingressPort port on which traffic will ingress
+ * @param egressPorts set of ports on which traffic will egress
+ * @throws NullPointerException if {@code ingressPort} or
+ * {@code egressPorts} is null
+ * @throws IllegalArgumentException if the size of {@code egressPorts} is
+ * not more than 1
+ */
+ public SinglePointToMultiPointIntent(IntentId id, TrafficSelector match, TrafficTreatment action,
+ ConnectPoint ingressPort,
+ Set<ConnectPoint> egressPorts) {
+ super(id, match, action);
+
+ checkNotNull(egressPorts);
+ checkArgument(!egressPorts.isEmpty(),
+ "there should be at least one egress port");
+
+ this.ingressPort = checkNotNull(ingressPort);
+ this.egressPorts = Sets.newHashSet(egressPorts);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected SinglePointToMultiPointIntent() {
+ super();
+ this.ingressPort = null;
+ this.egressPorts = null;
+ }
+
+ /**
+ * Returns the port on which the ingress traffic should be connected to the egress.
+ *
+ * @return ingress port
+ */
+ public ConnectPoint getIngressPort() {
+ return ingressPort;
+ }
+
+ /**
+ * Returns the set of ports on which the traffic should egress.
+ *
+ * @return set of egress ports
+ */
+ public Set<ConnectPoint> getEgressPorts() {
+ return egressPorts;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ SinglePointToMultiPointIntent that = (SinglePointToMultiPointIntent) o;
+ return Objects.equals(this.ingressPort, that.ingressPort)
+ && Objects.equals(this.egressPorts, that.egressPorts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), ingressPort, egressPorts);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", getId())
+ .add("match", getTrafficSelector())
+ .add("action", getTrafficTreatment())
+ .add("ingressPort", ingressPort)
+ .add("egressPort", egressPorts)
+ .toString();
+ }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java b/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java
new file mode 100644
index 0000000..e1e6782
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Intent Package. TODO
+ */
+
+package org.onlab.onos.net.intent;
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java
new file mode 100644
index 0000000..fb1efee
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java
@@ -0,0 +1,28 @@
+package org.onlab.onos.net.intent;
+
+import java.util.Set;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+
+/**
+ * Base facilities to test various connectivity tests.
+ */
+public abstract class ConnectivityIntentTest extends IntentTest {
+
+ public static final IntentId IID = new IntentId(123);
+ public static final TrafficSelector MATCH = (new DefaultTrafficSelector.Builder()).build();
+ public static final TrafficTreatment NOP = (new DefaultTrafficTreatment.Builder()).build();
+
+ public static final ConnectPoint P1 = new ConnectPoint(DeviceId.deviceId("111"), PortNumber.portNumber(0x1));
+ public static final ConnectPoint P2 = new ConnectPoint(DeviceId.deviceId("222"), PortNumber.portNumber(0x2));
+ public static final ConnectPoint P3 = new ConnectPoint(DeviceId.deviceId("333"), PortNumber.portNumber(0x3));
+
+ public static final Set<ConnectPoint> PS1 = itemSet(new ConnectPoint[]{P1, P3});
+ public static final Set<ConnectPoint> PS2 = itemSet(new ConnectPoint[]{P2, P3});
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
new file mode 100644
index 0000000..df46ec5
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
@@ -0,0 +1,268 @@
+package org.onlab.onos.net.intent;
+
+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.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Fake implementation of the intent service to assist in developing tests
+ * of the interface contract.
+ */
+public class FakeIntentManager implements TestableIntentService {
+
+ private final Map<IntentId, Intent> intents = new HashMap<>();
+ private final Map<IntentId, IntentState> intentStates = new HashMap<>();
+ private final Map<IntentId, List<InstallableIntent>> installables = new HashMap<>();
+ private final Set<IntentEventListener> listeners = new HashSet<>();
+
+ private final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers = new HashMap<>();
+ private final Map<Class<? extends InstallableIntent>,
+ IntentInstaller<? extends InstallableIntent>> installers = new HashMap<>();
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ private final List<IntentException> exceptions = new ArrayList<>();
+
+ @Override
+ public List<IntentException> getExceptions() {
+ return exceptions;
+ }
+
+ // Provides an out-of-thread simulation of intent submit life-cycle
+ private void executeSubmit(final Intent intent) {
+ registerSubclassCompilerIfNeeded(intent);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ List<InstallableIntent> installable = compileIntent(intent);
+ installIntents(intent, installable);
+ } catch (IntentException e) {
+ exceptions.add(e);
+ }
+ }
+ });
+ }
+
+ // Provides an out-of-thread simulation of intent withdraw life-cycle
+ private void executeWithdraw(final Intent intent) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ List<InstallableIntent> installable = getInstallable(intent.getId());
+ uninstallIntents(intent, installable);
+ } catch (IntentException e) {
+ exceptions.add(e);
+ }
+
+ }
+ });
+ }
+
+ private <T extends Intent> IntentCompiler<T> getCompiler(T intent) {
+ @SuppressWarnings("unchecked")
+ IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass());
+ if (compiler == null) {
+ throw new IntentException("no compiler for class " + intent.getClass());
+ }
+ return compiler;
+ }
+
+ private <T extends InstallableIntent> IntentInstaller<T> getInstaller(T intent) {
+ @SuppressWarnings("unchecked")
+ IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass());
+ if (installer == null) {
+ throw new IntentException("no installer for class " + intent.getClass());
+ }
+ return installer;
+ }
+
+ private <T extends Intent> List<InstallableIntent> compileIntent(T intent) {
+ try {
+ // For the fake, we compile using a single level pass
+ List<InstallableIntent> installable = new ArrayList<>();
+ for (Intent compiled : getCompiler(intent).compile(intent)) {
+ installable.add((InstallableIntent) compiled);
+ }
+ setState(intent, IntentState.COMPILED);
+ return installable;
+ } catch (IntentException e) {
+ setState(intent, IntentState.FAILED);
+ throw e;
+ }
+ }
+
+ private void installIntents(Intent intent, List<InstallableIntent> installable) {
+ try {
+ for (InstallableIntent ii : installable) {
+ registerSubclassInstallerIfNeeded(ii);
+ getInstaller(ii).install(ii);
+ }
+ setState(intent, IntentState.INSTALLED);
+ putInstallable(intent.getId(), installable);
+ } catch (IntentException e) {
+ setState(intent, IntentState.FAILED);
+ throw e;
+ }
+ }
+
+ private void uninstallIntents(Intent intent, List<InstallableIntent> installable) {
+ try {
+ for (InstallableIntent ii : installable) {
+ getInstaller(ii).uninstall(ii);
+ }
+ setState(intent, IntentState.WITHDRAWN);
+ removeInstallable(intent.getId());
+ } catch (IntentException e) {
+ setState(intent, IntentState.FAILED);
+ throw e;
+ }
+ }
+
+
+ // Sets the internal state for the given intent and dispatches an event
+ private void setState(Intent intent, IntentState state) {
+ IntentState previous = intentStates.get(intent.getId());
+ intentStates.put(intent.getId(), state);
+ dispatch(new IntentEvent(intent, state, previous, System.currentTimeMillis()));
+ }
+
+ private void putInstallable(IntentId id, List<InstallableIntent> installable) {
+ installables.put(id, installable);
+ }
+
+ private void removeInstallable(IntentId id) {
+ installables.remove(id);
+ }
+
+ private List<InstallableIntent> getInstallable(IntentId id) {
+ List<InstallableIntent> installable = installables.get(id);
+ if (installable != null) {
+ return installable;
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public void submit(Intent intent) {
+ intents.put(intent.getId(), intent);
+ setState(intent, IntentState.SUBMITTED);
+ executeSubmit(intent);
+ }
+
+ @Override
+ public void withdraw(Intent intent) {
+ intents.remove(intent.getId());
+ setState(intent, IntentState.WITHDRAWING);
+ executeWithdraw(intent);
+ }
+
+ @Override
+ public void execute(IntentOperations operations) {
+ // TODO: implement later
+ }
+
+ @Override
+ public Set<Intent> getIntents() {
+ return Collections.unmodifiableSet(new HashSet<>(intents.values()));
+ }
+
+ @Override
+ public Intent getIntent(IntentId id) {
+ return intents.get(id);
+ }
+
+ @Override
+ public IntentState getIntentState(IntentId id) {
+ return intentStates.get(id);
+ }
+
+ @Override
+ public void addListener(IntentEventListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(IntentEventListener listener) {
+ listeners.remove(listener);
+ }
+
+ private void dispatch(IntentEvent event) {
+ for (IntentEventListener listener : listeners) {
+ listener.event(event);
+ }
+ }
+
+ @Override
+ public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) {
+ compilers.put(cls, compiler);
+ }
+
+ @Override
+ public <T extends Intent> void unregisterCompiler(Class<T> cls) {
+ compilers.remove(cls);
+ }
+
+ @Override
+ public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() {
+ return Collections.unmodifiableMap(compilers);
+ }
+
+ @Override
+ public <T extends InstallableIntent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
+ installers.put(cls, installer);
+ }
+
+ @Override
+ public <T extends InstallableIntent> void unregisterInstaller(Class<T> cls) {
+ installers.remove(cls);
+ }
+
+ @Override
+ public Map<Class<? extends InstallableIntent>,
+ IntentInstaller<? extends InstallableIntent>> getInstallers() {
+ return Collections.unmodifiableMap(installers);
+ }
+
+ private void registerSubclassCompilerIfNeeded(Intent intent) {
+ if (!compilers.containsKey(intent.getClass())) {
+ Class<?> cls = intent.getClass();
+ while (cls != Object.class) {
+ // As long as we're within the Intent class descendants
+ if (Intent.class.isAssignableFrom(cls)) {
+ IntentCompiler<?> compiler = compilers.get(cls);
+ if (compiler != null) {
+ compilers.put(intent.getClass(), compiler);
+ return;
+ }
+ }
+ cls = cls.getSuperclass();
+ }
+ }
+ }
+
+ private void registerSubclassInstallerIfNeeded(InstallableIntent intent) {
+ if (!installers.containsKey(intent.getClass())) {
+ Class<?> cls = intent.getClass();
+ while (cls != Object.class) {
+ // As long as we're within the InstallableIntent class descendants
+ if (InstallableIntent.class.isAssignableFrom(cls)) {
+ IntentInstaller<?> installer = installers.get(cls);
+ if (installer != null) {
+ installers.put(intent.getClass(), installer);
+ return;
+ }
+ }
+ cls = cls.getSuperclass();
+ }
+ }
+ }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/ImmutableClassChecker.java b/core/api/src/test/java/org/onlab/onos/net/intent/ImmutableClassChecker.java
new file mode 100644
index 0000000..0e63af9
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/ImmutableClassChecker.java
@@ -0,0 +1,125 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+import org.hamcrest.Description;
+import org.hamcrest.StringDescription;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Hamcrest style class for verifying that a class follows the
+ * accepted rules for immutable classes.
+ *
+ * The rules that are enforced for immutable classes:
+ * - the class must be declared final
+ * - all data members of the class must be declared private and final
+ * - the class must not define any setter methods
+ */
+
+public class ImmutableClassChecker {
+
+ private String failureReason = "";
+
+ /**
+ * Method to determine if a given class is a properly specified
+ * immutable class.
+ *
+ * @param clazz the class to check
+ * @return true if the given class is a properly specified immutable class.
+ */
+ private boolean isImmutableClass(Class<?> clazz) {
+ // class must be declared final
+ if (!Modifier.isFinal(clazz.getModifiers())) {
+ failureReason = "a class that is not final";
+ return false;
+ }
+
+ // class must have only final and private data members
+ for (final Field field : clazz.getDeclaredFields()) {
+ if (field.getName().startsWith("__cobertura")) {
+ // cobertura sticks these fields into classes - ignore them
+ continue;
+ }
+ if (!Modifier.isFinal(field.getModifiers())) {
+ failureReason = "a field named '" + field.getName() +
+ "' that is not final";
+ return false;
+ }
+ if (!Modifier.isPrivate(field.getModifiers())) {
+ //
+ // NOTE: We relax the recommended rules for defining immutable
+ // objects and allow "static final" fields that are not
+ // private. The "final" check was already done above so we
+ // don't repeat it here.
+ //
+ if (!Modifier.isStatic(field.getModifiers())) {
+ failureReason = "a field named '" + field.getName() +
+ "' that is not private and is not static";
+ return false;
+ }
+ }
+ }
+
+ // class must not define any setters
+ for (final Method method : clazz.getMethods()) {
+ if (method.getDeclaringClass().equals(clazz)) {
+ if (method.getName().startsWith("set")) {
+ failureReason = "a class with a setter named '" + method.getName() + "'";
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Describe why an error was reported. Uses Hamcrest style Description
+ * interfaces.
+ *
+ * @param description the Description object to use for reporting the
+ * mismatch
+ */
+ public void describeMismatch(Description description) {
+ description.appendText(failureReason);
+ }
+
+ /**
+ * Describe the source object that caused an error, using a Hamcrest
+ * Matcher style interface. In this case, it always returns
+ * that we are looking for a properly defined utility class.
+ *
+ * @param description the Description object to use to report the "to"
+ * object
+ */
+ public void describeTo(Description description) {
+ description.appendText("a properly defined immutable class");
+ }
+
+ /**
+ * Assert that the given class adheres to the utility class rules.
+ *
+ * @param clazz the class to check
+ *
+ * @throws java.lang.AssertionError if the class is not a valid
+ * utility class
+ */
+ public static void assertThatClassIsImmutable(Class<?> clazz) {
+ final ImmutableClassChecker checker = new ImmutableClassChecker();
+ if (!checker.isImmutableClass(clazz)) {
+ final Description toDescription = new StringDescription();
+ final Description mismatchDescription = new StringDescription();
+
+ checker.describeTo(toDescription);
+ checker.describeMismatch(mismatchDescription);
+ final String reason =
+ "\n" +
+ "Expected: is \"" + toDescription.toString() + "\"\n" +
+ " but : was \"" + mismatchDescription.toString() + "\"";
+
+ throw new AssertionError(reason);
+ }
+ }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentExceptionTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentExceptionTest.java
new file mode 100644
index 0000000..02564e6
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentExceptionTest.java
@@ -0,0 +1,33 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the intent exception.
+ */
+public class IntentExceptionTest {
+
+ @Test
+ public void basics() {
+ validate(new IntentException(), null, null);
+ validate(new IntentException("foo"), "foo", null);
+
+ Throwable cause = new NullPointerException("bar");
+ validate(new IntentException("foo", cause), "foo", cause);
+ }
+
+ /**
+ * Validates that the specified exception has the correct message and cause.
+ *
+ * @param e exception to test
+ * @param message expected message
+ * @param cause expected cause
+ */
+ protected void validate(RuntimeException e, String message, Throwable cause) {
+ assertEquals("incorrect message", message, e.getMessage());
+ assertEquals("incorrect cause", cause, e.getCause());
+ }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdGenerator.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdGenerator.java
new file mode 100644
index 0000000..0ca669b
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdGenerator.java
@@ -0,0 +1,14 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * This interface is for generator of IntentId. It is defined only for
+ * testing purpose to keep type safety on mock creation.
+ *
+ * <p>
+ * {@link #getNewId()} generates a globally unique {@link IntentId} instance
+ * on each invocation. Application developers should not generate IntentId
+ * by themselves. Instead use an implementation of this interface.
+ * </p>
+ */
+public interface IntentIdGenerator extends IdGenerator<IntentId> {
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdTest.java
new file mode 100644
index 0000000..2a0824c
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdTest.java
@@ -0,0 +1,57 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * This class tests the immutability, equality, and non-equality of
+ * {@link IntentId}.
+ */
+public class IntentIdTest {
+ /**
+ * Tests the immutability of {@link IntentId}.
+ */
+ @Test
+ public void intentIdFollowsGuidelineForImmutableObject() {
+ ImmutableClassChecker.assertThatClassIsImmutable(IntentId.class);
+ }
+
+ /**
+ * Tests equality of {@link IntentId}.
+ */
+ @Test
+ public void testEquality() {
+ IntentId id1 = new IntentId(1L);
+ IntentId id2 = new IntentId(1L);
+
+ assertThat(id1, is(id2));
+ }
+
+ /**
+ * Tests non-equality of {@link IntentId}.
+ */
+ @Test
+ public void testNonEquality() {
+ IntentId id1 = new IntentId(1L);
+ IntentId id2 = new IntentId(2L);
+
+ assertThat(id1, is(not(id2)));
+ }
+
+ @Test
+ public void valueOf() {
+ IntentId id = new IntentId(12345);
+ assertEquals("incorrect valueOf", id, IntentId.valueOf("12345"));
+ }
+
+ @Test
+ public void valueOfHex() {
+ IntentId id = new IntentId(0xdeadbeefL);
+ assertEquals("incorrect valueOf", id, IntentId.valueOf(id.toString()));
+ }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
new file mode 100644
index 0000000..c7682b1
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
@@ -0,0 +1,310 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.onlab.onos.net.intent.IntentState.*;
+import static org.junit.Assert.*;
+
+// TODO: consider make it categorized as integration test when it become
+// slow test or fragile test
+/**
+ * Suite of tests for the intent service contract.
+ */
+public class IntentServiceTest {
+
+ public static final IntentId IID = new IntentId(123);
+ public static final IntentId INSTALLABLE_IID = new IntentId(234);
+
+ protected static final int GRACE_MS = 500; // millis
+
+ protected TestableIntentService service;
+ protected TestListener listener = new TestListener();
+
+ @Before
+ public void setUp() {
+ service = createIntentService();
+ service.addListener(listener);
+ }
+
+ @After
+ public void tearDown() {
+ service.removeListener(listener);
+ }
+
+ /**
+ * Creates a service instance appropriately instrumented for testing.
+ *
+ * @return testable intent service
+ */
+ protected TestableIntentService createIntentService() {
+ return new FakeIntentManager();
+ }
+
+ @Test
+ public void basics() {
+ // Make sure there are no intents
+ assertEquals("incorrect intent count", 0, service.getIntents().size());
+
+ // Register a compiler and an installer both setup for success.
+ service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID)));
+ service.registerInstaller(TestInstallableIntent.class, new TestInstaller(false));
+
+ final Intent intent = new TestIntent(IID);
+ service.submit(intent);
+
+ // Allow a small window of time until the intent is in the expected state
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", INSTALLED,
+ service.getIntentState(intent.getId()));
+ }
+ });
+
+ // Make sure that all expected events have been emitted
+ validateEvents(intent, SUBMITTED, COMPILED, INSTALLED);
+
+ // Make sure there is just one intent (and is ours)
+ assertEquals("incorrect intent count", 1, service.getIntents().size());
+ assertEquals("incorrect intent", intent, service.getIntent(intent.getId()));
+
+ // Reset the listener events
+ listener.events.clear();
+
+ // Now withdraw the intent
+ service.withdraw(intent);
+
+ // Allow a small window of time until the event is in the expected state
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", WITHDRAWN,
+ service.getIntentState(intent.getId()));
+ }
+ });
+
+ // Make sure that all expected events have been emitted
+ validateEvents(intent, WITHDRAWING, WITHDRAWN);
+
+ // TODO: discuss what is the fate of intents after they have been withdrawn
+ // Make sure that the intent is no longer in the system
+// assertEquals("incorrect intent count", 0, service.getIntents().size());
+// assertNull("intent should not be found", service.getIntent(intent.getId()));
+// assertNull("intent state should not be found", service.getIntentState(intent.getId()));
+ }
+
+ @Test
+ public void failedCompilation() {
+ // Register a compiler programmed for success
+ service.registerCompiler(TestIntent.class, new TestCompiler(true));
+
+ // Submit an intent
+ final Intent intent = new TestIntent(IID);
+ service.submit(intent);
+
+ // Allow a small window of time until the intent is in the expected state
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", FAILED,
+ service.getIntentState(intent.getId()));
+ }
+ });
+
+ // Make sure that all expected events have been emitted
+ validateEvents(intent, SUBMITTED, FAILED);
+ }
+
+ @Test
+ public void failedInstallation() {
+ // Register a compiler programmed for success and installer for failure
+ service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID)));
+ service.registerInstaller(TestInstallableIntent.class, new TestInstaller(true));
+
+ // Submit an intent
+ final Intent intent = new TestIntent(IID);
+ service.submit(intent);
+
+ // Allow a small window of time until the intent is in the expected state
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", FAILED,
+ service.getIntentState(intent.getId()));
+ }
+ });
+
+ // Make sure that all expected events have been emitted
+ validateEvents(intent, SUBMITTED, COMPILED, FAILED);
+ }
+
+ /**
+ * Validates that the test event listener has received the following events
+ * for the specified intent. Events received for other intents will not be
+ * considered.
+ *
+ * @param intent intent subject
+ * @param states list of states for which events are expected
+ */
+ protected void validateEvents(Intent intent, IntentState... states) {
+ Iterator<IntentEvent> events = listener.events.iterator();
+ for (IntentState state : states) {
+ IntentEvent event = events.hasNext() ? events.next() : null;
+ if (event == null) {
+ fail("expected event not found: " + state);
+ } else if (intent.equals(event.getIntent())) {
+ assertEquals("incorrect state", state, event.getState());
+ }
+ }
+
+ // Remainder of events should not apply to this intent; make sure.
+ while (events.hasNext()) {
+ assertFalse("unexpected event for intent",
+ intent.equals(events.next().getIntent()));
+ }
+ }
+
+ @Test
+ public void compilerBasics() {
+ // Make sure there are no compilers
+ assertEquals("incorrect compiler count", 0, service.getCompilers().size());
+
+ // Add a compiler and make sure that it appears in the map
+ IntentCompiler<TestIntent> compiler = new TestCompiler(false);
+ service.registerCompiler(TestIntent.class, compiler);
+ assertEquals("incorrect compiler", compiler,
+ service.getCompilers().get(TestIntent.class));
+
+ // Remove the same and make sure that it no longer appears in the map
+ service.unregisterCompiler(TestIntent.class);
+ assertNull("compiler should not be registered",
+ service.getCompilers().get(TestIntent.class));
+ }
+
+ @Test
+ public void installerBasics() {
+ // Make sure there are no installers
+ assertEquals("incorrect installer count", 0, service.getInstallers().size());
+
+ // Add an installer and make sure that it appears in the map
+ IntentInstaller<TestInstallableIntent> installer = new TestInstaller(false);
+ service.registerInstaller(TestInstallableIntent.class, installer);
+ assertEquals("incorrect installer", installer,
+ service.getInstallers().get(TestInstallableIntent.class));
+
+ // Remove the same and make sure that it no longer appears in the map
+ service.unregisterInstaller(TestInstallableIntent.class);
+ assertNull("installer should not be registered",
+ service.getInstallers().get(TestInstallableIntent.class));
+ }
+
+ @Test
+ public void implicitRegistration() {
+ // Add a compiler and make sure that it appears in the map
+ IntentCompiler<TestIntent> compiler = new TestCompiler(new TestSubclassInstallableIntent(INSTALLABLE_IID));
+ service.registerCompiler(TestIntent.class, compiler);
+ assertEquals("incorrect compiler", compiler,
+ service.getCompilers().get(TestIntent.class));
+
+ // Add a installer and make sure that it appears in the map
+ IntentInstaller<TestInstallableIntent> installer = new TestInstaller(false);
+ service.registerInstaller(TestInstallableIntent.class, installer);
+ assertEquals("incorrect installer", installer,
+ service.getInstallers().get(TestInstallableIntent.class));
+
+
+ // Submit an intent which is a subclass of the one we registered
+ final Intent intent = new TestSubclassIntent(IID);
+ service.submit(intent);
+
+ // Allow some time for the intent to be compiled and installed
+ TestTools.assertAfter(GRACE_MS, new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("incorrect intent state", INSTALLED,
+ service.getIntentState(intent.getId()));
+ }
+ });
+
+ // Make sure that now we have an implicit registration of the compiler
+ // under the intent subclass
+ assertEquals("incorrect compiler", compiler,
+ service.getCompilers().get(TestSubclassIntent.class));
+
+ // Make sure that now we have an implicit registration of the installer
+ // under the intent subclass
+ assertEquals("incorrect installer", installer,
+ service.getInstallers().get(TestSubclassInstallableIntent.class));
+
+ // TODO: discuss whether or if implicit registration should require implicit unregistration
+ // perhaps unregister by compiler or installer itself, rather than by class would be better
+ }
+
+
+ // Fixture to track emitted intent events
+ protected class TestListener implements IntentEventListener {
+ final List<IntentEvent> events = new ArrayList<>();
+
+ @Override
+ public void event(IntentEvent event) {
+ events.add(event);
+ }
+ }
+
+ // Controllable compiler
+ private class TestCompiler implements IntentCompiler<TestIntent> {
+ private final boolean fail;
+ private final List<Intent> result;
+
+ TestCompiler(boolean fail) {
+ this.fail = fail;
+ this.result = Collections.emptyList();
+ }
+
+ TestCompiler(Intent... result) {
+ this.fail = false;
+ this.result = Arrays.asList(result);
+ }
+
+ @Override
+ public List<Intent> compile(TestIntent intent) {
+ if (fail) {
+ throw new IntentException("compile failed by design");
+ }
+ List<Intent> compiled = new ArrayList<>(result);
+ return compiled;
+ }
+ }
+
+ // Controllable installer
+ private class TestInstaller implements IntentInstaller<TestInstallableIntent> {
+ private final boolean fail;
+
+ TestInstaller(boolean fail) {
+ this.fail = fail;
+ }
+
+ @Override
+ public void install(TestInstallableIntent intent) {
+ if (fail) {
+ throw new IntentException("install failed by design");
+ }
+ }
+
+ @Override
+ public void uninstall(TestInstallableIntent intent) {
+ if (fail) {
+ throw new IntentException("remove failed by design");
+ }
+ }
+ }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentTest.java
new file mode 100644
index 0000000..a6cedf9
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentTest.java
@@ -0,0 +1,65 @@
+package org.onlab.onos.net.intent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+/**
+ * Base facilities to test various intent tests.
+ */
+public abstract class IntentTest {
+ /**
+ * Produces a set of items from the supplied items.
+ *
+ * @param items items to be placed in set
+ * @param <T> item type
+ * @return set of items
+ */
+ protected static <T> Set<T> itemSet(T[] items) {
+ return new HashSet<>(Arrays.asList(items));
+ }
+
+ @Test
+ public void equalsAndHashCode() {
+ Intent one = createOne();
+ Intent like = createOne();
+ Intent another = createAnother();
+
+ assertTrue("should be equal", one.equals(like));
+ assertEquals("incorrect hashCode", one.hashCode(), like.hashCode());
+
+ assertFalse("should not be equal", one.equals(another));
+
+ assertFalse("should not be equal", one.equals(null));
+ assertFalse("should not be equal", one.equals("foo"));
+ }
+
+ @Test
+ public void testToString() {
+ Intent one = createOne();
+ Intent like = createOne();
+ assertEquals("incorrect toString", one.toString(), like.toString());
+ }
+
+ /**
+ * Creates a new intent, but always a like intent, i.e. all instances will
+ * be equal, but should not be the same.
+ *
+ * @return intent
+ */
+ protected abstract Intent createOne();
+
+ /**
+ * Creates another intent, not equals to the one created by
+ * {@link #createOne()} and with a different hash code.
+ *
+ * @return another intent
+ */
+ protected abstract Intent createAnother();
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java
new file mode 100644
index 0000000..d971ba2
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Suite of tests of the multi-to-single point intent descriptor.
+ */
+public class MultiPointToSinglePointIntentTest extends ConnectivityIntentTest {
+
+ @Test
+ public void basics() {
+ MultiPointToSinglePointIntent intent = createOne();
+ assertEquals("incorrect id", IID, intent.getId());
+ assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
+ assertEquals("incorrect ingress", PS1, intent.getIngressPorts());
+ assertEquals("incorrect egress", P2, intent.getEgressPort());
+ }
+
+ @Override
+ protected MultiPointToSinglePointIntent createOne() {
+ return new MultiPointToSinglePointIntent(IID, MATCH, NOP, PS1, P2);
+ }
+
+ @Override
+ protected MultiPointToSinglePointIntent createAnother() {
+ return new MultiPointToSinglePointIntent(IID, MATCH, NOP, PS2, P1);
+ }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
new file mode 100644
index 0000000..bd8dc08
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
@@ -0,0 +1,36 @@
+package org.onlab.onos.net.intent;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.onlab.onos.net.NetTestTools;
+import org.onlab.onos.net.Path;
+
+public class PathIntentTest extends ConnectivityIntentTest {
+ // 111:11 --> 222:22
+ private static final Path PATH1 = NetTestTools.createPath("111", "222");
+
+ // 111:11 --> 333:33
+ private static final Path PATH2 = NetTestTools.createPath("222", "333");
+
+ @Test
+ public void basics() {
+ PathIntent intent = createOne();
+ assertEquals("incorrect id", IID, intent.getId());
+ assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
+ assertEquals("incorrect action", NOP, intent.getTrafficTreatment());
+ assertEquals("incorrect ingress", P1, intent.getIngressPort());
+ assertEquals("incorrect egress", P2, intent.getEgressPort());
+ assertEquals("incorrect path", PATH1, intent.getPath());
+ }
+
+ @Override
+ protected PathIntent createOne() {
+ return new PathIntent(IID, MATCH, NOP, P1, P2, PATH1);
+ }
+
+ @Override
+ protected PathIntent createAnother() {
+ return new PathIntent(IID, MATCH, NOP, P1, P3, PATH2);
+ }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java
new file mode 100644
index 0000000..426a3d9
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Suite of tests of the point-to-point intent descriptor.
+ */
+public class PointToPointIntentTest extends ConnectivityIntentTest {
+
+ @Test
+ public void basics() {
+ PointToPointIntent intent = createOne();
+ assertEquals("incorrect id", IID, intent.getId());
+ assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
+ assertEquals("incorrect ingress", P1, intent.getIngressPort());
+ assertEquals("incorrect egress", P2, intent.getEgressPort());
+ }
+
+ @Override
+ protected PointToPointIntent createOne() {
+ return new PointToPointIntent(IID, MATCH, NOP, P1, P2);
+ }
+
+ @Override
+ protected PointToPointIntent createAnother() {
+ return new PointToPointIntent(IID, MATCH, NOP, P2, P1);
+ }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java
new file mode 100644
index 0000000..0561a87
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Suite of tests of the single-to-multi point intent descriptor.
+ */
+public class SinglePointToMultiPointIntentTest extends ConnectivityIntentTest {
+
+ @Test
+ public void basics() {
+ SinglePointToMultiPointIntent intent = createOne();
+ assertEquals("incorrect id", IID, intent.getId());
+ assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
+ assertEquals("incorrect ingress", P1, intent.getIngressPort());
+ assertEquals("incorrect egress", PS2, intent.getEgressPorts());
+ }
+
+ @Override
+ protected SinglePointToMultiPointIntent createOne() {
+ return new SinglePointToMultiPointIntent(IID, MATCH, NOP, P1, PS2);
+ }
+
+ @Override
+ protected SinglePointToMultiPointIntent createAnother() {
+ return new SinglePointToMultiPointIntent(IID, MATCH, NOP, P2, PS1);
+ }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java
new file mode 100644
index 0000000..a6ce52e
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java
@@ -0,0 +1,28 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An installable intent used in the unit test.
+ *
+ * FIXME: we don't want to expose this class publicly, but the current Kryo
+ * serialization mechanism does not allow this class to be private and placed
+ * on testing directory.
+ */
+public class TestInstallableIntent extends AbstractIntent implements InstallableIntent {
+ /**
+ * Constructs an instance with the specified intent ID.
+ *
+ * @param id intent ID
+ */
+ public TestInstallableIntent(IntentId id) {
+ super(id);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected TestInstallableIntent() {
+ super();
+ }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestIntent.java
new file mode 100644
index 0000000..2f30727
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestIntent.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An intent used in the unit test.
+ *
+ * FIXME: we don't want to expose this class publicly, but the current Kryo
+ * serialization mechanism does not allow this class to be private and placed
+ * on testing directory.
+ */
+public class TestIntent extends AbstractIntent {
+ /**
+ * Constructs an instance with the specified intent ID.
+ *
+ * @param id intent ID
+ */
+ public TestIntent(IntentId id) {
+ super(id);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected TestIntent() {
+ super();
+ }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassInstallableIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassInstallableIntent.java
new file mode 100644
index 0000000..40765c2
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassInstallableIntent.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An intent used in the unit test.
+ *
+ * FIXME: we don't want to expose this class publicly, but the current Kryo
+ * serialization mechanism does not allow this class to be private and placed
+ * on testing directory.
+ */
+public class TestSubclassInstallableIntent extends TestInstallableIntent implements InstallableIntent {
+ /**
+ * Constructs an instance with the specified intent ID.
+ *
+ * @param id intent ID
+ */
+ public TestSubclassInstallableIntent(IntentId id) {
+ super(id);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected TestSubclassInstallableIntent() {
+ super();
+ }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassIntent.java
new file mode 100644
index 0000000..43bb0dd
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassIntent.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An intent used in the unit test.
+ *
+ * FIXME: we don't want to expose this class publicly, but the current Kryo
+ * serialization mechanism does not allow this class to be private and placed
+ * on testing directory.
+ */
+public class TestSubclassIntent extends TestIntent {
+ /**
+ * Constructs an instance with the specified intent ID.
+ *
+ * @param id intent ID
+ */
+ public TestSubclassIntent(IntentId id) {
+ super(id);
+ }
+
+ /**
+ * Constructor for serializer.
+ */
+ protected TestSubclassIntent() {
+ super();
+ }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestTools.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestTools.java
new file mode 100644
index 0000000..f22585e
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestTools.java
@@ -0,0 +1,126 @@
+package org.onlab.onos.net.intent;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Set of test tools.
+ */
+public final class TestTools {
+
+ // Disallow construction
+ private TestTools() {
+ }
+
+ /**
+ * Utility method to pause the current thread for the specified number of
+ * milliseconds.
+ *
+ * @param ms number of milliseconds to pause
+ */
+ public static void delay(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ fail("unexpected interrupt");
+ }
+ }
+
+ /**
+ * Periodically runs the given runnable, which should contain a series of
+ * test assertions until all the assertions succeed, in which case it will
+ * return, or until the the time expires, in which case it will throw the
+ * first failed assertion error.
+ *
+ * @param start start time, in millis since start of epoch from which the
+ * duration will be measured
+ * @param delay initial delay (in milliseconds) before the first assertion
+ * attempt
+ * @param step delay (in milliseconds) between successive assertion
+ * attempts
+ * @param duration number of milliseconds beyond the given start time,
+ * after which the failed assertions will be propagated and allowed
+ * to fail the test
+ * @param assertions runnable housing the test assertions
+ */
+ public static void assertAfter(long start, int delay, int step,
+ int duration, Runnable assertions) {
+ delay(delay);
+ while (true) {
+ try {
+ assertions.run();
+ break;
+ } catch (AssertionError e) {
+ if (System.currentTimeMillis() - start > duration) {
+ throw e;
+ }
+ }
+ delay(step);
+ }
+ }
+
+ /**
+ * Periodically runs the given runnable, which should contain a series of
+ * test assertions until all the assertions succeed, in which case it will
+ * return, or until the the time expires, in which case it will throw the
+ * first failed assertion error.
+ * <p>
+ * The start of the period is the current time.
+ *
+ * @param delay initial delay (in milliseconds) before the first assertion
+ * attempt
+ * @param step delay (in milliseconds) between successive assertion
+ * attempts
+ * @param duration number of milliseconds beyond the current time time,
+ * after which the failed assertions will be propagated and allowed
+ * to fail the test
+ * @param assertions runnable housing the test assertions
+ */
+ public static void assertAfter(int delay, int step, int duration,
+ Runnable assertions) {
+ assertAfter(System.currentTimeMillis(), delay, step, duration,
+ assertions);
+ }
+
+ /**
+ * Periodically runs the given runnable, which should contain a series of
+ * test assertions until all the assertions succeed, in which case it will
+ * return, or until the the time expires, in which case it will throw the
+ * first failed assertion error.
+ * <p>
+ * The start of the period is the current time and the first assertion
+ * attempt is delayed by the value of {@code step} parameter.
+ *
+ * @param step delay (in milliseconds) between successive assertion
+ * attempts
+ * @param duration number of milliseconds beyond the current time time,
+ * after which the failed assertions will be propagated and allowed
+ * to fail the test
+ * @param assertions runnable housing the test assertions
+ */
+ public static void assertAfter(int step, int duration,
+ Runnable assertions) {
+ assertAfter(step, step, duration, assertions);
+ }
+
+ /**
+ * Periodically runs the given runnable, which should contain a series of
+ * test assertions until all the assertions succeed, in which case it will
+ * return, or until the the time expires, in which case it will throw the
+ * first failed assertion error.
+ * <p>
+ * The start of the period is the current time and each successive
+ * assertion attempt is delayed by at least 10 milliseconds unless the
+ * {@code duration} is less than that, in which case the one and only
+ * assertion is made after that delay.
+ *
+ * @param duration number of milliseconds beyond the current time,
+ * after which the failed assertions will be propagated and allowed
+ * to fail the test
+ * @param assertions runnable housing the test assertions
+ */
+ public static void assertAfter(int duration, Runnable assertions) {
+ int step = Math.min(duration, Math.max(10, duration / 10));
+ assertAfter(step, duration, assertions);
+ }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestableIntentService.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestableIntentService.java
new file mode 100644
index 0000000..95502d3
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestableIntentService.java
@@ -0,0 +1,12 @@
+package org.onlab.onos.net.intent;
+
+import java.util.List;
+
+/**
+ * Abstraction of an extensible intent service enabled for unit tests.
+ */
+public interface TestableIntentService extends IntentService, IntentExtensionService {
+
+ List<IntentException> getExceptions();
+
+}
diff --git a/core/store/dist/pom.xml b/core/store/dist/pom.xml
index 2451955..1faab74 100644
--- a/core/store/dist/pom.xml
+++ b/core/store/dist/pom.xml
@@ -33,6 +33,12 @@
<artifactId>onlab-nio</artifactId>
<version>${project.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onlab-netty</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@@ -51,15 +57,6 @@
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
</dependency>
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-all</artifactId>
- </dependency>
- <dependency>
- <groupId>commons-pool</groupId>
- <artifactId>commons-pool</artifactId>
- <version>1.6</version>
- </dependency>
</dependencies>
<build>
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/OnosClusterCommunicationManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/OnosClusterCommunicationManager.java
index 9bd25b4..e6e4a4d 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/OnosClusterCommunicationManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/OnosClusterCommunicationManager.java
@@ -23,10 +23,10 @@
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
-import org.onlab.onos.store.messaging.Endpoint;
-import org.onlab.onos.store.messaging.Message;
-import org.onlab.onos.store.messaging.MessageHandler;
-import org.onlab.onos.store.messaging.MessagingService;
+import org.onlab.netty.Endpoint;
+import org.onlab.netty.Message;
+import org.onlab.netty.MessageHandler;
+import org.onlab.netty.MessagingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Endpoint.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Endpoint.java
deleted file mode 100644
index bd6d45f..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Endpoint.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.onlab.onos.store.messaging;
-
-/**
- * Representation of a TCP/UDP communication end point.
- */
-public class Endpoint {
-
- private final int port;
- private final String host;
-
- public Endpoint(String host, int port) {
- this.host = host;
- this.port = port;
- }
-
- public String host() {
- return host;
- }
-
- public int port() {
- return port;
- }
-
- @Override
- public String toString() {
- return "Endpoint [port=" + port + ", host=" + host + "]";
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((host == null) ? 0 : host.hashCode());
- result = prime * result + port;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- Endpoint other = (Endpoint) obj;
- if (host == null) {
- if (other.host != null) {
- return false;
- }
- } else if (!host.equals(other.host)) {
- return false;
- }
- if (port != other.port) {
- return false;
- }
- return true;
- }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Message.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Message.java
deleted file mode 100644
index d814927..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Message.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.onlab.onos.store.messaging;
-
-import java.io.IOException;
-
-/**
- * A unit of communication.
- * Has a payload. Also supports a feature to respond back to the sender.
- */
-public interface Message {
-
- /**
- * Returns the payload of this message.
- * @return message payload.
- */
- public Object payload();
-
- /**
- * Sends a reply back to the sender of this messge.
- * @param data payload of the response.
- * @throws IOException if there is a communication error.
- */
- public void respond(Object data) throws IOException;
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessageHandler.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessageHandler.java
deleted file mode 100644
index 8eaef1e..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessageHandler.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.onlab.onos.store.messaging;
-
-import java.io.IOException;
-
-/**
- * Handler for a message.
- */
-public interface MessageHandler {
-
- /**
- * Handles the message.
- * @param message message.
- * @throws IOException.
- */
- public void handle(Message message) throws IOException;
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessagingService.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessagingService.java
deleted file mode 100644
index 4aa32cb..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessagingService.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.onlab.onos.store.messaging;
-
-import java.io.IOException;
-
-/**
- * Interface for low level messaging primitives.
- */
-public interface MessagingService {
- /**
- * Sends a message asynchronously to the specified communication end point.
- * The message is specified using the type and payload.
- * @param ep end point to send the message to.
- * @param type type of message.
- * @param payload message payload.
- * @throws IOException
- */
- public void sendAsync(Endpoint ep, String type, Object payload) throws IOException;
-
- /**
- * Sends a message synchronously and waits for a response.
- * @param ep end point to send the message to.
- * @param type type of message.
- * @param payload message payload.
- * @return a response future
- * @throws IOException
- */
- public <T> Response<T> sendAndReceive(Endpoint ep, String type, Object payload) throws IOException;
-
- /**
- * Registers a new message handler for message type.
- * @param type message type.
- * @param handler message handler
- */
- public void registerHandler(String type, MessageHandler handler);
-
- /**
- * Unregister current handler, if one exists for message type.
- * @param type message type
- */
- public void unregisterHandler(String type);
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Response.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Response.java
deleted file mode 100644
index ff0d84f..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Response.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.onlab.onos.store.messaging;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Response object returned when making synchronous requests.
- * Can you used to check is a response is ready and/or wait for a response
- * to become available.
- *
- * @param <T> type of response.
- */
-public interface Response<T> {
-
- /**
- * Gets the response waiting for a designated timeout period.
- * @param timeout timeout period (since request was sent out)
- * @param tu unit of time.
- * @return response
- * @throws TimeoutException if the timeout expires before the response arrives.
- */
- public T get(long timeout, TimeUnit tu) throws TimeoutException;
-
- /**
- * Gets the response waiting for indefinite timeout period.
- * @return response
- * @throws InterruptedException if the thread is interrupted before the response arrives.
- */
- public T get() throws InterruptedException;
-
- /**
- * Checks if the response is ready without blocking.
- * @return true if response is ready, false otherwise.
- */
- public boolean isReady();
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/AsyncResponse.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/AsyncResponse.java
deleted file mode 100644
index ac2337d..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/AsyncResponse.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.onlab.onos.store.messaging.Response;
-
-/**
- * An asynchronous response.
- * This class provides a base implementation of Response, with methods to retrieve the
- * result and query to see if the result is ready. The result can only be retrieved when
- * it is ready and the get methods will block if the result is not ready yet.
- * @param <T> type of response.
- */
-public class AsyncResponse<T> implements Response<T> {
-
- private T value;
- private boolean done = false;
- private final long start = System.nanoTime();
-
- @Override
- public T get(long timeout, TimeUnit tu) throws TimeoutException {
- timeout = tu.toNanos(timeout);
- boolean interrupted = false;
- try {
- synchronized (this) {
- while (!done) {
- try {
- long timeRemaining = timeout - (System.nanoTime() - start);
- if (timeRemaining <= 0) {
- throw new TimeoutException("Operation timed out.");
- }
- TimeUnit.NANOSECONDS.timedWait(this, timeRemaining);
- } catch (InterruptedException e) {
- interrupted = true;
- }
- }
- }
- } finally {
- if (interrupted) {
- Thread.currentThread().interrupt();
- }
- }
- return value;
- }
-
- @Override
- public T get() throws InterruptedException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isReady() {
- return done;
- }
-
- /**
- * Sets response value and unblocks any thread blocking on the response to become
- * available.
- * @param data response data.
- */
- @SuppressWarnings("unchecked")
- public synchronized void setResponse(Object data) {
- if (!done) {
- done = true;
- value = (T) data;
- this.notifyAll();
- }
- }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/EchoHandler.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/EchoHandler.java
deleted file mode 100644
index 7891c5c..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/EchoHandler.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import java.io.IOException;
-
-import org.onlab.onos.store.messaging.Message;
-import org.onlab.onos.store.messaging.MessageHandler;
-
-/**
- * Message handler that echos the message back to the sender.
- */
-public class EchoHandler implements MessageHandler {
-
- @Override
- public void handle(Message message) throws IOException {
- System.out.println("Received: " + message.payload() + ". Echoing it back to the sender.");
- message.respond(message.payload());
- }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/InternalMessage.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/InternalMessage.java
deleted file mode 100644
index 8a87a3e..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/InternalMessage.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import java.io.IOException;
-
-import org.onlab.onos.store.messaging.Endpoint;
-import org.onlab.onos.store.messaging.Message;
-
-/**
- * Internal message representation with additional attributes
- * for supporting, synchronous request/reply behavior.
- */
-public final class InternalMessage implements Message {
-
- private long id;
- private Endpoint sender;
- private String type;
- private Object payload;
- private transient NettyMessagingService messagingService;
- public static final String REPLY_MESSAGE_TYPE = "NETTY_MESSAGIG_REQUEST_REPLY";
-
- // Must be created using the Builder.
- private InternalMessage() {}
-
- public long id() {
- return id;
- }
-
- public String type() {
- return type;
- }
-
- public Endpoint sender() {
- return sender;
- }
-
- @Override
- public Object payload() {
- return payload;
- }
-
- @Override
- public void respond(Object data) throws IOException {
- Builder builder = new Builder(messagingService);
- InternalMessage message = builder.withId(this.id)
- // FIXME: Sender should be messagingService.localEp.
- .withSender(this.sender)
- .withPayload(data)
- .withType(REPLY_MESSAGE_TYPE)
- .build();
- messagingService.sendAsync(sender, message);
- }
-
-
- /**
- * Builder for InternalMessages.
- */
- public static class Builder {
- private InternalMessage message;
-
- public Builder(NettyMessagingService messagingService) {
- message = new InternalMessage();
- message.messagingService = messagingService;
- }
-
- public Builder withId(long id) {
- message.id = id;
- return this;
- }
-
- public Builder withType(String type) {
- message.type = type;
- return this;
- }
-
- public Builder withSender(Endpoint sender) {
- message.sender = sender;
- return this;
- }
- public Builder withPayload(Object payload) {
- message.payload = payload;
- return this;
- }
-
- public InternalMessage build() {
- return message;
- }
- }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/LoggingHandler.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/LoggingHandler.java
deleted file mode 100644
index bf871f8..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/LoggingHandler.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import org.onlab.onos.store.messaging.Message;
-import org.onlab.onos.store.messaging.MessageHandler;
-
-/**
- * A MessageHandler that simply logs the information.
- */
-public class LoggingHandler implements MessageHandler {
-
- @Override
- public void handle(Message message) {
- System.out.println("Received: " + message.payload());
- }
-}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageDecoder.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageDecoder.java
deleted file mode 100644
index 7f94015..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageDecoder.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import java.util.Arrays;
-import java.util.List;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import org.onlab.onos.store.cluster.messaging.SerializationService;
-import org.onlab.onos.store.messaging.Endpoint;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.ByteToMessageDecoder;
-
-/**
- * Decode bytes into a InrenalMessage.
- */
-public class MessageDecoder extends ByteToMessageDecoder {
-
- private final NettyMessagingService messagingService;
- private final SerializationService serializationService;
-
- public MessageDecoder(NettyMessagingService messagingService, SerializationService serializationService) {
- this.messagingService = messagingService;
- this.serializationService = serializationService;
- }
-
- @Override
- protected void decode(ChannelHandlerContext context, ByteBuf in,
- List<Object> messages) throws Exception {
-
- byte[] preamble = in.readBytes(MessageEncoder.PREAMBLE.length).array();
- checkState(Arrays.equals(MessageEncoder.PREAMBLE, preamble), "Message has wrong preamble");
-
- // read message Id.
- long id = in.readLong();
-
- // read message type; first read size and then bytes.
- String type = new String(in.readBytes(in.readInt()).array());
-
- // read sender host name; first read size and then bytes.
- String host = new String(in.readBytes(in.readInt()).array());
-
- // read sender port.
- int port = in.readInt();
-
- Endpoint sender = new Endpoint(host, port);
-
- // read message payload; first read size and then bytes.
- Object payload = serializationService.decode(in.readBytes(in.readInt()).array());
-
- InternalMessage message = new InternalMessage.Builder(messagingService)
- .withId(id)
- .withSender(sender)
- .withType(type)
- .withPayload(payload)
- .build();
-
- messages.add(message);
- }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageEncoder.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageEncoder.java
deleted file mode 100644
index b1c660c..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageEncoder.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import org.onlab.onos.store.cluster.messaging.SerializationService;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.MessageToByteEncoder;
-
-/**
- * Encode InternalMessage out into a byte buffer.
- */
-public class MessageEncoder extends MessageToByteEncoder<InternalMessage> {
-
- // onosiscool in ascii
- public static final byte[] PREAMBLE = "onosiscool".getBytes();
-
- private final SerializationService serializationService;
-
- public MessageEncoder(SerializationService serializationService) {
- this.serializationService = serializationService;
- }
-
- @Override
- protected void encode(ChannelHandlerContext context, InternalMessage message,
- ByteBuf out) throws Exception {
-
- // write preamble
- out.writeBytes(PREAMBLE);
-
- // write id
- out.writeLong(message.id());
-
- // write type length
- out.writeInt(message.type().length());
-
- // write type
- out.writeBytes(message.type().getBytes());
-
- // write sender host name size
- out.writeInt(message.sender().host().length());
-
- // write sender host name.
- out.writeBytes(message.sender().host().getBytes());
-
- // write port
- out.writeInt(message.sender().port());
-
- try {
- serializationService.encode(message.payload());
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- byte[] payload = serializationService.encode(message.payload());
-
- // write payload length.
- out.writeInt(payload.length);
-
- // write payload bytes
- out.writeBytes(payload);
- }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/NettyMessagingService.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/NettyMessagingService.java
deleted file mode 100644
index b6b3857..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/NettyMessagingService.java
+++ /dev/null
@@ -1,260 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-
-import io.netty.bootstrap.Bootstrap;
-import io.netty.bootstrap.ServerBootstrap;
-import io.netty.buffer.PooledByteBufAllocator;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelOption;
-import io.netty.channel.EventLoopGroup;
-import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.channel.socket.nio.NioServerSocketChannel;
-import io.netty.channel.socket.nio.NioSocketChannel;
-
-import org.apache.commons.lang.math.RandomUtils;
-import org.apache.commons.pool.KeyedObjectPool;
-import org.apache.commons.pool.KeyedPoolableObjectFactory;
-import org.apache.commons.pool.impl.GenericKeyedObjectPool;
-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.onlab.onos.store.cluster.messaging.SerializationService;
-import org.onlab.onos.store.messaging.Endpoint;
-import org.onlab.onos.store.messaging.MessageHandler;
-import org.onlab.onos.store.messaging.MessagingService;
-import org.onlab.onos.store.messaging.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-
-/**
- * A Netty based implementation of MessagingService.
- */
-@Component(immediate = true)
-@Service
-public class NettyMessagingService implements MessagingService {
-
- private final Logger log = LoggerFactory.getLogger(getClass());
-
- private KeyedObjectPool<Endpoint, Channel> channels =
- new GenericKeyedObjectPool<Endpoint, Channel>(new OnosCommunicationChannelFactory());
- private final int port;
- private final EventLoopGroup bossGroup = new NioEventLoopGroup();
- private final EventLoopGroup workerGroup = new NioEventLoopGroup();
- private final ConcurrentMap<String, MessageHandler> handlers = new ConcurrentHashMap<>();
- private Cache<Long, AsyncResponse<?>> responseFutures;
- private final Endpoint localEp;
-
- @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- protected SerializationService serializationService;
-
- public NettyMessagingService() {
- // TODO: Default port should be configurable.
- this(8080);
- }
-
- // FIXME: Constructor should not throw exceptions.
- public NettyMessagingService(int port) {
- this.port = port;
- try {
- localEp = new Endpoint(java.net.InetAddress.getLocalHost().getHostName(), port);
- } catch (UnknownHostException e) {
- // bailing out.
- throw new RuntimeException(e);
- }
- }
-
- @Activate
- public void activate() throws Exception {
- responseFutures = CacheBuilder.newBuilder()
- .maximumSize(100000)
- .weakValues()
- // TODO: Once the entry expires, notify blocking threads (if any).
- .expireAfterWrite(10, TimeUnit.MINUTES)
- .build();
- startAcceptingConnections();
- }
-
- @Deactivate
- public void deactivate() throws Exception {
- channels.close();
- bossGroup.shutdownGracefully();
- workerGroup.shutdownGracefully();
- }
-
- @Override
- public void sendAsync(Endpoint ep, String type, Object payload) throws IOException {
- InternalMessage message = new InternalMessage.Builder(this)
- .withId(RandomUtils.nextLong())
- .withSender(localEp)
- .withType(type)
- .withPayload(payload)
- .build();
- sendAsync(ep, message);
- }
-
- protected void sendAsync(Endpoint ep, InternalMessage message) throws IOException {
- Channel channel = null;
- try {
- channel = channels.borrowObject(ep);
- channel.eventLoop().execute(new WriteTask(channel, message));
- } catch (Exception e) {
- throw new IOException(e);
- } finally {
- try {
- channels.returnObject(ep, channel);
- } catch (Exception e) {
- log.warn("Error returning object back to the pool", e);
- // ignored.
- }
- }
- }
-
- @Override
- public <T> Response<T> sendAndReceive(Endpoint ep, String type, Object payload)
- throws IOException {
- AsyncResponse<T> futureResponse = new AsyncResponse<T>();
- Long messageId = RandomUtils.nextLong();
- responseFutures.put(messageId, futureResponse);
- InternalMessage message = new InternalMessage.Builder(this)
- .withId(messageId)
- .withSender(localEp)
- .withType(type)
- .withPayload(payload)
- .build();
- sendAsync(ep, message);
- return futureResponse;
- }
-
- @Override
- public void registerHandler(String type, MessageHandler handler) {
- // TODO: Is this the right semantics for handler registration?
- handlers.putIfAbsent(type, handler);
- }
-
- public void unregisterHandler(String type) {
- handlers.remove(type);
- }
-
- private MessageHandler getMessageHandler(String type) {
- return handlers.get(type);
- }
-
- private void startAcceptingConnections() throws InterruptedException {
- ServerBootstrap b = new ServerBootstrap();
- b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
- b.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .childHandler(new OnosCommunicationChannelInitializer())
- .option(ChannelOption.SO_BACKLOG, 128)
- .childOption(ChannelOption.SO_KEEPALIVE, true);
-
- // Bind and start to accept incoming connections.
- b.bind(port).sync();
- }
-
- private class OnosCommunicationChannelFactory
- implements KeyedPoolableObjectFactory<Endpoint, Channel> {
-
- @Override
- public void activateObject(Endpoint endpoint, Channel channel)
- throws Exception {
- }
-
- @Override
- public void destroyObject(Endpoint ep, Channel channel) throws Exception {
- channel.close();
- }
-
- @Override
- public Channel makeObject(Endpoint ep) throws Exception {
- Bootstrap b = new Bootstrap();
- b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
- b.group(workerGroup);
- // TODO: Make this faster:
- // http://normanmaurer.me/presentations/2014-facebook-eng-netty/slides.html#37.0
- b.channel(NioSocketChannel.class);
- b.option(ChannelOption.SO_KEEPALIVE, true);
- b.handler(new OnosCommunicationChannelInitializer());
-
- // Start the client.
- ChannelFuture f = b.connect(ep.host(), ep.port()).sync();
- return f.channel();
- }
-
- @Override
- public void passivateObject(Endpoint ep, Channel channel)
- throws Exception {
- }
-
- @Override
- public boolean validateObject(Endpoint ep, Channel channel) {
- return channel.isOpen();
- }
- }
-
- private class OnosCommunicationChannelInitializer extends ChannelInitializer<SocketChannel> {
-
- @Override
- protected void initChannel(SocketChannel channel) throws Exception {
- channel.pipeline()
- .addLast(new MessageEncoder(serializationService))
- .addLast(new MessageDecoder(NettyMessagingService.this, serializationService))
- .addLast(new NettyMessagingService.InboundMessageDispatcher());
- }
- }
-
- private class WriteTask implements Runnable {
-
- private final Object message;
- private final Channel channel;
-
- public WriteTask(Channel channel, Object message) {
- this.message = message;
- this.channel = channel;
- }
-
- @Override
- public void run() {
- channel.writeAndFlush(message);
- }
- }
-
- private class InboundMessageDispatcher extends SimpleChannelInboundHandler<InternalMessage> {
-
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, InternalMessage message) throws Exception {
- String type = message.type();
- if (type.equals(InternalMessage.REPLY_MESSAGE_TYPE)) {
- try {
- AsyncResponse<?> futureResponse =
- NettyMessagingService.this.responseFutures.getIfPresent(message.id());
- if (futureResponse != null) {
- futureResponse.setResponse(message.payload());
- }
- log.warn("Received a reply. But was unable to locate the request handle");
- } finally {
- NettyMessagingService.this.responseFutures.invalidate(message.id());
- }
- return;
- }
- MessageHandler handler = NettyMessagingService.this.getMessageHandler(type);
- handler.handle(message);
- }
- }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleClient.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleClient.java
deleted file mode 100644
index 95753e7..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleClient.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import java.util.concurrent.TimeUnit;
-
-import org.onlab.onos.store.cluster.impl.MessageSerializer;
-import org.onlab.onos.store.messaging.Endpoint;
-import org.onlab.onos.store.messaging.Response;
-
-public final class SimpleClient {
- private SimpleClient() {}
-
- public static void main(String... args) throws Exception {
- NettyMessagingService messaging = new TestNettyMessagingService(9081);
- messaging.activate();
-
- messaging.sendAsync(new Endpoint("localhost", 8080), "simple", "Hello World");
- Response<String> response = messaging.sendAndReceive(new Endpoint("localhost", 8080), "echo", "Hello World");
- System.out.println("Got back:" + response.get(2, TimeUnit.SECONDS));
- }
-
- public static class TestNettyMessagingService extends NettyMessagingService {
- public TestNettyMessagingService(int port) throws Exception {
- super(port);
- MessageSerializer mgr = new MessageSerializer();
- mgr.activate();
- this.serializationService = mgr;
- }
- }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleServer.java b/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleServer.java
deleted file mode 100644
index 1b331ba..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleServer.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.onlab.onos.store.messaging.impl;
-
-import org.onlab.onos.store.cluster.impl.MessageSerializer;
-
-public final class SimpleServer {
- private SimpleServer() {}
-
- public static void main(String... args) throws Exception {
- NettyMessagingService server = new TestNettyMessagingService();
- server.activate();
- server.registerHandler("simple", new LoggingHandler());
- server.registerHandler("echo", new EchoHandler());
- }
-
- public static class TestNettyMessagingService extends NettyMessagingService {
- protected TestNettyMessagingService() {
- MessageSerializer mgr = new MessageSerializer();
- mgr.activate();
- this.serializationService = mgr;
- }
- }
-}
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
index 3d87fb1..44e5421 100644
--- a/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
@@ -7,7 +7,7 @@
import org.onlab.onos.cluster.DefaultControllerNode;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.store.cluster.messaging.impl.OnosClusterCommunicationManager;
-import org.onlab.onos.store.messaging.impl.NettyMessagingService;
+import org.onlab.netty.NettyMessagingService;
import org.onlab.packet.IpPrefix;
import java.util.concurrent.CountDownLatch;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
index bc0a055..0b0ae37 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
@@ -9,6 +9,8 @@
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.net.Annotations;
+import org.onlab.onos.net.DefaultAnnotations;
import org.onlab.onos.net.DefaultDevice;
import org.onlab.onos.net.DefaultPort;
import org.onlab.onos.net.Device;
@@ -16,6 +18,9 @@
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.SparseAnnotations;
+import org.onlab.onos.net.device.DefaultDeviceDescription;
+import org.onlab.onos.net.device.DefaultPortDescription;
import org.onlab.onos.net.device.DeviceDescription;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceStore;
@@ -45,6 +50,7 @@
import static org.onlab.onos.net.device.DeviceEvent.Type.*;
import static org.slf4j.LoggerFactory.getLogger;
import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.onlab.onos.net.DefaultAnnotations.merge;
// TODO: synchronization should be done in more fine-grained manner.
/**
@@ -112,8 +118,8 @@
= createIfAbsentUnchecked(providerDescs, providerId,
new InitDeviceDescs(deviceDescription));
+ // update description
descs.putDeviceDesc(deviceDescription);
-
Device newDevice = composeDevice(deviceId, providerDescs);
if (oldDevice == null) {
@@ -144,7 +150,8 @@
// We allow only certain attributes to trigger update
if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
- !Objects.equals(oldDevice.swVersion(), newDevice.swVersion())) {
+ !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()) ||
+ !isAnnotationsEqual(oldDevice.annotations(), newDevice.annotations())) {
synchronized (this) {
devices.replace(newDevice.id(), oldDevice, newDevice);
@@ -203,7 +210,7 @@
PortNumber number = portDescription.portNumber();
Port oldPort = ports.get(number);
// update description
- descs.putPortDesc(number, portDescription);
+ descs.putPortDesc(portDescription);
Port newPort = composePort(device, number, descsMap);
events.add(oldPort == null ?
@@ -225,12 +232,14 @@
return new DeviceEvent(PORT_ADDED, device, newPort);
}
- // CHecks if the specified port requires update and if so, it replaces the
+ // Checks if the specified port requires update and if so, it replaces the
// existing entry in the map and returns corresponding event.
private DeviceEvent updatePort(Device device, Port oldPort,
Port newPort,
ConcurrentMap<PortNumber, Port> ports) {
- if (oldPort.isEnabled() != newPort.isEnabled()) {
+ if (oldPort.isEnabled() != newPort.isEnabled() ||
+ !isAnnotationsEqual(oldPort.annotations(), newPort.annotations())) {
+
ports.put(oldPort.number(), newPort);
return new DeviceEvent(PORT_UPDATED, device, newPort);
}
@@ -272,17 +281,17 @@
checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
DeviceDescriptions descs = descsMap.get(providerId);
+ // assuming all providers must to give DeviceDescription
checkArgument(descs != null,
"Device description for Device ID %s from Provider %s was not found",
deviceId, providerId);
- // TODO: implement multi-provider
synchronized (this) {
ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
final PortNumber number = portDescription.portNumber();
Port oldPort = ports.get(number);
// update description
- descs.putPortDesc(number, portDescription);
+ descs.putPortDesc(portDescription);
Port newPort = composePort(device, number, descsMap);
if (oldPort == null) {
return createPort(device, newPort, ports);
@@ -321,6 +330,26 @@
}
}
+ private static boolean isAnnotationsEqual(Annotations lhs, Annotations rhs) {
+ if (lhs == rhs) {
+ return true;
+ }
+ if (lhs == null || rhs == null) {
+ return false;
+ }
+
+ if (!lhs.keys().equals(rhs.keys())) {
+ return false;
+ }
+
+ for (String key : lhs.keys()) {
+ if (!lhs.value(key).equals(rhs.value(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Returns a Device, merging description given from multiple Providers.
*
@@ -336,46 +365,67 @@
ProviderId primary = pickPrimaryPID(providerDescs);
DeviceDescriptions desc = providerDescs.get(primary);
+
+ // base
Type type = desc.getDeviceDesc().type();
String manufacturer = desc.getDeviceDesc().manufacturer();
String hwVersion = desc.getDeviceDesc().hwVersion();
String swVersion = desc.getDeviceDesc().swVersion();
String serialNumber = desc.getDeviceDesc().serialNumber();
+ DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+ annotations = merge(annotations, desc.getDeviceDesc().annotations());
for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
if (e.getKey().equals(primary)) {
continue;
}
- // FIXME: implement attribute merging once we have K-V attributes
+ // 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);
+ return new DefaultDevice(primary, deviceId , type, manufacturer,
+ hwVersion, swVersion, serialNumber, annotations);
}
- // probably want composePorts
+ // probably want composePort"s" also
private Port composePort(Device device, PortNumber number,
ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
ProviderId primary = pickPrimaryPID(providerDescs);
DeviceDescriptions primDescs = providerDescs.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);
- boolean isEnabled;
if (portDesc != null) {
isEnabled = portDesc.isEnabled();
- } else {
- // if no primary, assume not enabled
- // TODO: revisit this port enabled/disabled behavior
- isEnabled = false;
+ annotations = merge(annotations, portDesc.annotations());
}
for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
if (e.getKey().equals(primary)) {
continue;
}
- // FIXME: implement attribute merging once we have K-V attributes
+ // 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 new DefaultPort(device, number, isEnabled);
+ return new DefaultPort(device, number, isEnabled, annotations);
}
/**
@@ -428,7 +478,7 @@
private final ConcurrentMap<PortNumber, PortDescription> portDescs;
public DeviceDescriptions(DeviceDescription desc) {
- this.deviceDesc = new AtomicReference<>(desc);
+ this.deviceDesc = new AtomicReference<>(checkNotNull(desc));
this.portDescs = new ConcurrentHashMap<>();
}
@@ -444,12 +494,38 @@
return Collections.unmodifiableCollection(portDescs.values());
}
- public DeviceDescription putDeviceDesc(DeviceDescription newDesc) {
- return deviceDesc.getAndSet(newDesc);
+ /**
+ * 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 = merge(oldOne.annotations(),
+ newDesc.annotations());
+ newOne = new DefaultDeviceDescription(newOne, merged);
+ }
+ return deviceDesc.getAndSet(newOne);
}
- public PortDescription putPortDesc(PortNumber number, PortDescription newDesc) {
- return portDescs.put(number, newDesc);
+ /**
+ * 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 = merge(oldOne.annotations(),
+ newDesc.annotations());
+ newOne = new DefaultPortDescription(newOne, merged);
+ }
+ return portDescs.put(newOne.portNumber(), newOne);
}
}
}
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
index 431fba3..a0d6e1c 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
@@ -22,10 +22,13 @@
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
+import org.onlab.onos.net.Annotations;
+import org.onlab.onos.net.DefaultAnnotations;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.SparseAnnotations;
import org.onlab.onos.net.device.DefaultDeviceDescription;
import org.onlab.onos.net.device.DefaultPortDescription;
import org.onlab.onos.net.device.DeviceDescription;
@@ -57,6 +60,23 @@
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;
@@ -106,6 +126,24 @@
assertEquals(SN, device.serialNumber());
}
+ /**
+ * Verifies that Annotations created by merging {@code annotations} is
+ * equal to actual Annotations.
+ *
+ * @param actual Annotations to check
+ * @param annotations
+ */
+ private static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
+ DefaultAnnotations expected = DefaultAnnotations.builder().build();
+ for (SparseAnnotations a : annotations) {
+ expected = DefaultAnnotations.merge(expected, a);
+ }
+ assertEquals(expected.keys(), actual.keys());
+ for (String key : expected.keys()) {
+ assertEquals(expected.value(key), actual.value(key));
+ }
+ }
+
@Test
public final void testGetDeviceCount() {
assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
@@ -171,26 +209,41 @@
public final void testCreateOrUpdateDeviceAncillary() {
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
- HW, SW1, SN);
+ HW, SW1, SN, 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);
+ HW, SW2, SN, 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, 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));
}
@@ -299,27 +352,40 @@
putDeviceAncillary(DID1, SW1);
putDevice(DID1, SW1);
List<PortDescription> pds = Arrays.<PortDescription>asList(
- new DefaultPortDescription(P1, true)
+ new DefaultPortDescription(P1, true, A1)
);
deviceStore.updatePorts(PID, DID1, pds);
DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
- new DefaultPortDescription(P1, false));
+ 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(P2, true));
- assertEquals(PORT_ADDED, event3.type());
+ new DefaultPortDescription(P1, true, A2));
+ assertEquals(PORT_UPDATED, event3.type());
assertDevice(DID1, SW1, event3.subject());
- assertEquals(P2, event3.port().number());
- assertFalse("Port is disabled if not given from provider", event3.port().isEnabled());
+ 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
diff --git a/pom.xml b/pom.xml
index 3124467..56bbd74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,19 @@
</dependency>
<dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-core</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.6</version>
@@ -98,16 +111,16 @@
<version>3.3.2</version>
</dependency>
- <dependency>
- <groupId>org.codehaus.jackson</groupId>
- <artifactId>jackson-core-asl</artifactId>
- <version>1.9.13</version>
- </dependency>
- <dependency>
- <groupId>org.codehaus.jackson</groupId>
- <artifactId>jackson-mapper-asl</artifactId>
- <version>1.9.13</version>
- </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ <version>1.9.13</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ <version>1.9.13</version>
+ </dependency>
<!-- Web related -->
@@ -244,6 +257,14 @@
<artifactId>junit</artifactId>
</dependency>
<dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</dependency>
@@ -320,6 +341,35 @@
</plugin>
<!-- TODO: add findbugs plugin for static code analysis; for explicit invocation only -->
+ <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.jacoco</groupId>
+ <artifactId>
+ jacoco-maven-plugin
+ </artifactId>
+ <versionRange>
+ [0.7.1.201405082137,)
+ </versionRange>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
</plugins>
</pluginManagement>
diff --git a/tools/build/conf/pom.xml b/tools/build/conf/pom.xml
index c2ad09c..4865705 100644
--- a/tools/build/conf/pom.xml
+++ b/tools/build/conf/pom.xml
@@ -6,5 +6,10 @@
<groupId>org.onlab.tools</groupId>
<artifactId>onos-build-conf</artifactId>
<version>1.0</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
</project>
diff --git a/tools/test/bin/onos-fetch-vms b/tools/test/bin/onos-fetch-vms
new file mode 100755
index 0000000..2fd4602
--- /dev/null
+++ b/tools/test/bin/onos-fetch-vms
@@ -0,0 +1,11 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# Remotely fetches the ONOS test VMs from a local share into ~/Downloads.
+#-------------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+mkdir -p /tmp/onos
+mount -t smbfs smb://guest:@10.254.1.15/onos /tmp/onos
+cp /tmp/onos/*.ova ~/Downloads
diff --git a/utils/netty/pom.xml b/utils/netty/pom.xml
new file mode 100644
index 0000000..d335117
--- /dev/null
+++ b/utils/netty/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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.onlab.onos</groupId>
+ <artifactId>onlab-utils</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onlab-netty</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Network I/O using Netty framework</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onlab-misc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>de.javakaffee</groupId>
+ <artifactId>kryo-serializers</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-all</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-pool</groupId>
+ <artifactId>commons-pool</artifactId>
+ <version>1.6</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/utils/pom.xml b/utils/pom.xml
index 2beeba8..feb60e9 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -19,6 +19,7 @@
<modules>
<module>junit</module>
<module>misc</module>
+ <module>netty</module>
<module>nio</module>
<module>osgi</module>
<module>rest</module>