Madan Jampani

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

Showing 33 changed files with 763 additions and 145 deletions
......@@ -30,6 +30,7 @@ import org.onlab.onos.core.CoreService;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.onos.sdnip.bgp.BgpRouteEntry;
import org.onlab.onos.sdnip.bgp.BgpSession;
import org.onlab.onos.sdnip.bgp.BgpSessionManager;
import org.onlab.onos.sdnip.config.SdnIpConfigReader;
import org.slf4j.Logger;
......@@ -97,6 +98,11 @@ public class SdnIp implements SdnIpService {
}
@Override
public Collection<BgpSession> getBgpSessions() {
return bgpSessionManager.getBgpSessions();
}
@Override
public Collection<BgpRouteEntry> getBgpRoutes() {
return bgpSessionManager.getBgpRoutes();
}
......
......@@ -18,12 +18,20 @@ package org.onlab.onos.sdnip;
import java.util.Collection;
import org.onlab.onos.sdnip.bgp.BgpRouteEntry;
import org.onlab.onos.sdnip.bgp.BgpSession;
/**
* Service interface exported by SDN-IP.
*/
public interface SdnIpService {
/**
* Gets the BGP sessions.
*
* @return the BGP sessions
*/
public Collection<BgpSession> getBgpSessions();
/**
* Gets the BGP routes.
*
* @return the BGP routes
......
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.sdnip.cli;
import java.util.Collection;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Command;
import org.apache.karaf.shell.commands.Option;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.sdnip.SdnIpService;
import org.onlab.onos.sdnip.bgp.BgpSession;
/**
* Command to show the BGP neighbors.
*/
@Command(scope = "onos", name = "bgp-neighbors",
description = "Lists the BGP neighbors")
public class BgpNeighborsListCommand extends AbstractShellCommand {
@Option(name = "-n", aliases = "--neighbor",
description = "BGP neighbor to display information about",
required = false, multiValued = false)
private String bgpNeighbor;
private static final String FORMAT_NEIGHBOR_LINE1 =
"BGP neighbor is %s, remote AS %d, local AS %d";
private static final String FORMAT_NEIGHBOR_LINE2 =
" Remote router ID %s, IP %s, BGP version %d, Hold time %d";
private static final String FORMAT_NEIGHBOR_LINE3 =
" Local router ID %s, IP %s, BGP version %d, Hold time %d";
@Override
protected void execute() {
SdnIpService service = get(SdnIpService.class);
Collection<BgpSession> bgpSessions = service.getBgpSessions();
if (bgpNeighbor != null) {
// Print a single neighbor (if found)
BgpSession foundBgpSession = null;
for (BgpSession bgpSession : bgpSessions) {
if (bgpSession.getRemoteBgpId().toString().equals(bgpNeighbor)) {
foundBgpSession = bgpSession;
break;
}
}
if (foundBgpSession != null) {
printNeighbor(foundBgpSession);
} else {
print("BGP neighbor %s not found", bgpNeighbor);
}
return;
}
// Print all neighbors
printNeighbors(bgpSessions);
}
/**
* Prints all BGP neighbors.
*
* @param bgpSessions the BGP sessions for the neighbors to print
*/
private void printNeighbors(Collection<BgpSession> bgpSessions) {
if (outputJson()) {
print("%s", json(bgpSessions));
} else {
for (BgpSession bgpSession : bgpSessions) {
printNeighbor(bgpSession);
}
}
}
/**
* Prints a BGP neighbor.
*
* @param bgpSession the BGP session for the neighbor to print
*/
private void printNeighbor(BgpSession bgpSession) {
print(FORMAT_NEIGHBOR_LINE1,
bgpSession.getRemoteBgpId().toString(),
bgpSession.getRemoteAs(),
bgpSession.getLocalAs());
print(FORMAT_NEIGHBOR_LINE2,
bgpSession.getRemoteBgpId().toString(),
bgpSession.getRemoteAddress().toString(),
bgpSession.getRemoteBgpVersion(),
bgpSession.getRemoteHoldtime());
print(FORMAT_NEIGHBOR_LINE3,
bgpSession.getLocalBgpId().toString(),
bgpSession.getLocalAddress().toString(),
bgpSession.getLocalBgpVersion(),
bgpSession.getLocalHoldtime());
}
/**
* Produces a JSON array of BGP neighbors.
*
* @param bgpSessions the BGP sessions with the data
* @return JSON array with the neighbors
*/
private JsonNode json(Collection<BgpSession> bgpSessions) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (BgpSession bgpSession : bgpSessions) {
result.add(json(mapper, bgpSession));
}
return result;
}
/**
* Produces JSON object for a BGP neighbor.
*
* @param mapper the JSON object mapper to use
* @param bgpSession the BGP session with the data
* @return JSON object for the route
*/
private ObjectNode json(ObjectMapper mapper, BgpSession bgpSession) {
ObjectNode result = mapper.createObjectNode();
result.put("remoteAddress", bgpSession.getRemoteAddress().toString());
result.put("remoteBgpVersion", bgpSession.getRemoteBgpVersion());
result.put("remoteAs", bgpSession.getRemoteAs());
result.put("remoteHoldtime", bgpSession.getRemoteHoldtime());
result.put("remoteBgpId", bgpSession.getRemoteBgpId().toString());
//
result.put("localAddress", bgpSession.getLocalAddress().toString());
result.put("localBgpVersion", bgpSession.getLocalBgpVersion());
result.put("localAs", bgpSession.getLocalAs());
result.put("localHoldtime", bgpSession.getLocalHoldtime());
result.put("localBgpId", bgpSession.getLocalBgpId().toString());
return result;
}
}
......@@ -17,6 +17,9 @@
<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
<command>
<action class="org.onlab.onos.sdnip.cli.BgpNeighborsListCommand"/>
</command>
<command>
<action class="org.onlab.onos.sdnip.cli.BgpRoutesListCommand"/>
</command>
<command>
......
......@@ -61,8 +61,8 @@ public class DefaultTopologyProvider extends AbstractProvider
// TODO: make these configurable
private static final int MAX_EVENTS = 100;
private static final int MAX_IDLE_MS = 50;
private static final int MAX_BATCH_MS = 200;
private static final int MAX_IDLE_MS = 5;
private static final int MAX_BATCH_MS = 50;
private static final int MAX_THREADS = 8;
// FIXME: Replace with a system-wide timer instance;
......
......@@ -34,10 +34,10 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.serializers.ClusterMessageSerializer;
import org.onlab.onos.store.serializers.KryoNamespaces;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.onos.store.serializers.MessageSubjectSerializer;
import org.onlab.onos.store.serializers.impl.ClusterMessageSerializer;
import org.onlab.onos.store.serializers.impl.MessageSubjectSerializer;
import org.onlab.util.KryoNamespace;
import org.onlab.netty.Endpoint;
import org.onlab.netty.Message;
......
......@@ -59,7 +59,7 @@ import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.impl.Timestamped;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.onos.store.serializers.DistributedStoreSerializers;
import org.onlab.onos.store.serializers.impl.DistributedStoreSerializers;
import org.onlab.packet.ChassisId;
import org.onlab.util.KryoNamespace;
import org.onlab.util.NewConcurrentHashMap;
......@@ -230,9 +230,10 @@ public class GossipDeviceStore
final Timestamped<DeviceDescription> deltaDesc = new Timestamped<>(deviceDescription, newTimestamp);
final DeviceEvent event;
final Timestamped<DeviceDescription> mergedDesc;
synchronized (getOrCreateDeviceDescriptionsMap(deviceId)) {
final Map<ProviderId, DeviceDescriptions> device = getOrCreateDeviceDescriptionsMap(deviceId);
synchronized (device) {
event = createOrUpdateDeviceInternal(providerId, deviceId, deltaDesc);
mergedDesc = getOrCreateDeviceDescriptionsMap(deviceId).get(providerId).getDeviceDesc();
mergedDesc = device.get(providerId).getDeviceDesc();
}
if (event != null) {
log.info("Notifying peers of a device update topology event for providerId: {} and deviceId: {}",
......@@ -252,10 +253,10 @@ public class GossipDeviceStore
Timestamped<DeviceDescription> deltaDesc) {
// Collection of DeviceDescriptions for a Device
Map<ProviderId, DeviceDescriptions> providerDescs
Map<ProviderId, DeviceDescriptions> device
= getOrCreateDeviceDescriptionsMap(deviceId);
synchronized (providerDescs) {
synchronized (device) {
// locking per device
if (isDeviceRemoved(deviceId, deltaDesc.timestamp())) {
......@@ -263,7 +264,7 @@ public class GossipDeviceStore
return null;
}
DeviceDescriptions descs = getOrCreateProviderDeviceDescriptions(providerDescs, providerId, deltaDesc);
DeviceDescriptions descs = getOrCreateProviderDeviceDescriptions(device, providerId, deltaDesc);
final Device oldDevice = devices.get(deviceId);
final Device newDevice;
......@@ -272,7 +273,7 @@ public class GossipDeviceStore
deltaDesc.isNewer(descs.getDeviceDesc())) {
// on new device or valid update
descs.putDeviceDesc(deltaDesc);
newDevice = composeDevice(deviceId, providerDescs);
newDevice = composeDevice(deviceId, device);
} else {
// outdated event, ignored.
return null;
......@@ -444,9 +445,10 @@ public class GossipDeviceStore
final List<DeviceEvent> events;
final Timestamped<List<PortDescription>> merged;
synchronized (getOrCreateDeviceDescriptionsMap(deviceId)) {
final Map<ProviderId, DeviceDescriptions> device = getOrCreateDeviceDescriptionsMap(deviceId);
synchronized (device) {
events = updatePortsInternal(providerId, deviceId, timestampedInput);
final DeviceDescriptions descs = getOrCreateDeviceDescriptionsMap(deviceId).get(providerId);
final DeviceDescriptions descs = device.get(providerId);
List<PortDescription> mergedList =
FluentIterable.from(portDescriptions)
.transform(new Function<PortDescription, PortDescription>() {
......@@ -632,9 +634,10 @@ public class GossipDeviceStore
= new Timestamped<>(portDescription, newTimestamp);
final DeviceEvent event;
final Timestamped<PortDescription> mergedDesc;
synchronized (getOrCreateDeviceDescriptionsMap(deviceId)) {
final Map<ProviderId, DeviceDescriptions> device = getOrCreateDeviceDescriptionsMap(deviceId);
synchronized (device) {
event = updatePortStatusInternal(providerId, deviceId, deltaDesc);
mergedDesc = getOrCreateDeviceDescriptionsMap(deviceId).get(providerId)
mergedDesc = device.get(providerId)
.getPortDesc(portDescription.portNumber());
}
if (event != null) {
......
......@@ -75,9 +75,9 @@ import org.onlab.onos.store.flow.ReplicaInfoService;
import org.onlab.onos.store.hz.AbstractHazelcastStore;
import org.onlab.onos.store.hz.SMap;
import org.onlab.onos.store.serializers.DecodeTo;
import org.onlab.onos.store.serializers.DistributedStoreSerializers;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.onos.store.serializers.StoreSerializer;
import org.onlab.onos.store.serializers.impl.DistributedStoreSerializers;
import org.onlab.util.KryoNamespace;
import org.slf4j.Logger;
......
......@@ -67,8 +67,8 @@ import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.impl.Timestamped;
import org.onlab.onos.store.serializers.DistributedStoreSerializers;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.onos.store.serializers.impl.DistributedStoreSerializers;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
......
......@@ -55,8 +55,8 @@ import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.impl.Timestamped;
import org.onlab.onos.store.serializers.DistributedStoreSerializers;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.onos.store.serializers.impl.DistributedStoreSerializers;
import org.onlab.util.KryoNamespace;
import org.slf4j.Logger;
......
......@@ -73,7 +73,7 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
* @param link the target link
* @return free resources
*/
private Set<ResourceAllocation> readOriginalFreeResources(Link link) {
private synchronized Set<ResourceAllocation> readOriginalFreeResources(Link link) {
// TODO read capacity and lambda resources from topology
Set<ResourceAllocation> allocations = new HashSet<>();
for (int i = 1; i <= 100; i++) {
......@@ -92,7 +92,7 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
* {@link org.onlab.onos.net.resource.BandwidthResourceAllocation} object with 0 bandwidth
*
*/
private BandwidthResourceAllocation getBandwidth(Set<ResourceAllocation> freeRes) {
private synchronized BandwidthResourceAllocation getBandwidth(Set<ResourceAllocation> freeRes) {
for (ResourceAllocation res : freeRes) {
if (res.type() == ResourceType.BANDWIDTH) {
return (BandwidthResourceAllocation) res;
......@@ -107,7 +107,7 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
* @param link the target link
* @param allocations the resources to be subtracted
*/
private void subtractFreeResources(Link link, LinkResourceAllocations allocations) {
private synchronized void subtractFreeResources(Link link, LinkResourceAllocations allocations) {
// TODO Use lock or version for updating freeResources.
checkNotNull(link);
Set<ResourceAllocation> freeRes = new HashSet<>(getFreeResources(link));
......@@ -141,7 +141,7 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
* @param link the target link
* @param allocations the resources to be added
*/
private void addFreeResources(Link link, LinkResourceAllocations allocations) {
private synchronized void addFreeResources(Link link, LinkResourceAllocations allocations) {
// TODO Use lock or version for updating freeResources.
Set<ResourceAllocation> freeRes = new HashSet<>(getFreeResources(link));
Set<ResourceAllocation> addRes = allocations.getResourceAllocation(link);
......@@ -167,7 +167,7 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
}
@Override
public Set<ResourceAllocation> getFreeResources(Link link) {
public synchronized Set<ResourceAllocation> getFreeResources(Link link) {
checkNotNull(link);
Set<ResourceAllocation> freeRes = freeResources.get(link);
if (freeRes == null) {
......@@ -178,7 +178,7 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
}
@Override
public void allocateResources(LinkResourceAllocations allocations) {
public synchronized void allocateResources(LinkResourceAllocations allocations) {
checkNotNull(allocations);
linkResourceAllocationsMap.put(allocations.intendId(), allocations);
for (Link link : allocations.links()) {
......@@ -193,7 +193,7 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
}
@Override
public void releaseResources(LinkResourceAllocations allocations) {
public synchronized void releaseResources(LinkResourceAllocations allocations) {
checkNotNull(allocations);
linkResourceAllocationsMap.remove(allocations.intendId());
for (Link link : allocations.links()) {
......@@ -209,13 +209,13 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
}
@Override
public LinkResourceAllocations getAllocations(IntentId intentId) {
public synchronized LinkResourceAllocations getAllocations(IntentId intentId) {
checkNotNull(intentId);
return linkResourceAllocationsMap.get(intentId);
}
@Override
public Iterable<LinkResourceAllocations> getAllocations(Link link) {
public synchronized Iterable<LinkResourceAllocations> getAllocations(Link link) {
checkNotNull(link);
Set<LinkResourceAllocations> result = allocatedResources.get(link);
if (result == null) {
......@@ -225,7 +225,7 @@ public class DistributedLinkResourceStore implements LinkResourceStore {
}
@Override
public Iterable<LinkResourceAllocations> getAllocations() {
public synchronized Iterable<LinkResourceAllocations> getAllocations() {
return Collections.unmodifiableCollection(linkResourceAllocationsMap.values());
}
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.store.serializers;
package org.onlab.onos.store.serializers.impl;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
......
......@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.store.serializers;
package org.onlab.onos.store.serializers.impl;
import org.onlab.onos.store.impl.MastershipBasedTimestamp;
import org.onlab.onos.store.impl.Timestamped;
import org.onlab.onos.store.impl.WallClockTimestamp;
import org.onlab.onos.store.serializers.KryoNamespaces;
import org.onlab.util.KryoNamespace;
public final class DistributedStoreSerializers {
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.store.serializers;
package org.onlab.onos.store.serializers.impl;
import org.onlab.onos.store.impl.MastershipBasedTimestamp;
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.store.serializers;
package org.onlab.onos.store.serializers.impl;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
......
......@@ -85,7 +85,7 @@ public class ClusterMessagingProtocolClient implements ProtocolClient {
return CompletableFuture.completedFuture(null);
}
public <I> MessageSubject messageType(I input) {
private <I> MessageSubject messageType(I input) {
Class<?> clazz = input.getClass();
if (clazz.equals(PollRequest.class)) {
return ClusterMessagingProtocol.COPYCAT_POLL;
......@@ -117,7 +117,7 @@ public class ClusterMessagingProtocolClient implements ProtocolClient {
this.request = request;
this.message =
new ClusterMessage(
null,
null, // FIXME fill in proper sender
messageType(request),
ClusterMessagingProtocol.SERIALIZER.encode(request));
this.future = future;
......@@ -132,19 +132,20 @@ public class ClusterMessagingProtocolClient implements ProtocolClient {
future.complete(ClusterMessagingProtocol.SERIALIZER.decode(response));
} catch (IOException | InterruptedException | ExecutionException | TimeoutException e) {
if (message.subject().equals(ClusterMessagingProtocol.COPYCAT_SYNC) ||
message.subject().equals(ClusterMessagingProtocol.COPYCAT_PING)) {
log.warn("{} Request to {} failed. Will retry in {} ms",
message.subject(), remoteNode, RETRY_INTERVAL_MILLIS);
THREAD_POOL.schedule(
this,
RETRY_INTERVAL_MILLIS,
TimeUnit.MILLISECONDS);
} else {
// if (message.subject().equals(ClusterMessagingProtocol.COPYCAT_SYNC) ||
// message.subject().equals(ClusterMessagingProtocol.COPYCAT_PING)) {
// log.warn("{} Request to {} failed. Will retry in {} ms",
// message.subject(), remoteNode, RETRY_INTERVAL_MILLIS);
// THREAD_POOL.schedule(
// this,
// RETRY_INTERVAL_MILLIS,
// TimeUnit.MILLISECONDS);
// } else {
log.warn("RPCTask for {} failed.", request, e);
future.completeExceptionally(e);
}
// }
} catch (Exception e) {
log.warn("RPCTask for {} terribly failed.", request, e);
future.completeExceptionally(e);
}
}
......
......@@ -67,14 +67,36 @@ public class ClusterMessagingProtocolServer implements ProtocolServer {
@Override
public void handle(ClusterMessage message) {
T request = ClusterMessagingProtocol.SERIALIZER.decode(message.payload());
if (handler == null) {
// there is a slight window of time during state transition,
// where handler becomes null
for (int i = 0; i < 10; ++i) {
if (handler != null) {
break;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
log.trace("Exception", e);
}
}
if (handler == null) {
log.error("There was no handler for registered!");
return;
}
}
if (request.getClass().equals(PingRequest.class)) {
handler.ping((PingRequest) request).whenComplete(new PostExecutionTask<PingResponse>(message));
handler.ping((PingRequest) request)
.whenComplete(new PostExecutionTask<PingResponse>(message));
} else if (request.getClass().equals(PollRequest.class)) {
handler.poll((PollRequest) request).whenComplete(new PostExecutionTask<PollResponse>(message));
handler.poll((PollRequest) request)
.whenComplete(new PostExecutionTask<PollResponse>(message));
} else if (request.getClass().equals(SyncRequest.class)) {
handler.sync((SyncRequest) request).whenComplete(new PostExecutionTask<SyncResponse>(message));
handler.sync((SyncRequest) request)
.whenComplete(new PostExecutionTask<SyncResponse>(message));
} else if (request.getClass().equals(SubmitRequest.class)) {
handler.submit((SubmitRequest) request).whenComplete(new PostExecutionTask<SubmitResponse>(message));
handler.submit((SubmitRequest) request)
.whenComplete(new PostExecutionTask<SubmitResponse>(message));
} else {
throw new IllegalStateException("Unknown request type: " + request.getClass().getName());
}
......@@ -94,6 +116,7 @@ public class ClusterMessagingProtocolServer implements ProtocolServer {
log.error("Processing for " + message.subject() + " failed.", t);
} else {
try {
log.trace("responding to {}", message.subject());
message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response));
} catch (Exception e) {
log.error("Failed to respond to " + response.getClass().getName(), e);
......
......@@ -21,7 +21,7 @@ import java.nio.ByteBuffer;
import org.junit.Test;
import org.onlab.onos.store.Timestamp;
import org.onlab.onos.store.serializers.MastershipBasedTimestampSerializer;
import org.onlab.onos.store.serializers.impl.MastershipBasedTimestampSerializer;
import org.onlab.util.KryoNamespace;
import com.google.common.testing.EqualsTester;
......
......@@ -73,7 +73,7 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
* @param link the target link
* @return free resources
*/
private Set<ResourceAllocation> readOriginalFreeResources(Link link) {
private synchronized Set<ResourceAllocation> readOriginalFreeResources(Link link) {
// TODO read capacity and lambda resources from topology
Set<ResourceAllocation> allocations = new HashSet<>();
for (int i = 1; i <= 100; i++) {
......@@ -92,7 +92,7 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
* {@link BandwidthResourceAllocation} object with 0 bandwidth
*
*/
private BandwidthResourceAllocation getBandwidth(Set<ResourceAllocation> freeRes) {
private synchronized BandwidthResourceAllocation getBandwidth(Set<ResourceAllocation> freeRes) {
for (ResourceAllocation res : freeRes) {
if (res.type() == ResourceType.BANDWIDTH) {
return (BandwidthResourceAllocation) res;
......@@ -107,7 +107,7 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
* @param link the target link
* @param allocations the resources to be subtracted
*/
private void subtractFreeResources(Link link, LinkResourceAllocations allocations) {
private synchronized void subtractFreeResources(Link link, LinkResourceAllocations allocations) {
// TODO Use lock or version for updating freeResources.
checkNotNull(link);
Set<ResourceAllocation> freeRes = new HashSet<>(getFreeResources(link));
......@@ -141,7 +141,7 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
* @param link the target link
* @param allocations the resources to be added
*/
private void addFreeResources(Link link, LinkResourceAllocations allocations) {
private synchronized void addFreeResources(Link link, LinkResourceAllocations allocations) {
// TODO Use lock or version for updating freeResources.
Set<ResourceAllocation> freeRes = new HashSet<>(getFreeResources(link));
Set<ResourceAllocation> addRes = allocations.getResourceAllocation(link);
......@@ -167,7 +167,7 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
}
@Override
public Set<ResourceAllocation> getFreeResources(Link link) {
public synchronized Set<ResourceAllocation> getFreeResources(Link link) {
checkNotNull(link);
Set<ResourceAllocation> freeRes = freeResources.get(link);
if (freeRes == null) {
......@@ -178,7 +178,7 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
}
@Override
public void allocateResources(LinkResourceAllocations allocations) {
public synchronized void allocateResources(LinkResourceAllocations allocations) {
checkNotNull(allocations);
linkResourceAllocationsMap.put(allocations.intendId(), allocations);
for (Link link : allocations.links()) {
......@@ -193,7 +193,7 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
}
@Override
public void releaseResources(LinkResourceAllocations allocations) {
public synchronized void releaseResources(LinkResourceAllocations allocations) {
checkNotNull(allocations);
linkResourceAllocationsMap.remove(allocations.intendId());
for (Link link : allocations.links()) {
......@@ -209,13 +209,13 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
}
@Override
public LinkResourceAllocations getAllocations(IntentId intentId) {
public synchronized LinkResourceAllocations getAllocations(IntentId intentId) {
checkNotNull(intentId);
return linkResourceAllocationsMap.get(intentId);
}
@Override
public Iterable<LinkResourceAllocations> getAllocations(Link link) {
public synchronized Iterable<LinkResourceAllocations> getAllocations(Link link) {
checkNotNull(link);
Set<LinkResourceAllocations> result = allocatedResources.get(link);
if (result == null) {
......@@ -225,7 +225,7 @@ public class SimpleLinkResourceStore implements LinkResourceStore {
}
@Override
public Iterable<LinkResourceAllocations> getAllocations() {
public synchronized Iterable<LinkResourceAllocations> getAllocations() {
return Collections.unmodifiableCollection(linkResourceAllocationsMap.values());
}
......
......@@ -316,7 +316,11 @@ public class NettyMessagingService implements MessagingService {
return;
}
MessageHandler handler = NettyMessagingService.this.getMessageHandler(type);
handler.handle(message);
if (handler != null) {
handler.handle(message);
} else {
log.debug("No handler registered for {}", type);
}
}
@Override
......
......@@ -15,8 +15,20 @@
*/
package org.onlab.onos.gui;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.jetty.websocket.WebSocket;
import org.onlab.onos.event.Event;
import org.onlab.onos.net.Annotations;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.topology.Topology;
import org.onlab.onos.net.topology.TopologyEdge;
import org.onlab.onos.net.topology.TopologyEvent;
......@@ -27,6 +39,15 @@ import org.onlab.onos.net.topology.TopologyVertex;
import org.onlab.osgi.ServiceDirectory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
/**
* Web socket capable of interacting with the GUI topology view.
......@@ -37,8 +58,16 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
private final TopologyService topologyService;
private final DeviceService deviceService;
private final ObjectMapper mapper = new ObjectMapper();
private Connection connection;
// TODO: extract into an external & durable state; good enough for now and demo
private static Map<String, ObjectNode> metaUi = new HashMap<>();
private static final String COMPACT = "%s/%s-%s/%s";
/**
* Creates a new web-socket for serving data to GUI topology view.
*
......@@ -58,22 +87,19 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
if (topologyService != null && deviceService != null) {
topologyService.addListener(this);
sendMessage("Yo!!!");
Topology topology = topologyService.currentTopology();
TopologyGraph graph = topologyService.getGraph(topology);
for (TopologyVertex vertex : graph.getVertexes()) {
sendMessage(deviceService.getDevice(vertex.deviceId()).toString());
sendMessage(message(new DeviceEvent(DEVICE_ADDED,
deviceService.getDevice(vertex.deviceId()))));
}
for (TopologyEdge edge : graph.getEdges()) {
sendMessage(edge.link().toString());
sendMessage(message(new LinkEvent(LINK_ADDED, edge.link())));
}
sendMessage("That's what we're starting with...");
} else {
sendMessage("No topology service!!!");
sendMessage(message("error", "No topology service!!!"));
}
}
......@@ -87,10 +113,57 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
@Override
public void onMessage(String data) {
System.out.println("Received: " + data);
try {
ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
String type = event.path("event").asText("unknown");
ObjectNode payload = (ObjectNode) event.path("payload");
switch (type) {
case "updateMeta":
metaUi.put(payload.path("id").asText(), payload);
break;
case "requestPath":
findPath(deviceId(payload.path("one").asText()),
deviceId(payload.path("two").asText()));
default:
break;
}
} catch (IOException e) {
System.out.println("Received: " + data);
}
}
private void findPath(DeviceId one, DeviceId two) {
Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(),
one, two);
if (!paths.isEmpty()) {
ObjectNode payload = mapper.createObjectNode();
ArrayNode links = mapper.createArrayNode();
Path path = paths.iterator().next();
for (Link link : path.links()) {
links.add(compactLinkString(link));
}
payload.set("links", links);
sendMessage(envelope("showPath", payload));
}
// TODO: when no path, send a message to the client
}
/**
* Returns a compact string representing the given link.
*
* @param link infrastructure link
* @return formatted link string
*/
public static String compactLinkString(Link link) {
return String.format(COMPACT, link.src().deviceId(), link.src().port(),
link.dst().deviceId(), link.dst().port());
}
public void sendMessage(String data) {
private void sendMessage(String data) {
try {
connection.sendMessage(data);
} catch (IOException e) {
......@@ -98,9 +171,84 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
}
}
// Produces a link event message to the client.
private String message(DeviceEvent event) {
Device device = event.subject();
ObjectNode payload = mapper.createObjectNode()
.put("id", device.id().toString())
.put("type", device.type().toString().toLowerCase())
.put("online", deviceService.isAvailable(device.id()));
// Generate labels: id, chassis id, no-label, optional-name
ArrayNode labels = mapper.createArrayNode();
labels.add(device.id().toString());
labels.add(device.chassisId().toString());
labels.add(" "); // compact no-label view
labels.add(device.annotations().value("name"));
// Add labels, props and stuff the payload into envelope.
payload.set("labels", labels);
payload.set("props", props(device.annotations()));
ObjectNode meta = metaUi.get(device.id().toString());
if (meta != null) {
payload.set("metaUi", meta);
}
String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
return envelope(type, payload);
}
// Produces a link event message to the client.
private String message(LinkEvent event) {
Link link = event.subject();
ObjectNode payload = mapper.createObjectNode()
.put("type", link.type().toString().toLowerCase())
.put("linkWidth", 2)
.put("src", link.src().deviceId().toString())
.put("srcPort", link.src().port().toString())
.put("dst", link.dst().deviceId().toString())
.put("dstPort", link.dst().port().toString());
String type = (event.type() == LINK_ADDED) ? "addLink" :
((event.type() == LINK_REMOVED) ? "removeLink" : "removeLink");
return envelope(type, payload);
}
// Produces JSON structure from annotations.
private JsonNode props(Annotations annotations) {
ObjectNode props = mapper.createObjectNode();
for (String key : annotations.keys()) {
props.put(key, annotations.value(key));
}
return props;
}
// Produces a log message event bound to the client.
private String message(String severity, String message) {
return envelope("message",
mapper.createObjectNode()
.put("severity", severity)
.put("message", message));
}
// Puts the payload into an envelope and returns it.
private String envelope(String type, ObjectNode payload) {
ObjectNode event = mapper.createObjectNode();
event.put("event", type);
event.set("payload", payload);
return event.toString();
}
@Override
public void event(TopologyEvent event) {
sendMessage(event.toString());
for (Event reason : event.reasons()) {
if (reason instanceof DeviceEvent) {
sendMessage(message((DeviceEvent) reason));
} else if (reason instanceof LinkEvent) {
sendMessage(message((LinkEvent) reason));
}
}
}
}
......
......@@ -72,9 +72,9 @@
<!-- Initialize the UI...-->
<script type="text/javascript">
var ONOS = $.onos({
comment: "configuration options",
comment: 'configuration options',
theme: 'light',
startVid: 'topo',
// startVid: 'sampleKeys',
trace: false
});
</script>
......
{
"event": "addHostIntent",
"sid": 1,
"payload": {
"one": "hostOne",
"two": "hostTwo"
}
}
{
"event": "showPath",
"sid": 1,
"payload": {
"intentId": "0x1234",
"path": {
"links": [ "1-2", "2-3" ],
"traffic": false
}
}
}
{
"event": "monitorIntent",
"sid": 2,
"payload": {
"intentId": "0x1234"
}
}
{
"event": "showPath",
"sid": 2,
"payload": {
"intentId": "0x1234",
"path": {
"links": [ "1-2", "2-3" ],
"traffic": true,
"srcLabel": "567 Mb",
"dstLabel": "6 Mb"
}
}
}
{
"event": "showPath",
"sid": 2,
"payload": {
"intentId": "0x1234",
"path": {
"links": [ "1-2", "2-3" ],
"traffic": true,
"srcLabel": "967 Mb",
"dstLabel": "65 Mb"
}
}
}
{
"event": "showPath",
"sid": 2,
"payload": {
"intentId": "0x1234",
"path": {
"links": [ "1-2", "2-3" ],
"traffic": false
}
}
}
{
"event": "cancelMonitorIntent",
"sid": 3,
"payload": {
"intentId": "0x1234"
}
}
......@@ -23,8 +23,15 @@
#mast {
height: 36px;
padding: 4px;
background-color: #bbb;
vertical-align: baseline;
}
.light #mast {
background-color: #bbb;
box-shadow: 0px 2px 8px #777;
}
.dark #mast {
background-color: #444;
box-shadow: 0px 2px 8px #777;
}
......@@ -35,12 +42,18 @@
}
#mast span.title {
color: #369;
font-size: 14pt;
font-style: italic;
vertical-align: 12px;
}
.light #mast span.title {
color: #369;
}
.dark #mast span.title {
color: #78a;
}
#mast span.right {
padding-top: 8px;
padding-right: 16px;
......@@ -50,16 +63,31 @@
#mast span.radio {
font-size: 10pt;
margin: 4px 2px;
border: 1px dotted #222;
padding: 1px 6px;
color: #eee;
cursor: pointer;
}
.light #mast span.radio {
border: 1px dotted #222;
color: #eee;
}
.dark #mast span.radio {
border: 1px dotted #bbb;
color: #888;
}
#mast span.radio.active {
padding: 1px 6px;
font-weight: bold;
}
.light #mast span.radio.active {
background-color: #bbb;
border: 1px solid #eee;
padding: 1px 6px;
color: #666;
font-weight: bold;
}
.dark #mast span.radio.active {
background-color: #222;
border: 1px solid #eee;
color: #aaf;
}
......
......@@ -32,7 +32,7 @@ div.onosView.currentView {
display: block;
}
div#alerts {
#alerts {
display: none;
position: absolute;
z-index: 2000;
......@@ -45,21 +45,28 @@ div#alerts {
box-shadow: 4px 6px 12px #777;
}
div#alerts pre {
#alerts pre {
margin: 0.2em 6px;
}
div#alerts span.close {
#alerts span.close {
color: #6af;
float: right;
right: 2px;
cursor: pointer;
}
div#alerts span.close:hover {
#alerts span.close:hover {
color: #fff;
}
#alerts p.footnote {
text-align: right;
font-size: 8pt;
margin: 8px 0 0 0;
color: #66d;
}
/*
* ==============================================================
* END OF NEW ONOS.JS file
......
......@@ -37,23 +37,34 @@
var defaultOptions = {
trace: false,
theme: 'light',
startVid: defaultVid
};
// compute runtime settings
var settings = $.extend({}, defaultOptions, options);
// set the selected theme
d3.select('body').classed(settings.theme, true);
// internal state
var views = {},
current = {
view: null,
ctx: ''
ctx: '',
theme: settings.theme
},
built = false,
errorCount = 0,
keyHandler = {
fn: null,
map: {}
globalKeys: {},
maskedKeys: {},
viewKeys: {},
viewFn: null
},
alerts = {
open: false,
count: 0
};
// DOM elements etc.
......@@ -240,8 +251,8 @@
// detach radio buttons, key handlers, etc.
$('#mastRadio').children().detach();
keyHandler.fn = null;
keyHandler.map = {};
keyHandler.viewKeys = {};
keyHandler.viewFn = null;
}
// cache new view and context
......@@ -322,20 +333,74 @@
$mastRadio.node().appendChild(btnG.node());
}
function setupGlobalKeys() {
keyHandler.globalKeys = {
esc: escapeKey,
T: toggleTheme
};
// Masked keys are global key handlers that always return true.
// That is, the view will never see the event for that key.
keyHandler.maskedKeys = {
T: true
};
}
function escapeKey(view, key, code, ev) {
if (alerts.open) {
closeAlerts();
return true;
}
return false;
}
function toggleTheme(view, key, code, ev) {
var body = d3.select('body');
current.theme = (current.theme === 'light') ? 'dark' : 'light';
body.classed('light dark', false);
body.classed(current.theme, true);
return true;
}
function setKeyBindings(keyArg) {
var viewKeys,
masked = [];
if ($.isFunction(keyArg)) {
// set general key handler callback
keyHandler.fn = keyArg;
keyHandler.viewFn = keyArg;
} else {
// set specific key filter map
keyHandler.map = keyArg;
viewKeys = d3.map(keyArg).keys();
viewKeys.forEach(function (key) {
if (keyHandler.maskedKeys[key]) {
masked.push(' Key "' + key + '" is reserved');
}
});
if (masked.length) {
doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
}
keyHandler.viewKeys = keyArg;
}
}
var alerts = {
open: false,
count: 0
};
function keyIn() {
var event = d3.event,
keyCode = event.keyCode,
key = whatKey(keyCode),
gcb = isF(keyHandler.globalKeys[key]),
vcb = isF(keyHandler.viewKeys[key]) || isF(keyHandler.viewFn);
// global callback?
if (gcb && gcb(current.view.token(), key, keyCode, event)) {
// if the event was 'handled', we are done
return;
}
// otherwise, let the view callback have a shot
if (vcb) {
vcb(current.view.token(), key, keyCode, event);
}
}
function createAlerts() {
var al = d3.select('#alerts')
......@@ -345,15 +410,16 @@
.text('X')
.on('click', closeAlerts);
al.append('pre');
al.append('p').attr('class', 'footnote')
.text('Press ESCAPE to close');
alerts.open = true;
alerts.count = 0;
}
function closeAlerts() {
d3.select('#alerts')
.style('display', 'none');
d3.select('#alerts span').remove();
d3.select('#alerts pre').remove();
.style('display', 'none')
.html('');
alerts.open = false;
}
......@@ -384,17 +450,6 @@
addAlert(msg);
}
function keyIn() {
var event = d3.event,
keyCode = event.keyCode,
key = whatKey(keyCode),
cb = isF(keyHandler.map[key]) || isF(keyHandler.fn);
if (cb) {
cb(current.view.token(), key, keyCode, event);
}
}
function resize(e) {
d3.selectAll('.onosView').call(setViewDimensions);
// allow current view to react to resize event...
......@@ -683,6 +738,7 @@
$(window).on('resize', resize);
d3.select('body').on('keydown', keyIn);
setupGlobalKeys();
// Invoke hashchange callback to navigate to content
// indicated by the window location hash.
......
......@@ -28,7 +28,7 @@
// configuration data
var config = {
useLiveData: false,
useLiveData: true,
debugOn: false,
debug: {
showNodeXY: true,
......@@ -113,27 +113,31 @@
// key bindings
var keyDispatch = {
space: injectTestEvent, // TODO: remove (testing only)
S: injectStartupEvents, // TODO: remove (testing only)
A: testAlert, // TODO: remove (testing only)
M: testMe, // TODO: remove (testing only)
S: injectStartupEvents, // TODO: remove (testing only)
space: injectTestEvent, // TODO: remove (testing only)
B: toggleBg,
G: toggleLayout,
B: toggleBg, // TODO: do we really need this?
L: cycleLabels,
P: togglePorts,
U: unpin
U: unpin,
X: requestPath
};
// state variables
var network = {
view: null, // view token reference
nodes: [],
links: [],
lookup: {}
},
webSock,
labelIdx = 0,
selected = {},
selectOrder = [],
selections = {},
highlighted = null,
hovered = null,
viewMode = 'showAll',
......@@ -167,19 +171,19 @@
// ==============================
// Key Callbacks
function testAlert(view) {
alertNumber++;
view.alert("Test me! -- " + alertNumber);
}
function testMe(view) {
view.alert('test');
}
function injectTestEvent(view) {
if (config.useLiveData) {
view.alert("Sorry, currently using live data..");
return;
}
eventNumber++;
var eventUrl = eventPrefix + eventNumber + '.json';
console.log('Fetching JSON: ' + eventUrl);
d3.json(eventUrl, function(err, data) {
if (err) {
view.dataLoadError(err, eventUrl);
......@@ -190,6 +194,11 @@
}
function injectStartupEvents(view) {
if (config.useLiveData) {
view.alert("Sorry, currently using live data..");
return;
}
var lastStartupEvent = 32;
while (eventNumber < lastStartupEvent) {
injectTestEvent(view);
......@@ -201,19 +210,20 @@
bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
}
function toggleLayout(view) {
}
function cycleLabels() {
labelIdx = (labelIdx === network.deviceLabelCount - 1) ? 0 : labelIdx + 1;
function niceLabel(label) {
return (label && label.trim()) ? label : '.';
}
network.nodes.forEach(function (d) {
var idx = (labelIdx < d.labels.length) ? labelIdx : 0,
node = d3.select('#' + safeId(d.id)),
box;
node.select('text')
.text(d.labels[idx])
.text(niceLabel(d.labels[idx]))
.style('opacity', 0)
.transition()
.style('opacity', 1);
......@@ -232,11 +242,19 @@
}
function togglePorts(view) {
view.alert('togglePorts() callback')
}
function unpin(view) {
view.alert('unpin() callback')
}
function requestPath(view) {
var payload = {
one: selections[selectOrder[0]].obj.id,
two: selections[selectOrder[1]].obj.id
}
sendMessage('requestPath', payload);
}
// ==============================
......@@ -248,19 +266,19 @@
// d3.selectAll('svg .port').classed('inactive', false);
// d3.selectAll('svg .portText').classed('inactive', false);
// TODO ...
console.log('showAllLayers()');
network.view.alert('showAllLayers() callback');
}
function showPacketLayer() {
showAllLayers();
// TODO ...
console.log('showPacketLayer()');
network.view.alert('showPacketLayer() callback');
}
function showOpticalLayer() {
showAllLayers();
// TODO ...
console.log('showOpticalLayer()');
network.view.alert('showOpticalLayer() callback');
}
// ==============================
......@@ -279,11 +297,6 @@
});
}
function establishWebSocket() {
// TODO: establish a real web-socket
// NOTE, for now, we are using the 'Q' key to artificially inject
// "events" from the server.
}
// ==============================
// Event handlers for server-pushed events
......@@ -292,7 +305,8 @@
addDevice: addDevice,
updateDevice: updateDevice,
removeDevice: removeDevice,
addLink: addLink
addLink: addLink,
showPath: showPath
};
function addDevice(data) {
......@@ -331,11 +345,14 @@
}
}
function showPath(data) {
network.view.alert(data.event + "\n" + data.payload.links.length);
}
// ....
function unknownEvent(data) {
// TODO: use dialog, not alert
alert('Unknown event type: "' + data.event + '"');
network.view.alert('Unknown event type: "' + data.event + '"');
}
function handleServerEvent(data) {
......@@ -360,7 +377,9 @@
lnk;
if (!(srcNode && dstNode)) {
alert('nodes not on map');
// TODO: send warning message back to server on websocket
network.view.alert('nodes not on map for link\n\n' +
'src = ' + src + '\ndst = ' + dst);
return null;
}
......@@ -381,6 +400,7 @@
function linkWidth(w) {
// w is number of links between nodes. Scale appropriately.
// TODO: use a d3.scale (linear, log, ... ?)
return w * 1.2;
}
......@@ -604,6 +624,7 @@
webSock.ws.onmessage = function(m) {
if (m.data) {
console.log(m.data);
handleServerEvent(JSON.parse(m.data));
}
};
......@@ -613,7 +634,7 @@
},
send : function(text) {
if (text != null && text.length > 0) {
if (text != null) {
webSock._send(text);
}
},
......@@ -621,11 +642,87 @@
_send : function(message) {
if (webSock.ws) {
webSock.ws.send(message);
} else {
network.view.alert('no web socket open');
}
}
};
var sid = 0;
function sendMessage(evType, payload) {
var toSend = {
event: evType,
sid: ++sid,
payload: payload
};
webSock.send(JSON.stringify(toSend));
}
// ==============================
// Selection stuff
function selectObject(obj, el) {
var n,
meta = d3.event.sourceEvent.metaKey;
if (el) {
n = d3.select(el);
} else {
node.each(function(d) {
if (d == obj) {
n = d3.select(el = this);
}
});
}
if (!n) return;
if (meta && n.classed('selected')) {
deselectObject(obj.id);
//flyinPane(null);
return;
}
if (!meta) {
deselectAll();
}
selections[obj.id] = { obj: obj, el : el};
selectOrder.push(obj.id);
n.classed('selected', true);
//flyinPane(obj);
}
function deselectObject(id) {
var obj = selections[id];
if (obj) {
d3.select(obj.el).classed('selected', false);
selections[id] = null;
// TODO: use splice to remove element
}
//flyinPane(null);
}
function deselectAll() {
// deselect all nodes in the network...
node.classed('selected', false);
selections = {};
selectOrder = [];
//flyinPane(null);
}
$('#view').on('click', function(e) {
if (!$(e.target).closest('.node').length) {
if (!e.metaKey) {
deselectAll();
}
}
});
// ==============================
// View life-cycle callbacks
......@@ -680,7 +777,7 @@
}
function selectCb(d, self) {
// TODO: selectObject(d, self);
selectObject(d, self);
}
function atDragEnd(d, self) {
......@@ -688,11 +785,22 @@
// if it is a device (not a host)
if (d.class === 'device') {
d.fixed = true;
d3.select(self).classed('fixed', true)
// TODO: send new [x,y] back to server, via websocket.
d3.select(self).classed('fixed', true);
if (config.useLiveData) {
tellServerCoords(d);
}
}
}
function tellServerCoords(d) {
sendMessage('updateMeta', {
id: d.id,
'class': d.class,
x: Math.floor(d.x),
y: Math.floor(d.y)
});
}
// set up the force layout
network.force = d3.layout.force()
.size(forceDim)
......@@ -704,7 +812,6 @@
.on('tick', tick);
network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
webSock.connect();
}
function load(view, ctx) {
......@@ -715,7 +822,9 @@
view.setRadio(btnSet);
view.setKeys(keyDispatch);
establishWebSocket();
if (config.useLiveData) {
webSock.connect();
}
}
function resize(view, ctx) {
......