Adding enhancements to the GUI server-side.
Fixing a few intent-related glitches for the optical use-case.
Fixing lat/lng information in the optical config.
Change-Id: I6a1dd1ee69c2db2f0e351d191627bba468a3c49c
diff --git a/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java b/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
index 34d5814..30fc51f 100644
--- a/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
+++ b/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
@@ -21,6 +21,8 @@
import org.onlab.onos.net.host.HostProviderRegistry;
import org.onlab.onos.net.link.LinkProviderRegistry;
import org.onlab.rest.BaseResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
@@ -31,6 +33,8 @@
import java.io.IOException;
import java.io.InputStream;
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+
/**
* Resource that acts as an ancillary provider for uploading pre-configured
* devices, ports and links.
@@ -38,17 +42,24 @@
@Path("config")
public class ConfigResource extends BaseResource {
+ private static Logger log = LoggerFactory.getLogger(ConfigResource.class);
+
@POST
@Path("topology")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response topology(InputStream input) throws IOException {
- ObjectMapper mapper = new ObjectMapper();
- JsonNode cfg = mapper.readTree(input);
- new ConfigProvider(cfg, get(DeviceProviderRegistry.class),
- get(LinkProviderRegistry.class),
- get(HostProviderRegistry.class)).parse();
- return Response.ok(mapper.createObjectNode().toString()).build();
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode cfg = mapper.readTree(input);
+ new ConfigProvider(cfg, get(DeviceProviderRegistry.class),
+ get(LinkProviderRegistry.class),
+ get(HostProviderRegistry.class)).parse();
+ return Response.ok(mapper.createObjectNode().toString()).build();
+ } catch (Exception e) {
+ log.error("Unable to parse topology configuration", e);
+ return Response.status(INTERNAL_SERVER_ERROR).entity(e.toString()).build();
+ }
}
}
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyMessages.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyMessages.java
index f747423..e7bc37e 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyMessages.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyMessages.java
@@ -40,7 +40,12 @@
import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.ConnectivityIntent;
+import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.LinkCollectionIntent;
+import org.onlab.onos.net.intent.PathIntent;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.link.LinkService;
import org.onlab.onos.net.provider.ProviderId;
@@ -50,6 +55,7 @@
import org.slf4j.LoggerFactory;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -350,7 +356,7 @@
new Prop("Longitude", annot.value("longitude"))));
}
- // Produces a path message to the client.
+ // Produces a path payload to the client.
protected ObjectNode pathMessage(Path path, String type) {
ObjectNode payload = mapper.createObjectNode();
ArrayNode links = mapper.createArrayNode();
@@ -362,6 +368,50 @@
return payload;
}
+
+ // Produces JSON message to trigger traffic visualization
+ protected ObjectNode trafficMessage(Set<Intent> intents, long sid) {
+ ObjectNode payload = mapper.createObjectNode();
+ ArrayNode paths = mapper.createArrayNode();
+ payload.set("paths", paths);
+
+ for (Intent intent : intents) {
+ List<Intent> installables = intentService.getInstallableIntents(intent.id());
+ IntentState state = intentService.getIntentState(intent.id());
+ String type = state == IntentState.FAILED ? "inactive" : "active";
+ for (Intent installable : installables) {
+ if (installable instanceof ConnectivityIntent) {
+ addPathTraffic(paths, type, (ConnectivityIntent) installable);
+ }
+ }
+ }
+
+ return envelope("showTraffic", sid, payload);
+ }
+
+ // Adds the link segments (path or tree) associated with the specified
+ // connectivity intent
+ protected void addPathTraffic(ArrayNode paths, String type,
+ ConnectivityIntent installable) {
+ ObjectNode pathNode = mapper.createObjectNode();
+ ArrayNode linksNode = mapper.createArrayNode();
+
+ Iterable<Link> links;
+ if (installable instanceof PathIntent) {
+ links = ((PathIntent) installable).path().links();
+ } else if (installable instanceof LinkCollectionIntent) {
+ links = ((LinkCollectionIntent) installable).links();
+ } else {
+ return;
+ }
+
+ for (Link link : links) {
+ linksNode.add(compactLinkString(link));
+ }
+ pathNode.put("type", type).set("links", linksNode);
+ paths.add(pathNode);
+ }
+
// Produces compact string representation of a link.
private static String compactLinkString(Link link) {
return String.format(COMPACT, link.src().elementId(), link.src().port(),
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
index e4bfccd..46bc930 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
@@ -15,6 +15,8 @@
*/
package org.onlab.onos.gui;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.jetty.websocket.WebSocket;
import org.onlab.onos.cluster.ClusterEvent;
@@ -24,6 +26,7 @@
import org.onlab.onos.core.CoreService;
import org.onlab.onos.mastership.MastershipEvent;
import org.onlab.onos.mastership.MastershipListener;
+import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.HostId;
@@ -38,16 +41,19 @@
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.MultiPointToSinglePointIntent;
import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.intent.PointToPointIntent;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.link.LinkListener;
import org.onlab.osgi.ServiceDirectory;
import java.io.IOException;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
@@ -85,9 +91,10 @@
private final IntentListener intentListener = new InternalIntentListener();
// Intents that are being monitored for the GUI
- private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
+ private static Map<Intent, Long> intentsToMonitor = new ConcurrentHashMap<>();
private long lastActive = System.currentTimeMillis();
+ private boolean listenersRemoved = false;
/**
* Creates a new web-socket for serving data to GUI topology view.
@@ -103,8 +110,8 @@
* Issues a close on the connection.
*/
synchronized void close() {
+ removeListeners();
if (connection.isOpen()) {
- removeListeners();
connection.close();
}
}
@@ -140,9 +147,7 @@
@Override
public synchronized void onClose(int closeCode, String message) {
- if (connection.isOpen()) {
- removeListeners();
- }
+ removeListeners();
log.info("GUI client disconnected");
}
@@ -165,7 +170,7 @@
} else if (type.equals("requestPath")) {
createHostIntent(event);
} else if (type.equals("requestTraffic")) {
- sendTraffic(event);
+ requestTraffic(event);
} else if (type.equals("cancelTraffic")) {
cancelTraffic(event);
}
@@ -217,12 +222,12 @@
private void requestDetails(ObjectNode event) {
ObjectNode payload = payload(event);
String type = string(payload, "class", "unknown");
+ long sid = number(event, "sid");
+
if (type.equals("device")) {
- sendMessage(deviceDetails(deviceId(string(payload, "id")),
- number(event, "sid")));
+ sendMessage(deviceDetails(deviceId(string(payload, "id")), sid));
} else if (type.equals("host")) {
- sendMessage(hostDetails(hostId(string(payload, "id")),
- number(event, "sid")));
+ sendMessage(hostDetails(hostId(string(payload, "id")), sid));
}
}
@@ -237,27 +242,137 @@
HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
DefaultTrafficSelector.builder().build(),
DefaultTrafficTreatment.builder().build());
- intentsToMonitor.put(hostIntent.id(), number(event, "sid"));
+ intentsToMonitor.put(hostIntent, number(event, "sid"));
intentService.submit(hostIntent);
}
// Sends traffic message.
- private void sendTraffic(ObjectNode event) {
+ private void requestTraffic(ObjectNode event) {
ObjectNode payload = payload(event);
- long id = number(event, "sid");
- IntentId intentId = IntentId.valueOf(payload.path("intentId").asLong());
+ long sid = number(event, "sid");
+ Set<Intent> intents = findPathIntents(payload);
- if (payload != null) {
- payload.put("traffic", true);
- sendMessage(envelope("showPath", id, payload));
- } else {
- sendMessage(warning(id, "No path found"));
+ // Add all those intents to the list of monitored intents & flows.
+ intentsToMonitor.clear();
+ for (Intent intent : intents) {
+ intentsToMonitor.put(intent, sid);
}
+
+ // Send an initial message to highlight all links of all monitored intents.
+ sendMessage(trafficMessage(intents, sid));
}
// Cancels sending traffic messages.
private void cancelTraffic(ObjectNode event) {
- // TODO: implement this
+ ObjectNode payload = payload(event);
+ long sid = number(event, "sid");
+ Set<Intent> intents = findPathIntents(payload);
+
+ // Remove all those intents from the list of monitored intents & flows.
+ intentsToMonitor.clear(); // TODO: remove when ready
+ for (Intent intent : intents) {
+ intentsToMonitor.remove(intent.id());
+ }
+ sendMessage(trafficMessage(intents, sid));
+ }
+
+ // Finds all path (host-to-host or point-to-point) intents that pertains
+ // to the hosts indicated in the given event payload.
+ private Set<Intent> findPathIntents(ObjectNode payload) {
+ // Get the list of selected hosts.
+ Set<Host> hosts = getHosts((ArrayNode) payload.path("ids"));
+
+ // Derive from this the set of edge connect points.
+ Set<ConnectPoint> edgePoints = getEdgePoints(hosts);
+
+ // Iterate over all intents and produce a set that contains only those
+ // intents that target all selected hosts or derived edge connect points.
+ return getIntents(hosts, edgePoints);
+ }
+
+ // Produces a set of intents that target all selected hosts or connect points.
+ private Set<Intent> getIntents(Set<Host> hosts, Set<ConnectPoint> edgePoints) {
+ Set<Intent> intents = new HashSet<>();
+ for (Intent intent : intentService.getIntents()) {
+ boolean isRelevant = false;
+ if (intent instanceof HostToHostIntent) {
+ isRelevant = isIntentRelevant((HostToHostIntent) intent, hosts);
+ } else if (intent instanceof PointToPointIntent) {
+ isRelevant = isIntentRelevant((PointToPointIntent) intent, edgePoints);
+ } else if (intent instanceof MultiPointToSinglePointIntent) {
+ isRelevant = isIntentRelevant((MultiPointToSinglePointIntent) intent, edgePoints);
+ }
+ // TODO: add other intents, e.g. SinglePointToMultiPointIntent
+
+ if (isRelevant) {
+ intents.add(intent);
+ }
+ }
+ return intents;
+ }
+
+ // Indicates whether the specified intent involves all of the given hosts.
+ private boolean isIntentRelevant(HostToHostIntent intent, Set<Host> hosts) {
+ for (Host host : hosts) {
+ HostId id = host.id();
+ // Bail if intent does not involve this host.
+ if (!id.equals(intent.one()) && !id.equals(intent.two())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Indicates whether the specified intent involves all of the given edge points.
+ private boolean isIntentRelevant(PointToPointIntent intent,
+ Set<ConnectPoint> edgePoints) {
+ for (ConnectPoint point : edgePoints) {
+ // Bail if intent does not involve this edge point.
+ if (!point.equals(intent.egressPoint()) &&
+ !point.equals(intent.ingressPoint())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Indicates whether the specified intent involves all of the given edge points.
+ private boolean isIntentRelevant(MultiPointToSinglePointIntent intent,
+ Set<ConnectPoint> edgePoints) {
+ for (ConnectPoint point : edgePoints) {
+ // Bail if intent does not involve this edge point.
+ if (!point.equals(intent.egressPoint()) &&
+ !intent.ingressPoints().contains(point)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ // Produces a set of all host ids listed in the specified JSON array.
+ private Set<Host> getHosts(ArrayNode array) {
+ Set<Host> hosts = new HashSet<>();
+ for (JsonNode node : array) {
+ try {
+ Host host = hostService.getHost(hostId(node.asText()));
+ if (host != null) {
+ hosts.add(host);
+ }
+ } catch (IllegalArgumentException e) {
+ log.debug("Skipping ID {}", node.asText());
+ }
+ }
+ return hosts;
+ }
+
+ // Produces a set of edge points from the specified set of hosts.
+ private Set<ConnectPoint> getEdgePoints(Set<Host> hosts) {
+ Set<ConnectPoint> edgePoints = new HashSet<>();
+ for (Host host : hosts) {
+ edgePoints.add(host.location());
+ }
+ return edgePoints;
}
@@ -272,13 +387,16 @@
}
// Removes all internal listeners.
- private void removeListeners() {
- clusterService.removeListener(clusterListener);
- deviceService.removeListener(deviceListener);
- linkService.removeListener(linkListener);
- hostService.removeListener(hostListener);
- mastershipService.removeListener(mastershipListener);
- intentService.removeListener(intentListener);
+ private synchronized void removeListeners() {
+ if (!listenersRemoved) {
+ listenersRemoved = true;
+ clusterService.removeListener(clusterListener);
+ deviceService.removeListener(deviceListener);
+ linkService.removeListener(linkListener);
+ hostService.removeListener(hostListener);
+ mastershipService.removeListener(mastershipListener);
+ intentService.removeListener(intentListener);
+ }
}
// Cluster event listener.
@@ -327,7 +445,7 @@
@Override
public void event(IntentEvent event) {
Intent intent = event.subject();
- Long sid = intentsToMonitor.get(intent.id());
+ Long sid = intentsToMonitor.get(intent);
if (sid != null) {
List<Intent> installable = intentService.getInstallableIntents(intent.id());
if (installable != null && !installable.isEmpty()) {
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index 8934ec3..f93595f 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -130,8 +130,8 @@
U: unpin,
W: requestTraffic, // bag of selections
- Z: requestPath, // host-to-host intent (and monitor)
- X: cancelMonitor
+ X: cancelTraffic,
+ Z: requestPath // host-to-host intent (and monitor)
};
// state variables
@@ -496,7 +496,23 @@
}
function showTraffic(data) {
- network.view.alert("showTraffic() -- TODO")
+ fnTrace('showTraffic', data.payload.id);
+ data.payload.paths.forEach(function () {
+ 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) {
+ link.el.classed('showPath', true);
+ }
+ });
+ });
+ //network.view.alert("showTraffic() -- TODO")
}
// ...............................
@@ -566,9 +582,9 @@
}
}
- function cancelMonitor() {
+ function cancelTraffic() {
// FIXME: from where do we get the intent id(s) to send to the server?
- sendMessage('cancelMonitor', {
+ sendMessage('cancelTraffic', {
ids: ["need_the_intent_id"]
});
}