Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
index f9d6951..604d12d 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
@@ -53,11 +53,13 @@
public Interface getInterface(ConnectPoint connectPoint) {
checkNotNull(connectPoint);
- PortAddresses portAddresses =
+ Set<PortAddresses> portAddresses =
hostService.getAddressBindingsForPort(connectPoint);
- if (!portAddresses.ipAddresses().isEmpty()) {
- return new Interface(portAddresses);
+ for (PortAddresses addresses : portAddresses) {
+ if (addresses.connectPoint().equals(connectPoint)) {
+ return new Interface(addresses);
+ }
}
return null;
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java
index 2a65616..9e31389 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -63,10 +64,6 @@
private static final ConnectPoint NON_EXISTENT_CP = new ConnectPoint(
DeviceId.deviceId("doesnotexist"), PortNumber.portNumber(1));
- private static final PortAddresses DEFAULT_PA = new PortAddresses(
- NON_EXISTENT_CP, null, null);
-
-
@Before
public void setUp() throws Exception {
hostService = createMock(HostService.class);
@@ -123,7 +120,8 @@
MacAddress mac) {
PortAddresses pa = new PortAddresses(cp, ipAddresses, mac);
portAddresses.add(pa);
- expect(hostService.getAddressBindingsForPort(cp)).andReturn(pa).anyTimes();
+ expect(hostService.getAddressBindingsForPort(cp)).andReturn(
+ Collections.singleton(pa)).anyTimes();
Interface intf = new Interface(cp, ipAddresses, mac);
interfaces.put(cp, intf);
@@ -158,7 +156,7 @@
// Try and get an interface for a connect point with no addresses
reset(hostService);
expect(hostService.getAddressBindingsForPort(NON_EXISTENT_CP))
- .andReturn(DEFAULT_PA).anyTimes();
+ .andReturn(Collections.<PortAddresses>emptySet()).anyTimes();
replay(hostService);
assertNull(adaptor.getInterface(NON_EXISTENT_CP));
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/HostAdminService.java b/core/api/src/main/java/org/onlab/onos/net/host/HostAdminService.java
index f421fd8..c51a847 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/HostAdminService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/HostAdminService.java
@@ -34,11 +34,7 @@
* Binds IP and MAC addresses to the given connection point.
* <p>
* The addresses are added to the set of addresses already bound to the
- * connection point. If any of the fields in addresses is null, no change
- * is made to the corresponding addresses in the store.
- * {@link #unbindAddressesFromPort(PortAddresses)} must be use to unbind
- * addresses that have previously been bound.
- * </p>
+ * connection point.
*
* @param addresses address object containing addresses to add and the port
* to add them to
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/HostService.java b/core/api/src/main/java/org/onlab/onos/net/host/HostService.java
index aa31459..7f7be50 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/HostService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/HostService.java
@@ -135,7 +135,7 @@
* @param connectPoint the connection point to retrieve address bindings for
* @return addresses bound to the port
*/
- PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint);
+ Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint);
/**
* Adds the specified host listener.
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/HostStore.java b/core/api/src/main/java/org/onlab/onos/net/host/HostStore.java
index a6bf96d..0316dcf 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/HostStore.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/HostStore.java
@@ -15,6 +15,8 @@
*/
package org.onlab.onos.net.host;
+import java.util.Set;
+
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Host;
@@ -25,8 +27,6 @@
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
-import java.util.Set;
-
/**
* Manages inventory of end-station hosts; not intended for direct use.
*/
@@ -153,5 +153,5 @@
* for
* @return address information for the connection point
*/
- PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint);
+ Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint);
}
diff --git a/core/api/src/test/java/org/onlab/onos/net/host/HostServiceAdapter.java b/core/api/src/test/java/org/onlab/onos/net/host/HostServiceAdapter.java
index a0a0e36..03a8a43 100644
--- a/core/api/src/test/java/org/onlab/onos/net/host/HostServiceAdapter.java
+++ b/core/api/src/test/java/org/onlab/onos/net/host/HostServiceAdapter.java
@@ -95,7 +95,7 @@
}
@Override
- public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
+ public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
return null;
}
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
index 762bae0..5b5e5b7 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
@@ -207,7 +207,7 @@
}
@Override
- public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
+ public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
return store.getAddressBindingsForPort(connectPoint);
}
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
index a6af018..d7299e8 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
@@ -175,13 +175,15 @@
for (Device device : deviceService.getDevices()) {
for (Port port : deviceService.getPorts(device.id())) {
ConnectPoint cp = new ConnectPoint(device.id(), port.number());
- PortAddresses portAddresses =
+ Set<PortAddresses> portAddressSet =
hostManager.getAddressBindingsForPort(cp);
- for (InterfaceIpAddress ia : portAddresses.ipAddresses()) {
- if (ia.subnetAddress().contains(targetIp)) {
- sendProbe(device.id(), port, targetIp,
- ia.ipAddress(), portAddresses.mac());
+ for (PortAddresses portAddresses : portAddressSet) {
+ for (InterfaceIpAddress ia : portAddresses.ipAddresses()) {
+ if (ia.subnetAddress().contains(targetIp)) {
+ sendProbe(device.id(), port, targetIp,
+ ia.ipAddress(), portAddresses.mac());
+ }
}
}
}
diff --git a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
index 0aae62d..49528d0 100644
--- a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
@@ -134,14 +134,16 @@
IpAddress target =
IpAddress.valueOf(IpAddress.Version.INET,
arp.getTargetProtocolAddress());
- PortAddresses addresses =
+ Set<PortAddresses> addressSet =
hostService.getAddressBindingsForPort(inPort);
- for (InterfaceIpAddress ia : addresses.ipAddresses()) {
- if (ia.ipAddress().equals(target)) {
- Ethernet arpReply =
- buildArpReply(ia.ipAddress(), addresses.mac(), eth);
- sendTo(arpReply, inPort);
+ for (PortAddresses addresses : addressSet) {
+ for (InterfaceIpAddress ia : addresses.ipAddresses()) {
+ if (ia.ipAddress().equals(target)) {
+ Ethernet arpReply =
+ buildArpReply(ia.ipAddress(), addresses.mac(), eth);
+ sendTo(arpReply, inPort);
+ }
}
}
return;
@@ -244,7 +246,7 @@
// TODO: Is this sufficient to identify outside-facing ports: just
// having IP addresses on a port?
//
- return !hostService.getAddressBindingsForPort(port).ipAddresses().isEmpty();
+ return !hostService.getAddressBindingsForPort(port).isEmpty();
}
@Override
diff --git a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
index cbc9cf1..6a058ab 100644
--- a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
@@ -234,10 +234,10 @@
new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1);
mgr.bindAddressesToPort(add1);
- PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(add1.ipAddresses().equals(storedAddresses.ipAddresses()));
- assertTrue(add1.mac().equals(storedAddresses.mac()));
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
// Add some more addresses and check that they're added correctly
PortAddresses add2 =
@@ -246,18 +246,19 @@
mgr.bindAddressesToPort(add2);
storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(storedAddresses.ipAddresses().equals(
- Sets.newHashSet(IA1, IA2, IA3)));
- assertTrue(storedAddresses.mac().equals(MAC1));
+ assertEquals(2, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+ assertTrue(storedAddresses.contains(add2));
PortAddresses add3 = new PortAddresses(CP1, null, MAC2);
mgr.bindAddressesToPort(add3);
storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(storedAddresses.ipAddresses().equals(
- Sets.newHashSet(IA1, IA2, IA3)));
- assertTrue(storedAddresses.mac().equals(MAC2));
+ assertEquals(3, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
+ assertTrue(storedAddresses.contains(add2));
+ assertTrue(storedAddresses.contains(add3));
}
@Test
@@ -266,10 +267,10 @@
new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1);
mgr.bindAddressesToPort(add1);
- PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(storedAddresses.ipAddresses().size() == 2);
- assertNotNull(storedAddresses.mac());
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
PortAddresses rem1 =
new PortAddresses(CP1, Sets.newHashSet(IA1), null);
@@ -277,25 +278,15 @@
mgr.unbindAddressesFromPort(rem1);
storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(storedAddresses.ipAddresses().equals(Sets.newHashSet(IA2)));
- assertTrue(storedAddresses.mac().equals(MAC1));
+ // It shouldn't have been removed because it didn't match the originally
+ // submitted address object
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
- PortAddresses rem2 = new PortAddresses(CP1, null, MAC1);
-
- mgr.unbindAddressesFromPort(rem2);
+ mgr.unbindAddressesFromPort(add1);
storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(storedAddresses.ipAddresses().equals(Sets.newHashSet(IA2)));
- assertNull(storedAddresses.mac());
-
- PortAddresses rem3 =
- new PortAddresses(CP1, Sets.newHashSet(IA2), MAC1);
-
- mgr.unbindAddressesFromPort(rem3);
- storedAddresses = mgr.getAddressBindingsForPort(CP1);
-
- assertTrue(storedAddresses.ipAddresses().isEmpty());
- assertNull(storedAddresses.mac());
+ assertTrue(storedAddresses.isEmpty());
}
@Test
@@ -304,16 +295,15 @@
new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1);
mgr.bindAddressesToPort(add1);
- PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(storedAddresses.ipAddresses().size() == 2);
- assertNotNull(storedAddresses.mac());
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
mgr.clearAddresses(CP1);
storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(storedAddresses.ipAddresses().isEmpty());
- assertNull(storedAddresses.mac());
+ assertTrue(storedAddresses.isEmpty());
}
@Test
@@ -322,12 +312,10 @@
new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1);
mgr.bindAddressesToPort(add1);
- PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+ Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
- assertTrue(storedAddresses.connectPoint().equals(CP1));
- assertTrue(storedAddresses.ipAddresses().equals(
- Sets.newHashSet(IA1, IA2)));
- assertTrue(storedAddresses.mac().equals(MAC1));
+ assertEquals(1, storedAddresses.size());
+ assertTrue(storedAddresses.contains(add1));
}
@Test
diff --git a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
index 654cab1..a8febd7 100644
--- a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
@@ -20,7 +20,9 @@
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
@@ -130,7 +132,7 @@
expect(hostManager.getHostsByIp(TARGET_IP_ADDR))
.andReturn(Collections.<Host>emptySet()).anyTimes();
expect(hostManager.getAddressBindingsForPort(cp))
- .andReturn(pa).anyTimes();
+ .andReturn(Collections.singleton(pa)).anyTimes();
replay(hostManager);
TestPacketService packetService = new TestPacketService();
diff --git a/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
index e82151e..8beac4a 100644
--- a/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
@@ -19,7 +19,10 @@
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
@@ -207,13 +210,18 @@
IpAddress addr2 = IpAddress.valueOf("10.0." + (2 * i) + ".1");
InterfaceIpAddress ia1 = new InterfaceIpAddress(addr1, prefix1);
InterfaceIpAddress ia2 = new InterfaceIpAddress(addr2, prefix2);
- PortAddresses pa =
- new PortAddresses(cp, Sets.newHashSet(ia1, ia2),
- MacAddress.valueOf(i));
- addresses.add(pa);
+ PortAddresses pa1 =
+ new PortAddresses(cp, Sets.newHashSet(ia1),
+ MacAddress.valueOf(2 * i - 1));
+ PortAddresses pa2 =
+ new PortAddresses(cp, Sets.newHashSet(ia2),
+ MacAddress.valueOf(2 * i));
+
+ addresses.add(pa1);
+ addresses.add(pa2);
expect(hostService.getAddressBindingsForPort(cp))
- .andReturn(pa).anyTimes();
+ .andReturn(Sets.newHashSet(pa1, pa2)).anyTimes();
}
expect(hostService.getAddressBindings()).andReturn(addresses).anyTimes();
@@ -222,7 +230,7 @@
ConnectPoint cp = new ConnectPoint(getDeviceId(i + NUM_ADDRESS_PORTS),
P1);
expect(hostService.getAddressBindingsForPort(cp))
- .andReturn(new PortAddresses(cp, null, null)).anyTimes();
+ .andReturn(Collections.<PortAddresses>emptySet()).anyTimes();
}
}
@@ -339,7 +347,8 @@
IpAddress theirIp = IpAddress.valueOf("10.0.1.254");
IpAddress ourFirstIp = IpAddress.valueOf("10.0.1.1");
IpAddress ourSecondIp = IpAddress.valueOf("10.0.2.1");
- MacAddress ourMac = MacAddress.valueOf(1L);
+ MacAddress firstMac = MacAddress.valueOf(1L);
+ MacAddress secondMac = MacAddress.valueOf(2L);
Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1,
Collections.singleton(theirIp));
@@ -352,7 +361,7 @@
proxyArp.reply(arpRequest, LOC1);
assertEquals(1, packetService.packets.size());
- Ethernet arpReply = buildArp(ARP.OP_REPLY, ourMac, MAC2, ourFirstIp, theirIp);
+ Ethernet arpReply = buildArp(ARP.OP_REPLY, firstMac, MAC2, ourFirstIp, theirIp);
verifyPacketOut(arpReply, LOC1, packetService.packets.get(0));
// Test a request for the second address on that port
@@ -362,7 +371,7 @@
proxyArp.reply(arpRequest, LOC1);
assertEquals(1, packetService.packets.size());
- arpReply = buildArp(ARP.OP_REPLY, ourMac, MAC2, ourSecondIp, theirIp);
+ arpReply = buildArp(ARP.OP_REPLY, secondMac, MAC2, ourSecondIp, theirIp);
verifyPacketOut(arpReply, LOC1, packetService.packets.get(0));
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
index 8029e27..30a73e0 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
@@ -15,12 +15,25 @@
*/
package org.onlab.onos.store.host.impl;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_MOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_UPDATED;
+import static org.onlab.util.Tools.namedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.RandomUtils;
import org.apache.felix.scr.annotations.Activate;
@@ -45,7 +58,6 @@
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostStore;
import org.onlab.onos.net.host.HostStoreDelegate;
-import org.onlab.onos.net.host.InterfaceIpAddress;
import org.onlab.onos.net.host.PortAddresses;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.AbstractStore;
@@ -63,21 +75,13 @@
import org.onlab.util.KryoNamespace;
import org.slf4j.Logger;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
-import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
-import static org.onlab.onos.net.host.HostEvent.Type.*;
-import static org.onlab.util.Tools.namedThreads;
-import static org.slf4j.LoggerFactory.getLogger;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
//TODO: multi-provider, annotation not supported.
/**
@@ -100,8 +104,9 @@
// Hosts tracked by their location
private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
- private final Map<ConnectPoint, PortAddresses> portAddresses =
- new ConcurrentHashMap<>();
+ private final SetMultimap<ConnectPoint, PortAddresses> portAddresses =
+ Multimaps.synchronizedSetMultimap(
+ HashMultimap.<ConnectPoint, PortAddresses>create());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostClockService hostClockService;
@@ -343,77 +348,37 @@
@Override
public void updateAddressBindings(PortAddresses addresses) {
- synchronized (portAddresses) {
- PortAddresses existing = portAddresses.get(addresses.connectPoint());
- if (existing == null) {
- portAddresses.put(addresses.connectPoint(), addresses);
- } else {
- Set<InterfaceIpAddress> union =
- Sets.union(existing.ipAddresses(),
- addresses.ipAddresses()).immutableCopy();
-
- MacAddress newMac = (addresses.mac() == null) ? existing.mac()
- : addresses.mac();
-
- PortAddresses newAddresses =
- new PortAddresses(addresses.connectPoint(), union, newMac);
-
- portAddresses.put(newAddresses.connectPoint(), newAddresses);
- }
- }
+ portAddresses.put(addresses.connectPoint(), addresses);
}
@Override
public void removeAddressBindings(PortAddresses addresses) {
- synchronized (portAddresses) {
- PortAddresses existing = portAddresses.get(addresses.connectPoint());
- if (existing != null) {
- Set<InterfaceIpAddress> difference =
- Sets.difference(existing.ipAddresses(),
- addresses.ipAddresses()).immutableCopy();
-
- // If they removed the existing mac, set the new mac to null.
- // Otherwise, keep the existing mac.
- MacAddress newMac = existing.mac();
- if (addresses.mac() != null && addresses.mac().equals(existing.mac())) {
- newMac = null;
- }
-
- PortAddresses newAddresses =
- new PortAddresses(addresses.connectPoint(), difference, newMac);
-
- portAddresses.put(newAddresses.connectPoint(), newAddresses);
- }
- }
+ portAddresses.remove(addresses.connectPoint(), addresses);
}
@Override
public void clearAddressBindings(ConnectPoint connectPoint) {
- synchronized (portAddresses) {
- portAddresses.remove(connectPoint);
- }
+ portAddresses.removeAll(connectPoint);
}
@Override
public Set<PortAddresses> getAddressBindings() {
synchronized (portAddresses) {
- return new HashSet<>(portAddresses.values());
+ return ImmutableSet.copyOf(portAddresses.values());
}
}
@Override
- public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
- PortAddresses addresses;
-
+ public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
synchronized (portAddresses) {
- addresses = portAddresses.get(connectPoint);
- }
+ Set<PortAddresses> addresses = portAddresses.get(connectPoint);
- if (addresses == null) {
- addresses = new PortAddresses(connectPoint, null, null);
+ if (addresses == null) {
+ return Collections.emptySet();
+ } else {
+ return ImmutableSet.copyOf(addresses);
+ }
}
-
- return addresses;
}
// Auxiliary extension to allow location to mutate.
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
index 713be4e..42e0799 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
@@ -447,13 +447,16 @@
RoleValue oldValue = event.getOldValue();
RoleValue newValue = event.getValue();
+ // There will be no oldValue at the very first instance of an EntryEvent.
+ // Technically, the progression is: null event -> null master -> some master;
+ // We say a null master and a null oldValue are the same condition.
NodeId oldMaster = null;
if (oldValue != null) {
oldMaster = oldValue.get(MASTER);
}
NodeId newMaster = newValue.get(MASTER);
- if (Objects.equal(oldMaster, newMaster)) {
+ if (!Objects.equal(oldMaster, newMaster)) {
notifyDelegate(new MastershipEvent(
MASTER_CHANGED, event.getKey(), event.getValue().roleInfo()));
} else {
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
index 6c27ea8..2f8fbd2 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
@@ -15,10 +15,18 @@
*/
package org.onlab.onos.store.trivial.impl;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_MOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_UPDATED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -34,7 +42,6 @@
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostStore;
import org.onlab.onos.net.host.HostStoreDelegate;
-import org.onlab.onos.net.host.InterfaceIpAddress;
import org.onlab.onos.net.host.PortAddresses;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.AbstractStore;
@@ -43,13 +50,11 @@
import org.onlab.packet.VlanId;
import org.slf4j.Logger;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static org.onlab.onos.net.host.HostEvent.Type.*;
-import static org.slf4j.LoggerFactory.getLogger;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
// TODO: multi-provider, annotation not supported.
/**
@@ -70,8 +75,9 @@
// Hosts tracked by their location
private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
- private final Map<ConnectPoint, PortAddresses> portAddresses =
- new ConcurrentHashMap<>();
+ private final SetMultimap<ConnectPoint, PortAddresses> portAddresses =
+ Multimaps.synchronizedSetMultimap(
+ HashMultimap.<ConnectPoint, PortAddresses>create());
@Activate
public void activate() {
@@ -213,77 +219,37 @@
@Override
public void updateAddressBindings(PortAddresses addresses) {
- synchronized (portAddresses) {
- PortAddresses existing = portAddresses.get(addresses.connectPoint());
- if (existing == null) {
- portAddresses.put(addresses.connectPoint(), addresses);
- } else {
- Set<InterfaceIpAddress> union =
- Sets.union(existing.ipAddresses(),
- addresses.ipAddresses()).immutableCopy();
-
- MacAddress newMac = (addresses.mac() == null) ? existing.mac()
- : addresses.mac();
-
- PortAddresses newAddresses =
- new PortAddresses(addresses.connectPoint(), union, newMac);
-
- portAddresses.put(newAddresses.connectPoint(), newAddresses);
- }
- }
+ portAddresses.put(addresses.connectPoint(), addresses);
}
@Override
public void removeAddressBindings(PortAddresses addresses) {
- synchronized (portAddresses) {
- PortAddresses existing = portAddresses.get(addresses.connectPoint());
- if (existing != null) {
- Set<InterfaceIpAddress> difference =
- Sets.difference(existing.ipAddresses(),
- addresses.ipAddresses()).immutableCopy();
-
- // If they removed the existing mac, set the new mac to null.
- // Otherwise, keep the existing mac.
- MacAddress newMac = existing.mac();
- if (addresses.mac() != null && addresses.mac().equals(existing.mac())) {
- newMac = null;
- }
-
- PortAddresses newAddresses =
- new PortAddresses(addresses.connectPoint(), difference, newMac);
-
- portAddresses.put(newAddresses.connectPoint(), newAddresses);
- }
- }
+ portAddresses.remove(addresses.connectPoint(), addresses);
}
@Override
public void clearAddressBindings(ConnectPoint connectPoint) {
- synchronized (portAddresses) {
- portAddresses.remove(connectPoint);
- }
+ portAddresses.removeAll(connectPoint);
}
@Override
public Set<PortAddresses> getAddressBindings() {
synchronized (portAddresses) {
- return new HashSet<>(portAddresses.values());
+ return ImmutableSet.copyOf(portAddresses.values());
}
}
@Override
- public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
- PortAddresses addresses;
-
+ public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
synchronized (portAddresses) {
- addresses = portAddresses.get(connectPoint);
- }
+ Set<PortAddresses> addresses = portAddresses.get(connectPoint);
- if (addresses == null) {
- addresses = new PortAddresses(connectPoint, null, null);
+ if (addresses == null) {
+ return Collections.emptySet();
+ } else {
+ return ImmutableSet.copyOf(addresses);
+ }
}
-
- return addresses;
}
// Auxiliary extension to allow location to mutate.
diff --git a/tools/build/envDefaults b/tools/build/envDefaults
index 184ad33..bfe9366 100644
--- a/tools/build/envDefaults
+++ b/tools/build/envDefaults
@@ -5,7 +5,7 @@
# M2 repository and Karaf gold bits
export M2_REPO=${M2_REPO:-~/.m2/repository}
-export KARAF_VERSION=${KARAF_VERSION:-3.0.1}
+export KARAF_VERSION=${KARAF_VERSION:-3.0.2}
export KARAF_ZIP=${KARAF_ZIP:-~/Downloads/apache-karaf-$KARAF_VERSION.zip}
export KARAF_TAR=${KARAF_TAR:-~/Downloads/apache-karaf-$KARAF_VERSION.tar.gz}
export KARAF_DIST=$(basename $KARAF_ZIP .zip)
diff --git a/tools/dev/bash_profile b/tools/dev/bash_profile
index 1a0c93f..fecc5b9 100644
--- a/tools/dev/bash_profile
+++ b/tools/dev/bash_profile
@@ -8,7 +8,9 @@
# Setup some environmental context for developers
if [ -z "${JAVA_HOME}" ]; then
if [ -x /usr/libexec/java_home ]; then
- export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
+ export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
+ elif [ -d /usr/lib/jvm/java-8-oracle ]; then
+ export JAVA_HOME="/usr/lib/jvm/java-8-oracle"
elif [ -d /usr/lib/jvm/java-7-openjdk-amd64 ]; then
export JAVA_HOME="/usr/lib/jvm/java-7-openjdk-amd64"
fi
@@ -16,7 +18,7 @@
export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2}
-export KARAF_VERSION=${KARAF_VERSION:-3.0.1}
+export KARAF_VERSION=${KARAF_VERSION:-3.0.2}
export KARAF=${KARAF:-~/Applications/apache-karaf-$KARAF_VERSION}
export KARAF_LOG=$KARAF/data/log/karaf.log
diff --git a/tools/test/cells/local b/tools/test/cells/local
index e8ca7e9..acf76ef 100644
--- a/tools/test/cells/local
+++ b/tools/test/cells/local
@@ -6,4 +6,4 @@
export OCN="192.168.56.103"
export OCI="${OC1}"
-export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-openflow,onos-rest,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
+export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-openflow,onos-gui,onos-rest,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
diff --git a/tools/test/cells/triple b/tools/test/cells/triple
index 439b837..880dd55 100644
--- a/tools/test/cells/triple
+++ b/tools/test/cells/triple
@@ -7,4 +7,4 @@
export OCN="192.168.56.103"
export OCI="${OC1}"
-export ONOS_FEATURES=""
+export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-openflow,onos-gui,onos-rest,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
diff --git a/web/gui/src/main/webapp/geometry2.js b/web/gui/src/main/webapp/geometry2.js
new file mode 100644
index 0000000..5ede643
--- /dev/null
+++ b/web/gui/src/main/webapp/geometry2.js
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+/*
+ Geometry library - based on work by Mike Bostock.
+ */
+
+(function() {
+
+ if (typeof geo == 'undefined') {
+ geo = {};
+ }
+
+ var tolerance = 1e-10;
+
+ function eq(a, b) {
+ return (Math.abs(a - b) < tolerance);
+ }
+
+ function gt(a, b) {
+ return (a - b > -tolerance);
+ }
+
+ function lt(a, b) {
+ return gt(b, a);
+ }
+
+ geo.eq = eq;
+ geo.gt = gt;
+ geo.lt = lt;
+
+ geo.LineSegment = function(x1, y1, x2, y2) {
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+
+ // Ax + By = C
+ this.a = y2 - y1;
+ this.b = x1 - x2;
+ this.c = x1 * this.a + y1 * this.b;
+
+ if (eq(this.a, 0) && eq(this.b, 0)) {
+ throw new Error(
+ 'Cannot construct a LineSegment with two equal endpoints.');
+ }
+ };
+
+ geo.LineSegment.prototype.intersect = function(that) {
+ var d = (this.x1 - this.x2) * (that.y1 - that.y2) -
+ (this.y1 - this.y2) * (that.x1 - that.x2);
+
+ if (eq(d, 0)) {
+ // The two lines are parallel or very close.
+ return {
+ x : NaN,
+ y : NaN
+ };
+ }
+
+ var t1 = this.x1 * this.y2 - this.y1 * this.x2,
+ t2 = that.x1 * that.y2 - that.y1 * that.x2,
+ x = (t1 * (that.x1 - that.x2) - t2 * (this.x1 - this.x2)) / d,
+ y = (t1 * (that.y1 - that.y2) - t2 * (this.y1 - this.y2)) / d,
+ in1 = (gt(x, Math.min(this.x1, this.x2)) && lt(x, Math.max(this.x1, this.x2)) &&
+ gt(y, Math.min(this.y1, this.y2)) && lt(y, Math.max(this.y1, this.y2))),
+ in2 = (gt(x, Math.min(that.x1, that.x2)) && lt(x, Math.max(that.x1, that.x2)) &&
+ gt(y, Math.min(that.y1, that.y2)) && lt(y, Math.max(that.y1, that.y2)));
+
+ return {
+ x : x,
+ y : y,
+ in1 : in1,
+ in2 : in2
+ };
+ };
+
+ geo.LineSegment.prototype.x = function(y) {
+ // x = (C - By) / a;
+ if (this.a) {
+ return (this.c - this.b * y) / this.a;
+ } else {
+ // a == 0 -> horizontal line
+ return NaN;
+ }
+ };
+
+ geo.LineSegment.prototype.y = function(x) {
+ // y = (C - Ax) / b;
+ if (this.b) {
+ return (this.c - this.a * x) / this.b;
+ } else {
+ // b == 0 -> vertical line
+ return NaN;
+ }
+ };
+
+ geo.LineSegment.prototype.length = function() {
+ return Math.sqrt(
+ (this.y2 - this.y1) * (this.y2 - this.y1) +
+ (this.x2 - this.x1) * (this.x2 - this.x1));
+ };
+
+ geo.LineSegment.prototype.offset = function(x, y) {
+ return new geo.LineSegment(
+ this.x1 + x, this.y1 + y,
+ this.x2 + x, this.y2 + y);
+ };
+
+})();
diff --git a/web/gui/src/main/webapp/index2.html b/web/gui/src/main/webapp/index2.html
new file mode 100644
index 0000000..4c4a9c8
--- /dev/null
+++ b/web/gui/src/main/webapp/index2.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<!--
+ ~ 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.
+ -->
+
+<!--
+ ONOS UI - single page web app
+ Version 1.1
+
+ @author Simon Hunt
+ -->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ONOS GUI (v1.1)</title>
+
+ <link rel="shortcut icon" href="img/onos-logo.png">
+
+ <!-- first script to be run -->
+ <script src="preamble.js"></script>
+
+ <!-- Third party library code included here -->
+ <!--TODO: use the minified version of d3, once debugging is complete -->
+ <script src="libs/d3.js"></script>
+ <script src="libs/jquery-2.1.1.min.js"></script>
+
+ <!-- Base library and framework stylesheets included here -->
+ <link rel="stylesheet" href="base.css">
+ <link rel="stylesheet" href="onos2.css">
+ <link rel="stylesheet" href="mast2.css">
+
+ <!-- This is where contributed stylesheets get INJECTED -->
+ <!-- TODO: replace with template marker and inject refs server-side -->
+ <link rel="stylesheet" href="topo2.css">
+
+
+ <!-- General library modules included here-->
+ <script src="geometry2.js"></script>
+
+ <!-- ONOS UI Framework included here-->
+ <script src="onos2.js"></script>
+
+</head>
+<body>
+ <div id="frame">
+ <div id="mast">
+ <!-- NOTE: masthead injected here by mast.js -->
+ </div>
+ <div id="view">
+ <!-- NOTE: views injected here by onos.js -->
+ </div>
+ <div id="overlays">
+ <!-- NOTE: overlays injected here, as needed -->
+ </div>
+ </div>
+
+ <!-- Initialize the UI...-->
+ <script type="text/javascript">
+ var ONOS = $.onos({note: "config, if needed"});
+ </script>
+
+ <!-- Framework module files included here -->
+ <script src="mast2.js"></script>
+
+ <!-- Contributed (application) views injected here -->
+ <!-- TODO: replace with template marker and inject refs server-side -->
+ <script src="temp2.js"></script>
+
+ <!-- finally, build the UI-->
+ <script type="text/javascript">
+ $(ONOS.buildUi);
+ </script>
+
+</body>
+</html>
diff --git a/web/gui/src/main/webapp/mast2.css b/web/gui/src/main/webapp/mast2.css
new file mode 100644
index 0000000..4502776
--- /dev/null
+++ b/web/gui/src/main/webapp/mast2.css
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Masthead -- CSS file
+
+ @author Simon Hunt
+ */
+
+#mast {
+ height: 36px;
+ padding: 4px;
+ background-color: #bbb;
+ vertical-align: baseline;
+ box-shadow: 0px 2px 8px #777;
+}
+
+#mast img#logo {
+ height: 38px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+#mast span.title {
+ color: #369;
+ font-size: 14pt;
+ font-style: italic;
+ vertical-align: 12px;
+}
+
+#mast span.right {
+ padding-top: 8px;
+ padding-right: 16px;
+ float: right;
+}
+
+#mast span.radio {
+ color: darkslateblue;
+ font-size: 10pt;
+}
+
+#mast span.radio {
+ margin: 4px 0;
+ border: 1px dotted #222;
+ padding: 1px 6px;
+ color: #eee;
+ cursor: pointer;
+}
+
+#mast span.radio.active {
+ background-color: #bbb;
+ border: 1px solid #eee;
+ padding: 1px 6px;
+ color: #666;
+ font-weight: bold;
+}
diff --git a/web/gui/src/main/webapp/mast2.js b/web/gui/src/main/webapp/mast2.js
new file mode 100644
index 0000000..169dd35
--- /dev/null
+++ b/web/gui/src/main/webapp/mast2.js
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Masthead
+
+ Defines the masthead for the UI. Injects logo and title, as well as providing
+ the placeholder for a set of radio buttons.
+
+ @author Simon Hunt
+ */
+
+(function (onos){
+ 'use strict';
+
+ // API's
+ var api = onos.api;
+
+ // Config variables
+ var guiTitle = 'Open Networking Operating System';
+
+ // DOM elements and the like
+ var mast = d3.select('#mast');
+
+ mast.append('img')
+ .attr({
+ id: 'logo',
+ src: 'img/onos-logo.png'
+ });
+
+ mast.append('span')
+ .attr({
+ class: 'title'
+ })
+ .text(guiTitle);
+
+ mast.append('span')
+ .attr({
+ id: 'mastRadio',
+ class: 'right'
+ });
+
+}(ONOS));
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/onos2.css b/web/gui/src/main/webapp/onos2.css
new file mode 100644
index 0000000..63acd00
--- /dev/null
+++ b/web/gui/src/main/webapp/onos2.css
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Base Framework -- CSS file
+
+ @author Simon Hunt
+ */
+
+html, body {
+ height: 100%;
+}
+
+
+/*
+ * === DEBUGGING ======
+ */
+svg {
+ /*border: 1px dashed red;*/
+}
+
+svg #bg {
+ opacity: 0.5;
+}
+
+
+/*
+ * Network Graph elements ======================================
+ */
+
+svg .link {
+ fill: none;
+ stroke: #666;
+ stroke-width: 2.0px;
+ opacity: .7;
+
+ transition: opacity 250ms;
+ -webkit-transition: opacity 250ms;
+ -moz-transition: opacity 250ms;
+}
+
+svg .link.host {
+ stroke: #666;
+ stroke-width: 1px;
+}
+
+svg g.portLayer rect.port {
+ fill: #ccc;
+}
+
+svg g.portLayer text {
+ font: 8pt sans-serif;
+ pointer-events: none;
+}
+
+svg .node.device rect {
+ stroke-width: 1.5px;
+
+ transition: opacity 250ms;
+ -webkit-transition: opacity 250ms;
+ -moz-transition: opacity 250ms;
+}
+
+svg .node.device.fixed rect {
+ stroke-width: 1.5;
+ stroke: #ccc;
+}
+
+svg .node.device.roadm rect {
+ fill: #03c;
+}
+
+svg .node.device.switch rect {
+ fill: #06f;
+}
+
+svg .node.host circle {
+ fill: #c96;
+ stroke: #000;
+}
+
+svg .node text {
+ fill: white;
+ font: 10pt sans-serif;
+ pointer-events: none;
+}
+
+/* for debugging */
+svg .node circle.debug {
+ fill: white;
+ stroke: red;
+}
+svg .node rect.debug {
+ fill: yellow;
+ stroke: red;
+ opacity: 0.35;
+}
+
+
+svg .node.selected rect,
+svg .node.selected circle {
+ filter: url(#blue-glow);
+}
+
+svg .link.inactive,
+svg .port.inactive,
+svg .portText.inactive,
+svg .node.inactive rect,
+svg .node.inactive circle,
+svg .node.inactive text,
+svg .node.inactive image {
+ opacity: .1;
+}
+
+svg .node.inactive.selected rect,
+svg .node.inactive.selected text,
+svg .node.inactive.selected image {
+ opacity: .6;
+}
+
+/*
+ * =============================================================
+ */
+
+/*
+ * Specific structural elements
+ */
+
+/* This is to ensure that the body does not expand to account for the
+ flyout details pane, that is positioned "off screen".
+ */
+body {
+ overflow: hidden;
+}
+
+
+#frame {
+ width: 100%;
+ height: 100%;
+ background-color: #fff;
+}
+
+#flyout {
+ position: absolute;
+ z-index: 100;
+ display: block;
+ top: 10%;
+ width: 280px;
+ right: -300px;
+ opacity: 0;
+ background-color: rgba(255,255,255,0.8);
+
+ padding: 10px;
+ color: black;
+ font-size: 10pt;
+ box-shadow: 2px 2px 16px #777;
+}
+
+#flyout h2 {
+ margin: 8px 4px;
+ color: black;
+ vertical-align: middle;
+}
+
+#flyout h2 img {
+ height: 32px;
+ padding-right: 8px;
+ vertical-align: middle;
+}
+
+#flyout p, table {
+ margin: 4px 4px;
+}
+
+#flyout td.label {
+ font-style: italic;
+ color: #777;
+ padding-right: 12px;
+}
+
+#flyout td.value {
+
+}
+
+#flyout hr {
+ height: 1px;
+ color: #ccc;
+ background-color: #ccc;
+ border: 0;
+}
+
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
new file mode 100644
index 0000000..bd001a5
--- /dev/null
+++ b/web/gui/src/main/webapp/onos2.js
@@ -0,0 +1,303 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Base Framework
+
+ @author Simon Hunt
+ */
+
+(function ($) {
+ 'use strict';
+ var tsI = new Date().getTime(), // initialize time stamp
+ tsB, // build time stamp
+ defaultHash = 'temp1';
+
+
+ // attach our main function to the jQuery object
+ $.onos = function (options) {
+ var publicApi; // public api
+
+ // internal state
+ var views = {},
+ current = {
+ view: null,
+ ctx: ''
+ },
+ built = false,
+ errorCount = 0;
+
+ // DOM elements etc.
+ var $view;
+
+
+ // ..........................................................
+ // Internal functions
+
+ // throw an error
+ function throwError(msg) {
+ // separate function, as we might add tracing here too, later
+ throw new Error(msg);
+ }
+
+ function doError(msg) {
+ errorCount++;
+ console.warn(msg);
+ }
+
+ // hash navigation
+ function hash() {
+ var hash = window.location.hash,
+ redo = false,
+ view,
+ t;
+
+ if (!hash) {
+ hash = defaultHash;
+ redo = true;
+ }
+
+ t = parseHash(hash);
+ if (!t || !t.vid) {
+ doError('Unable to parse target hash: ' + hash);
+ }
+
+ view = views[t.vid];
+ if (!view) {
+ doError('No view defined with id: ' + t.vid);
+ }
+
+ if (redo) {
+ window.location.hash = makeHash(t);
+ // the above will result in a hashchange event, invoking
+ // this function again
+ } else {
+ // hash was not modified... navigate to where we need to be
+ navigate(hash, view, t);
+ }
+
+ }
+
+ function parseHash(s) {
+ // extract navigation coordinates from the supplied string
+ // "vid,ctx" --> { vid:vid, ctx:ctx }
+
+ var m = /^[#]{0,1}(\S+),(\S*)$/.exec(s);
+ if (m) {
+ return { vid: m[1], ctx: m[2] };
+ }
+
+ m = /^[#]{0,1}(\S+)$/.exec(s);
+ return m ? { vid: m[1] } : null;
+ }
+
+ function makeHash(t, ctx) {
+ // make a hash string from the given navigation coordinates.
+ // if t is not an object, then it is a vid
+ var h = t,
+ c = ctx || '';
+
+ if ($.isPlainObject(t)) {
+ h = t.vid;
+ c = t.ctx || '';
+ }
+
+ if (c) {
+ h += ',' + c;
+ }
+ return h;
+ }
+
+ function navigate(hash, view, t) {
+ // closePanes() // flyouts etc.
+ // updateNav() // accordion / selected nav item
+ createView(view);
+ setView(view, hash, t);
+ }
+
+ function reportBuildErrors() {
+ // TODO: validate registered views / nav-item linkage etc.
+ console.log('(no build errors)');
+ }
+
+ // ..........................................................
+ // View life-cycle functions
+
+ function createView(view) {
+ var $d;
+ // lazy initialization of the view
+ if (view && !view.$div) {
+ $d = $view.append('div')
+ .attr({
+ id: view.vid
+ });
+ view.$div = $d; // cache a reference to the selected div
+ }
+ }
+
+ function setView(view, hash, t) {
+ // set the specified view as current, while invoking the
+ // appropriate life-cycle callbacks
+
+ // if there is a current view, and it is not the same as
+ // the incoming view, then unload it...
+ if (current.view && !(current.view.vid !== view.vid)) {
+ current.view.unload();
+ }
+
+ // cache new view and context
+ current.view = view;
+ current.ctx = t.ctx || '';
+
+ // TODO: clear radio button set (store on view?)
+
+ // preload is called only once, after the view is in the DOM
+ if (!view.preloaded) {
+ view.preload(t.ctx);
+ }
+
+ // clear the view of stale data
+ view.reset();
+
+ // load the view
+ view.load(t.ctx);
+ }
+
+ function resizeView() {
+ if (current.view) {
+ current.view.resize();
+ }
+ }
+
+ // ..........................................................
+ // View class
+ // Captures state information about a view.
+
+ // Constructor
+ // vid : view id
+ // nid : id of associated nav-item (optional)
+ // cb : callbacks (preload, reset, load, resize, unload, error)
+ // data: custom data object (optional)
+ function View(vid) {
+ var av = 'addView(): ',
+ args = Array.prototype.slice.call(arguments),
+ nid,
+ cb,
+ data;
+
+ args.shift(); // first arg is always vid
+ if (typeof args[0] === 'string') { // nid specified
+ nid = args.shift();
+ }
+ cb = args.shift();
+ data = args.shift();
+
+ this.vid = vid;
+
+ if (validateViewArgs(vid)) {
+ this.nid = nid; // explicit navitem id (can be null)
+ this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
+ this.data = data; // custom data (can be null)
+ this.$div = null; // view not yet added to DOM
+ this.ok = true; // valid view
+ }
+
+ }
+
+ function validateViewArgs(vid) {
+ var ok = false;
+ if (typeof vid !== 'string' || !vid) {
+ doError(av + 'vid required');
+ } else if (views[vid]) {
+ doError(av + 'View ID "' + vid + '" already exists');
+ } else {
+ ok = true;
+ }
+ return ok;
+ }
+
+ var viewInstanceMethods = {
+ toString: function () {
+ return '[View: id="' + this.vid + '"]';
+ },
+
+ token: function() {
+ return {
+ vid: this.vid,
+ nid: this.nid,
+ data: this.data
+ }
+ }
+ // TODO: create, preload, reset, load, error, resize, unload
+ };
+
+ // attach instance methods to the view prototype
+ $.extend(View.prototype, viewInstanceMethods);
+
+ // ..........................................................
+ // Exported API
+
+ publicApi = {
+ printTime: function () {
+ console.log("the time is " + new Date());
+ },
+
+ addView: function (vid, nid, cb, data) {
+ var view = new View(vid, nid, cb, data),
+ token;
+ if (view.ok) {
+ views[vid] = view;
+ token = view.token();
+ } else {
+ token = { vid: view.vid, bad: true };
+ }
+ return token;
+ }
+ };
+
+ // function to be called from index.html to build the ONOS UI
+ function buildOnosUi() {
+ tsB = new Date().getTime();
+ tsI = tsB - tsI; // initialization duration
+
+ console.log('ONOS UI initialized in ' + tsI + 'ms');
+
+ if (built) {
+ throwError("ONOS UI already built!");
+ }
+ built = true;
+
+ $view = d3.select('#view');
+
+ $(window).on('hashchange', hash);
+
+ // Invoke hashchange callback to navigate to content
+ // indicated by the window location hash.
+ hash();
+
+ // If there were any build errors, report them
+ reportBuildErrors();
+ }
+
+
+ // export the api and build-UI function
+ return {
+ api: publicApi,
+ buildUi: buildOnosUi
+ };
+ };
+
+}(jQuery));
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/preamble.js b/web/gui/src/main/webapp/preamble.js
new file mode 100644
index 0000000..8ee8e45
--- /dev/null
+++ b/web/gui/src/main/webapp/preamble.js
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Preamble -- the first thing we do
+
+ @author Simon Hunt
+ */
+
+(function () {
+ // Check if the URL in the address bar contains a parameter section
+ // (delineated by '?'). If this is the case, rewrite using '#' instead.
+
+ var m = /([^?]*)\?(.*)/.exec(window.location.href);
+ if (m) {
+ window.location.href = m[1] + '#' + m[2];
+ }
+
+}());
diff --git a/web/gui/src/main/webapp/temp2.js b/web/gui/src/main/webapp/temp2.js
new file mode 100644
index 0000000..cc174c0
--- /dev/null
+++ b/web/gui/src/main/webapp/temp2.js
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+/*
+ Temporary module file to test the framework integration.
+
+ @author Simon Hunt
+ */
+
+(function (onos) {
+ 'use strict';
+
+ var api = onos.api;
+
+ var vid,
+ svg;
+
+ // == define your functions here.....
+
+
+ // NOTE: view is a data structure:
+ // {
+ // id: 'view-id',
+ // el: ... // d3 selection of dom view div.
+ // }
+
+ function load(view) {
+ vid = view.id;
+ svg = view.el.append('svg')
+ .attr({
+ width: 400,
+ height: 300
+ });
+
+ var fill = (vid === 'temp1') ? 'red' : 'blue',
+ stroke = (vid === 'temp2') ? 'yellow' : 'black';
+
+ svg.append('circle')
+ .attr({
+ cx: 200,
+ cy: 150,
+ r: 30
+ })
+ .style({
+ fill: fill,
+ stroke: stroke,
+ 'stroke-width': 3.5
+ });
+ }
+
+ // == register views here, with links to lifecycle callbacks
+
+ api.addView('temp1', {
+ load: load
+ });
+
+ api.addView('temp2', {
+ load: load
+ });
+
+
+}(ONOS));
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
new file mode 100644
index 0000000..2e058c1
--- /dev/null
+++ b/web/gui/src/main/webapp/topo2.css
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Topology view -- CSS file
+
+ @author Simon Hunt
+ */
+
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
new file mode 100644
index 0000000..b249c09
--- /dev/null
+++ b/web/gui/src/main/webapp/topo2.js
@@ -0,0 +1,1222 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS network topology viewer - PoC version 1.0
+
+ @author Simon Hunt
+ */
+
+(function (onos) {
+ 'use strict';
+
+ // reference to the framework api
+ var api = onos.api;
+
+ // configuration data
+ var config = {
+ useLiveData: true,
+ debugOn: false,
+ debug: {
+ showNodeXY: false,
+ showKeyHandler: true
+ },
+ options: {
+ layering: true,
+ collisionPrevention: true,
+ loadBackground: true
+ },
+ backgroundUrl: 'img/us-map.png',
+ data: {
+ live: {
+ jsonUrl: 'rs/topology/graph',
+ detailPrefix: 'rs/topology/graph/',
+ detailSuffix: ''
+ },
+ fake: {
+ jsonUrl: 'json/network2.json',
+ detailPrefix: 'json/',
+ detailSuffix: '.json'
+ }
+ },
+ iconUrl: {
+ device: 'img/device.png',
+ host: 'img/host.png',
+ pkt: 'img/pkt.png',
+ opt: 'img/opt.png'
+ },
+ mastHeight: 36,
+ force: {
+ note: 'node.class or link.class is used to differentiate',
+ linkDistance: {
+ infra: 200,
+ host: 40
+ },
+ linkStrength: {
+ infra: 1.0,
+ host: 1.0
+ },
+ charge: {
+ device: -800,
+ host: -1000
+ },
+ ticksWithoutCollisions: 50,
+ marginLR: 20,
+ marginTB: 20,
+ translate: function() {
+ return 'translate(' +
+ config.force.marginLR + ',' +
+ config.force.marginTB + ')';
+ }
+ },
+ labels: {
+ imgPad: 16,
+ padLR: 8,
+ padTB: 6,
+ marginLR: 3,
+ marginTB: 2,
+ port: {
+ gap: 3,
+ width: 18,
+ height: 14
+ }
+ },
+ icons: {
+ w: 32,
+ h: 32,
+ xoff: -12,
+ yoff: -8
+ },
+ constraints: {
+ ypos: {
+ host: 0.05,
+ switch: 0.3,
+ roadm: 0.7
+ }
+ },
+ hostLinkWidth: 1.0,
+ hostRadius: 7,
+ mouseOutTimerDelayMs: 120
+ };
+
+ // state variables
+ var view = {},
+ network = {},
+ selected = {},
+ highlighted = null,
+ hovered = null,
+ viewMode = 'showAll',
+ portLabelsOn = false;
+
+
+ function debug(what) {
+ return config.debugOn && config.debug[what];
+ }
+
+ function urlData() {
+ return config.data[config.useLiveData ? 'live' : 'fake'];
+ }
+
+ function networkJsonUrl() {
+ return urlData().jsonUrl;
+ }
+
+ function safeId(id) {
+ return id.replace(/[^a-z0-9]/gi, '_');
+ }
+
+ function detailJsonUrl(id) {
+ var u = urlData(),
+ encId = config.useLiveData ? encodeURIComponent(id) : safeId(id);
+ return u.detailPrefix + encId + u.detailSuffix;
+ }
+
+
+ // load the topology view of the network
+ function loadNetworkView() {
+ // Hey, here I am, calling something on the ONOS api:
+ api.printTime();
+
+ resize();
+
+ // go get our network data from the server...
+ var url = networkJsonUrl();
+ d3.json(url , function (err, data) {
+ if (err) {
+ alert('Oops! Error reading JSON...\n\n' +
+ 'URL: ' + url + '\n\n' +
+ 'Error: ' + err.message);
+ return;
+ }
+// console.log("here is the JSON data...");
+// console.log(data);
+
+ network.data = data;
+ drawNetwork();
+ });
+
+ // while we wait for the data, set up the handlers...
+ setUpClickHandler();
+ setUpRadioButtonHandler();
+ setUpKeyHandler();
+ $(window).on('resize', resize);
+ }
+
+ function setUpClickHandler() {
+ // click handler for "selectable" objects
+ $(document).on('click', '.select-object', function () {
+ // when any object of class "select-object" is clicked...
+ var obj = network.lookup[$(this).data('id')];
+ if (obj) {
+ selectObject(obj);
+ }
+ // stop propagation of event (I think) ...
+ return false;
+ });
+ }
+
+ function setUpRadioButtonHandler() {
+ d3.selectAll('#displayModes .radio').on('click', function () {
+ var id = d3.select(this).attr('id');
+ if (id !== viewMode) {
+ radioButton('displayModes', id);
+ viewMode = id;
+ doRadioAction(id);
+ }
+ });
+ }
+
+ function doRadioAction(id) {
+ showAllLayers();
+ if (id === 'showPkt') {
+ showPacketLayer();
+ } else if (id === 'showOpt') {
+ showOpticalLayer();
+ }
+ }
+
+ function showAllLayers() {
+ network.node.classed('inactive', false);
+ network.link.classed('inactive', false);
+ d3.selectAll('svg .port').classed('inactive', false)
+ d3.selectAll('svg .portText').classed('inactive', false)
+ }
+
+ function showPacketLayer() {
+ network.node.each(function(d) {
+ // deactivate nodes that are not hosts or switches
+ if (d.class === 'device' && d.type !== 'switch') {
+ d3.select(this).classed('inactive', true);
+ }
+ });
+
+ network.link.each(function(lnk) {
+ // deactivate infrastructure links that have opt's as endpoints
+ if (lnk.source.type === 'roadm' || lnk.target.type === 'roadm') {
+ d3.select(this).classed('inactive', true);
+ }
+ });
+
+ // deactivate non-packet ports
+ d3.selectAll('svg .optPort').classed('inactive', true)
+ }
+
+ function showOpticalLayer() {
+ network.node.each(function(d) {
+ // deactivate nodes that are not optical devices
+ if (d.type !== 'roadm') {
+ d3.select(this).classed('inactive', true);
+ }
+ });
+
+ network.link.each(function(lnk) {
+ // deactivate infrastructure links that have opt's as endpoints
+ if (lnk.source.type !== 'roadm' || lnk.target.type !== 'roadm') {
+ d3.select(this).classed('inactive', true);
+ }
+ });
+
+ // deactivate non-packet ports
+ d3.selectAll('svg .pktPort').classed('inactive', true)
+ }
+
+ function setUpKeyHandler() {
+ d3.select('body')
+ .on('keydown', function () {
+ processKeyEvent();
+ if (debug('showKeyHandler')) {
+ network.svg.append('text')
+ .attr('x', 5)
+ .attr('y', 15)
+ .style('font-size', '20pt')
+ .text('keyCode: ' + d3.event.keyCode +
+ ' applied to : ' + contextLabel())
+ .transition().duration(2000)
+ .style('font-size', '2pt')
+ .style('fill-opacity', 0.01)
+ .remove();
+ }
+ });
+ }
+
+ function contextLabel() {
+ return hovered === null ? "(nothing)" : hovered.id;
+ }
+
+ function radioButton(group, id) {
+ d3.selectAll("#" + group + " .radio").classed("active", false);
+ d3.select("#" + group + " #" + id).classed("active", true);
+ }
+
+ function processKeyEvent() {
+ var code = d3.event.keyCode;
+ switch (code) {
+ case 66: // B
+ toggleBackground();
+ break;
+ case 71: // G
+ cycleLayout();
+ break;
+ case 76: // L
+ cycleLabels();
+ break;
+ case 80: // P
+ togglePorts();
+ break;
+ case 85: // U
+ unpin();
+ break;
+ }
+
+ }
+
+ function toggleBackground() {
+ var bg = d3.select('#bg'),
+ vis = bg.style('visibility'),
+ newvis = (vis === 'hidden') ? 'visible' : 'hidden';
+ bg.style('visibility', newvis);
+ }
+
+ function cycleLayout() {
+ config.options.layering = !config.options.layering;
+ network.force.resume();
+ }
+
+ function cycleLabels() {
+ console.log('Cycle Labels - context = ' + contextLabel());
+ }
+
+ function togglePorts() {
+ portLabelsOn = !portLabelsOn;
+ var portVis = portLabelsOn ? 'visible' : 'hidden';
+ d3.selectAll('.port').style('visibility', portVis);
+ d3.selectAll('.portText').style('visibility', portVis);
+ }
+
+ function unpin() {
+ if (hovered) {
+ hovered.fixed = false;
+ findNodeFromData(hovered).classed('fixed', false);
+ network.force.resume();
+ }
+ console.log('Unpin - context = ' + contextLabel());
+ }
+
+
+ // ========================================================
+
+ function drawNetwork() {
+ $('#view').empty();
+
+ prepareNodesAndLinks();
+ createLayout();
+ console.log("\n\nHere is the augmented network object...");
+ console.log(network);
+ }
+
+ function prepareNodesAndLinks() {
+ network.lookup = {};
+ network.nodes = [];
+ network.links = [];
+
+ var nw = network.forceWidth,
+ nh = network.forceHeight;
+
+ function yPosConstraintForNode(n) {
+ return config.constraints.ypos[n.type || 'host'];
+ }
+
+ // Note that both 'devices' and 'hosts' get mapped into the nodes array
+
+ // first, the devices...
+ network.data.devices.forEach(function(n) {
+ var ypc = yPosConstraintForNode(n),
+ ix = Math.random() * 0.6 * nw + 0.2 * nw,
+ iy = ypc * nh,
+ node = {
+ id: n.id,
+ labels: n.labels,
+ class: 'device',
+ icon: 'device',
+ type: n.type,
+ x: ix,
+ y: iy,
+ constraint: {
+ weight: 0.7,
+ y: iy
+ }
+ };
+ network.lookup[n.id] = node;
+ network.nodes.push(node);
+ });
+
+ // then, the hosts...
+ network.data.hosts.forEach(function(n) {
+ var ypc = yPosConstraintForNode(n),
+ ix = Math.random() * 0.6 * nw + 0.2 * nw,
+ iy = ypc * nh,
+ node = {
+ id: n.id,
+ labels: n.labels,
+ class: 'host',
+ icon: 'host',
+ type: n.type,
+ x: ix,
+ y: iy,
+ constraint: {
+ weight: 0.7,
+ y: iy
+ }
+ };
+ network.lookup[n.id] = node;
+ network.nodes.push(node);
+ });
+
+
+ // now, process the explicit links...
+ network.data.links.forEach(function(lnk) {
+ var src = network.lookup[lnk.src],
+ dst = network.lookup[lnk.dst],
+ id = src.id + "-" + dst.id;
+
+ var link = {
+ class: 'infra',
+ id: id,
+ type: lnk.type,
+ width: lnk.linkWidth,
+ source: src,
+ srcPort: lnk.srcPort,
+ target: dst,
+ tgtPort: lnk.dstPort,
+ strength: config.force.linkStrength.infra
+ };
+ network.links.push(link);
+ });
+
+ // finally, infer host links...
+ network.data.hosts.forEach(function(n) {
+ var src = network.lookup[n.id],
+ dst = network.lookup[n.cp.device],
+ id = src.id + "-" + dst.id;
+
+ var link = {
+ class: 'host',
+ id: id,
+ type: 'hostLink',
+ width: config.hostLinkWidth,
+ source: src,
+ target: dst,
+ strength: config.force.linkStrength.host
+ };
+ network.links.push(link);
+ });
+ }
+
+ function createLayout() {
+
+ var cfg = config.force;
+
+ network.force = d3.layout.force()
+ .size([network.forceWidth, network.forceHeight])
+ .nodes(network.nodes)
+ .links(network.links)
+ .linkStrength(function(d) { return cfg.linkStrength[d.class]; })
+ .linkDistance(function(d) { return cfg.linkDistance[d.class]; })
+ .charge(function(d) { return cfg.charge[d.class]; })
+ .on('tick', tick);
+
+ network.svg = d3.select('#view').append('svg')
+ .attr('width', view.width)
+ .attr('height', view.height)
+ .append('g')
+ .attr('transform', config.force.translate());
+// .attr('id', 'zoomable')
+// .call(d3.behavior.zoom().on("zoom", zoomRedraw));
+
+ network.svg.append('svg:image')
+ .attr({
+ id: 'bg',
+ width: view.width,
+ height: view.height,
+ 'xlink:href': config.backgroundUrl
+ })
+ .style('visibility',
+ config.options.loadBackground ? 'visible' : 'hidden');
+
+// function zoomRedraw() {
+// d3.select("#zoomable").attr("transform",
+// "translate(" + d3.event.translate + ")"
+// + " scale(" + d3.event.scale + ")");
+// }
+
+ // TODO: move glow/blur stuff to util script
+ var glow = network.svg.append('filter')
+ .attr('x', '-50%')
+ .attr('y', '-50%')
+ .attr('width', '200%')
+ .attr('height', '200%')
+ .attr('id', 'blue-glow');
+
+ glow.append('feColorMatrix')
+ .attr('type', 'matrix')
+ .attr('values', '0 0 0 0 0 ' +
+ '0 0 0 0 0 ' +
+ '0 0 0 0 .7 ' +
+ '0 0 0 1 0 ');
+
+ glow.append('feGaussianBlur')
+ .attr('stdDeviation', 3)
+ .attr('result', 'coloredBlur');
+
+ glow.append('feMerge').selectAll('feMergeNode')
+ .data(['coloredBlur', 'SourceGraphic'])
+ .enter().append('feMergeNode')
+ .attr('in', String);
+
+ // TODO: legend (and auto adjust on scroll)
+// $('#view').on('scroll', function() {
+//
+// });
+
+
+ // TODO: move drag behavior into separate method.
+ // == define node drag behavior...
+ network.draggedThreshold = d3.scale.linear()
+ .domain([0, 0.1])
+ .range([5, 20])
+ .clamp(true);
+
+ function dragged(d) {
+ var threshold = network.draggedThreshold(network.force.alpha()),
+ dx = d.oldX - d.px,
+ dy = d.oldY - d.py;
+ if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
+ d.dragged = true;
+ }
+ return d.dragged;
+ }
+
+ network.drag = d3.behavior.drag()
+ .origin(function(d) { return d; })
+ .on('dragstart', function(d) {
+ d.oldX = d.x;
+ d.oldY = d.y;
+ d.dragged = false;
+ d.fixed |= 2;
+ })
+ .on('drag', function(d) {
+ d.px = d3.event.x;
+ d.py = d3.event.y;
+ if (dragged(d)) {
+ if (!network.force.alpha()) {
+ network.force.alpha(.025);
+ }
+ }
+ })
+ .on('dragend', function(d) {
+ if (!dragged(d)) {
+ selectObject(d, this);
+ }
+ d.fixed &= ~6;
+
+ // once we've finished moving, pin the node in position,
+ // if it is a device (not a host)
+ if (d.class === 'device') {
+ d.fixed = true;
+ d3.select(this).classed('fixed', true)
+ }
+ });
+
+ $('#view').on('click', function(e) {
+ if (!$(e.target).closest('.node').length) {
+ deselectObject();
+ }
+ });
+
+ // ...............................................................
+
+ // add links to the display
+ network.link = network.svg.append('g').attr('id', 'links')
+ .selectAll('.link')
+ .data(network.force.links(), function(d) {return d.id})
+ .enter().append('line')
+ .attr('class', function(d) {return 'link ' + d.class});
+
+ network.linkSrcPort = network.svg.append('g')
+ .attr({
+ id: 'srcPorts',
+ class: 'portLayer'
+ });
+ network.linkTgtPort = network.svg.append('g')
+ .attr({
+ id: 'tgtPorts',
+ class: 'portLayer'
+ });
+
+ var portVis = portLabelsOn ? 'visible' : 'hidden',
+ pw = config.labels.port.width,
+ ph = config.labels.port.height;
+
+ network.link.filter('.infra').each(function(d) {
+ var srcType = d.source.type === 'roadm' ? 'optPort' : 'pktPort',
+ tgtType = d.target.type === 'roadm' ? 'optPort' : 'pktPort';
+
+ if (d.source.type)
+
+ network.linkSrcPort.append('rect').attr({
+ id: 'srcPort-' + safeId(d.id),
+ class: 'port ' + srcType,
+ width: pw,
+ height: ph,
+ rx: 4,
+ ry: 4
+ }).style('visibility', portVis);
+
+ network.linkTgtPort.append('rect').attr({
+ id: 'tgtPort-' + safeId(d.id),
+ class: 'port ' + tgtType,
+ width: pw,
+ height: ph,
+ rx: 4,
+ ry: 4
+ }).style('visibility', portVis);
+
+ network.linkSrcPort.append('text').attr({
+ id: 'srcText-' + safeId(d.id),
+ class: 'portText ' + srcType
+ }).text(d.srcPort)
+ .style('visibility', portVis);
+
+ network.linkTgtPort.append('text').attr({
+ id: 'tgtText-' + safeId(d.id),
+ class: 'portText ' + tgtType
+ }).text(d.tgtPort)
+ .style('visibility', portVis);
+ });
+
+ // ...............................................................
+
+ // add nodes to the display
+ network.node = network.svg.selectAll('.node')
+ .data(network.force.nodes(), function(d) {return d.id})
+ .enter().append('g')
+ .attr('class', function(d) {
+ var cls = 'node ' + d.class;
+ if (d.type) {
+ cls += ' ' + d.type;
+ }
+ return cls;
+ })
+ .attr('transform', function(d) {
+ return translate(d.x, d.y);
+ })
+ .call(network.drag)
+ .on('mouseover', function(d) {
+ // TODO: show tooltip
+ if (network.mouseoutTimeout) {
+ clearTimeout(network.mouseoutTimeout);
+ network.mouseoutTimeout = null;
+ }
+ hoverObject(d);
+ })
+ .on('mouseout', function(d) {
+ // TODO: hide tooltip
+ if (network.mouseoutTimeout) {
+ clearTimeout(network.mouseoutTimeout);
+ network.mouseoutTimeout = null;
+ }
+ network.mouseoutTimeout = setTimeout(function() {
+ hoverObject(null);
+ }, config.mouseOutTimerDelayMs);
+ });
+
+
+ // deal with device nodes first
+ network.nodeRect = network.node.filter('.device')
+ .append('rect')
+ .attr({
+ rx: 5,
+ ry: 5,
+ width: 100,
+ height: 12
+ });
+ // note that width/height are adjusted to fit the label text
+ // then padded, and space made for the icon.
+
+ network.node.filter('.device').each(function(d) {
+ var node = d3.select(this),
+ icon = iconUrl(d);
+
+ node.append('text')
+ // TODO: add label cycle behavior
+ .text(d.id)
+ .attr('dy', '1.1em');
+
+ if (icon) {
+ var cfg = config.icons;
+ node.append('svg:image')
+ .attr({
+ width: cfg.w,
+ height: cfg.h,
+ 'xlink:href': icon
+ });
+ // note, icon relative positioning (x,y) is done after we have
+ // adjusted the bounds of the rectangle...
+ }
+
+ // debug function to show the modelled x,y coordinates of nodes...
+ if (debug('showNodeXY')) {
+ node.select('rect').attr('fill-opacity', 0.5);
+ node.append('circle')
+ .attr({
+ class: 'debug',
+ cx: 0,
+ cy: 0,
+ r: '3px'
+ });
+ }
+ });
+
+ // now process host nodes
+ network.nodeCircle = network.node.filter('.host')
+ .append('circle')
+ .attr({
+ r: config.hostRadius
+ });
+
+ network.node.filter('.host').each(function(d) {
+ var node = d3.select(this),
+ icon = iconUrl(d);
+
+ // debug function to show the modelled x,y coordinates of nodes...
+ if (debug('showNodeXY')) {
+ node.select('circle').attr('fill-opacity', 0.5);
+ node.append('circle')
+ .attr({
+ class: 'debug',
+ cx: 0,
+ cy: 0,
+ r: '3px'
+ });
+ }
+ });
+
+ // this function is scheduled to happen soon after the given thread ends
+ setTimeout(function() {
+ var lab = config.labels,
+ portGap = lab.port.gap,
+ midW = portGap + lab.port.width/ 2,
+ midH = portGap + lab.port.height / 2;
+
+ // post process the device nodes, to pad their size to fit the
+ // label text and attach the icon to the right location.
+ network.node.filter('.device').each(function(d) {
+ // for every node, recompute size, padding, etc. so text fits
+ var node = d3.select(this),
+ text = node.select('text'),
+ box = adjustRectToFitText(node);
+
+ // now make the computed adjustment
+ node.select('rect')
+ .attr(box);
+
+ node.select('image')
+ .attr('x', box.x + config.icons.xoff)
+ .attr('y', box.y + config.icons.yoff);
+
+ var bounds = boundsFromBox(box),
+ portBounds = {
+ x1: bounds.x1 - midW,
+ x2: bounds.x2 + midW,
+ y1: bounds.y1 - midH,
+ y2: bounds.y2 + midH
+ };
+
+ // todo: clean up extent and edge work..
+ d.extent = {
+ left: bounds.x1 - lab.marginLR,
+ right: bounds.x2 + lab.marginLR,
+ top: bounds.y1 - lab.marginTB,
+ bottom: bounds.y2 + lab.marginTB
+ };
+
+ d.edge = {
+ left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2),
+ right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2),
+ top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1),
+ bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
+ };
+
+ d.portEdge = {
+ left : new geo.LineSegment(
+ portBounds.x1, portBounds.y1, portBounds.x1, portBounds.y2
+ ),
+ right : new geo.LineSegment(
+ portBounds.x2, portBounds.y1, portBounds.x2, portBounds.y2
+ ),
+ top : new geo.LineSegment(
+ portBounds.x1, portBounds.y1, portBounds.x2, portBounds.y1
+ ),
+ bottom : new geo.LineSegment(
+ portBounds.x1, portBounds.y2, portBounds.x2, portBounds.y2
+ )
+ };
+
+ });
+
+ network.numTicks = 0;
+ network.preventCollisions = false;
+ network.force.start();
+ for (var i = 0; i < config.force.ticksWithoutCollisions; i++) {
+ network.force.tick();
+ }
+ network.preventCollisions = true;
+ $('#view').css('visibility', 'visible');
+ });
+
+
+ // returns the newly computed bounding box of the rectangle
+ function adjustRectToFitText(n) {
+ var text = n.select('text'),
+ box = text.node().getBBox(),
+ lab = config.labels;
+
+ // not sure why n.data() returns an array of 1 element...
+ var data = n.data()[0];
+
+ text.attr('text-anchor', 'middle')
+ .attr('y', '-0.8em')
+ .attr('x', lab.imgPad/2)
+ ;
+
+ // translate the bbox so that it is centered on [x,y]
+ box.x = -box.width / 2;
+ box.y = -box.height / 2;
+
+ // add padding
+ box.x -= (lab.padLR + lab.imgPad/2);
+ box.width += lab.padLR * 2 + lab.imgPad;
+ box.y -= lab.padTB;
+ box.height += lab.padTB * 2;
+
+ return box;
+ }
+
+ function boundsFromBox(box) {
+ return {
+ x1: box.x,
+ y1: box.y,
+ x2: box.x + box.width,
+ y2: box.y + box.height
+ };
+ }
+
+ }
+
+ function iconUrl(d) {
+ return 'img/' + d.type + '.png';
+// return config.iconUrl[d.icon];
+ }
+
+ function translate(x, y) {
+ return 'translate(' + x + ',' + y + ')';
+ }
+
+ // prevents collisions amongst device nodes
+ function preventCollisions() {
+ var quadtree = d3.geom.quadtree(network.nodes),
+ hrad = config.hostRadius;
+
+ network.nodes.forEach(function(n) {
+ var nx1, nx2, ny1, ny2;
+
+ if (n.class === 'device') {
+ nx1 = n.x + n.extent.left;
+ nx2 = n.x + n.extent.right;
+ ny1 = n.y + n.extent.top;
+ ny2 = n.y + n.extent.bottom;
+
+ } else {
+ nx1 = n.x - hrad;
+ nx2 = n.x + hrad;
+ ny1 = n.y - hrad;
+ ny2 = n.y + hrad;
+ }
+
+ quadtree.visit(function(quad, x1, y1, x2, y2) {
+ if (quad.point && quad.point !== n) {
+ // check if the rectangles/circles intersect
+ var p = quad.point,
+ px1, px2, py1, py2, ix;
+
+ if (p.class === 'device') {
+ px1 = p.x + p.extent.left;
+ px2 = p.x + p.extent.right;
+ py1 = p.y + p.extent.top;
+ py2 = p.y + p.extent.bottom;
+
+ } else {
+ px1 = p.x - hrad;
+ px2 = p.x + hrad;
+ py1 = p.y - hrad;
+ py2 = p.y + hrad;
+ }
+
+ ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2);
+
+ if (ix) {
+ var xa1 = nx2 - px1, // shift n left , p right
+ xa2 = px2 - nx1, // shift n right, p left
+ ya1 = ny2 - py1, // shift n up , p down
+ ya2 = py2 - ny1, // shift n down , p up
+ adj = Math.min(xa1, xa2, ya1, ya2);
+
+ if (adj == xa1) {
+ n.x -= adj / 2;
+ p.x += adj / 2;
+ } else if (adj == xa2) {
+ n.x += adj / 2;
+ p.x -= adj / 2;
+ } else if (adj == ya1) {
+ n.y -= adj / 2;
+ p.y += adj / 2;
+ } else if (adj == ya2) {
+ n.y += adj / 2;
+ p.y -= adj / 2;
+ }
+ }
+ return ix;
+ }
+ });
+
+ });
+ }
+
+ function tick(e) {
+ network.numTicks++;
+
+ if (config.options.layering) {
+ // adjust the y-coord of each node, based on y-pos constraints
+ network.nodes.forEach(function (n) {
+ var z = e.alpha * n.constraint.weight;
+ if (!isNaN(n.constraint.y)) {
+ n.y = (n.constraint.y * z + n.y * (1 - z));
+ }
+ });
+ }
+
+ if (config.options.collisionPrevention && network.preventCollisions) {
+ preventCollisions();
+ }
+
+ var portHalfW = config.labels.port.width / 2,
+ portHalfH = config.labels.port.height / 2;
+
+ // clip visualization of links at bounds of nodes...
+ network.link.each(function(d) {
+ var xs = d.source.x,
+ ys = d.source.y,
+ xt = d.target.x,
+ yt = d.target.y,
+ line = new geo.LineSegment(xs, ys, xt, yt),
+ e, ix,
+ exs, eys, ext, eyt,
+ pxs, pys, pxt, pyt;
+
+ if (d.class === 'host') {
+ // no adjustment for source end of link, since hosts are dots
+ exs = xs;
+ eys = ys;
+
+ } else {
+ for (e in d.source.edge) {
+ ix = line.intersect(d.source.edge[e].offset(xs, ys));
+ if (ix.in1 && ix.in2) {
+ exs = ix.x;
+ eys = ix.y;
+
+ // also pick off the port label intersection
+ ix = line.intersect(d.source.portEdge[e].offset(xs, ys));
+ pxs = ix.x;
+ pys = ix.y;
+ break;
+ }
+ }
+ }
+
+ for (e in d.target.edge) {
+ ix = line.intersect(d.target.edge[e].offset(xt, yt));
+ if (ix.in1 && ix.in2) {
+ ext = ix.x;
+ eyt = ix.y;
+
+ // also pick off the port label intersection
+ ix = line.intersect(d.target.portEdge[e].offset(xt, yt));
+ pxt = ix.x;
+ pyt = ix.y;
+ break;
+ }
+ }
+
+ // adjust the endpoints of the link's line to match rectangles
+ var sid = safeId(d.id);
+ d3.select(this)
+ .attr('x1', exs)
+ .attr('y1', eys)
+ .attr('x2', ext)
+ .attr('y2', eyt);
+
+ d3.select('#srcPort-' + sid)
+ .attr('x', pxs - portHalfW)
+ .attr('y', pys - portHalfH);
+
+ d3.select('#tgtPort-' + sid)
+ .attr('x', pxt - portHalfW)
+ .attr('y', pyt - portHalfH);
+
+ // TODO: fit label rect to size of port number.
+ d3.select('#srcText-' + sid)
+ .attr('x', pxs - 5)
+ .attr('y', pys + 3);
+
+ d3.select('#tgtText-' + sid)
+ .attr('x', pxt - 5)
+ .attr('y', pyt + 3);
+
+ });
+
+ // position each node by translating the node (group) by x,y
+ network.node
+ .attr('transform', function(d) {
+ return translate(d.x, d.y);
+ });
+
+ }
+
+ // $('#docs-close').on('click', function() {
+ // deselectObject();
+ // return false;
+ // });
+
+ // $(document).on('click', '.select-object', function() {
+ // var obj = graph.data[$(this).data('name')];
+ // if (obj) {
+ // selectObject(obj);
+ // }
+ // return false;
+ // });
+
+ function findNodeFromData(d) {
+ var el = null;
+ network.node.filter('.' + d.class).each(function(n) {
+ if (n.id === d.id) {
+ el = d3.select(this);
+ }
+ });
+ return el;
+ }
+
+ function selectObject(obj, el) {
+ var node;
+ if (el) {
+ node = d3.select(el);
+ } else {
+ network.node.each(function(d) {
+ if (d == obj) {
+ node = d3.select(el = this);
+ }
+ });
+ }
+ if (!node) return;
+
+ if (node.classed('selected')) {
+ deselectObject();
+ flyinPane(null);
+ return;
+ }
+ deselectObject(false);
+
+ selected = {
+ obj : obj,
+ el : el
+ };
+
+ node.classed('selected', true);
+ flyinPane(obj);
+ }
+
+ function deselectObject(doResize) {
+ // Review: logic of 'resize(...)' function.
+ if (doResize || typeof doResize == 'undefined') {
+ resize(false);
+ }
+
+ // deselect all nodes in the network...
+ network.node.classed('selected', false);
+ selected = {};
+ flyinPane(null);
+ }
+
+ function flyinPane(obj) {
+ var pane = d3.select('#flyout'),
+ url;
+
+ if (obj) {
+ // go get details of the selected object from the server...
+ url = detailJsonUrl(obj.id);
+ d3.json(url, function (err, data) {
+ if (err) {
+ alert('Oops! Error reading JSON...\n\n' +
+ 'URL: ' + url + '\n\n' +
+ 'Error: ' + err.message);
+ return;
+ }
+// console.log("JSON data... " + url);
+// console.log(data);
+
+ displayDetails(data, pane);
+ });
+
+ } else {
+ // hide pane
+ pane.transition().duration(750)
+ .style('right', '-320px')
+ .style('opacity', 0.0);
+ }
+ }
+
+ function displayDetails(data, pane) {
+ $('#flyout').empty();
+
+ var title = pane.append("h2"),
+ table = pane.append("table"),
+ tbody = table.append("tbody");
+
+ $('<img src="img/' + data.type + '.png">').appendTo(title);
+ $('<span>').attr('class', 'icon').text(data.id).appendTo(title);
+
+
+ // TODO: consider using d3 data bind to TR/TD
+
+ data.propOrder.forEach(function(p) {
+ if (p === '-') {
+ addSep(tbody);
+ } else {
+ 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);
+ }
+
+ // show pane
+ pane.transition().duration(750)
+ .style('right', '20px')
+ .style('opacity', 1.0);
+ }
+
+ function highlightObject(obj) {
+ if (obj) {
+ if (obj != highlighted) {
+ // TODO set or clear "inactive" class on nodes, based on criteria
+ network.node.classed('inactive', function(d) {
+ // return (obj !== d &&
+ // d.relation(obj.id));
+ return (obj !== d);
+ });
+ // TODO: same with links
+ network.link.classed('inactive', function(d) {
+ return (obj !== d.source && obj !== d.target);
+ });
+ }
+ highlighted = obj;
+ } else {
+ if (highlighted) {
+ // clear the inactive flag (no longer suppressed visually)
+ network.node.classed('inactive', false);
+ network.link.classed('inactive', false);
+ }
+ highlighted = null;
+
+ }
+ }
+
+ function hoverObject(obj) {
+ if (obj) {
+ hovered = obj;
+ } else {
+ if (hovered) {
+ hovered = null;
+ }
+ }
+ }
+
+
+ function resize() {
+ view.height = window.innerHeight - config.mastHeight;
+ view.width = window.innerWidth;
+ $('#view')
+ .css('height', view.height + 'px')
+ .css('width', view.width + 'px');
+
+ network.forceWidth = view.width - config.force.marginLR;
+ network.forceHeight = view.height - config.force.marginTB;
+ }
+
+ // ======================================================================
+ // register with the UI framework
+
+ api.addView('network', {
+ load: loadNetworkView
+ });
+
+
+}(ONOS));
+