Ayaka Koshibe

Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Showing 72 changed files with 3162 additions and 851 deletions
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 @@ public final class DefaultAnnotations implements SparseAnnotations {
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
......
......@@ -45,6 +45,18 @@ public class DefaultDeviceDescription extends AbstractDescription
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;
......
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;
......
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
......
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();
}
}
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();
}
}
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();
}
}
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 {
}
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);
}
}
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();
}
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 {
}
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();
}
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;
}
}
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);
}
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();
}
}
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);
}
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);
}
}
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();
}
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);
}
}
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);
}
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
}
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 &amp; 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);
}
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,
}
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();
}
}
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;
}
}
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;
}
}
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();
}
}
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();
}
}
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();
}
}
/**
* Intent Package. TODO
*/
package org.onlab.onos.net.intent;
\ No newline at end of file
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});
}
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();
}
}
}
}
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);
}
}
}
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());
}
}
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> {
}
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()));
}
}
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");
}
}
}
}
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();
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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);
}
}
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();
}
......@@ -35,6 +35,12 @@
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-netty</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
......@@ -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>
......
......@@ -23,10 +23,10 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
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;
......
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;
}
}
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;
}
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;
}
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);
}
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();
}
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();
}
}
}
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());
}
}
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;
}
}
}
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
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);
}
}
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);
}
}
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);
}
}
}
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;
}
}
}
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;
}
}
}
......@@ -7,7 +7,7 @@ import org.junit.Test;
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;
......
......@@ -9,6 +9,8 @@ import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onlab.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.Device.Type;
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 com.google.common.base.Predicates.notNull;
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 @@ public class SimpleDeviceStore
= createIfAbsentUnchecked(providerDescs, providerId,
new InitDeviceDescs(deviceDescription));
// update description
descs.putDeviceDesc(deviceDescription);
Device newDevice = composeDevice(deviceId, providerDescs);
if (oldDevice == null) {
......@@ -144,7 +150,8 @@ public class SimpleDeviceStore
// 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 @@ public class SimpleDeviceStore
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 @@ public class SimpleDeviceStore
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 @@ public class SimpleDeviceStore
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 @@ public class SimpleDeviceStore
}
}
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 @@ public class SimpleDeviceStore
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 @@ public class SimpleDeviceStore
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 @@ public class SimpleDeviceStore
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);
}
}
}
......
......@@ -22,10 +22,13 @@ import org.junit.Before;
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 @@ public class SimpleDeviceStoreTest {
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 @@ public class SimpleDeviceStoreTest {
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 class SimpleDeviceStoreTest {
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 @@ public class SimpleDeviceStoreTest {
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
......
......@@ -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>
......@@ -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>
......
......@@ -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>
......
#!/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
<?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>
......@@ -19,6 +19,7 @@
<modules>
<module>junit</module>
<module>misc</module>
<module>netty</module>
<module>nio</module>
<module>osgi</module>
<module>rest</module>
......