weibit

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

Showing 29 changed files with 2356 additions and 228 deletions
......@@ -53,11 +53,13 @@ public class HostToInterfaceAdaptor implements InterfaceService {
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;
......
......@@ -23,6 +23,7 @@ import static org.junit.Assert.assertEquals;
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 @@ public class HostToInterfaceAdaptorTest {
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 @@ public class HostToInterfaceAdaptorTest {
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 @@ public class HostToInterfaceAdaptorTest {
// 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));
......
......@@ -34,11 +34,7 @@ public interface HostAdminService {
* 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
......
......@@ -135,7 +135,7 @@ public interface HostService {
* @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.
......
......@@ -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.IpAddress;
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 @@ public interface HostStore extends Store<HostEvent, HostStoreDelegate> {
* for
* @return address information for the connection point
*/
PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint);
Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint);
}
......
......@@ -95,7 +95,7 @@ public class HostServiceAdapter implements HostService {
}
@Override
public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
return null;
}
......
......@@ -207,7 +207,7 @@ public class HostManager
}
@Override
public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
return store.getAddressBindingsForPort(connectPoint);
}
......
......@@ -175,13 +175,15 @@ public class HostMonitor implements TimerTask {
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());
}
}
}
}
......
......@@ -134,14 +134,16 @@ public class ProxyArpManager implements ProxyArpService {
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 @@ public class ProxyArpManager implements ProxyArpService {
// 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
......
......@@ -234,10 +234,10 @@ public class HostManagerTest {
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 @@ public class HostManagerTest {
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 @@ public class HostManagerTest {
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 @@ public class HostManagerTest {
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 @@ public class HostManagerTest {
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 @@ public class HostManagerTest {
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
......
......@@ -20,7 +20,9 @@ import static org.easymock.EasyMock.expect;
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 @@ public class HostMonitorTest {
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();
......
......@@ -19,7 +19,10 @@ import static org.easymock.EasyMock.anyObject;
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 @@ public class ProxyArpManagerTest {
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 @@ public class ProxyArpManagerTest {
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 @@ public class ProxyArpManagerTest {
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 @@ public class ProxyArpManagerTest {
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 @@ public class ProxyArpManagerTest {
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));
}
......
......@@ -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.HostDescription;
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.packet.VlanId;
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 @@ public class GossipHostStore
// 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 @@ public class GossipHostStore
@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.
......
......@@ -447,13 +447,16 @@ implements MastershipStore {
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 {
......
......@@ -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.HostDescription;
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.MacAddress;
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 @@ public class SimpleHostStore
// 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 @@ public class SimpleHostStore
@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.
......
......@@ -5,7 +5,7 @@ export ONOS_ROOT=${ONOS_ROOT:-~/onos-next}
# 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)
......
......@@ -8,7 +8,9 @@ export ONOS_ROOT=${ONOS_ROOT:-~/onos-next}
# 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 @@ fi
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
......
......@@ -6,4 +6,4 @@ export OC2="192.168.56.102"
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}"
......
......@@ -7,4 +7,4 @@ export OC3="192.168.56.104"
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}"
......
/*
* 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);
};
})();
<!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>
/*
* 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;
}
/*
* 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
/*
* 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;
}
/*
* 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
/*
* 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];
}
}());
/*
* 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));
/*
* 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
*/
/*
* 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));