Merge "Follow javadoc convention"
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java b/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
index 4439db5..b6f1eb9 100644
--- a/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
@@ -157,7 +157,9 @@
final String someTable = "admin";
final String someKey = "long";
- dbAdminService.createTable(someTable);
+ if (!dbAdminService.listTables().contains(someTable)) {
+ dbAdminService.createTable(someTable);
+ }
VersionedValue vv = dbService.get(someTable, someKey);
if (vv == null) {
diff --git a/apps/oecfg/src/main/java/org/onlab/onos/oecfg/OELinkConfig.java b/apps/oecfg/src/main/java/org/onlab/onos/oecfg/OELinkConfig.java
index 8a1c6fc..d844f62 100644
--- a/apps/oecfg/src/main/java/org/onlab/onos/oecfg/OELinkConfig.java
+++ b/apps/oecfg/src/main/java/org/onlab/onos/oecfg/OELinkConfig.java
@@ -51,8 +51,8 @@
private JsonNode convert(InputStream input) throws IOException {
JsonNode json = mapper.readTree(input);
ObjectNode result = mapper.createObjectNode();
- result.set("opticalSwitches", opticalSwitches(json));
- result.set("opticalLinks", opticalLinks(json));
+ result.set("switchConfig", opticalSwitches(json));
+ result.set("linkConfig", opticalLinks(json));
return result;
}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
index cffdc09..3ae5b82 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
@@ -598,7 +598,7 @@
Set<ConnectPoint> ingressPorts = new HashSet<>();
for (Interface intf : interfaceService.getInterfaces()) {
- if (!intf.equals(egressInterface)) {
+ if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
ConnectPoint srcPort = intf.connectPoint();
ingressPorts.add(srcPort);
}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
index 8b5ed41..38fad6c 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
@@ -223,11 +223,6 @@
synchronized void routeUpdates(BgpSession bgpSession,
Collection<BgpRouteEntry> addedBgpRouteEntries,
Collection<BgpRouteEntry> deletedBgpRouteEntries) {
- //
- // TODO: Merge the updates from different BGP Peers,
- // by choosing the best route.
- //
-
// Process the deleted route entries
for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
processDeletedRoute(bgpSession, bgpRouteEntry);
diff --git a/core/api/src/main/java/org/onlab/onos/event/AbstractEvent.java b/core/api/src/main/java/org/onlab/onos/event/AbstractEvent.java
index 0fc8eaa..49fb268 100644
--- a/core/api/src/main/java/org/onlab/onos/event/AbstractEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/event/AbstractEvent.java
@@ -26,7 +26,7 @@
private final long time;
private final T type;
- private S subject;
+ private final S subject;
/**
* Creates an event of a given type and for the specified subject and the
diff --git a/core/api/src/main/java/org/onlab/onos/store/service/ReadResult.java b/core/api/src/main/java/org/onlab/onos/store/service/ReadResult.java
index 943bc63..7aeddda 100644
--- a/core/api/src/main/java/org/onlab/onos/store/service/ReadResult.java
+++ b/core/api/src/main/java/org/onlab/onos/store/service/ReadResult.java
@@ -22,6 +22,7 @@
/**
* Returns the status of the read operation.
+ * @return read operation status
*/
public ReadStatus status() {
return status;
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowEntryTest.java b/core/api/src/test/java/org/onlab/onos/net/flow/DefaultFlowEntryTest.java
similarity index 100%
rename from core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowEntryTest.java
rename to core/api/src/test/java/org/onlab/onos/net/flow/DefaultFlowEntryTest.java
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowRuleTest.java b/core/api/src/test/java/org/onlab/onos/net/flow/DefaultFlowRuleTest.java
similarity index 100%
rename from core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowRuleTest.java
rename to core/api/src/test/java/org/onlab/onos/net/flow/DefaultFlowRuleTest.java
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
similarity index 100%
rename from core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
rename to core/api/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
diff --git a/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java b/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
index 0989867..dbb3ae4 100644
--- a/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
+++ b/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
@@ -269,7 +269,7 @@
@Override
public void notify(MastershipEvent event) {
- log.info("dispatching mastership event {}", event);
+ log.trace("dispatching mastership event {}", event);
eventDispatcher.post(event);
}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
index 9c196a2..2505be5 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
@@ -138,9 +138,6 @@
* @return allocated resources if any are required, null otherwise
*/
private LinkResourceAllocations allocateResources(PathIntent intent) {
- if (intent.constraints() == null) {
- return null;
- }
LinkResourceRequest.Builder builder =
DefaultLinkResourceRequest.builder(intent.id(), intent.path().links());
for (Constraint constraint : intent.constraints()) {
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/FlowIdTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/FlowIdTest.java
new file mode 100644
index 0000000..92c3bd0
--- /dev/null
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/FlowIdTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onlab.onos.net.flow;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Unit tests for flow id class.
+ */
+public class FlowIdTest {
+
+ final FlowId flowId1 = FlowId.valueOf(1);
+ final FlowId sameAsFlowId1 = FlowId.valueOf(1);
+ final FlowId flowId2 = FlowId.valueOf(2);
+
+ /**
+ * Checks that the DefaultFlowRule class is immutable.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(FlowId.class);
+ }
+
+ /**
+ * Checks the operation of equals(), hashCode and toString() methods.
+ */
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(flowId1, sameAsFlowId1)
+ .addEqualityGroup(flowId2)
+ .testEquals();
+ }
+
+ /**
+ * Checks the construction of a FlowId object.
+ */
+ @Test
+ public void testConstruction() {
+ final long flowIdValue = 7777L;
+ final FlowId flowId = FlowId.valueOf(flowIdValue);
+ assertThat(flowId, is(notNullValue()));
+ assertThat(flowId.value(), is(flowIdValue));
+ }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
index 0ba67cf..97cf50e 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
@@ -1,6 +1,5 @@
package org.onlab.onos.store.service.impl;
-import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.ArrayList;
@@ -38,7 +37,6 @@
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.cluster.ClusterService;
-import org.onlab.onos.cluster.ControllerNode;
import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.serializers.ImmutableListSerializer;
@@ -172,20 +170,9 @@
@Override
public ProtocolClient createClient(TcpMember member) {
- ControllerNode remoteNode = getControllerNode(member.host(), member.port());
- checkNotNull(remoteNode,
- "A valid controller node is expected for %s:%s",
- member.host(), member.port());
- return new ClusterMessagingProtocolClient(
- clusterCommunicator, clusterService.getLocalNode(), remoteNode);
- }
-
- private ControllerNode getControllerNode(String host, int port) {
- for (ControllerNode node : clusterService.getNodes()) {
- if (node.ip().toString().equals(host) && node.tcpPort() == port) {
- return node;
- }
- }
- return null;
+ return new ClusterMessagingProtocolClient(clusterService,
+ clusterCommunicator,
+ clusterService.getLocalNode(),
+ member);
}
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolClient.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolClient.java
index 23c34b2..3dd93b9 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolClient.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolClient.java
@@ -13,6 +13,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import net.kuujo.copycat.cluster.TcpMember;
import net.kuujo.copycat.protocol.PingRequest;
import net.kuujo.copycat.protocol.PingResponse;
import net.kuujo.copycat.protocol.PollRequest;
@@ -23,6 +24,9 @@
import net.kuujo.copycat.protocol.SyncResponse;
import net.kuujo.copycat.spi.protocol.ProtocolClient;
+import org.onlab.onos.cluster.ClusterEvent;
+import org.onlab.onos.cluster.ClusterEventListener;
+import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.cluster.ControllerNode;
import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
@@ -43,21 +47,30 @@
public static final long RETRY_INTERVAL_MILLIS = 2000;
+ private final ClusterService clusterService;
private final ClusterCommunicationService clusterCommunicator;
private final ControllerNode localNode;
- private final ControllerNode remoteNode;
+ private final TcpMember remoteMember;
+ private ControllerNode remoteNode;
// FIXME: Thread pool sizing.
private static final ScheduledExecutorService THREAD_POOL =
new ScheduledThreadPoolExecutor(10, THREAD_FACTORY);
+ private volatile CompletableFuture<Void> appeared;
+
+ private volatile InternalClusterEventListener listener;
+
public ClusterMessagingProtocolClient(
+ ClusterService clusterService,
ClusterCommunicationService clusterCommunicator,
ControllerNode localNode,
- ControllerNode remoteNode) {
+ TcpMember remoteMember) {
+
+ this.clusterService = clusterService;
this.clusterCommunicator = clusterCommunicator;
this.localNode = localNode;
- this.remoteNode = remoteNode;
+ this.remoteMember = remoteMember;
}
@Override
@@ -81,15 +94,64 @@
}
@Override
- public CompletableFuture<Void> connect() {
- return CompletableFuture.completedFuture(null);
+ public synchronized CompletableFuture<Void> connect() {
+ if (remoteNode != null) {
+ // done
+ return CompletableFuture.completedFuture(null);
+ }
+
+ remoteNode = getControllerNode(remoteMember);
+
+ if (remoteNode != null) {
+ // done
+ return CompletableFuture.completedFuture(null);
+ }
+
+ if (appeared != null) {
+ // already waiting for member to appear
+ return appeared;
+ }
+
+ appeared = new CompletableFuture<>();
+ listener = new InternalClusterEventListener();
+ clusterService.addListener(listener);
+
+ // wait for specified controller node to come up
+ return null;
}
@Override
- public CompletableFuture<Void> close() {
+ public synchronized CompletableFuture<Void> close() {
+ if (listener != null) {
+ clusterService.removeListener(listener);
+ listener = null;
+ }
+ if (appeared != null) {
+ appeared.cancel(true);
+ appeared = null;
+ }
return CompletableFuture.completedFuture(null);
}
+ private synchronized void checkIfMemberAppeared() {
+ final ControllerNode controllerNode = getControllerNode(remoteMember);
+ if (controllerNode == null) {
+ // still not there: no-op
+ return;
+ }
+
+ // found
+ remoteNode = controllerNode;
+ if (appeared != null) {
+ appeared.complete(null);
+ }
+
+ if (listener != null) {
+ clusterService.removeListener(listener);
+ listener = null;
+ }
+ }
+
private <I> MessageSubject messageType(I input) {
Class<?> clazz = input.getClass();
if (clazz.equals(PollRequest.class)) {
@@ -112,6 +174,30 @@
return future;
}
+ private ControllerNode getControllerNode(TcpMember remoteMember) {
+ final String host = remoteMember.host();
+ final int port = remoteMember.port();
+ for (ControllerNode node : clusterService.getNodes()) {
+ if (node.ip().toString().equals(host) && node.tcpPort() == port) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ private final class InternalClusterEventListener
+ implements ClusterEventListener {
+
+ public InternalClusterEventListener() {
+ }
+
+ @Override
+ public void event(ClusterEvent event) {
+ checkIfMemberAppeared();
+ }
+
+ }
+
private class RPCTask<I, O> implements Runnable {
private final I request;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseEntryExpirationTracker.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseEntryExpirationTracker.java
new file mode 100644
index 0000000..c9775ca
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseEntryExpirationTracker.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.onos.store.service.impl;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import net.jodah.expiringmap.ExpiringMap;
+import net.jodah.expiringmap.ExpiringMap.ExpirationListener;
+import net.jodah.expiringmap.ExpiringMap.ExpirationPolicy;
+import net.kuujo.copycat.cluster.Member;
+import net.kuujo.copycat.event.EventHandler;
+import net.kuujo.copycat.event.LeaderElectEvent;
+
+import org.onlab.onos.cluster.ClusterService;
+import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
+import org.onlab.onos.store.cluster.messaging.ClusterMessage;
+import org.onlab.onos.store.cluster.messaging.MessageSubject;
+import org.onlab.onos.store.service.DatabaseService;
+import org.onlab.onos.store.service.VersionedValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Plugs into the database update stream and track the TTL of entries added to
+ * the database. For tables with pre-configured finite TTL, this class has
+ * mechanisms for expiring (deleting) old, expired entries from the database.
+ */
+public class DatabaseEntryExpirationTracker implements
+ DatabaseUpdateEventListener, EventHandler<LeaderElectEvent> {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ public static final MessageSubject DATABASE_UPDATES = new MessageSubject(
+ "database-update-event");
+
+ private DatabaseService databaseService;
+ private ClusterService cluster;
+ private ClusterCommunicationService clusterCommunicator;
+
+ private final Member localMember;
+ private final AtomicBoolean isLocalMemberLeader = new AtomicBoolean(false);
+
+ private final Map<String, Map<DatabaseRow, VersionedValue>> tableEntryExpirationMap = new HashMap<>();
+
+ private final ExpirationListener<DatabaseRow, VersionedValue> expirationObserver = new ExpirationObserver();
+
+ DatabaseEntryExpirationTracker(Member localMember) {
+ this.localMember = localMember;
+ }
+
+ @Override
+ public void tableModified(TableModificationEvent event) {
+ DatabaseRow row = new DatabaseRow(event.tableName(), event.key());
+ Map<DatabaseRow, VersionedValue> map = tableEntryExpirationMap
+ .get(event.tableName());
+
+ switch (event.type()) {
+ case ROW_DELETED:
+ if (isLocalMemberLeader.get()) {
+ try {
+ clusterCommunicator.broadcast(new ClusterMessage(cluster
+ .getLocalNode().id(), DATABASE_UPDATES,
+ DatabaseStateMachine.SERIALIZER.encode(event)));
+ } catch (IOException e) {
+ log.error(
+ "Failed to broadcast a database table modification event.",
+ e);
+ }
+ }
+ break;
+ case ROW_ADDED:
+ case ROW_UPDATED:
+ map.put(row, null);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void tableCreated(String tableName, int expirationTimeMillis) {
+ // make this explicit instead of relying on a negative value
+ // to indicate no expiration.
+ if (expirationTimeMillis > 0) {
+ tableEntryExpirationMap.put(tableName, ExpiringMap.builder()
+ .expiration(expirationTimeMillis, TimeUnit.SECONDS)
+ .expirationListener(expirationObserver)
+ // FIXME: make the expiration policy configurable.
+ .expirationPolicy(ExpirationPolicy.CREATED).build());
+ }
+ }
+
+ @Override
+ public void tableDeleted(String tableName) {
+ tableEntryExpirationMap.remove(tableName);
+ }
+
+ private class ExpirationObserver implements
+ ExpirationListener<DatabaseRow, VersionedValue> {
+ @Override
+ public void expired(DatabaseRow key, VersionedValue value) {
+ try {
+ if (isLocalMemberLeader.get()) {
+ if (!databaseService.removeIfVersionMatches(key.tableName,
+ key.key, value.version())) {
+ log.info("Entry in the database changed before right its TTL expiration.");
+ }
+ } else {
+ // If this node is not the current leader, we should never
+ // let the expiring entries drop off
+ // Under stable conditions (i.e no leadership switch) the
+ // current leader will initiate
+ // a database remove and this instance will get notified
+ // of a tableModification event causing it to remove from
+ // the map.
+ Map<DatabaseRow, VersionedValue> map = tableEntryExpirationMap
+ .get(key.tableName);
+ if (map != null) {
+ map.put(key, value);
+ }
+ }
+
+ } catch (Exception e) {
+ log.warn(
+ "Failed to delete entry from the database after ttl expiration. Will retry eviction",
+ e);
+ tableEntryExpirationMap.get(key.tableName).put(
+ new DatabaseRow(key.tableName, key.key), value);
+ }
+ }
+ }
+
+ @Override
+ public void handle(LeaderElectEvent event) {
+ if (localMember.equals(event.leader())) {
+ isLocalMemberLeader.set(true);
+ }
+ }
+
+ /**
+ * Wrapper class for a database row identifier.
+ */
+ private class DatabaseRow {
+
+ String tableName;
+ String key;
+
+ public DatabaseRow(String tableName, String key) {
+ this.tableName = tableName;
+ this.key = key;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DatabaseRow)) {
+ return false;
+ }
+ DatabaseRow that = (DatabaseRow) obj;
+
+ return Objects.equals(this.tableName, that.tableName)
+ && Objects.equals(this.key, that.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(tableName, key);
+ }
+ }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
index fc73374..b2fe19f 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
@@ -67,6 +67,7 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DatabaseProtocolService copycatMessagingProtocol;
+ // FIXME: point to appropriate path
public static final String LOG_FILE_PREFIX = "/tmp/onos-copy-cat-log_";
// Current working dir seems to be /opt/onos/apache-karaf-3.0.2
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java
index 9ca69ee..62a06b4 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java
@@ -237,8 +237,8 @@
WriteResult putResult = new WriteResult(WriteStatus.OK, previousValue);
results.add(putResult);
tableModificationEvent = (previousValue == null) ?
- TableModificationEvent.rowAdded(request.tableName(), request.key()) :
- TableModificationEvent.rowUpdated(request.tableName(), request.key());
+ TableModificationEvent.rowAdded(request.tableName(), request.key(), newValue) :
+ TableModificationEvent.rowUpdated(request.tableName(), request.key(), newValue);
break;
case REMOVE:
@@ -249,7 +249,7 @@
results.add(removeResult);
if (removedValue != null) {
tableModificationEvent =
- TableModificationEvent.rowDeleted(request.tableName(), request.key());
+ TableModificationEvent.rowDeleted(request.tableName(), request.key(), removedValue);
}
break;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseUpdateEventHandler.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseUpdateEventHandler.java
deleted file mode 100644
index 21028e4..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseUpdateEventHandler.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2014 Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.onlab.onos.store.service.impl;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-//import net.jodah.expiringmap.ExpiringMap;
-//import net.jodah.expiringmap.ExpiringMap.ExpirationListener;
-//import net.jodah.expiringmap.ExpiringMap.ExpirationPolicy;
-import net.kuujo.copycat.cluster.Member;
-import net.kuujo.copycat.event.EventHandler;
-import net.kuujo.copycat.event.LeaderElectEvent;
-
-import org.onlab.onos.cluster.ClusterService;
-import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
-import org.onlab.onos.store.cluster.messaging.ClusterMessage;
-import org.onlab.onos.store.cluster.messaging.MessageSubject;
-import org.onlab.onos.store.service.DatabaseService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Database update event handler.
- */
-public class DatabaseUpdateEventHandler implements
- DatabaseUpdateEventListener, EventHandler<LeaderElectEvent> {
-
- private final Logger log = LoggerFactory.getLogger(getClass());
-
- public static final MessageSubject DATABASE_UPDATES =
- new MessageSubject("database-update-event");
-
- private DatabaseService databaseService;
- private ClusterService cluster;
- private ClusterCommunicationService clusterCommunicator;
-
- private final Member localMember;
- private final AtomicBoolean isLocalMemberLeader = new AtomicBoolean(false);
- private final Map<String, Map<DatabaseRow, Void>> tableEntryExpirationMap = new HashMap<>();
- //private final ExpirationListener<DatabaseRow, Void> expirationObserver = new ExpirationObserver();
-
- DatabaseUpdateEventHandler(Member localMember) {
- this.localMember = localMember;
- }
-
- @Override
- public void tableModified(TableModificationEvent event) {
- DatabaseRow row = new DatabaseRow(event.tableName(), event.key());
- Map<DatabaseRow, Void> map = tableEntryExpirationMap.get(event.tableName());
-
- switch (event.type()) {
- case ROW_DELETED:
- if (isLocalMemberLeader.get()) {
- try {
- clusterCommunicator.broadcast(
- new ClusterMessage(
- cluster.getLocalNode().id(),
- DATABASE_UPDATES,
- DatabaseStateMachine.SERIALIZER.encode(event)));
- } catch (IOException e) {
- log.error("Failed to broadcast a database table modification event.", e);
- }
- }
- break;
- case ROW_ADDED:
- case ROW_UPDATED:
- map.put(row, null);
- break;
- default:
- break;
- }
- }
-
- @Override
- public void tableCreated(String tableName, int expirationTimeMillis) {
- // make this explicit instead of relying on a negative value
- // to indicate no expiration.
- if (expirationTimeMillis > 0) {
- tableEntryExpirationMap.put(tableName, null);
- /*
- ExpiringMap.builder()
- .expiration(expirationTimeMillis, TimeUnit.SECONDS)
- .expirationListener(expirationObserver)
- // FIXME: make the expiration policy configurable.
- .expirationPolicy(ExpirationPolicy.CREATED)
- .build());
- */
- }
- }
-
- @Override
- public void tableDeleted(String tableName) {
- tableEntryExpirationMap.remove(tableName);
- }
-
- /*
- private class ExpirationObserver implements ExpirationListener<DatabaseRow, Void> {
- @Override
- public void expired(DatabaseRow key, Void value) {
- try {
- // TODO: The safety of this check needs to be verified.
- // Couple of issues:
- // 1. It is very likely that only one member should attempt deletion of the entry from database.
- // 2. A potential race condition exists where the entry expires, but before its can be deleted
- // from the database, a new entry is added or existing entry is updated.
- // That means ttl and expiration should be for a given version.
- if (isLocalMemberLeader.get()) {
- databaseService.remove(key.tableName, key.key);
- }
- } catch (Exception e) {
- log.warn("Failed to delete entry from the database after ttl expiration. Will retry eviction", e);
- tableEntryExpirationMap.get(key.tableName).put(new DatabaseRow(key.tableName, key.key), null);
- }
- }
- }
- */
-
- @Override
- public void handle(LeaderElectEvent event) {
- if (localMember.equals(event.leader())) {
- isLocalMemberLeader.set(true);
- }
- }
-
- private class DatabaseRow {
-
- String tableName;
- String key;
-
- public DatabaseRow(String tableName, String key) {
- this.tableName = tableName;
- this.key = key;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof DatabaseRow)) {
- return false;
- }
- DatabaseRow that = (DatabaseRow) obj;
-
- return Objects.equals(this.tableName, that.tableName) &&
- Objects.equals(this.key, that.key);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(tableName, key);
- }
- }
-}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseUpdateEventListener.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseUpdateEventListener.java
index d97191c..1dc0e9d 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseUpdateEventListener.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseUpdateEventListener.java
@@ -29,15 +29,14 @@
/**
* Notifies listeners of a table created event.
- * @param tableName
- * @param expirationTimeMillis
+ * @param tableName name of the table created
+ * @param expirationTimeMillis TTL for entries added to the table (measured since last update time)
*/
public void tableCreated(String tableName, int expirationTimeMillis);
/**
* Notifies listeners of a table deleted event.
- * @param tableName
+ * @param tableName name of the table deleted
*/
public void tableDeleted(String tableName);
-
}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DistributedLockManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DistributedLockManager.java
index f83b042..6d99ba7 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DistributedLockManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DistributedLockManager.java
@@ -33,7 +33,8 @@
public static final String ONOS_LOCK_TABLE_NAME = "onos-locks";
- private final ArrayListMultimap<String, LockRequest> locksToAcquire = ArrayListMultimap.create();
+ private final ArrayListMultimap<String, LockRequest> locksToAcquire = ArrayListMultimap
+ .create();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private ClusterCommunicationService clusterCommunicator;
@@ -61,11 +62,7 @@
@Override
public Lock create(String path) {
- return new DistributedLock(
- path,
- databaseService,
- clusterService,
- this);
+ return new DistributedLock(path, databaseService, clusterService, this);
}
@Override
@@ -80,21 +77,19 @@
throw new UnsupportedOperationException();
}
- protected CompletableFuture<Void> lockIfAvailable(
- Lock lock,
- long waitTimeMillis,
- int leaseDurationMillis) {
+ protected CompletableFuture<Void> lockIfAvailable(Lock lock,
+ long waitTimeMillis, int leaseDurationMillis) {
CompletableFuture<Void> future = new CompletableFuture<>();
- locksToAcquire.put(
- lock.path(),
- new LockRequest(lock, waitTimeMillis, leaseDurationMillis, future));
+ locksToAcquire.put(lock.path(), new LockRequest(lock, waitTimeMillis,
+ leaseDurationMillis, future));
return future;
}
private class LockEventMessageListener implements ClusterMessageHandler {
@Override
public void handle(ClusterMessage message) {
- TableModificationEvent event = DatabaseStateMachine.SERIALIZER.decode(message.payload());
+ TableModificationEvent event = DatabaseStateMachine.SERIALIZER
+ .decode(message.payload());
if (!event.tableName().equals(ONOS_LOCK_TABLE_NAME)) {
return;
}
@@ -110,15 +105,20 @@
return;
}
- Iterator<LockRequest> existingRequestIterator = existingRequests.iterator();
- while (existingRequestIterator.hasNext()) {
- LockRequest request = existingRequestIterator.next();
- if (request.expirationTime().isAfter(DateTime.now())) {
- existingRequestIterator.remove();
- } else {
- if (request.lock().tryLock(request.leaseDurationMillis())) {
- request.future().complete(null);
- existingRequests.remove(0);
+ synchronized (existingRequests) {
+
+ Iterator<LockRequest> existingRequestIterator = existingRequests
+ .iterator();
+ while (existingRequestIterator.hasNext()) {
+ LockRequest request = existingRequestIterator.next();
+ if (request.expirationTime().isAfter(DateTime.now())) {
+ existingRequestIterator.remove();
+ } else {
+ if (request.lock().tryLock(
+ request.leaseDurationMillis())) {
+ request.future().complete(null);
+ existingRequestIterator.remove();
+ }
}
}
}
@@ -133,14 +133,12 @@
private final int leaseDurationMillis;
private final CompletableFuture<Void> future;
- public LockRequest(
- Lock lock,
- long waitTimeMillis,
- int leaseDurationMillis,
- CompletableFuture<Void> future) {
+ public LockRequest(Lock lock, long waitTimeMillis,
+ int leaseDurationMillis, CompletableFuture<Void> future) {
this.lock = lock;
- this.expirationTime = DateTime.now().plusMillis((int) waitTimeMillis);
+ this.expirationTime = DateTime.now().plusMillis(
+ (int) waitTimeMillis);
this.leaseDurationMillis = leaseDurationMillis;
this.future = future;
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TableModificationEvent.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TableModificationEvent.java
index 885c9fa..b0962dc 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TableModificationEvent.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TableModificationEvent.java
@@ -1,5 +1,7 @@
package org.onlab.onos.store.service.impl;
+import org.onlab.onos.store.service.VersionedValue;
+
/**
* A table modification event.
*/
@@ -17,41 +19,46 @@
private final String tableName;
private final String key;
+ private final VersionedValue value;
private final Type type;
/**
* Creates a new row deleted table modification event.
* @param tableName table name.
* @param key row key
+ * @param value value associated with the key when it was deleted.
* @return table modification event.
*/
- public static TableModificationEvent rowDeleted(String tableName, String key) {
- return new TableModificationEvent(tableName, key, Type.ROW_DELETED);
+ public static TableModificationEvent rowDeleted(String tableName, String key, VersionedValue value) {
+ return new TableModificationEvent(tableName, key, value, Type.ROW_DELETED);
}
/**
* Creates a new row added table modification event.
* @param tableName table name.
* @param key row key
+ * @param value value associated with the key
* @return table modification event.
*/
- public static TableModificationEvent rowAdded(String tableName, String key) {
- return new TableModificationEvent(tableName, key, Type.ROW_ADDED);
+ public static TableModificationEvent rowAdded(String tableName, String key, VersionedValue value) {
+ return new TableModificationEvent(tableName, key, value, Type.ROW_ADDED);
}
/**
* Creates a new row updated table modification event.
* @param tableName table name.
* @param key row key
+ * @param newValue value
* @return table modification event.
*/
- public static TableModificationEvent rowUpdated(String tableName, String key) {
- return new TableModificationEvent(tableName, key, Type.ROW_UPDATED);
+ public static TableModificationEvent rowUpdated(String tableName, String key, VersionedValue newValue) {
+ return new TableModificationEvent(tableName, key, newValue, Type.ROW_UPDATED);
}
- private TableModificationEvent(String tableName, String key, Type type) {
+ private TableModificationEvent(String tableName, String key, VersionedValue value, Type type) {
this.tableName = tableName;
this.key = key;
+ this.value = value;
this.type = type;
}
@@ -72,6 +79,15 @@
}
/**
+ * Returns the value associated with the key. If the event for a deletion, this
+ * method returns value that was deleted.
+ * @return row value
+ */
+ public VersionedValue value() {
+ return value;
+ }
+
+ /**
* Returns the type of table modification event.
* @return event type.
*/
diff --git a/tools/package/etc/hazelcast.xml b/tools/package/etc/hazelcast.xml
index b950768..b92a793 100644
--- a/tools/package/etc/hazelcast.xml
+++ b/tools/package/etc/hazelcast.xml
@@ -176,7 +176,7 @@
com.hazelcast.map.merge.HigherHitsMapMergePolicy ; entry with the higher hits wins.
com.hazelcast.map.merge.LatestUpdateMapMergePolicy ; entry with the latest update wins.
-->
- <merge-policy>com.hazelcast.map.merge.PassThroughMergePolicy</merge-policy>
+ <merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
</map>
diff --git a/tools/test/bin/onos-push-update-bundle b/tools/test/bin/onos-push-update-bundle
index 1539467..f8d682a 100755
--- a/tools/test/bin/onos-push-update-bundle
+++ b/tools/test/bin/onos-push-update-bundle
@@ -13,6 +13,8 @@
bundle=$(echo $(basename $jar .jar) | sed 's/-[0-9].*//g')
+echo "pushing bundle: $bundle"
+
nodes=$(env | sort | egrep "OC[0-9]+" | cut -d= -f2)
for node in $nodes; do
scp -q $jar $ONOS_USER@$node:.m2/repository/$jar
diff --git a/tools/test/topos/oe-nonlinear-10.json b/tools/test/topos/oe-nonlinear-10.json
index 522215b..59c5b89 100644
--- a/tools/test/topos/oe-nonlinear-10.json
+++ b/tools/test/topos/oe-nonlinear-10.json
@@ -61,14 +61,6 @@
"ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
},
{
- "uri": "of:0000ffffffffff06", "mac": "ffffffffffff06", "type": "ROADM",
- "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "DFW-M10",
- "annotations": { "latitude": 32.8, "longitude": 97.1, "optical.regens": 3 },
- "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
- },
-
-
- {
"uri": "of:0000ffffffff0001", "mac": "ffffffffff0001", "type": "SWITCH",
"mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "SFO-R10",
"annotations": { "latitude": 37.6, "longitude": 122.3 },
diff --git a/tools/test/topos/oe-nonlinear-4.json b/tools/test/topos/oe-nonlinear-4.json
new file mode 100644
index 0000000..ac86a01
--- /dev/null
+++ b/tools/test/topos/oe-nonlinear-4.json
@@ -0,0 +1,51 @@
+{
+ "devices" : [
+ {
+ "uri": "of:0000ffffffffff01", "mac": "ffffffffffff01", "type": "ROADM",
+ "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM1",
+ "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 },
+ "ports": [ { "port": 10, "speed": 100000, "type": "FIBER" }, { "port": 20, "speed": 0, "type": "FIBER" }, { "port": 22, "speed": 0, "type": "FIBER" }]
+ },
+ {
+ "uri": "of:0000ffffffffff02", "mac": "ffffffffffff02", "type": "ROADM",
+ "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM2",
+ "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 },
+ "ports": [ { "port": 11, "speed": 100000, "type": "FIBER" }, { "port": 21, "speed": 0, "type": "FIBER" }, { "port": 22, "speed": 0, "type": "FIBER" }]
+ },
+ {
+ "uri": "of:0000ffffffffff03", "mac": "ffffffffffff03", "type": "ROADM",
+ "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM3",
+ "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 2 },
+ "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" }]
+ },
+ {
+ "uri": "of:0000ffffffffff04", "mac": "ffffffffffff04", "type":"ROADM",
+ "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM4",
+ "annotations": { "latitude": 39.9, "longitude": 119.4, "optical.regens": 2 },
+ "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" }]
+ },
+ {
+ "uri": "of:0000ffffffff0001", "mac": "ffffffffff0001", "type": "SWITCH",
+ "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ROUTER1",
+ "annotations": { "latitude": 37.6, "longitude": 122.3 },
+ "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
+ },
+ {
+ "uri": "of:0000ffffffff0002", "mac": "ffffffffff0002", "type": "SWITCH",
+ "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ROUTER2",
+ "annotations": { "latitude": 37.3, "longitude": 121.9 },
+ "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
+ }
+ ],
+
+ "links" : [
+ { "src": "of:0000ffffffffff01/20", "dst": "of:0000ffffffffff03/30", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "durable": "true" } },
+ { "src": "of:0000ffffffffff02/21", "dst": "of:0000ffffffffff03/31", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "durable": "true" } },
+ { "src": "of:0000ffffffffff01/22", "dst": "of:0000ffffffffff04/30", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "durable": "true" } },
+ { "src": "of:0000ffffffffff04/31", "dst": "of:0000ffffffffff02/22", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "durable": "true" } },
+
+ { "src": "of:0000ffffffff0001/2", "dst": "of:0000ffffffffff01/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
+ { "src": "of:0000ffffffff0002/2", "dst": "of:0000ffffffffff02/11", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } }
+ ]
+
+}
diff --git a/utils/thirdparty/pom.xml b/utils/thirdparty/pom.xml
index 164f7c8..58c6a9b 100644
--- a/utils/thirdparty/pom.xml
+++ b/utils/thirdparty/pom.xml
@@ -39,30 +39,23 @@
</dependency>
<dependency>
+ <groupId>net.jodah</groupId>
+ <artifactId>expiringmap</artifactId>
+ <version>0.3.1</version>
+ </dependency>
+
+ <dependency>
<groupId>net.kuujo.copycat</groupId>
<artifactId>copycat</artifactId>
<version>${copycat.version}</version>
</dependency>
-<!-- Commented out due to Chronicle + OSGi issue
- <dependency>
- <groupId>net.kuujo.copycat</groupId>
- <artifactId>copycat-chronicle</artifactId>
- <version>${copycat.version}</version>
- </dependency>
--->
+
<dependency>
<groupId>net.kuujo.copycat</groupId>
<artifactId>copycat-tcp</artifactId>
<version>${copycat.version}</version>
</dependency>
-<!-- chronicle transitive dependency
- <dependency>
- <groupId>net.java.dev.jna</groupId>
- <artifactId>jna</artifactId>
- <version>4.1.0</version>
- </dependency>
--->
</dependencies>
<build>
@@ -89,20 +82,19 @@
</filter>
<filter>
+ <artifact>net.jodah.expiringmap:*</artifact>
+ <includes>
+ <include>net/jodah/expiringmap/**</include>
+ </includes>
+ </filter>
+
+ <filter>
<artifact>net.kuujo.copycat:*</artifact>
<includes>
<include>net/kuujo/copycat/**</include>
</includes>
</filter>
-<!-- chronicle transitive dependency
- <filter>
- <artifact>net.java.dev.jna:*</artifact>
- <includes>
- <include>com/sun/jna/**</include>
- </includes>
- </filter>
--->
</filters>
</configuration>
<executions>
@@ -120,7 +112,7 @@
<configuration>
<instructions>
<Export-Package>
- com.googlecode.concurrenttrees.*;net.kuujo.copycat.*
+ com.googlecode.concurrenttrees.*;net.kuujo.copycat.*;net.jodah.expiringmap.*
</Export-Package>
</instructions>
</configuration>
diff --git a/web/gui/src/main/webapp/json/ev/_capture/rx/showPath_ex2.json b/web/gui/src/main/webapp/json/ev/_capture/rx/showPath_ex2.json
new file mode 100644
index 0000000..2a05249
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/_capture/rx/showPath_ex2.json
@@ -0,0 +1,11 @@
+{
+ "event": "showPath",
+ "sid": 3,
+ "payload": {
+ "ids": [
+ "of:0000000000000007"
+ ],
+ "traffic": true
+ }
+}
+// what is the client supposed to do with this?
diff --git a/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex1_devs.json b/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex1_devs.json
new file mode 100644
index 0000000..725c15f
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex1_devs.json
@@ -0,0 +1,11 @@
+{
+ "event": "requestTraffic",
+ "sid": 6,
+ "payload": {
+ "ids": [
+ "of:0000000000000007",
+ "of:000000000000000c",
+ "of:000000000000000a"
+ ]
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex2_hosts.json b/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex2_hosts.json
new file mode 100644
index 0000000..84f17df
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex2_hosts.json
@@ -0,0 +1,12 @@
+{
+ "event": "requestTraffic",
+ "sid": 12,
+ "payload": {
+ "ids": [
+ "86:C3:7B:90:79:CD/-1",
+ "22:BA:28:81:FD:45/-1",
+ "BA:91:F6:8E:B6:B6/-1",
+ "06:E2:E6:F7:03:12/-1"
+ ]
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex3_devs_hosts.json b/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex3_devs_hosts.json
new file mode 100644
index 0000000..3f915df
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/_capture/tx/requestTraffic_ex3_devs_hosts.json
@@ -0,0 +1,12 @@
+{
+ "event": "requestTraffic",
+ "sid": 18,
+ "payload": {
+ "ids": [
+ "of:0000000000000001",
+ "86:C3:7B:90:79:CD/-1",
+ "7E:D2:EE:0F:12:4A/-1",
+ "of:000000000000000c"
+ ]
+ }
+}
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 0644c32..7632148 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -115,7 +115,6 @@
}
function doError(msg) {
- errorCount++;
console.error(msg);
doAlert(msg);
}
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index aeaad2d..a8c67a1 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -24,14 +24,33 @@
opacity: 0.5;
}
+
/* NODES */
-#topo svg .node.device {
- stroke: none;
- stroke-width: 1.5px;
+#topo svg .node {
cursor: pointer;
}
+#topo svg .node.selected rect,
+#topo svg .node.selected circle {
+ filter: url(#blue-glow);
+}
+
+/* for debugging */
+#topo svg .node circle.debug {
+ fill: white;
+ stroke: red;
+}
+
+#topo svg .node text {
+ pointer-events: none;
+}
+
+/* Device Nodes */
+
+#topo svg .node.device {
+}
+
#topo svg .node.device rect {
stroke-width: 1.5px;
}
@@ -54,31 +73,28 @@
fill: #03c;
}
-#topo svg .node.host {
- fill: #846;
-}
-
/* note: device is offline without the 'online' class */
#topo svg .node.device text {
fill: #aaa;
font: 10pt sans-serif;
- pointer-events: none;
}
#topo svg .node.device.online text {
fill: white;
}
+
+/* Host Nodes */
+
+#topo svg .node.host {
+ fill: #846;
+}
+
#topo svg .node.host text {
fill: #846;
font: 9pt sans-serif;
- pointer-events: none;
}
-#topo svg .node.selected rect,
-#topo svg .node.selected circle {
- filter: url(#blue-glow);
-}
/* LINKS */
@@ -91,20 +107,13 @@
stroke-width: 6px;
}
-/* for debugging */
-#topo svg .node circle.debug {
- fill: white;
- stroke: red;
-}
-
-/* detail topo-detail pane */
+/* Fly-in details pane */
#topo-detail {
/* gets base CSS from .fpanel in floatPanel.css */
}
-
#topo-detail h2 {
margin: 8px 4px;
color: black;
@@ -128,9 +137,10 @@
}
#topo-detail td.value {
-
}
+
+
#topo-detail hr {
height: 1px;
color: #ccc;
@@ -138,3 +148,24 @@
border: 0;
}
+/* Web Socket Closed Mask (starts hidden) */
+
+#topo-mask {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 10000px;
+ height: 8000px;
+ z-index: 5000;
+ background-color: rgba(0,0,0,0.75);
+ padding: 60px;
+}
+
+#topo-mask p {
+ margin: 8px 20px;
+ color: #ddd;
+ font-size: 14pt;
+ font-style: italic;
+}
+
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index a23f48d..8a3bc5d 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -127,7 +127,9 @@
P: togglePorts,
U: unpin,
- X: requestPath
+ W: requestTraffic, // bag of selections
+ Z: requestPath, // host-to-host intent (and monitor)
+ X: cancelMonitor
};
// state variables
@@ -150,6 +152,7 @@
debug: false
},
webSock,
+ sid = 0,
deviceLabelIndex = 0,
hostLabelIndex = 0,
detailPane,
@@ -168,7 +171,8 @@
nodeG,
linkG,
node,
- link;
+ link,
+ mask;
// ==============================
// For Debugging / Development
@@ -192,15 +196,11 @@
function testMe(view) {
view.alert('test');
- detailPane.show();
- setTimeout(function () {
- detailPane.hide();
- }, 3000);
}
function abortIfLive() {
if (config.useLiveData) {
- scenario.view.alert("Sorry, currently using live data..");
+ network.view.alert("Sorry, currently using live data..");
return true;
}
return false;
@@ -343,14 +343,18 @@
addDevice: addDevice,
addLink: addLink,
addHost: addHost,
+
updateDevice: updateDevice,
updateLink: updateLink,
updateHost: updateHost,
+
removeDevice: stillToImplement,
removeLink: removeLink,
removeHost: removeHost,
+
showDetails: showDetails,
- showPath: showPath
+ showPath: showPath,
+ showTraffic: showTraffic
};
function addDevice(data) {
@@ -463,6 +467,7 @@
function showDetails(data) {
fnTrace('showDetails', data.payload.id);
populateDetails(data.payload);
+ // TODO: Add single-select actions ...
detailPane.show();
}
@@ -485,6 +490,10 @@
// TODO: add selection-highlite lines to links
}
+ function showTraffic(data) {
+ network.view.alert("showTraffic() -- TODO")
+ }
+
// ...............................
function stillToImplement(data) {
@@ -505,17 +514,58 @@
// ==============================
// Out-going messages...
+ function userFeedback(msg) {
+ // for now, use the alert pane as is. Maybe different alert style in
+ // the future (centered on view; dismiss button?)
+ network.view.alert(msg);
+ }
+
+ function nSel() {
+ return selectOrder.length;
+ }
function getSel(idx) {
return selections[selectOrder[idx]];
}
+ function getSelId(idx) {
+ return getSel(idx).obj.id;
+ }
+ function allSelectionsClass(cls) {
+ for (var i=0, n=nSel(); i<n; i++) {
+ if (getSel(i).obj.class !== cls) {
+ return false;
+ }
+ }
+ return true;
+ }
- // for now, just a host-to-host intent, (and implicit start-monitoring)
+ function requestTraffic() {
+ if (nSel() > 0) {
+ sendMessage('requestTraffic', {
+ ids: selectOrder
+ });
+ } else {
+ userFeedback('Request-Traffic requires one or\n' +
+ 'more items to be selected.');
+ }
+ }
+
function requestPath() {
- var payload = {
- one: getSel(0).obj.id,
- two: getSel(1).obj.id
- };
- sendMessage('requestPath', payload);
+ if (nSel() === 2 && allSelectionsClass('host')) {
+ sendMessage('requestPath', {
+ one: getSelId(0),
+ two: getSelId(1)
+ });
+ } else {
+ userFeedback('Request-Path requires two\n' +
+ 'hosts to be selected.');
+ }
+ }
+
+ function cancelMonitor() {
+ // FIXME: from where do we get the intent id(s) to send to the server?
+ sendMessage('cancelMonitor', {
+ ids: ["need_the_intent_id"]
+ });
}
// request details for the selected element
@@ -701,18 +751,57 @@
function positionNode(node) {
var meta = node.metaUi,
- x = 0,
- y = 0;
+ x = meta && meta.x,
+ y = meta && meta.y,
+ xy;
- if (meta) {
- x = meta.x;
- y = meta.y;
- }
+ // If we have [x,y] already, use that...
if (x && y) {
node.fixed = true;
+ node.x = x;
+ node.y = y;
+ return;
}
- node.x = x || network.view.width() / 2;
- node.y = y || network.view.height() / 2;
+
+ // Note: Placing incoming unpinned nodes at exactly the same point
+ // (center of the view) causes them to explode outwards when
+ // the force layout kicks in. So, we spread them out a bit
+ // initially, to provide a more serene layout convergence.
+ // Additionally, if the node is a host, we place it near
+ // the device it is connected to.
+
+ function spread(s) {
+ return Math.floor((Math.random() * s) - s/2);
+ }
+
+ function randDim(dim) {
+ return dim / 2 + spread(dim * 0.7071);
+ }
+
+ function rand() {
+ return {
+ x: randDim(network.view.width()),
+ y: randDim(network.view.height())
+ };
+ }
+
+ function near(node) {
+ var min = 12,
+ dx = spread(12),
+ dy = spread(12);
+ return {
+ x: node.x + min + dx,
+ y: node.y + min + dy
+ };
+ }
+
+ function getDevice(cp) {
+ var d = network.lookup[cp.device];
+ return d || rand();
+ }
+
+ xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
+ $.extend(node, xy);
}
function iconUrl(d) {
@@ -1012,6 +1101,7 @@
webSock.ws = new WebSocket(webSockUrl());
webSock.ws.onopen = function() {
+ noWebSock(false);
};
webSock.ws.onmessage = function(m) {
@@ -1023,6 +1113,7 @@
webSock.ws.onclose = function(m) {
webSock.ws = null;
+ noWebSock(true);
};
},
@@ -1042,7 +1133,9 @@
};
- var sid = 0;
+ function noWebSock(b) {
+ mask.style('display',b ? 'block' : 'none');
+ }
// TODO: use cache of pending messages (key = sid) to reconcile responses
@@ -1100,7 +1193,7 @@
deselectAll();
}
- selections[obj.id] = { obj: obj, el : el};
+ selections[obj.id] = { obj: obj, el: el };
selectOrder.push(obj.id);
n.classed('selected', true);
@@ -1108,12 +1201,16 @@
}
function deselectObject(id) {
- var obj = selections[id];
+ var obj = selections[id],
+ idx;
if (obj) {
d3.select(obj.el).classed('selected', false);
delete selections[id];
+ idx = $.inArray(id, selectOrder);
+ if (idx >= 0) {
+ selectOrder.splice(idx, 1);
+ }
}
- updateDetailPane();
}
function deselectAll() {
@@ -1147,12 +1244,43 @@
function singleSelect() {
requestDetails();
- // NOTE: detail pane will be shown from showDetails event.
+ // NOTE: detail pane will be shown from showDetails event callback
}
function multiSelect() {
- // TODO: use detail pane for multi-select view.
- //detailPane.show();
+ populateMultiSelect();
+ // TODO: Add multi-select actions ...
+ }
+
+ function addSep(tbody) {
+ var tr = tbody.append('tr');
+ $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
+ }
+
+ function addProp(tbody, label, value) {
+ var tr = tbody.append('tr');
+
+ tr.append('td')
+ .attr('class', 'label')
+ .text(label + ' :');
+
+ tr.append('td')
+ .attr('class', 'value')
+ .text(value);
+ }
+
+ function populateMultiSelect() {
+ detailPane.empty();
+
+ var title = detailPane.append("h2"),
+ table = detailPane.append("table"),
+ tbody = table.append("tbody");
+
+ title.text('Multi-Select...');
+
+ selectOrder.forEach(function (d, i) {
+ addProp(tbody, i+1, d);
+ });
}
function populateDetails(data) {
@@ -1172,23 +1300,6 @@
addProp(tbody, p, data.props[p]);
}
});
-
- function addSep(tbody) {
- var tr = tbody.append('tr');
- $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
- }
-
- function addProp(tbody, label, value) {
- var tr = tbody.append('tr');
-
- tr.append('td')
- .attr('class', 'label')
- .text(label + ' :');
-
- tr.append('td')
- .attr('class', 'value')
- .text(value);
- }
}
// ==============================
@@ -1226,6 +1337,11 @@
}
+
+ function para(sel, text) {
+ sel.append('p').text(text);
+ }
+
// ==============================
// View life-cycle callbacks
@@ -1320,6 +1436,12 @@
.on('tick', tick);
network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
+
+ // create mask layer for when we lose connection to server.
+ mask = view.$div.append('div').attr('id','topo-mask');
+ para(mask, 'Oops!');
+ para(mask, 'Web-socket connection to server closed...');
+ para(mask, 'Try refreshing the page.');
}
function load(view, ctx, flags) {