Madan Jampani

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

Showing 62 changed files with 980 additions and 214 deletions
......@@ -296,7 +296,7 @@ public class FlowRuleManager
post(event);
}
} else {
log.info("Removing flow rules....");
log.debug("Removing flow rules....");
removeFlowRules(flowEntry);
}
......
......@@ -2,13 +2,16 @@ package org.onlab.onos.store.service.impl;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verifyNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentNavigableMap;
import net.kuujo.copycat.log.Entry;
......@@ -25,8 +28,6 @@ import org.mapdb.TxMaker;
import org.onlab.onos.store.serializers.StoreSerializer;
import org.slf4j.Logger;
import com.google.common.collect.Lists;
/**
* MapDB based log implementation.
*/
......@@ -84,7 +85,7 @@ public class MapDBLog implements Log {
public List<Long> appendEntries(List<Entry> entries) {
assertIsOpen();
checkArgument(entries != null, "expecting non-null entries");
final List<Long> indices = Lists.newArrayList();
final List<Long> indices = new ArrayList<>(entries.size());
txMaker.execute(new TxBlock() {
@Override
......@@ -92,13 +93,15 @@ public class MapDBLog implements Log {
BTreeMap<Long, byte[]> log = getLogMap(db);
Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1;
long addedBytes = 0;
for (Entry entry : entries) {
byte[] entryBytes = serializer.encode(entry);
log.put(nextIndex, entryBytes);
size.addAndGet(entryBytes.length);
addedBytes += entryBytes.length;
indices.add(nextIndex);
nextIndex++;
}
size.addAndGet(addedBytes);
}
});
......@@ -236,12 +239,15 @@ public class MapDBLog implements Log {
public void tx(DB db) {
BTreeMap<Long, byte[]> log = getLogMap(db);
Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
long startIndex = index + 1;
long endIndex = log.lastKey();
for (long i = startIndex; i <= endIndex; ++i) {
byte[] entryBytes = log.remove(i);
size.addAndGet(-1L * entryBytes.length);
long removedBytes = 0;
ConcurrentNavigableMap<Long, byte[]> tailMap = log.tailMap(index, false);
Iterator<Map.Entry<Long, byte[]>> it = tailMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Long, byte[]> entry = it.next();
removedBytes += entry.getValue().length;
it.remove();
}
size.addAndGet(-removedBytes);
}
});
}
......@@ -273,9 +279,16 @@ public class MapDBLog implements Log {
BTreeMap<Long, byte[]> log = getLogMap(db);
Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index);
long deletedBytes = headMap.keySet().stream().mapToLong(i -> log.remove(i).length).sum();
size.addAndGet(-1 * deletedBytes);
byte[] entryBytes = serializer.encode(entry);
Iterator<Map.Entry<Long, byte[]>> it = headMap.entrySet().iterator();
long deletedBytes = 0;
while (it.hasNext()) {
Map.Entry<Long, byte[]> e = it.next();
deletedBytes += e.getValue().length;
it.remove();
}
size.addAndGet(-deletedBytes);
byte[] entryBytes = verifyNotNull(serializer.encode(entry));
byte[] existingEntry = log.put(index, entryBytes);
if (existingEntry != null) {
size.addAndGet(entryBytes.length - existingEntry.length);
......
......@@ -139,7 +139,7 @@ public class FlowModBuilderVer13 extends FlowModBuilder {
.setXid(cookie)
.setCookie(U64.of(cookie))
.setBufferId(OFBufferId.NO_BUFFER)
.setActions(actions)
//.setActions(actions) //FIXME do we want to send actions in flowdel?
//.setInstructions(Collections.singletonList(writeActions))
.setMatch(match)
.setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
......
......@@ -19,13 +19,13 @@ fi
export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2}
export KARAF_VERSION=${KARAF_VERSION:-3.0.2}
export KARAF=${KARAF:-~/Applications/apache-karaf-$KARAF_VERSION}
export KARAF_LOG=$KARAF/data/log/karaf.log
export KARAF_HOME=${KARAF:-~/Applications/apache-karaf-$KARAF_VERSION}
export KARAF_LOG=$KARAF_HOME/data/log/karaf.log
# Setup a path
export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin"
export PATH="$PATH:$ONOS_ROOT/tools/build"
export PATH="$PATH:$MAVEN/bin:$KARAF/bin"
export PATH="$PATH:$MAVEN/bin:$KARAF_HOME/bin"
# Convenience utility to warp to various ONOS source projects
# e.g. 'o api', 'o dev', 'o'
......
......@@ -29,8 +29,8 @@ endif
if ( ! $?KARAF_VERSION ) then
setenv KARAF_VERSION 3.0.2
endif
if ( ! $?KARAF ) then
setenv KARAF $HOME/Applications/apache-karaf-$KARAF_VERSION
if ( ! $?KARAF_HOME ) then
setenv KARAF_HOME $HOME/Applications/apache-karaf-$KARAF_VERSION
endif
setenv KARAF_LOG $KARAF/data/log/karaf.log
......
......@@ -20,50 +20,95 @@ 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.core.ApplicationId;
import org.onlab.onos.core.CoreService;
import org.onlab.onos.mastership.MastershipEvent;
import org.onlab.onos.mastership.MastershipListener;
import org.onlab.onos.mastership.MastershipService;
import org.onlab.onos.net.Annotations;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultEdgeLink;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.ElementId;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.HostLocation;
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.DeviceListener;
import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
import org.onlab.onos.net.flow.DefaultTrafficTreatment;
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostListener;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.intent.HostToHostIntent;
import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentEvent;
import org.onlab.onos.net.intent.IntentId;
import org.onlab.onos.net.intent.IntentListener;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.onos.net.intent.PathIntent;
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;
import org.onlab.onos.net.topology.TopologyGraph;
import org.onlab.onos.net.topology.TopologyListener;
import org.onlab.onos.net.topology.TopologyService;
import org.onlab.onos.net.topology.TopologyVertex;
import org.onlab.onos.net.link.LinkListener;
import org.onlab.onos.net.link.LinkService;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.net.topology.PathService;
import org.onlab.osgi.ServiceDirectory;
import org.onlab.packet.IpAddress;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.HostId.hostId;
import static org.onlab.onos.net.PortNumber.portNumber;
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.host.HostEvent.Type.HOST_ADDED;
import static org.onlab.onos.net.host.HostEvent.Type.HOST_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.
*/
public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListener {
public class TopologyWebSocket implements WebSocket.OnTextMessage {
private static final String APP_ID = "org.onlab.onos.gui";
private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
private final ApplicationId appId;
private final ServiceDirectory directory;
private final TopologyService topologyService;
private final DeviceService deviceService;
private final ObjectMapper mapper = new ObjectMapper();
private Connection connection;
private final DeviceService deviceService;
private final LinkService linkService;
private final HostService hostService;
private final MastershipService mastershipService;
private final IntentService intentService;
private final DeviceListener deviceListener = new InternalDeviceListener();
private final LinkListener linkListener = new InternalLinkListener();
private final HostListener hostListener = new InternalHostListener();
private final MastershipListener mastershipListener = new InternalMastershipListener();
private final IntentListener intentListener = new InternalIntentListener();
// TODO: extract into an external & durable state; good enough for now and demo
private static Map<String, ObjectNode> metaUi = new HashMap<>();
private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
// Intents that are being monitored for the GUI
private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
private static final String COMPACT = "%s/%s-%s/%s";
......@@ -74,81 +119,231 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
* @param directory service directory
*/
public TopologyWebSocket(ServiceDirectory directory) {
this.directory = directory;
topologyService = directory.get(TopologyService.class);
this.directory = checkNotNull(directory, "Directory cannot be null");
deviceService = directory.get(DeviceService.class);
linkService = directory.get(LinkService.class);
hostService = directory.get(HostService.class);
mastershipService = directory.get(MastershipService.class);
intentService = directory.get(IntentService.class);
appId = directory.get(CoreService.class).registerApplication(APP_ID);
}
@Override
public void onOpen(Connection connection) {
this.connection = connection;
deviceService.addListener(deviceListener);
linkService.addListener(linkListener);
hostService.addListener(hostListener);
mastershipService.addListener(mastershipListener);
intentService.addListener(intentListener);
sendAllDevices();
sendAllLinks();
sendAllHosts();
}
// Register for topology events...
if (topologyService != null && deviceService != null) {
topologyService.addListener(this);
Topology topology = topologyService.currentTopology();
TopologyGraph graph = topologyService.getGraph(topology);
for (TopologyVertex vertex : graph.getVertexes()) {
sendMessage(message(new DeviceEvent(DEVICE_ADDED,
deviceService.getDevice(vertex.deviceId()))));
}
private void sendAllHosts() {
for (Host host : hostService.getHosts()) {
sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
}
}
for (TopologyEdge edge : graph.getEdges()) {
sendMessage(message(new LinkEvent(LINK_ADDED, edge.link())));
}
private void sendAllDevices() {
for (Device device : deviceService.getDevices()) {
sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
}
}
} else {
sendMessage(message("error", "No topology service!!!"));
private void sendAllLinks() {
for (Link link : linkService.getLinks()) {
sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
}
}
@Override
public void onClose(int closeCode, String message) {
TopologyService topologyService = directory.get(TopologyService.class);
if (topologyService != null) {
topologyService.removeListener(this);
}
deviceService.removeListener(deviceListener);
linkService.removeListener(linkListener);
hostService.removeListener(hostListener);
mastershipService.removeListener(mastershipListener);
}
@Override
public void onMessage(String 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;
String type = string(event, "event", "unknown");
if (type.equals("showDetails")) {
showDetails(event);
} else if (type.equals("updateMeta")) {
updateMetaInformation(event);
} else if (type.equals("requestPath")) {
createHostIntent(event);
} else if (type.equals("requestTraffic")) {
sendTraffic(event);
} else if (type.equals("cancelTraffic")) {
cancelTraffic(event);
}
} catch (Exception e) {
System.out.println("WTF?! " + data);
e.printStackTrace();
}
}
// Sends the specified data to the client.
private void sendMessage(ObjectNode data) {
try {
connection.sendMessage(data.toString());
} catch (IOException e) {
System.out.println("Received: " + data);
e.printStackTrace();
}
}
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();
// Retrieves the payload from the specified event.
private ObjectNode payload(ObjectNode event) {
return (ObjectNode) event.path("payload");
}
Path path = paths.iterator().next();
for (Link link : path.links()) {
links.add(compactLinkString(link));
}
// Returns the specified node property as a number
private long number(ObjectNode node, String name) {
return node.path(name).asLong();
}
// Returns the specified node property as a string.
private String string(ObjectNode node, String name) {
return node.path(name).asText();
}
// Returns the specified node property as a string.
private String string(ObjectNode node, String name, String defaultValue) {
return node.path(name).asText(defaultValue);
}
// Returns the specified set of IP addresses as a string.
private String ip(Set<IpAddress> ipAddresses) {
Iterator<IpAddress> it = ipAddresses.iterator();
return it.hasNext() ? it.next().toString() : "unknown";
}
// Encodes the specified host location into a JSON object.
private ObjectNode location(ObjectMapper mapper, HostLocation location) {
return mapper.createObjectNode()
.put("device", location.deviceId().toString())
.put("port", location.port().toLong());
}
// Encodes the specified list of labels a JSON array.
private ArrayNode labels(ObjectMapper mapper, String... labels) {
ArrayNode json = mapper.createArrayNode();
for (String label : labels) {
json.add(label);
}
return json;
}
// 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 ObjectNode message(String severity, long id, String message) {
return envelope("message", id,
mapper.createObjectNode()
.put("severity", severity)
.put("message", message));
}
// Puts the payload into an envelope and returns it.
private ObjectNode envelope(String type, long sid, ObjectNode payload) {
ObjectNode event = mapper.createObjectNode();
event.put("event", type);
if (sid > 0) {
event.put("sid", sid);
}
event.set("payload", payload);
return event;
}
// Sends back device or host details.
private void showDetails(ObjectNode event) {
ObjectNode payload = payload(event);
String type = string(payload, "type", "unknown");
if (type.equals("device")) {
sendMessage(deviceDetails(deviceId(string(payload, "id")),
number(event, "sid")));
} else if (type.equals("host")) {
sendMessage(hostDetails(hostId(string(payload, "id")),
number(event, "sid")));
}
}
// Updates device/host meta information.
private void updateMetaInformation(ObjectNode event) {
ObjectNode payload = payload(event);
metaUi.put(string(payload, "id"), payload);
}
// Creates host-to-host intent.
private void createHostIntent(ObjectNode event) {
ObjectNode payload = payload(event);
long id = number(event, "sid");
// TODO: add protection against device ids and non-existent hosts.
HostId one = hostId(string(payload, "one"));
HostId two = hostId(string(payload, "two"));
HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
DefaultTrafficSelector.builder().build(),
DefaultTrafficTreatment.builder().build());
intentsToMonitor.put(hostIntent.id(), number(event, "sid"));
intentService.submit(hostIntent);
}
// Sends traffic message.
private void sendTraffic(ObjectNode event) {
ObjectNode payload = payload(event);
long id = number(event, "sid");
IntentId intentId = IntentId.valueOf(payload.path("intentId").asLong());
payload.set("links", links);
sendMessage(envelope("showPath", payload));
if (payload != null) {
payload.put("traffic", true);
sendMessage(envelope("showPath", id, payload));
} else {
sendMessage(message("warn", id, "No path found"));
}
// TODO: when no path, send a message to the client
}
// Cancels sending traffic messages.
private void cancelTraffic(ObjectNode event) {
// TODO: implement this
}
// Finds the path between the specified devices.
private ObjectNode findPath(DeviceId one, DeviceId two) {
PathService pathService = directory.get(PathService.class);
Set<Path> paths = pathService.getPaths(one, two);
if (paths.isEmpty()) {
return null;
} else {
return pathMessage(paths.iterator().next());
}
}
// Produces a path message to the client.
private ObjectNode pathMessage(Path path) {
ObjectNode payload = mapper.createObjectNode();
ArrayNode links = mapper.createArrayNode();
for (Link link : path.links()) {
links.add(compactLinkString(link));
}
payload.set("links", links);
return payload;
}
/**
......@@ -158,21 +353,13 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
* @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());
return String.format(COMPACT, link.src().elementId(), link.src().port(),
link.dst().elementId(), link.dst().port());
}
private void sendMessage(String data) {
try {
connection.sendMessage(data);
} catch (IOException e) {
e.printStackTrace();
}
}
// Produces a link event message to the client.
private String message(DeviceEvent event) {
private ObjectNode deviceMessage(DeviceEvent event) {
Device device = event.subject();
ObjectNode payload = mapper.createObjectNode()
.put("id", device.id().toString())
......@@ -183,27 +370,24 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
ArrayNode labels = mapper.createArrayNode();
labels.add(device.id().toString());
labels.add(device.chassisId().toString());
labels.add(" "); // compact no-label view
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);
}
addMetaUi(device.id(), payload);
String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
return envelope(type, payload);
return envelope(type, 0, payload);
}
// Produces a link event message to the client.
private String message(LinkEvent event) {
private ObjectNode linkMessage(LinkEvent event) {
Link link = event.subject();
ObjectNode payload = mapper.createObjectNode()
.put("id", compactLinkString(link))
.put("type", link.type().toString().toLowerCase())
.put("linkWidth", 2)
.put("src", link.src().deviceId().toString())
......@@ -211,42 +395,147 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
.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);
((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
return envelope(type, 0, 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));
// Produces a host event message to the client.
private ObjectNode hostMessage(HostEvent event) {
Host host = event.subject();
ObjectNode payload = mapper.createObjectNode()
.put("id", host.id().toString())
.put("ingress", compactLinkString(edgeLink(host, true)))
.put("egress", compactLinkString(edgeLink(host, false)));
payload.set("cp", location(mapper, host.location()));
payload.set("labels", labels(mapper, ip(host.ipAddresses()),
host.mac().toString()));
payload.set("props", props(host.annotations()));
addMetaUi(host.id(), payload);
String type = (event.type() == HOST_ADDED) ? "addHost" :
((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
return envelope(type, 0, payload);
}
private DefaultEdgeLink edgeLink(Host host, boolean ingress) {
return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
host.location(), ingress);
}
private void addMetaUi(ElementId id, ObjectNode payload) {
ObjectNode meta = metaUi.get(id.toString());
if (meta != null) {
payload.set("metaUi", meta);
}
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));
// Returns device details response.
private ObjectNode deviceDetails(DeviceId deviceId, long sid) {
Device device = deviceService.getDevice(deviceId);
Annotations annot = device.annotations();
int portCount = deviceService.getPorts(deviceId).size();
return envelope("showDetails", sid,
json(deviceId.toString(),
device.type().toString().toLowerCase(),
new Prop("Name", annot.value("name")),
new Prop("Vendor", device.manufacturer()),
new Prop("H/W Version", device.hwVersion()),
new Prop("S/W Version", device.swVersion()),
new Prop("Serial Number", device.serialNumber()),
new Separator(),
new Prop("Latitude", annot.value("latitude")),
new Prop("Longitude", annot.value("longitude")),
new Prop("Ports", Integer.toString(portCount))));
}
// 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();
// Returns host details response.
private ObjectNode hostDetails(HostId hostId, long sid) {
Host host = hostService.getHost(hostId);
Annotations annot = host.annotations();
return envelope("showDetails", sid,
json(hostId.toString(), "host",
new Prop("MAC", host.mac().toString()),
new Prop("IP", host.ipAddresses().toString()),
new Separator(),
new Prop("Latitude", annot.value("latitude")),
new Prop("Longitude", annot.value("longitude"))));
}
@Override
public void event(TopologyEvent event) {
for (Event reason : event.reasons()) {
if (reason instanceof DeviceEvent) {
sendMessage(message((DeviceEvent) reason));
} else if (reason instanceof LinkEvent) {
sendMessage(message((LinkEvent) reason));
// Produces JSON property details.
private ObjectNode json(String id, String type, Prop... props) {
ObjectMapper mapper = new ObjectMapper();
ObjectNode result = mapper.createObjectNode()
.put("id", id).put("type", type);
ObjectNode pnode = mapper.createObjectNode();
ArrayNode porder = mapper.createArrayNode();
for (Prop p : props) {
porder.add(p.key);
pnode.put(p.key, p.value);
}
result.set("propOrder", porder);
result.set("props", pnode);
return result;
}
// Auxiliary key/value carrier.
private class Prop {
private final String key;
private final String value;
protected Prop(String key, String value) {
this.key = key;
this.value = value;
}
}
private class Separator extends Prop {
protected Separator() {
super("-", "");
}
}
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
sendMessage(deviceMessage(event));
}
}
private class InternalLinkListener implements LinkListener {
@Override
public void event(LinkEvent event) {
sendMessage(linkMessage(event));
}
}
private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
sendMessage(hostMessage(event));
}
}
private class InternalMastershipListener implements MastershipListener {
@Override
public void event(MastershipEvent event) {
}
}
private class InternalIntentListener implements IntentListener {
@Override
public void event(IntentEvent event) {
Intent intent = event.subject();
Long sid = intentsToMonitor.get(intent.id());
if (sid != null) {
List<Intent> installable = intentService.getInstallableIntents(intent.id());
if (installable != null && !installable.isEmpty()) {
PathIntent pathIntent = (PathIntent) installable.iterator().next();
Path path = pathIntent.path();
ObjectNode payload = pathMessage(path).put("intentId", intent.id().toString());
sendMessage(envelope("showPath", sid, payload));
}
}
}
}
......
......@@ -90,6 +90,7 @@
<script src="sampleAlt2.js"></script>
<script src="sampleRadio.js"></script>
<script src="sampleKeys.js"></script>
<script src="sampleHash.js"></script>
<!-- Contributed (application) views injected here -->
<!-- TODO: replace with template marker and inject refs server-side -->
......
{
"comments": [
"This scenario steps through adding a host intent."
],
"title": "Host Intent Scenario",
"params": {
"lastAuto": 0
}
}
\ No newline at end of file
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffff0008",
"type": "switch",
"online": false,
"labels": [
"0000ffffffff0008",
"FF:FF:FF:FF:00:08",
"sw-8"
],
"metaUi": {
"x": 400,
"y": 280
}
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffff0003",
"type": "switch",
"online": false,
"labels": [
"0000ffffffff0003",
"FF:FF:FF:FF:00:03",
"sw-3"
],
"metaUi": {
"x": 800,
"y": 280
}
}
}
{
"event": "addLink",
"payload": {
"src": "of:0000ffffffff0003",
"srcPort": "21",
"dst": "of:0000ffffffff0008",
"dstPort": "20",
"type": "infra",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
}
}
{
"event": "addHost",
"payload": {
"id": "00:00:00:00:00:03/-1",
"cp": {
"device": "of:0000ffffffff0003",
"port": 1
},
"labels": [
"10.0.0.3",
"00:00:00:00:00:03"
],
"metaUi": {
}
}
}
{
"event": "addHost",
"payload": {
"id": "00:00:00:00:00:08/-1",
"cp": {
"device": "of:0000ffffffff0008",
"port": 1
},
"labels": [
"10.0.0.8",
"00:00:00:00:00:08"
],
"metaUi": {
}
}
}
{
"comments": [
"Add two devices and one link (auto), and two hosts."
],
"title": "Simple Startup Scenario",
"params": {
"lastAuto": 0
}
}
\ No newline at end of file
{
"comments": [
"This scenario steps through adding devices and links.",
"(Typical 'start-ip' of the view.)"
],
"title": "Startup Scenario",
"params": {
"lastAuto": 32
}
}
\ No newline at end of file
......@@ -52,6 +52,7 @@
current = {
view: null,
ctx: '',
flags: {},
theme: settings.theme
},
built = false,
......@@ -110,6 +111,7 @@
function doError(msg) {
errorCount++;
console.error(msg);
doAlert(msg);
}
function trace(msg) {
......@@ -140,7 +142,7 @@
t = parseHash(hash);
if (!t || !t.vid) {
doError('Unable to parse target hash: ' + hash);
doError('Unable to parse target hash: "' + hash + '"');
}
view = views[t.vid];
......@@ -160,33 +162,72 @@
function parseHash(s) {
// extract navigation coordinates from the supplied string
// "vid,ctx" --> { vid:vid, ctx:ctx }
// "vid,ctx?flag1,flag2" --> { vid:vid, ctx:ctx, flags:{...} }
traceFn('parseHash', s);
var m = /^[#]{0,1}(\S+),(\S*)$/.exec(s);
// look for use of flags, first
var vidctx,
vid,
ctx,
flags,
flagMap,
m;
// RE that includes flags ('?flag1,flag2')
m = /^[#]{0,1}(.+)\?(.+)$/.exec(s);
if (m) {
return { vid: m[1], ctx: m[2] };
vidctx = m[1];
flags = m[2];
flagMap = {};
} else {
// no flags
m = /^[#]{0,1}((.+)(,.+)*)$/.exec(s);
if (m) {
vidctx = m[1];
} else {
// bad hash
return null;
}
}
vidctx = vidctx.split(',');
vid = vidctx[0];
ctx = vidctx[1];
if (flags) {
flags.split(',').forEach(function (f) {
flagMap[f.trim()] = true;
});
}
m = /^[#]{0,1}(\S+)$/.exec(s);
return m ? { vid: m[1] } : null;
return {
vid: vid.trim(),
ctx: ctx ? ctx.trim() : '',
flags: flagMap
};
}
function makeHash(t, ctx) {
function makeHash(t, ctx, flags) {
traceFn('makeHash');
// make a hash string from the given navigation coordinates.
// make a hash string from the given navigation coordinates,
// and optional flags map.
// if t is not an object, then it is a vid
var h = t,
c = ctx || '';
c = ctx || '',
f = $.isPlainObject(flags) ? flags : null;
if ($.isPlainObject(t)) {
h = t.vid;
c = t.ctx || '';
f = t.flags || null;
}
if (c) {
h += ',' + c;
}
if (f) {
h += '?' + d3.map(f).keys().join(',');
}
trace('hash = "' + h + '"');
return h;
}
......@@ -244,6 +285,9 @@
// set the specified view as current, while invoking the
// appropriate life-cycle callbacks
// first, we'll start by closing the alerts pane, if open
closeAlerts();
// 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)) {
......@@ -258,10 +302,11 @@
// cache new view and context
current.view = view;
current.ctx = t.ctx || '';
current.flags = t.flags || {};
// preload is called only once, after the view is in the DOM
if (!view.preloaded) {
view.preload(current.ctx);
view.preload(current.ctx, current.flags);
view.preloaded = true;
}
......@@ -269,7 +314,7 @@
view.reset();
// load the view
view.load(current.ctx);
view.load(current.ctx, current.flags);
}
// generate 'unique' id by prefixing view id
......@@ -454,7 +499,7 @@
d3.selectAll('.onosView').call(setViewDimensions);
// allow current view to react to resize event...
if (current.view) {
current.view.resize(current.ctx);
current.view.resize(current.ctx, current.flags);
}
}
......@@ -521,13 +566,13 @@
}
},
preload: function (ctx) {
preload: function (ctx, flags) {
var c = ctx || '',
fn = isF(this.cb.preload);
traceFn('View.preload', this.vid + ', ' + c);
if (fn) {
trace('PRELOAD cb for ' + this.vid);
fn(this.token(), c);
fn(this.token(), c, flags);
}
},
......@@ -544,15 +589,14 @@
}
},
load: function (ctx) {
load: function (ctx, flags) {
var c = ctx || '',
fn = isF(this.cb.load);
traceFn('View.load', this.vid + ', ' + c);
this.$div.classed('currentView', true);
// TODO: add radio button set, if needed
if (fn) {
trace('LOAD cb for ' + this.vid);
fn(this.token(), c);
fn(this.token(), c, flags);
}
},
......@@ -560,14 +604,13 @@
var fn = isF(this.cb.unload);
traceFn('View.unload', this.vid);
this.$div.classed('currentView', false);
// TODO: remove radio button set, if needed
if (fn) {
trace('UNLOAD cb for ' + this.vid);
fn(this.token());
}
},
resize: function (ctx) {
resize: function (ctx, flags) {
var c = ctx || '',
fn = isF(this.cb.resize),
w = this.width(),
......@@ -576,7 +619,7 @@
' [' + w + 'x' + h + ']');
if (fn) {
trace('RESIZE cb for ' + this.vid);
fn(this.token(), c);
fn(this.token(), c, flags);
}
},
......
......@@ -21,12 +21,17 @@
*/
(function () {
// NOTE: DON'T Want to do this.. we want to be able to
// use the parameter section, for example:
// #viewId,context?flag1,flag2,flag3
// 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];
}
//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.
*/
/*
Sample view to illustrate hash formats.
@author Simon Hunt
*/
(function (onos) {
'use strict';
var intro = "Try using the following hashes in the address bar:",
hashPrefix = '#sampleHash',
suffixes = [
'',
',one',
',two',
',context,ignored',
',context,ignored?a,b,c',
',two?foo',
',three?foo,bar'
],
$d;
function note(txt) {
$d.append('p')
.text(txt)
.style({
'font-size': '10pt',
color: 'darkorange',
padding: '0 20px',
margin: 0
});
}
function para(txt, color) {
var c = color || 'black';
$d.append('p')
.text(txt)
.style({
padding: '2px 8px',
color: c
});
}
function load(view, ctx, flags) {
var c = ctx || '(undefined)',
f = flags ? d3.map(flags).keys() : [];
$d = view.$div;
para(intro);
suffixes.forEach(function (s) {
note(hashPrefix + s);
});
para('View ID: ' + view.vid, 'blue');
para('Context: ' + c, 'blue');
para('Flags: { ' + f.join(', ') + ' }', 'magenta');
}
// == register the view here, with links to lifecycle callbacks
onos.ui.addView('sampleHash', {
reset: true, // empty the div on reset
load: load
});
}(ONOS));
......@@ -20,55 +20,64 @@
@author Simon Hunt
*/
svg #topo-bg {
#topo svg #topo-bg {
opacity: 0.5;
}
/* NODES */
svg .node.device {
#topo svg .node.device {
stroke: none;
stroke-width: 1.5px;
cursor: pointer;
}
svg .node.device rect {
#topo svg .node.device rect {
stroke-width: 1.5px;
}
svg .node.device.fixed rect {
#topo svg .node.device.fixed rect {
stroke-width: 1.5;
stroke: #ccc;
}
svg .node.device.switch {
#topo svg .node.device.switch {
fill: #17f;
}
svg .node.device.roadm {
#topo svg .node.device.roadm {
fill: #03c;
}
svg .node text {
#topo svg .node.host {
fill: #846;
}
#topo svg .node text {
stroke: none;
fill: white;
font: 10pt sans-serif;
pointer-events: none;
}
svg .node.selected rect,
svg .node.selected circle {
#topo svg .node.selected rect,
#topo svg .node.selected circle {
filter: url(#blue-glow);
}
/* LINKS */
svg .link {
#topo svg .link {
opacity: .7;
}
#topo svg .link.showPath {
stroke: #f00;
stroke-width: 4px;
}
/* for debugging */
svg .node circle.debug {
#topo svg .node circle.debug {
fill: white;
stroke: red;
}
......
......@@ -132,8 +132,21 @@
links: [],
lookup: {}
},
scenario = {
evDir: 'json/ev/',
evScenario: '/scenario.json',
evPrefix: '/ev_',
evOnos: '_onos.json',
evUi: '_ui.json',
ctx: null,
params: {},
evNumber: 0,
view: null,
debug: false
},
webSock,
labelIdx = 0,
deviceLabelIndex = 0,
hostLabelIndex = 0,
selectOrder = [],
selections = {},
......@@ -155,10 +168,6 @@
// ==============================
// For Debugging / Development
var eventPrefix = 'json/eventTest_',
eventNumber = 0,
alertNumber = 0;
function note(label, msg) {
console.log('NOTE: ' + label + ': ' + msg);
}
......@@ -175,32 +184,71 @@
view.alert('test');
}
function injectTestEvent(view) {
function abortIfLive() {
if (config.useLiveData) {
view.alert("Sorry, currently using live data..");
return;
scenario.view.alert("Sorry, currently using live data..");
return true;
}
return false;
}
eventNumber++;
var eventUrl = eventPrefix + eventNumber + '.json';
function testDebug(msg) {
if (scenario.debug) {
scenario.view.alert(msg);
}
}
d3.json(eventUrl, function(err, data) {
function injectTestEvent(view) {
if (abortIfLive()) { return; }
var sc = scenario,
evn = ++sc.evNumber,
pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
onosUrl = pfx + sc.evOnos,
uiUrl = pfx + sc.evUi;
tryOnosEvent(onosUrl, uiUrl);
}
// TODO: tryOnosEvent/tryUiEvent folded into recursive function.
function tryOnosEvent(onosUrl, uiUrl) {
var v = scenario.view;
d3.json(onosUrl, function(err, data) {
if (err) {
view.dataLoadError(err, eventUrl);
if (err.status === 404) {
tryUiEvent(uiUrl);
} else {
v.alert('non-404 error:\n\n' + onosUrl + '\n\n' + err);
}
} else {
testDebug('loaded: ' + onosUrl);
handleServerEvent(data);
}
});
}
function tryUiEvent(uiUrl) {
var v = scenario.view;
d3.json(uiUrl, function(err, data) {
if (err) {
v.alert('Error:\n\n' + uiUrl + '\n\n' +
err.status + ': ' + err.statusText);
} else {
testDebug('loaded: ' + uiUrl);
handleUiEvent(data);
}
});
}
function handleUiEvent(data) {
testDebug('handleUiEvent(): ' + data.event);
// TODO:
}
function injectStartupEvents(view) {
if (config.useLiveData) {
view.alert("Sorry, currently using live data..");
return;
}
var last = scenario.params.lastAuto || 0;
if (abortIfLive()) { return; }
var lastStartupEvent = 32;
while (eventNumber < lastStartupEvent) {
while (scenario.evNumber < last) {
injectTestEvent(view);
}
}
......@@ -211,14 +259,16 @@
}
function cycleLabels() {
labelIdx = (labelIdx === network.deviceLabelCount - 1) ? 0 : labelIdx + 1;
deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1) ? 0 : deviceLabelIndex + 1;
function niceLabel(label) {
return (label && label.trim()) ? label : '.';
}
network.nodes.forEach(function (d) {
var idx = (labelIdx < d.labels.length) ? labelIdx : 0,
if (d.class !== 'device') { return; }
var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0,
node = d3.select('#' + safeId(d.id)),
box;
......@@ -303,9 +353,14 @@
var eventDispatch = {
addDevice: addDevice,
updateDevice: updateDevice,
removeDevice: removeDevice,
updateDevice: stillToImplement,
removeDevice: stillToImplement,
addLink: addLink,
updateLink: stillToImplement,
removeLink: stillToImplement,
addHost: addHost,
updateHost: stillToImplement,
removeHost: stillToImplement,
showPath: showPath
};
......@@ -320,18 +375,6 @@
network.force.start();
}
function updateDevice(data) {
var device = data.payload;
note('updateDevice', device.id);
}
function removeDevice(data) {
var device = data.payload;
note('removeDevice', device.id);
}
function addLink(data) {
var link = data.payload,
lnk = createLink(link);
......@@ -340,16 +383,57 @@
note('addLink', lnk.id);
network.links.push(lnk);
network.lookup[lnk.id] = lnk;
updateLinks();
network.force.start();
}
}
function addHost(data) {
var host = data.payload,
node = createHostNode(host),
lnk;
note('addHost', node.id);
network.nodes.push(node);
network.lookup[host.id] = node;
updateNodes();
lnk = createHostLink(host);
if (lnk) {
network.links.push(lnk);
network.lookup[host.ingress] = lnk;
network.lookup[host.egress] = lnk;
updateLinks();
}
network.force.start();
}
function showPath(data) {
network.view.alert(data.event + "\n" + data.payload.links.length);
var links = data.payload.links,
s = [ data.event + "\n" + links.length ];
links.forEach(function (d, i) {
s.push(d);
});
network.view.alert(s.join('\n'));
links.forEach(function (d, i) {
var link = network.lookup[d];
if (link) {
d3.select('#' + link.svgId).classed('showPath', true);
}
});
// TODO: add selection-highlite lines to links
}
// ....
// ...............................
function stillToImplement(data) {
var p = data.payload;
note(data.event, p.id);
//network.view.alert('Not yet implemented: "' + data.event + '"');
}
function unknownEvent(data) {
network.view.alert('Unknown event type: "' + data.event + '"');
......@@ -367,10 +451,42 @@
return 'translate(' + x + ',' + y + ')';
}
function createHostLink(host) {
var src = host.id,
dst = host.cp.device,
id = host.id,
srcNode = network.lookup[src],
dstNode = network.lookup[dst],
lnk;
if (!dstNode) {
// TODO: send warning message back to server on websocket
network.view.alert('switch not on map for link\n\n' +
'src = ' + src + '\ndst = ' + dst);
return null;
}
lnk = {
svgId: safeId(src) + '-' + safeId(dst),
id: id,
source: srcNode,
target: dstNode,
class: 'link',
svgClass: 'link hostLink',
x1: srcNode.x,
y1: srcNode.y,
x2: dstNode.x,
y2: dstNode.y,
width: 1
};
return lnk;
}
function createLink(link) {
var type = link.type,
src = link.src,
dst = link.dst,
id = link.id,
w = link.linkWidth,
srcNode = network.lookup[src],
dstNode = network.lookup[dst],
......@@ -384,7 +500,8 @@
}
lnk = {
id: safeId(src) + '~' + safeId(dst),
svgId: safeId(src) + '-' + safeId(dst),
id: id,
source: srcNode,
target: dstNode,
class: 'link',
......@@ -415,7 +532,7 @@
var entering = link.enter()
.append('line')
.attr({
id: function (d) { return d.id; },
id: function (d) { return d.svgId; },
class: function (d) { return d.svgClass; },
x1: function (d) { return d.x1; },
y1: function (d) { return d.y1; },
......@@ -433,6 +550,19 @@
// augment links
// TODO: add src/dst port labels etc.
// operate on both existing and new links, if necessary
//link .foo() .bar() ...
// operate on exiting links:
// TODO: figure out how to remove the node 'g' AND its children
link.exit()
.transition()
.duration(750)
.attr({
opacity: 0
})
.remove();
}
function createDeviceNode(device) {
......@@ -451,6 +581,22 @@
return node;
}
function createHostNode(host) {
// start with the object as is
var node = host;
// Augment as needed...
node.class = 'host';
node.svgClass = 'node host';
// TODO: consider placing near its switch, if [x,y] not defined
positionNode(node);
// cache label array length
network.hostLabelCount = host.labels.length;
return node;
}
function positionNode(node) {
var meta = node.metaUi,
x = 0,
......@@ -525,7 +671,7 @@
entering.filter('.device').each(function (d) {
var node = d3.select(this),
icon = iconUrl(d),
idx = (labelIdx < d.labels.length) ? labelIdx : 0,
idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0,
box;
node.append('rect')
......@@ -568,6 +714,32 @@
}
});
// augment host nodes...
entering.filter('.host').each(function (d) {
var node = d3.select(this),
idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0,
box;
node.append('circle')
.attr('r', 8); // TODO: define host circle radius
// TODO: are we attaching labels to hosts?
node.append('text')
.text(d.labels[idx])
.attr('dy', '1.1em');
// 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'
});
}
});
// operate on both existing and new nodes, if necessary
//node .foo() .bar() ...
......@@ -618,7 +790,6 @@
webSock.ws = new WebSocket(webSockUrl());
webSock.ws.onopen = function() {
webSock._send("Hi there!");
};
webSock.ws.onmessage = function(m) {
......@@ -643,7 +814,7 @@
if (webSock.ws) {
webSock.ws.send(message);
} else {
network.view.alert('no web socket open');
network.view.alert('no web socket open\n\n' + message);
}
}
......@@ -714,7 +885,7 @@
//flyinPane(null);
}
// TODO: this click handler does not get unloaded when the view does
$('#view').on('click', function(e) {
if (!$(e.target).closest('.node').length) {
if (!e.metaKey) {
......@@ -723,6 +894,33 @@
}
});
function prepareScenario(view, ctx, dbg) {
var sc = scenario,
urlSc = sc.evDir + ctx + sc.evScenario;
if (!ctx) {
view.alert("No scenario specified (null ctx)");
return;
}
sc.view = view;
sc.ctx = ctx;
sc.debug = dbg;
sc.evNumber = 0;
d3.json(urlSc, function(err, data) {
var p = data && data.params || {};
if (err) {
view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
} else {
sc.params = p;
view.alert("Scenario loaded: " + ctx + '\n\n' + data.title);
}
});
}
// ==============================
// View life-cycle callbacks
......@@ -767,10 +965,12 @@
node = nodeG.selectAll('.node');
function ldist(d) {
return fcfg.linkDistance[d.class] || 150;
return 2 * 30;
//return fcfg.linkDistance[d.class] || 150;
}
function lstrg(d) {
return fcfg.linkStrength[d.class] || 1;
return 2 * 0.6;
//return fcfg.linkStrength[d.class] || 1;
}
function lchrg(d) {
return fcfg.charge[d.class] || -200;
......@@ -781,14 +981,11 @@
}
function atDragEnd(d, self) {
// 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(self).classed('fixed', true);
if (config.useLiveData) {
tellServerCoords(d);
}
// once we've finished moving, pin the node in position
d.fixed = true;
d3.select(self).classed('fixed', true);
if (config.useLiveData) {
tellServerCoords(d);
}
}
......@@ -806,17 +1003,34 @@
.size(forceDim)
.nodes(network.nodes)
.links(network.links)
.charge(lchrg)
.gravity(0.3)
.charge(-15000)
.friction(0.1)
//.charge(lchrg)
.linkDistance(ldist)
.linkStrength(lstrg)
.on('tick', tick);
// TVUE
//.gravity(0.3)
//.charge(-15000)
//.friction(0.1)
//.linkDistance(function(d) { return d.value * 30; })
//.linkStrength(function(d) { return d.value * 0.6; })
//.size([w, h])
//.start();
network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
}
function load(view, ctx) {
function load(view, ctx, flags) {
// cache the view token, so network topo functions can access it
network.view = view;
config.useLiveData = !flags.local;
if (!config.useLiveData) {
prepareScenario(view, ctx, flags.debug);
}
// set our radio buttons and key bindings
view.setRadio(btnSet);
......