Merge "ONOS-22 - Add Constraints to CLI Commands"
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
index 1d32aa5..29452f7 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
@@ -25,6 +25,7 @@
import org.onlab.onos.cli.Comparators;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.device.DeviceService;
import java.util.ArrayList;
@@ -108,7 +109,7 @@
for (Port port : service.getPorts(device.id())) {
if (isIncluded(port)) {
ports.add(mapper.createObjectNode()
- .put("port", port.number().toString())
+ .put("port", portName(port.number()))
.put("isEnabled", port.isEnabled())
.put("type", port.type().toString().toLowerCase())
.put("portSpeed", port.portSpeed())
@@ -120,6 +121,10 @@
return result;
}
+ private String portName(PortNumber port) {
+ return port.equals(PortNumber.LOCAL) ? "local" : port.toString();
+ }
+
// Determines if a port should be included in output.
private boolean isIncluded(Port port) {
return enabled && port.isEnabled() || disabled && !port.isEnabled() ||
@@ -133,7 +138,8 @@
Collections.sort(ports, Comparators.PORT_COMPARATOR);
for (Port port : ports) {
if (isIncluded(port)) {
- print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled",
+ print(FMT, portName(port.number()),
+ port.isEnabled() ? "enabled" : "disabled",
port.type().toString().toLowerCase(), port.portSpeed(),
annotations(port.annotations()));
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
index 38e1322..849ad17 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
@@ -159,7 +159,7 @@
return messagingService.sendAndReceive(nodeEp, message.subject().value(), SERIALIZER.encode(message));
} catch (IOException e) {
- log.error("Failed interaction with remote nodeId: " + toNodeId, e);
+ log.trace("Failed interaction with remote nodeId: " + toNodeId, e);
throw e;
}
}
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 c561221..56dba79 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
@@ -149,12 +149,12 @@
@Activate
public void activate() {
- log.info("Started.");
+ log.info("Started");
}
@Deactivate
public void deactivate() {
- log.info("Stopped.");
+ log.info("Stopped");
}
@Override
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 183a6db..2779b35 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
@@ -5,20 +5,26 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import net.kuujo.copycat.Copycat;
import net.kuujo.copycat.StateMachine;
+import net.kuujo.copycat.cluster.ClusterConfig;
import net.kuujo.copycat.cluster.TcpCluster;
import net.kuujo.copycat.cluster.TcpClusterConfig;
import net.kuujo.copycat.cluster.TcpMember;
import net.kuujo.copycat.log.InMemoryLog;
import net.kuujo.copycat.log.Log;
+
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.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.service.DatabaseAdminService;
@@ -35,8 +41,6 @@
import org.onlab.onos.store.service.WriteResult;
import org.slf4j.Logger;
-import com.google.common.collect.Lists;
-
/**
* Strongly consistent and durable state management service based on
* Copycat implementation of Raft consensus protocol.
@@ -58,17 +62,34 @@
private Copycat copycat;
private DatabaseClient client;
+ // guarded by synchronized block
+ private ClusterConfig<TcpMember> clusterConfig;
+
+ private CountDownLatch clusterEventLatch;
+
+ private ClusterEventListener clusterEventListener;
+
@Activate
public void activate() {
- log.info("Starting.");
- // TODO: Not every node can be part of the consensus ring.
+ // TODO: Not every node should be part of the consensus ring.
+ final ControllerNode localNode = clusterService.getLocalNode();
TcpMember localMember =
new TcpMember(
- clusterService.getLocalNode().ip().toString(),
- clusterService.getLocalNode().tcpPort());
- List<TcpMember> remoteMembers = Lists.newArrayList();
+ localNode.ip().toString(),
+ localNode.tcpPort());
+
+ clusterConfig = new TcpClusterConfig();
+ clusterConfig.setLocalMember(localMember);
+
+ List<TcpMember> remoteMembers = new ArrayList<>(clusterService.getNodes().size());
+
+ clusterEventLatch = new CountDownLatch(1);
+ clusterEventListener = new InternalClusterEventListener();
+ clusterService.addListener(clusterEventListener);
+
+ // note: from this point beyond, clusterConfig requires synchronization
for (ControllerNode node : clusterService.getNodes()) {
TcpMember member = new TcpMember(node.ip().toString(), node.tcpPort());
@@ -77,20 +98,37 @@
}
}
- // Configure the cluster.
- TcpClusterConfig config = new TcpClusterConfig();
+ if (remoteMembers.isEmpty()) {
+ log.info("This node is the only node in the cluster. "
+ + "Waiting for others to show up.");
+ // FIXME: hack trying to relax cases forming multiple consensus rings.
+ // add seed node configuration to avoid this
- config.setLocalMember(localMember);
- config.setRemoteMembers(remoteMembers.toArray(new TcpMember[]{}));
+ // If the node is alone on it's own, wait some time
+ // hoping other will come up soon
+ try {
+ if (!clusterEventLatch.await(120, TimeUnit.SECONDS)) {
+ log.info("Starting as single node cluster");
+ }
+ } catch (InterruptedException e) {
+ log.info("Interrupted waiting for others", e);
+ }
+ }
- // Create the cluster.
- TcpCluster cluster = new TcpCluster(config);
+ final TcpCluster cluster;
+ synchronized (clusterConfig) {
+ clusterConfig.addRemoteMembers(remoteMembers);
+
+ // Create the cluster.
+ cluster = new TcpCluster(clusterConfig);
+ }
+ log.info("Starting cluster: {}", cluster);
+
StateMachine stateMachine = new DatabaseStateMachine();
- ControllerNode thisNode = clusterService.getLocalNode();
// FIXME resolve Chronicle + OSGi issue
//Log consensusLog = new ChronicleLog(LOG_FILE_PREFIX + "_" + thisNode.id());
- Log consensusLog = new InMemoryLog();
+ Log consensusLog = new KryoRegisteredInMemoryLog();
copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol);
copycat.start();
@@ -102,6 +140,7 @@
@Deactivate
public void deactivate() {
+ clusterService.removeListener(clusterEventListener);
copycat.stop();
log.info("Stopped.");
}
@@ -179,6 +218,53 @@
}
+ private final class InternalClusterEventListener
+ implements ClusterEventListener {
+
+ @Override
+ public void event(ClusterEvent event) {
+ // TODO: Not every node should be part of the consensus ring.
+
+ final ControllerNode node = event.subject();
+ final TcpMember tcpMember = new TcpMember(node.ip().toString(),
+ node.tcpPort());
+
+ log.trace("{}", event);
+ switch (event.type()) {
+ case INSTANCE_ACTIVATED:
+ case INSTANCE_ADDED:
+ log.info("{} was added to the cluster", tcpMember);
+ synchronized (clusterConfig) {
+ clusterConfig.addRemoteMember(tcpMember);
+ }
+ break;
+ case INSTANCE_DEACTIVATED:
+ case INSTANCE_REMOVED:
+ log.info("{} was removed from the cluster", tcpMember);
+ synchronized (clusterConfig) {
+ clusterConfig.removeRemoteMember(tcpMember);
+ }
+ break;
+ default:
+ break;
+ }
+ if (copycat != null) {
+ log.debug("Current cluster: {}", copycat.cluster());
+ }
+ clusterEventLatch.countDown();
+ }
+
+ }
+
+ public static final class KryoRegisteredInMemoryLog extends InMemoryLog {
+ public KryoRegisteredInMemoryLog() {
+ super();
+ // required to deserialize object across bundles
+ super.kryo.register(TcpMember.class, new TcpMemberSerializer());
+ super.kryo.register(TcpClusterConfig.class, new TcpClusterConfigSerializer());
+ }
+ }
+
private class DatabaseOperationResult<R, E extends DatabaseException> implements OptionalResult<R, E> {
private final R result;
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 ad6773e..2822f25 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
@@ -1,5 +1,7 @@
package org.onlab.onos.store.service.impl;
+import static org.slf4j.LoggerFactory.getLogger;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -16,6 +18,7 @@
import org.onlab.onos.store.service.WriteRequest;
import org.onlab.onos.store.service.WriteResult;
import org.onlab.util.KryoNamespace;
+import org.slf4j.Logger;
import com.google.common.collect.Maps;
@@ -28,6 +31,8 @@
*/
public class DatabaseStateMachine implements StateMachine {
+ private final Logger log = getLogger(getClass());
+
public static final KryoSerializer SERIALIZER = new KryoSerializer() {
@Override
protected void setupKryoPool() {
@@ -161,7 +166,7 @@
try {
return SERIALIZER.encode(state);
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Snapshot serialization error", e);
return null;
}
}
@@ -171,7 +176,7 @@
try {
this.state = SERIALIZER.decode(data);
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Snapshot deserialization error", e);
}
}
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpClusterConfigSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpClusterConfigSerializer.java
new file mode 100644
index 0000000..48887b9
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpClusterConfigSerializer.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.store.service.impl;
+
+import java.util.Collection;
+
+import net.kuujo.copycat.cluster.TcpClusterConfig;
+import net.kuujo.copycat.cluster.TcpMember;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public class TcpClusterConfigSerializer extends Serializer<TcpClusterConfig> {
+
+ @Override
+ public void write(Kryo kryo, Output output, TcpClusterConfig object) {
+ kryo.writeClassAndObject(output, object.getLocalMember());
+ kryo.writeClassAndObject(output, object.getRemoteMembers());
+ }
+
+ @Override
+ public TcpClusterConfig read(Kryo kryo, Input input,
+ Class<TcpClusterConfig> type) {
+ TcpMember localMember = (TcpMember) kryo.readClassAndObject(input);
+ @SuppressWarnings("unchecked")
+ Collection<TcpMember> remoteMembers = (Collection<TcpMember>) kryo.readClassAndObject(input);
+ return new TcpClusterConfig(localMember, remoteMembers);
+ }
+
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpMemberSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpMemberSerializer.java
new file mode 100644
index 0000000..e729f9b
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpMemberSerializer.java
@@ -0,0 +1,24 @@
+package org.onlab.onos.store.service.impl;
+
+import net.kuujo.copycat.cluster.TcpMember;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public class TcpMemberSerializer extends Serializer<TcpMember> {
+
+ @Override
+ public void write(Kryo kryo, Output output, TcpMember object) {
+ output.writeString(object.host());
+ output.writeInt(object.port());
+ }
+
+ @Override
+ public TcpMember read(Kryo kryo, Input input, Class<TcpMember> type) {
+ String host = input.readString();
+ int port = input.readInt();
+ return new TcpMember(host, port);
+ }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip4AddressSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip4AddressSerializer.java
new file mode 100644
index 0000000..1fee80f
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip4AddressSerializer.java
@@ -0,0 +1,53 @@
+/*
+ * 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.serializers;
+
+import org.onlab.packet.Ip4Address;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link Ip4Address}.
+ */
+public class Ip4AddressSerializer extends Serializer<Ip4Address> {
+
+ /**
+ * Creates {@link Ip4Address} serializer instance.
+ */
+ public Ip4AddressSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, Ip4Address object) {
+ byte[] octs = object.toOctets();
+ // TODO: Writing (and reading) the number of octets is redundant:
+ // It is always Ip4Address.BYTE_LENGTH
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ }
+
+ @Override
+ public Ip4Address read(Kryo kryo, Input input, Class<Ip4Address> type) {
+ final int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ return Ip4Address.valueOf(octs);
+ }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip4PrefixSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip4PrefixSerializer.java
new file mode 100644
index 0000000..577d20b
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip4PrefixSerializer.java
@@ -0,0 +1,58 @@
+/*
+ * 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.serializers;
+
+import org.onlab.packet.Ip4Prefix;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link Ip4Prefix}.
+ */
+public final class Ip4PrefixSerializer extends Serializer<Ip4Prefix> {
+
+ /**
+ * Creates {@link Ip4Prefix} serializer instance.
+ */
+ public Ip4PrefixSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output,
+ Ip4Prefix object) {
+ byte[] octs = object.address().toOctets();
+ // TODO: Writing (and reading) the number of octets is redundant:
+ // It is always Ip6Address.BYTE_LENGTH
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ output.writeInt(object.prefixLength());
+ }
+
+ @Override
+ public Ip4Prefix read(Kryo kryo, Input input,
+ Class<Ip4Prefix> type) {
+ int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ int prefLen = input.readInt();
+ return Ip4Prefix.valueOf(octs, prefLen);
+ }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip6AddressSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip6AddressSerializer.java
new file mode 100644
index 0000000..83c6a56
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip6AddressSerializer.java
@@ -0,0 +1,53 @@
+/*
+ * 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.serializers;
+
+import org.onlab.packet.Ip6Address;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link Ip6Address}.
+ */
+public class Ip6AddressSerializer extends Serializer<Ip6Address> {
+
+ /**
+ * Creates {@link Ip6Address} serializer instance.
+ */
+ public Ip6AddressSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, Ip6Address object) {
+ byte[] octs = object.toOctets();
+ // TODO: Writing (and reading) the number of octets is redundant:
+ // It is always Ip6Address.BYTE_LENGTH
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ }
+
+ @Override
+ public Ip6Address read(Kryo kryo, Input input, Class<Ip6Address> type) {
+ final int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ return Ip6Address.valueOf(octs);
+ }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip6PrefixSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip6PrefixSerializer.java
new file mode 100644
index 0000000..8ecd13a
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/Ip6PrefixSerializer.java
@@ -0,0 +1,58 @@
+/*
+ * 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.serializers;
+
+import org.onlab.packet.Ip6Prefix;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link Ip6Prefix}.
+ */
+public final class Ip6PrefixSerializer extends Serializer<Ip6Prefix> {
+
+ /**
+ * Creates {@link Ip6Prefix} serializer instance.
+ */
+ public Ip6PrefixSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output,
+ Ip6Prefix object) {
+ byte[] octs = object.address().toOctets();
+ // TODO: Writing (and reading) the number of octets is redundant:
+ // It is always Ip6Address.BYTE_LENGTH
+ output.writeInt(octs.length);
+ output.writeBytes(octs);
+ output.writeInt(object.prefixLength());
+ }
+
+ @Override
+ public Ip6Prefix read(Kryo kryo, Input input,
+ Class<Ip6Prefix> type) {
+ int octLen = input.readInt();
+ byte[] octs = new byte[octLen];
+ input.readBytes(octs);
+ int prefLen = input.readInt();
+ return Ip6Prefix.valueOf(octs, prefLen);
+ }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
index 80db22e..4cba9f0 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
@@ -88,7 +88,11 @@
import org.onlab.onos.store.Timestamp;
import org.onlab.packet.ChassisId;
import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Prefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.KryoNamespace;
@@ -99,12 +103,30 @@
public final class KryoNamespaces {
+ public static final KryoNamespace BASIC = KryoNamespace.newBuilder()
+ .register(ImmutableMap.class, new ImmutableMapSerializer())
+ .register(ImmutableList.class, new ImmutableListSerializer())
+ .register(ImmutableSet.class, new ImmutableSetSerializer())
+ .register(
+ ArrayList.class,
+ Arrays.asList().getClass(),
+ HashMap.class,
+ HashSet.class,
+ LinkedList.class,
+ byte[].class
+ )
+ .build();
+
/**
* KryoNamespace which can serialize ON.lab misc classes.
*/
public static final KryoNamespace MISC = KryoNamespace.newBuilder()
.register(IpPrefix.class, new IpPrefixSerializer())
+ .register(Ip4Prefix.class, new Ip4PrefixSerializer())
+ .register(Ip6Prefix.class, new Ip6PrefixSerializer())
.register(IpAddress.class, new IpAddressSerializer())
+ .register(Ip4Address.class, new Ip4AddressSerializer())
+ .register(Ip6Address.class, new Ip6AddressSerializer())
.register(MacAddress.class, new MacAddressSerializer())
.register(VlanId.class)
.build();
@@ -115,19 +137,8 @@
*/
public static final KryoNamespace API = KryoNamespace.newBuilder()
.register(MISC)
- .register(ImmutableMap.class, new ImmutableMapSerializer())
- .register(ImmutableList.class, new ImmutableListSerializer())
- .register(ImmutableSet.class, new ImmutableSetSerializer())
+ .register(BASIC)
.register(
- //
- ArrayList.class,
- Arrays.asList().getClass(),
- HashMap.class,
- HashSet.class,
- LinkedList.class,
- byte[].class,
- //
- //
ControllerNode.State.class,
Device.Type.class,
Port.Type.class,
diff --git a/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java b/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java
index 654ab11..82588b7 100644
--- a/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java
+++ b/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java
@@ -43,7 +43,11 @@
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Prefix;
import org.onlab.packet.MacAddress;
import org.onlab.util.KryoNamespace;
@@ -168,11 +172,31 @@
}
@Test
+ public void testIp4Prefix() {
+ testSerialized(Ip4Prefix.valueOf("192.168.0.1/24"));
+ }
+
+ @Test
+ public void testIp6Prefix() {
+ testSerialized(Ip6Prefix.valueOf("1111:2222::/120"));
+ }
+
+ @Test
public void testIpAddress() {
testSerialized(IpAddress.valueOf("192.168.0.1"));
}
@Test
+ public void testIp4Address() {
+ testSerialized(Ip4Address.valueOf("192.168.0.1"));
+ }
+
+ @Test
+ public void testIp6Address() {
+ testSerialized(Ip6Address.valueOf("1111:2222::"));
+ }
+
+ @Test
public void testMacAddress() {
testSerialized(MacAddress.valueOf("12:34:56:78:90:ab"));
}
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
index e5131df..16a32db 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
@@ -31,8 +31,8 @@
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.openflow.controller.Dpid;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
@@ -168,12 +168,12 @@
case SET_NW_DST:
OFActionSetNwDst nwdst = (OFActionSetNwDst) act;
IPv4Address di = nwdst.getNwAddr();
- builder.setIpDst(IpAddress.valueOf(di.getInt()));
+ builder.setIpDst(Ip4Address.valueOf(di.getInt()));
break;
case SET_NW_SRC:
OFActionSetNwSrc nwsrc = (OFActionSetNwSrc) act;
IPv4Address si = nwsrc.getNwAddr();
- builder.setIpSrc(IpAddress.valueOf(si.getInt()));
+ builder.setIpSrc(Ip4Address.valueOf(si.getInt()));
break;
case EXPERIMENTER:
OFActionExperimenter exp = (OFActionExperimenter) act;
@@ -237,33 +237,33 @@
builder.matchEthType((short) ethType);
break;
case IPV4_DST:
- IpPrefix dip;
+ Ip4Prefix dip;
if (match.isPartiallyMasked(MatchField.IPV4_DST)) {
Masked<IPv4Address> maskedIp = match.getMasked(MatchField.IPV4_DST);
- dip = IpPrefix.valueOf(
+ dip = Ip4Prefix.valueOf(
maskedIp.getValue().getInt(),
maskedIp.getMask().asCidrMaskLength());
} else {
- dip = IpPrefix.valueOf(
+ dip = Ip4Prefix.valueOf(
match.get(MatchField.IPV4_DST).getInt(),
- IpPrefix.MAX_INET_MASK_LENGTH);
+ Ip4Prefix.MAX_MASK_LENGTH);
}
builder.matchIPDst(dip);
break;
case IPV4_SRC:
- IpPrefix sip;
+ Ip4Prefix sip;
if (match.isPartiallyMasked(MatchField.IPV4_SRC)) {
Masked<IPv4Address> maskedIp = match.getMasked(MatchField.IPV4_SRC);
- sip = IpPrefix.valueOf(
+ sip = Ip4Prefix.valueOf(
maskedIp.getValue().getInt(),
maskedIp.getMask().asCidrMaskLength());
} else {
- sip = IpPrefix.valueOf(
+ sip = Ip4Prefix.valueOf(
match.get(MatchField.IPV4_SRC).getInt(),
- IpPrefix.MAX_INET_MASK_LENGTH);
+ Ip4Prefix.MAX_MASK_LENGTH);
}
builder.matchIPSrc(sip);
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 427a23f..375fe6b 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -407,7 +407,8 @@
height: this.height,
uid: this.uid,
setRadio: this.setRadio,
- setKeys: this.setKeys
+ setKeys: this.setKeys,
+ dataLoadError: this.dataLoadError
}
},
@@ -498,6 +499,16 @@
uid: function (id) {
return makeUid(this, id);
+ },
+
+ // TODO : implement custom dialogs (don't use alerts)
+
+ dataLoadError: function (err, url) {
+ var msg = 'Data Load Error\n\n' +
+ err.status + ' -- ' + err.statusText + '\n\n' +
+ 'relative-url: "' + url + '"\n\n' +
+ 'complete-url: "' + err.responseURL + '"';
+ alert(msg);
}
// TODO: consider schedule, clearTimer, etc.
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index 88fcd94..eee9244 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -24,3 +24,6 @@
opacity: 0.5;
}
+svg .node {
+ fill: #03c;
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index 7ac9adc..f5e1792 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -56,12 +56,24 @@
opt: 'img/opt.png'
},
force: {
- marginLR: 20,
- marginTB: 20,
+ note: 'node.class or link.class is used to differentiate',
+ linkDistance: {
+ infra: 200,
+ host: 40
+ },
+ linkStrength: {
+ infra: 1.0,
+ host: 1.0
+ },
+ charge: {
+ device: -400,
+ host: -100
+ },
+ pad: 20,
translate: function() {
return 'translate(' +
- config.force.marginLR + ',' +
- config.force.marginTB + ')';
+ config.force.pad + ',' +
+ config.force.pad + ')';
}
}
};
@@ -94,7 +106,11 @@
// D3 selections
var svg,
bgImg,
- topoG;
+ topoG,
+ nodeG,
+ linkG,
+ node,
+ link;
// ==============================
// For Debugging / Development
@@ -175,22 +191,145 @@
// ==============================
// Private functions
- // set the size of the given element to that of the view
- function setSize(el, view) {
+ // set the size of the given element to that of the view (reduced if padded)
+ function setSize(el, view, pad) {
+ var padding = pad ? pad * 2 : 0;
el.attr({
- width: view.width(),
- height: view.height()
+ width: view.width() - padding,
+ height: view.height() - padding
});
}
-
function getNetworkData(view) {
var url = getTopoUrl();
- // TODO ...
-
+ console.log('Fetching JSON: ' + url);
+ d3.json(url, function(err, data) {
+ if (err) {
+ view.dataLoadError(err, url);
+ } else {
+ network.data = data;
+ drawNetwork(view);
+ }
+ });
}
+ function drawNetwork(view) {
+ preprocessData(view);
+ updateLayout(view);
+ }
+
+ function preprocessData(view) {
+ var w = view.width(),
+ h = view.height(),
+ hDevice = h * 0.6,
+ hHost = h * 0.3,
+ data = network.data,
+ deviceLayout = computeInitLayout(w, hDevice, data.devices.length),
+ hostLayout = computeInitLayout(w, hHost, data.hosts.length);
+
+ network.lookup = {};
+ network.nodes = [];
+ network.links = [];
+ // we created new arrays, so need to set the refs in the force layout
+ network.force.nodes(network.nodes);
+ network.force.links(network.links);
+
+ // let's just start with the nodes
+
+ // note that both 'devices' and 'hosts' get mapped into the nodes array
+ function makeNode(d, cls, layout) {
+ var node = {
+ id: d.id,
+ labels: d.labels,
+ class: cls,
+ icon: cls,
+ type: d.type,
+ x: layout.x(),
+ y: layout.y()
+ };
+ network.lookup[d.id] = node;
+ network.nodes.push(node);
+ }
+
+ // first the devices...
+ network.data.devices.forEach(function (d) {
+ makeNode(d, 'device', deviceLayout);
+ });
+
+ // then the hosts...
+ network.data.hosts.forEach(function (d) {
+ makeNode(d, 'host', hostLayout);
+ });
+
+ // TODO: process links
+ }
+
+ function computeInitLayout(w, h, n) {
+ var maxdw = 60,
+ compdw, dw, ox, layout;
+
+ if (n < 2) {
+ layout = { ox: w/2, dw: 0 }
+ } else {
+ compdw = (0.8 * w) / (n - 1);
+ dw = Math.min(maxdw, compdw);
+ ox = w/2 - ((n - 1)/2 * dw);
+ layout = { ox: ox, dw: dw }
+ }
+
+ layout.i = 0;
+
+ layout.x = function () {
+ var x = layout.ox + layout.i*layout.dw;
+ layout.i++;
+ return x;
+ };
+
+ layout.y = function () {
+ return h;
+ };
+
+ return layout;
+ }
+
+ function linkId(d) {
+ return d.source.id + '~' + d.target.id;
+ }
+
+ function nodeId(d) {
+ return d.id;
+ }
+
+ function updateLayout(view) {
+ link = link.data(network.force.links(), linkId);
+ link.enter().append('line')
+ .attr('class', 'link');
+ link.exit().remove();
+
+ node = node.data(network.force.nodes(), nodeId);
+ node.enter().append('circle')
+ .attr('id', function (d) { return 'nodeId-' + d.id; })
+ .attr('class', function (d) { return 'node'; })
+ .attr('r', 12);
+
+ network.force.start();
+ }
+
+
+ function tick() {
+ node.attr({
+ cx: function(d) { return d.x; },
+ cy: function(d) { return d.y; }
+ });
+
+ link.attr({
+ x1: function (d) { return d.source.x; },
+ y1: function (d) { return d.source.y; },
+ x2: function (d) { return d.target.x; },
+ y2: function (d) { return d.target.y; }
+ });
+ }
// ==============================
// View life-cycle callbacks
@@ -199,15 +338,15 @@
var w = view.width(),
h = view.height(),
idBg = view.uid('bg'),
- showBg = config.options.showBackground ? 'visible' : 'hidden';
+ showBg = config.options.showBackground ? 'visible' : 'hidden',
+ fcfg = config.force,
+ fpad = fcfg.pad,
+ forceDim = [w - 2*fpad, h - 2*fpad];
// NOTE: view.$div is a D3 selection of the view's div
svg = view.$div.append('svg');
setSize(svg, view);
- topoG = svg.append('g')
- .attr('transform', config.force.translate());
-
// load the background image
bgImg = svg.append('svg:image')
.attr({
@@ -219,6 +358,28 @@
.style({
visibility: showBg
});
+
+ // group for the topology
+ topoG = svg.append('g')
+ .attr('transform', fcfg.translate());
+
+ // subgroups for links and nodes
+ linkG = topoG.append('g').attr('id', 'links');
+ nodeG = topoG.append('g').attr('id', 'nodes');
+
+ // selection of nodes and links
+ link = linkG.selectAll('.link');
+ node = nodeG.selectAll('.node');
+
+ // set up the force layout
+ network.force = d3.layout.force()
+ .size(forceDim)
+ .nodes(network.nodes)
+ .links(network.links)
+ .charge(function (d) { return fcfg.charge[d.class]; })
+ .linkDistance(function (d) { return fcfg.linkDistance[d.class]; })
+ .linkStrength(function (d) { return fcfg.linkStrength[d.class]; })
+ .on('tick', tick);
}