aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/web/gui/src/main/java/org
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/web/gui/src/main/java/org
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/web/gui/src/main/java/org')
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java44
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java125
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java101
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java209
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java164
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/GroupViewMessageHandler.java131
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java130
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java276
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java127
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/LogoutResource.java46
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainIndexResource.java102
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainModuleResource.java77
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainNavResource.java124
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainViewResource.java60
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java96
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java95
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/SpriteService.java50
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopoOverlayCache.java116
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyResource.java94
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java781
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java509
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java675
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TrafficOverlay.java62
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TunnelViewMessageHandler.java80
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java181
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java240
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocketServlet.java95
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/package-info.java20
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java175
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java132
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoIntentFilter.java274
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLink.java223
-rw-r--r--framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLinkMap.java33
33 files changed, 5647 insertions, 0 deletions
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
new file mode 100644
index 00000000..398bfee0
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.sun.jersey.multipart.FormDataParam;
+import org.onlab.rest.BaseResource;
+import org.onosproject.app.ApplicationAdminService;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Application upload resource.
+ */
+@Path("applications")
+public class ApplicationResource extends BaseResource {
+
+ @Path("upload")
+ @POST
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ public Response upload(@FormDataParam("file") InputStream stream) throws IOException {
+ get(ApplicationAdminService.class).install(stream);
+ return Response.ok().build();
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
new file mode 100644
index 00000000..77a58972
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+
+import java.util.Collection;
+
+import static org.onosproject.app.ApplicationState.ACTIVE;
+
+/**
+ * Message handler for application view related messages.
+ */
+public class ApplicationViewMessageHandler extends UiMessageHandler {
+
+ private static final String APP_DATA_REQ = "appDataRequest";
+ private static final String APP_DATA_RESP = "appDataResponse";
+ private static final String APPS = "apps";
+
+ private static final String APP_MGMT_REQ = "appManagementRequest";
+
+ private static final String STATE = "state";
+ private static final String STATE_IID = "_iconid_state";
+ private static final String ID = "id";
+ private static final String VERSION = "version";
+ private static final String ORIGIN = "origin";
+ private static final String DESC = "desc";
+
+ private static final String ICON_ID_ACTIVE = "active";
+ private static final String ICON_ID_INACTIVE = "appInactive";
+
+ private static final String[] COL_IDS = {
+ STATE, STATE_IID, ID, VERSION, ORIGIN, DESC
+ };
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(
+ new AppDataRequest(),
+ new AppMgmtRequest()
+ );
+ }
+
+ // handler for application table requests
+ private final class AppDataRequest extends TableRequestHandler {
+ private AppDataRequest() {
+ super(APP_DATA_REQ, APP_DATA_RESP, APPS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ ApplicationService as = get(ApplicationService.class);
+ for (Application app : as.getApplications()) {
+ populateRow(tm.addRow(), app, as);
+ }
+ }
+
+ private void populateRow(TableModel.Row row, Application app,
+ ApplicationService as) {
+ ApplicationId id = app.id();
+ ApplicationState state = as.getState(id);
+ String iconId = state == ACTIVE ? ICON_ID_ACTIVE : ICON_ID_INACTIVE;
+
+ row.cell(STATE, state)
+ .cell(STATE_IID, iconId)
+ .cell(ID, id.name())
+ .cell(VERSION, app.version())
+ .cell(ORIGIN, app.origin())
+ .cell(DESC, app.description());
+ }
+ }
+
+ // handler for application management control button actions
+ private final class AppMgmtRequest extends RequestHandler {
+ private AppMgmtRequest() {
+ super(APP_MGMT_REQ);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ String action = string(payload, "action");
+ String name = string(payload, "name");
+ if (action != null && name != null) {
+ ApplicationAdminService service = get(ApplicationAdminService.class);
+ ApplicationId appId = service.getId(name);
+ if (action.equals("activate")) {
+ service.activate(appId);
+ } else if (action.equals("deactivate")) {
+ service.deactivate(appId);
+ } else if (action.equals("uninstall")) {
+ service.uninstall(appId);
+ }
+ chain(APP_DATA_REQ, sid, payload);
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java
new file mode 100644
index 00000000..da0eae07
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.joda.time.DateTime;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.TimeFormatter;
+
+import java.util.Collection;
+
+
+/**
+ * Message handler for cluster view related messages.
+ */
+public class ClusterViewMessageHandler extends UiMessageHandler {
+
+ private static final String CLUSTER_DATA_REQ = "clusterDataRequest";
+ private static final String CLUSTER_DATA_RESP = "clusterDataResponse";
+ private static final String CLUSTERS = "clusters";
+
+ private static final String ID = "id";
+ private static final String IP = "ip";
+ private static final String TCP_PORT = "tcp";
+ private static final String STATE_IID = "_iconid_state";
+ private static final String UPDATED = "updated";
+
+ private static final String[] COL_IDS = {
+ ID, IP, TCP_PORT, STATE_IID, UPDATED
+ };
+
+ private static final String ICON_ID_ONLINE = "active";
+ private static final String ICON_ID_OFFLINE = "inactive";
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new ClusterDataRequest());
+ }
+
+ // handler for cluster table requests
+ private final class ClusterDataRequest extends TableRequestHandler {
+ private ClusterDataRequest() {
+ super(CLUSTER_DATA_REQ, CLUSTER_DATA_RESP, CLUSTERS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected TableModel createTableModel() {
+ TableModel tm = super.createTableModel();
+ tm.setFormatter(UPDATED, new TimeFormatter());
+ return tm;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ ClusterService cs = get(ClusterService.class);
+ for (ControllerNode node : cs.getNodes()) {
+ populateRow(tm.addRow(), node, cs);
+ }
+ }
+
+ private void populateRow(TableModel.Row row, ControllerNode node,
+ ClusterService cs) {
+ NodeId id = node.id();
+ DateTime lastUpdated = cs.getLastUpdated(id);
+ String iconId = (cs.getState(id) == ControllerNode.State.ACTIVE) ?
+ ICON_ID_ONLINE : ICON_ID_OFFLINE;
+
+ row.cell(ID, id)
+ .cell(IP, node.ip())
+ .cell(TCP_PORT, node.tcpPort())
+ .cell(STATE_IID, iconId)
+ .cell(UPDATED, lastUpdated);
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
new file mode 100644
index 00000000..fb83cdd7
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.apache.commons.lang.WordUtils.capitalizeFully;
+
+/**
+ * Message handler for device view related messages.
+ */
+public class DeviceViewMessageHandler extends UiMessageHandler {
+
+ private static final String DEV_DATA_REQ = "deviceDataRequest";
+ private static final String DEV_DATA_RESP = "deviceDataResponse";
+ private static final String DEVICES = "devices";
+
+ private static final String DEV_DETAILS_REQ = "deviceDetailsRequest";
+ private static final String DEV_DETAILS_RESP = "deviceDetailsResponse";
+ private static final String DETAILS = "details";
+
+ private static final String ID = "id";
+ private static final String TYPE = "type";
+ private static final String AVAILABLE = "available";
+ private static final String AVAILABLE_IID = "_iconid_available";
+ private static final String TYPE_IID = "_iconid_type";
+ private static final String DEV_ICON_PREFIX = "devIcon_";
+ private static final String NUM_PORTS = "num_ports";
+ private static final String LINK_DEST = "elinks_dest";
+ private static final String MFR = "mfr";
+ private static final String HW = "hw";
+ private static final String SW = "sw";
+ private static final String PROTOCOL = "protocol";
+ private static final String MASTER_ID = "masterid";
+ private static final String CHASSIS_ID = "chassisid";
+ private static final String SERIAL = "serial";
+ private static final String PORTS = "ports";
+ private static final String ENABLED = "enabled";
+ private static final String SPEED = "speed";
+ private static final String NAME = "name";
+
+
+ private static final String[] COL_IDS = {
+ AVAILABLE, AVAILABLE_IID, TYPE_IID, ID,
+ NUM_PORTS, MASTER_ID, MFR, HW, SW,
+ PROTOCOL, CHASSIS_ID, SERIAL
+ };
+
+ private static final String ICON_ID_ONLINE = "active";
+ private static final String ICON_ID_OFFLINE = "inactive";
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(
+ new DataRequestHandler(),
+ new DetailRequestHandler()
+ );
+ }
+
+ private static String getTypeIconId(Device d) {
+ return DEV_ICON_PREFIX + d.type().toString();
+ }
+
+ // handler for device table requests
+ private final class DataRequestHandler extends TableRequestHandler {
+ private DataRequestHandler() {
+ super(DEV_DATA_REQ, DEV_DATA_RESP, DEVICES);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ DeviceService ds = get(DeviceService.class);
+ MastershipService ms = get(MastershipService.class);
+ for (Device dev : ds.getDevices()) {
+ populateRow(tm.addRow(), dev, ds, ms);
+ }
+ }
+
+ private void populateRow(TableModel.Row row, Device dev,
+ DeviceService ds, MastershipService ms) {
+ DeviceId id = dev.id();
+ boolean available = ds.isAvailable(id);
+ String iconId = available ? ICON_ID_ONLINE : ICON_ID_OFFLINE;
+
+ String protocol = dev.annotations().value(PROTOCOL);
+
+ row.cell(ID, id)
+ .cell(AVAILABLE, available)
+ .cell(AVAILABLE_IID, iconId)
+ .cell(TYPE_IID, getTypeIconId(dev))
+ .cell(MFR, dev.manufacturer())
+ .cell(HW, dev.hwVersion())
+ .cell(SW, dev.swVersion())
+ .cell(PROTOCOL, protocol != null ? protocol : "")
+ .cell(NUM_PORTS, ds.getPorts(id).size())
+ .cell(MASTER_ID, ms.getMasterFor(id));
+ }
+ }
+
+ // handler for selected device detail requests
+ private final class DetailRequestHandler extends RequestHandler {
+ private DetailRequestHandler() {
+ super(DEV_DETAILS_REQ);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ String id = string(payload, "id", "of:0000000000000000");
+
+ DeviceId deviceId = DeviceId.deviceId(id);
+ DeviceService service = get(DeviceService.class);
+ MastershipService ms = get(MastershipService.class);
+ Device device = service.getDevice(deviceId);
+ ObjectNode data = MAPPER.createObjectNode();
+
+ data.put(ID, deviceId.toString());
+ data.put(TYPE, capitalizeFully(device.type().toString()));
+ data.put(TYPE_IID, getTypeIconId(device));
+ data.put(MFR, device.manufacturer());
+ data.put(HW, device.hwVersion());
+ data.put(SW, device.swVersion());
+ data.put(SERIAL, device.serialNumber());
+ data.put(CHASSIS_ID, device.chassisId().toString());
+ data.put(MASTER_ID, ms.getMasterFor(deviceId).toString());
+ data.put(PROTOCOL, device.annotations().value(PROTOCOL));
+
+ ArrayNode ports = MAPPER.createArrayNode();
+
+ List<Port> portList = new ArrayList<>(service.getPorts(deviceId));
+ Collections.sort(portList, (p1, p2) -> {
+ long delta = p1.number().toLong() - p2.number().toLong();
+ return delta == 0 ? 0 : (delta < 0 ? -1 : +1);
+ });
+
+ for (Port p : portList) {
+ ports.add(portData(p, deviceId));
+ }
+ data.set(PORTS, ports);
+
+ ObjectNode rootNode = MAPPER.createObjectNode();
+ rootNode.set(DETAILS, data);
+ sendMessage(DEV_DETAILS_RESP, 0, rootNode);
+ }
+
+ private ObjectNode portData(Port p, DeviceId id) {
+ ObjectNode port = MAPPER.createObjectNode();
+ LinkService ls = get(LinkService.class);
+ String name = p.annotations().value(AnnotationKeys.PORT_NAME);
+
+ port.put(ID, capitalizeFully(p.number().toString()));
+ port.put(TYPE, capitalizeFully(p.type().toString()));
+ port.put(SPEED, p.portSpeed());
+ port.put(ENABLED, p.isEnabled());
+ port.put(NAME, name != null ? name : "");
+
+ Set<Link> links = ls.getEgressLinks(new ConnectPoint(id, p.number()));
+ if (!links.isEmpty()) {
+ StringBuilder egressLinks = new StringBuilder();
+ for (Link l : links) {
+ ConnectPoint dest = l.dst();
+ egressLinks.append(dest.elementId()).append("/")
+ .append(dest.port()).append(" ");
+ }
+ port.put(LINK_DEST, egressLinks.toString());
+ }
+
+ return port;
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
new file mode 100644
index 00000000..02179115
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.CellFormatter;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.EnumFormatter;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Message handler for flow view related messages.
+ */
+public class FlowViewMessageHandler extends UiMessageHandler {
+
+ private static final String FLOW_DATA_REQ = "flowDataRequest";
+ private static final String FLOW_DATA_RESP = "flowDataResponse";
+ private static final String FLOWS = "flows";
+
+ private static final String ID = "id";
+ private static final String APP_ID = "appId";
+ private static final String GROUP_ID = "groupId";
+ private static final String TABLE_ID = "tableId";
+ private static final String PRIORITY = "priority";
+ private static final String SELECTOR = "selector";
+ private static final String TREATMENT = "treatment";
+ private static final String TIMEOUT = "timeout";
+ private static final String PERMANENT = "permanent";
+ private static final String STATE = "state";
+ private static final String PACKETS = "packets";
+ private static final String BYTES = "bytes";
+
+ private static final String COMMA = ", ";
+
+ private static final String[] COL_IDS = {
+ ID, APP_ID, GROUP_ID, TABLE_ID, PRIORITY, SELECTOR,
+ TREATMENT, TIMEOUT, PERMANENT, STATE, PACKETS, BYTES
+ };
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new FlowDataRequest());
+ }
+
+ // handler for flow table requests
+ private final class FlowDataRequest extends TableRequestHandler {
+
+ private FlowDataRequest() {
+ super(FLOW_DATA_REQ, FLOW_DATA_RESP, FLOWS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected TableModel createTableModel() {
+ TableModel tm = super.createTableModel();
+ tm.setFormatter(SELECTOR, new SelectorFormatter());
+ tm.setFormatter(TREATMENT, new TreatmentFormatter());
+ tm.setFormatter(STATE, EnumFormatter.INSTANCE);
+ return tm;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ String uri = string(payload, "devId");
+ if (!Strings.isNullOrEmpty(uri)) {
+ DeviceId deviceId = DeviceId.deviceId(uri);
+ FlowRuleService frs = get(FlowRuleService.class);
+ for (FlowEntry flow : frs.getFlowEntries(deviceId)) {
+ populateRow(tm.addRow(), flow);
+ }
+ }
+ }
+
+ private void populateRow(TableModel.Row row, FlowEntry flow) {
+ row.cell(ID, flow.id().value())
+ .cell(APP_ID, flow.appId())
+ .cell(GROUP_ID, flow.groupId().id())
+ .cell(TABLE_ID, flow.tableId())
+ .cell(PRIORITY, flow.priority())
+ .cell(SELECTOR, flow)
+ .cell(TREATMENT, flow)
+ .cell(TIMEOUT, flow.timeout())
+ .cell(PERMANENT, flow.isPermanent())
+ .cell(STATE, flow.state())
+ .cell(PACKETS, flow.packets())
+ .cell(BYTES, flow.bytes());
+ }
+
+ private final class SelectorFormatter implements CellFormatter {
+ @Override
+ public String format(Object value) {
+ FlowEntry flow = (FlowEntry) value;
+ Set<Criterion> criteria = flow.selector().criteria();
+
+ if (criteria.isEmpty()) {
+ return "(No traffic selector criteria for this flow)";
+ }
+ StringBuilder sb = new StringBuilder("Criteria: ");
+ for (Criterion c : criteria) {
+ sb.append(c).append(COMMA);
+ }
+ removeTrailingComma(sb);
+
+ return sb.toString();
+ }
+ }
+
+ private final class TreatmentFormatter implements CellFormatter {
+ @Override
+ public String format(Object value) {
+ FlowEntry flow = (FlowEntry) value;
+ List<Instruction> instructions = flow.treatment().allInstructions();
+
+ if (instructions.isEmpty()) {
+ return "(No traffic treatment instructions for this flow)";
+ }
+ StringBuilder sb = new StringBuilder("Treatment Instructions: ");
+ for (Instruction i : instructions) {
+ sb.append(i).append(COMMA);
+ }
+ removeTrailingComma(sb);
+
+ return sb.toString();
+ }
+ }
+
+ private StringBuilder removeTrailingComma(StringBuilder sb) {
+ int pos = sb.lastIndexOf(COMMA);
+ sb.delete(pos, sb.length());
+ return sb;
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/GroupViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/GroupViewMessageHandler.java
new file mode 100644
index 00000000..c7d539b1
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/GroupViewMessageHandler.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.CellFormatter;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.EnumFormatter;
+
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * Message handler for group view related messages.
+ */
+public class GroupViewMessageHandler extends UiMessageHandler {
+
+ private static final String GROUP_DATA_REQ = "groupDataRequest";
+ private static final String GROUP_DATA_RESP = "groupDataResponse";
+ private static final String GROUPS = "groups";
+
+ private static final String ID = "id";
+ private static final String APP_ID = "app_id";
+ private static final String STATE = "state";
+ private static final String TYPE = "type";
+ private static final String PACKETS = "packets";
+ private static final String BYTES = "bytes";
+ private static final String BUCKETS = "buckets";
+
+ private static final String[] COL_IDS = {
+ ID, APP_ID, STATE, TYPE, PACKETS, BYTES, BUCKETS
+ };
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new GroupDataRequest());
+ }
+
+ // handler for group table requests
+ private final class GroupDataRequest extends TableRequestHandler {
+
+ private GroupDataRequest() {
+ super(GROUP_DATA_REQ, GROUP_DATA_RESP, GROUPS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected TableModel createTableModel() {
+ TableModel tm = super.createTableModel();
+ tm.setFormatter(TYPE, EnumFormatter.INSTANCE);
+ tm.setFormatter(BUCKETS, new BucketFormatter());
+ return tm;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ String uri = string(payload, "devId");
+ if (!Strings.isNullOrEmpty(uri)) {
+ DeviceId deviceId = DeviceId.deviceId(uri);
+ GroupService gs = get(GroupService.class);
+ for (Group group : gs.getGroups(deviceId)) {
+ populateRow(tm.addRow(), group);
+ }
+ }
+ }
+
+ private void populateRow(TableModel.Row row, Group g) {
+ row.cell(ID, g.id().id())
+ .cell(APP_ID, g.appId().name())
+ .cell(STATE, g.state())
+ .cell(TYPE, g.type())
+ .cell(PACKETS, g.packets())
+ .cell(BYTES, g.bytes())
+ .cell(BUCKETS, g.buckets().buckets());
+ }
+
+ private final class BucketFormatter implements CellFormatter {
+ private static final String BREAK = "<br>";
+
+ @Override
+ public String format(Object value) {
+ StringBuilder sb = new StringBuilder();
+ List<GroupBucket> buckets = (List<GroupBucket>) value;
+
+ if (buckets.isEmpty()) {
+ return "(No buckets for this group)";
+ }
+
+ for (GroupBucket b : buckets) {
+ sb.append("Bytes: ")
+ .append(b.bytes())
+ .append(" Packets: ")
+ .append(b.packets())
+ .append(" Actions: ")
+ .append(b.treatment().allInstructions())
+ .append(BREAK);
+ }
+
+ return sb.toString();
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
new file mode 100644
index 00000000..12a2b664
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.CellFormatter;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.HostLocationFormatter;
+
+import java.util.Collection;
+import java.util.Set;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * Message handler for host view related messages.
+ */
+public class HostViewMessageHandler extends UiMessageHandler {
+
+ private static final String HOST_DATA_REQ = "hostDataRequest";
+ private static final String HOST_DATA_RESP = "hostDataResponse";
+ private static final String HOSTS = "hosts";
+
+ private static final String TYPE_IID = "_iconid_type";
+ private static final String ID = "id";
+ private static final String MAC = "mac";
+ private static final String VLAN = "vlan";
+ private static final String IPS = "ips";
+ private static final String LOCATION = "location";
+
+ private static final String HOST_ICON_PREFIX = "hostIcon_";
+
+ private static final String[] COL_IDS = {
+ TYPE_IID, ID, MAC, VLAN, IPS, LOCATION
+ };
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new HostDataRequest());
+ }
+
+ // handler for host table requests
+ private final class HostDataRequest extends TableRequestHandler {
+ private HostDataRequest() {
+ super(HOST_DATA_REQ, HOST_DATA_RESP, HOSTS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected TableModel createTableModel() {
+ TableModel tm = super.createTableModel();
+ tm.setFormatter(LOCATION, HostLocationFormatter.INSTANCE);
+ tm.setFormatter(IPS, new IpSetFormatter());
+ return tm;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ HostService hs = get(HostService.class);
+ for (Host host : hs.getHosts()) {
+ populateRow(tm.addRow(), host);
+ }
+ }
+
+ private void populateRow(TableModel.Row row, Host host) {
+ row.cell(TYPE_IID, getTypeIconId(host))
+ .cell(ID, host.id())
+ .cell(MAC, host.mac())
+ .cell(VLAN, host.vlan())
+ .cell(IPS, host.ipAddresses())
+ .cell(LOCATION, host.location());
+ }
+
+ private String getTypeIconId(Host host) {
+ String hostType = host.annotations().value(AnnotationKeys.TYPE);
+ return HOST_ICON_PREFIX +
+ (isNullOrEmpty(hostType) ? "endstation" : hostType);
+ }
+
+ private final class IpSetFormatter implements CellFormatter {
+ private static final String COMMA = ", ";
+
+ @Override
+ public String format(Object value) {
+ Set<IpAddress> ips = (Set<IpAddress>) value;
+ if (ips.isEmpty()) {
+ return "(No IP Addresses for this host)";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (IpAddress ip : ips) {
+ sb.append(ip.toString())
+ .append(COMMA);
+ }
+ removeTrailingComma(sb);
+ return sb.toString();
+ }
+
+ private StringBuilder removeTrailingComma(StringBuilder sb) {
+ int pos = sb.lastIndexOf(COMMA);
+ sb.delete(pos, sb.length());
+ return sb;
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java
new file mode 100644
index 00000000..bacb76d2
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.NetworkResource;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.intent.ConnectivityIntent;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.LinkCollectionIntent;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.PathIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.CellFormatter;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.AppIdFormatter;
+import org.onosproject.ui.table.cell.EnumFormatter;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Message handler for intent view related messages.
+ */
+public class IntentViewMessageHandler extends UiMessageHandler {
+
+ private static final String INTENT_DATA_REQ = "intentDataRequest";
+ private static final String INTENT_DATA_RESP = "intentDataResponse";
+ private static final String INTENTS = "intents";
+
+ private static final String APP_ID = "appId";
+ private static final String KEY = "key";
+ private static final String TYPE = "type";
+ private static final String PRIORITY = "priority";
+ private static final String STATE = "state";
+ private static final String RESOURCES = "resources";
+ private static final String DETAILS = "details";
+
+ private static final String[] COL_IDS = {
+ APP_ID, KEY, TYPE, PRIORITY, STATE, RESOURCES, DETAILS
+ };
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new IntentDataRequest());
+ }
+
+ // handler for intent table requests
+ private final class IntentDataRequest extends TableRequestHandler {
+ private IntentDataRequest() {
+ super(INTENT_DATA_REQ, INTENT_DATA_RESP, INTENTS);
+ }
+
+ @Override
+ protected String defaultColumnId() {
+ return APP_ID;
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected TableModel createTableModel() {
+ TableModel tm = super.createTableModel();
+ tm.setFormatter(APP_ID, AppIdFormatter.INSTANCE);
+ tm.setFormatter(RESOURCES, new ResourcesFormatter());
+ tm.setFormatter(DETAILS, new DetailsFormatter());
+ tm.setFormatter(STATE, EnumFormatter.INSTANCE);
+ return tm;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ IntentService is = get(IntentService.class);
+ for (Intent intent : is.getIntents()) {
+ populateRow(tm.addRow(), intent, is);
+ }
+ }
+
+ private void populateRow(TableModel.Row row, Intent intent, IntentService is) {
+ row.cell(APP_ID, intent.appId())
+ .cell(KEY, intent.key())
+ .cell(TYPE, intent.getClass().getSimpleName())
+ .cell(PRIORITY, intent.priority())
+ .cell(STATE, is.getIntentState(intent.key()))
+ .cell(RESOURCES, intent)
+ .cell(DETAILS, intent);
+ }
+
+ private final class ResourcesFormatter implements CellFormatter {
+ private static final String COMMA = ", ";
+
+ @Override
+ public String format(Object value) {
+ Intent intent = (Intent) value;
+ Collection<NetworkResource> resources = intent.resources();
+ if (resources.isEmpty()) {
+ return "(No resources for this intent)";
+ }
+ StringBuilder sb = new StringBuilder("Resources: ");
+ for (NetworkResource nr : resources) {
+ sb.append(nr).append(COMMA);
+ }
+ removeTrailingComma(sb);
+
+ return sb.toString();
+ }
+
+ private StringBuilder removeTrailingComma(StringBuilder sb) {
+ int pos = sb.lastIndexOf(COMMA);
+ sb.delete(pos, sb.length());
+ return sb;
+ }
+ }
+
+ private final class DetailsFormatter implements CellFormatter {
+ @Override
+ public String format(Object value) {
+ return formatDetails((Intent) value, new StringBuilder()).toString();
+ }
+
+ private StringBuilder formatDetails(Intent intent, StringBuilder sb) {
+ if (intent instanceof ConnectivityIntent) {
+ buildConnectivityDetails((ConnectivityIntent) intent, sb);
+ }
+
+ if (intent instanceof HostToHostIntent) {
+ buildHostToHostDetails((HostToHostIntent) intent, sb);
+
+ } else if (intent instanceof PointToPointIntent) {
+ buildPointToPointDetails((PointToPointIntent) intent, sb);
+
+ } else if (intent instanceof MultiPointToSinglePointIntent) {
+ buildMPToSPDetails((MultiPointToSinglePointIntent) intent, sb);
+
+ } else if (intent instanceof SinglePointToMultiPointIntent) {
+ buildSPToMPDetails((SinglePointToMultiPointIntent) intent, sb);
+
+ } else if (intent instanceof PathIntent) {
+ buildPathDetails((PathIntent) intent, sb);
+
+ } else if (intent instanceof LinkCollectionIntent) {
+ buildLinkConnectionDetails((LinkCollectionIntent) intent, sb);
+ }
+
+ if (sb.length() == 0) {
+ sb.append("(No details for this intent)");
+ } else {
+ sb.insert(0, "Details: ");
+ }
+ return sb;
+ }
+
+ private void appendMultiPointsDetails(Set<ConnectPoint> points,
+ StringBuilder sb) {
+ for (ConnectPoint point : points) {
+ sb.append(point.elementId())
+ .append('/')
+ .append(point.port())
+ .append(' ');
+ }
+ }
+
+ private void buildConnectivityDetails(ConnectivityIntent intent,
+ StringBuilder sb) {
+ Set<Criterion> criteria = intent.selector().criteria();
+ List<Instruction> instructions = intent.treatment().allInstructions();
+ List<Constraint> constraints = intent.constraints();
+
+ if (!criteria.isEmpty()) {
+ sb.append("Selector: ").append(criteria);
+ }
+ if (!instructions.isEmpty()) {
+ sb.append("Treatment: ").append(instructions);
+ }
+ if (constraints != null && !constraints.isEmpty()) {
+ sb.append("Constraints: ").append(constraints);
+ }
+ }
+
+ private void buildHostToHostDetails(HostToHostIntent intent,
+ StringBuilder sb) {
+ sb.append(" Host 1: ")
+ .append(intent.one())
+ .append(", Host 2: ")
+ .append(intent.two());
+ }
+
+ private void buildPointToPointDetails(PointToPointIntent intent,
+ StringBuilder sb) {
+ ConnectPoint ingress = intent.ingressPoint();
+ ConnectPoint egress = intent.egressPoint();
+ sb.append(" Ingress: ")
+ .append(ingress.elementId())
+ .append('/')
+ .append(ingress.port())
+
+ .append(", Egress: ")
+ .append(egress.elementId())
+ .append('/')
+ .append(egress.port())
+ .append(' ');
+ }
+
+ private void buildMPToSPDetails(MultiPointToSinglePointIntent intent,
+ StringBuilder sb) {
+ ConnectPoint egress = intent.egressPoint();
+
+ sb.append(" Ingress=");
+ appendMultiPointsDetails(intent.ingressPoints(), sb);
+
+ sb.append(", Egress=")
+ .append(egress.elementId())
+ .append('/')
+ .append(egress.port())
+ .append(' ');
+ }
+
+ private void buildSPToMPDetails(SinglePointToMultiPointIntent intent,
+ StringBuilder sb) {
+ ConnectPoint ingress = intent.ingressPoint();
+
+ sb.append(" Ingress=")
+ .append(ingress.elementId())
+ .append('/')
+ .append(ingress.port())
+ .append(", Egress=");
+
+ appendMultiPointsDetails(intent.egressPoints(), sb);
+ }
+
+ private void buildPathDetails(PathIntent intent, StringBuilder sb) {
+ sb.append(" path=")
+ .append(intent.path().links())
+ .append(", cost=")
+ .append(intent.path().cost());
+ }
+
+ private void buildLinkConnectionDetails(LinkCollectionIntent intent,
+ StringBuilder sb) {
+ sb.append(" links=")
+ .append(intent.links())
+ .append(", egress=");
+
+ appendMultiPointsDetails(intent.egressPoints(), sb);
+ }
+
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
new file mode 100644
index 00000000..7de6d624
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.Link;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.topo.BaseLink;
+import org.onosproject.ui.topo.BaseLinkMap;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.ConnectPointFormatter;
+import org.onosproject.ui.table.cell.EnumFormatter;
+
+import java.util.Collection;
+
+/**
+ * Message handler for link view related messages.
+ */
+public class LinkViewMessageHandler extends UiMessageHandler {
+
+ private static final String A_BOTH_B = "A &harr; B";
+ private static final String A_SINGLE_B = "A &rarr; B";
+ private static final String SLASH = " / ";
+
+ private static final String LINK_DATA_REQ = "linkDataRequest";
+ private static final String LINK_DATA_RESP = "linkDataResponse";
+ private static final String LINKS = "links";
+
+ private static final String ONE = "one";
+ private static final String TWO = "two";
+ private static final String TYPE = "type";
+ private static final String STATE = "_iconid_state";
+ private static final String DIRECTION = "direction";
+ private static final String DURABLE = "durable";
+
+ private static final String[] COL_IDS = {
+ ONE, TWO, TYPE, STATE, DIRECTION, DURABLE
+ };
+
+ private static final String ICON_ID_ONLINE = "active";
+ private static final String ICON_ID_OFFLINE = "inactive";
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new LinkDataRequest());
+ }
+
+ // handler for link table requests
+ private final class LinkDataRequest extends TableRequestHandler {
+ private LinkDataRequest() {
+ super(LINK_DATA_REQ, LINK_DATA_RESP, LINKS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected String defaultColumnId() {
+ return ONE;
+ }
+
+ @Override
+ protected TableModel createTableModel() {
+ TableModel tm = super.createTableModel();
+ tm.setFormatter(ONE, ConnectPointFormatter.INSTANCE);
+ tm.setFormatter(TWO, ConnectPointFormatter.INSTANCE);
+ tm.setFormatter(TYPE, EnumFormatter.INSTANCE);
+ return tm;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ LinkService ls = get(LinkService.class);
+ BaseLinkMap linkMap = new BaseLinkMap();
+ ls.getLinks().forEach(linkMap::add);
+ linkMap.biLinks().forEach(blink -> populateRow(tm.addRow(), blink));
+ }
+
+ private void populateRow(TableModel.Row row, BaseLink blink) {
+ row.cell(ONE, blink.one().src())
+ .cell(TWO, blink.one().dst())
+ .cell(TYPE, linkType(blink))
+ .cell(STATE, linkState(blink))
+ .cell(DIRECTION, linkDir(blink))
+ .cell(DURABLE, blink.one().isDurable());
+ }
+
+ private String linkType(BaseLink link) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(link.one().type());
+ if (link.two() != null && link.two().type() != link.one().type()) {
+ sb.append(SLASH).append(link.two().type());
+ }
+ return sb.toString();
+ }
+
+ private String linkState(BaseLink link) {
+ return (link.one().state() == Link.State.ACTIVE ||
+ link.two().state() == Link.State.ACTIVE) ?
+ ICON_ID_ONLINE : ICON_ID_OFFLINE;
+ }
+
+ private String linkDir(BaseLink link) {
+ return link.two() != null ? A_BOTH_B : A_SINGLE_B;
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/LogoutResource.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/LogoutResource.java
new file mode 100644
index 00000000..b21b5cdb
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/LogoutResource.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import org.onlab.rest.BaseResource;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Application upload resource.
+ */
+@Path("logout")
+public class LogoutResource extends BaseResource {
+
+ @Context
+ private HttpServletRequest servletRequest;
+
+ @GET
+ public Response logout() throws IOException, URISyntaxException {
+ servletRequest.getSession().invalidate();
+ String url = servletRequest.getRequestURL().toString();
+ url = url.replaceFirst("/onos/ui/.*", "/onos/ui/login.html");
+ return Response.temporaryRedirect(new URI(url)).build();
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainIndexResource.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainIndexResource.java
new file mode 100644
index 00000000..a30c0470
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainIndexResource.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import org.onlab.osgi.ServiceNotFoundException;
+import org.onosproject.rest.AbstractInjectionResource;
+import org.onosproject.ui.UiExtensionService;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.io.ByteStreams.toByteArray;
+
+/**
+ * Resource for serving the dynamically composed index.html.
+ */
+@Path("/")
+public class MainIndexResource extends AbstractInjectionResource {
+
+ private static final String INDEX = "index.html";
+ private static final String NOT_READY = "not-ready.html";
+
+ private static final String INJECT_CSS_START = "<!-- {INJECTED-STYLESHEETS-START} -->";
+ private static final String INJECT_CSS_END = "<!-- {INJECTED-STYLESHEETS-END} -->";
+
+ private static final String INJECT_JS_START = "<!-- {INJECTED-JAVASCRIPT-START} -->";
+ private static final String INJECT_JS_END = "<!-- {INJECTED-JAVASCRIPT-END} -->";
+
+ @GET
+ @Produces(MediaType.TEXT_HTML)
+ public Response getMainIndex() throws IOException {
+ ClassLoader classLoader = getClass().getClassLoader();
+ UiExtensionService service;
+ try {
+ service = get(UiExtensionService.class);
+ } catch (ServiceNotFoundException e) {
+ return Response.ok(classLoader.getResourceAsStream(NOT_READY)).build();
+ }
+
+ InputStream indexTemplate = classLoader.getResourceAsStream(INDEX);
+ String index = new String(toByteArray(indexTemplate));
+
+ int p1s = split(index, 0, INJECT_JS_START);
+ int p1e = split(index, p1s, INJECT_JS_END);
+ int p2s = split(index, p1e, INJECT_CSS_START);
+ int p2e = split(index, p2s, INJECT_CSS_END);
+ int p3s = split(index, p2e, null);
+
+ StreamEnumeration streams =
+ new StreamEnumeration(of(stream(index, 0, p1s),
+ includeJs(service),
+ stream(index, p1e, p2s),
+ includeCss(service),
+ stream(index, p2e, p3s)));
+
+ return Response.ok(new SequenceInputStream(streams)).build();
+ }
+
+ // Produces an input stream including CSS injections from all extensions.
+ private InputStream includeCss(UiExtensionService service) {
+ Builder<InputStream> builder = ImmutableList.builder();
+ service.getExtensions().forEach(ext -> add(builder, ext.css()));
+ return new SequenceInputStream(new StreamEnumeration(builder.build()));
+ }
+
+ // Produces an input stream including JS injections from all extensions.
+ private InputStream includeJs(UiExtensionService service) {
+ Builder<InputStream> builder = ImmutableList.builder();
+ service.getExtensions().forEach(ext -> add(builder, ext.js()));
+ return new SequenceInputStream(new StreamEnumeration(builder.build()));
+ }
+
+ // Safely adds the stream to the list builder only if stream is not null.
+ private void add(Builder<InputStream> builder, InputStream inputStream) {
+ if (inputStream != null) {
+ builder.add(inputStream);
+ }
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainModuleResource.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainModuleResource.java
new file mode 100644
index 00000000..42d1dff9
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainModuleResource.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import org.onosproject.rest.AbstractInjectionResource;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiView;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.io.ByteStreams.toByteArray;
+import static org.onosproject.ui.impl.MainViewResource.SCRIPT;
+
+/**
+ * Resource for serving the dynamically composed onos.js.
+ */
+@Path("/")
+public class MainModuleResource extends AbstractInjectionResource {
+
+ private static final String MAIN_JS = "onos.js";
+
+ private static final String INJECT_VIEW_IDS_START = "// {INJECTED-VIEW-IDS-START}";
+ private static final String INJECT_VIEW_IDS_END = "// {INJECTED-VIEW-IDS-END}";
+
+ @GET
+ @Produces(SCRIPT)
+ public Response getMainModule() throws IOException {
+ UiExtensionService service = get(UiExtensionService.class);
+ InputStream jsTemplate = getClass().getClassLoader().getResourceAsStream(MAIN_JS);
+ String js = new String(toByteArray(jsTemplate));
+
+ int p1s = split(js, 0, INJECT_VIEW_IDS_START);
+ int p1e = split(js, 0, INJECT_VIEW_IDS_END);
+ int p2s = split(js, p1e, null);
+
+ StreamEnumeration streams =
+ new StreamEnumeration(of(stream(js, 0, p1s),
+ includeViewIds(service),
+ stream(js, p1e, p2s)));
+
+ return Response.ok(new SequenceInputStream(streams)).build();
+ }
+
+ // Produces an input stream including view id injections from all extensions.
+ private InputStream includeViewIds(UiExtensionService service) {
+ StringBuilder sb = new StringBuilder("\n");
+ for (UiExtension extension : service.getExtensions()) {
+ for (UiView view : extension.views()) {
+ sb.append(" '").append(view.id()).append("',");
+ }
+ }
+ return new ByteArrayInputStream(sb.toString().getBytes());
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainNavResource.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainNavResource.java
new file mode 100644
index 00000000..7f0af3a5
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainNavResource.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import org.onosproject.rest.AbstractInjectionResource;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiView;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.io.ByteStreams.toByteArray;
+
+/**
+ * Resource for serving the dynamically composed nav.html.
+ */
+@Path("/")
+public class MainNavResource extends AbstractInjectionResource {
+
+ private static final String NAV_HTML = "nav.html";
+
+ private static final String INJECT_VIEW_ITEMS_START =
+ "<!-- {INJECTED-VIEW-NAV-START} -->";
+ private static final String INJECT_VIEW_ITEMS_END =
+ "<!-- {INJECTED-VIEW-NAV-END} -->";
+
+ private static final String HDR_FORMAT =
+ "<div class=\"nav-hdr\">%s</div>\n";
+ private static final String NAV_FORMAT =
+ "<a ng-click=\"navCtrl.hideNav()\" href=\"#/%s\">%s %s</a>\n";
+
+ private static final String BLANK_GLYPH = "unknown";
+
+ @GET
+ @Produces(MediaType.TEXT_HTML)
+ public Response getNavigation() throws IOException {
+ UiExtensionService service = get(UiExtensionService.class);
+ InputStream navTemplate =
+ getClass().getClassLoader().getResourceAsStream(NAV_HTML);
+ String html = new String(toByteArray(navTemplate));
+
+ int p1s = split(html, 0, INJECT_VIEW_ITEMS_START);
+ int p1e = split(html, 0, INJECT_VIEW_ITEMS_END);
+ int p2s = split(html, p1e, null);
+
+ StreamEnumeration streams =
+ new StreamEnumeration(of(stream(html, 0, p1s),
+ includeNavItems(service),
+ stream(html, p1e, p2s)));
+
+ return Response.ok(new SequenceInputStream(streams)).build();
+ }
+
+ // Produces an input stream of nav item injections from all extensions.
+ private InputStream includeNavItems(UiExtensionService service) {
+ List<UiExtension> extensions = service.getExtensions();
+ StringBuilder sb = new StringBuilder("\n");
+
+ for (UiView.Category cat : UiView.Category.values()) {
+ if (cat == UiView.Category.HIDDEN) {
+ continue;
+ }
+
+ List<UiView> catViews = getViewsForCat(extensions, cat);
+ if (!catViews.isEmpty()) {
+ addCatHeader(sb, cat);
+ addCatItems(sb, catViews);
+ }
+ }
+
+ return new ByteArrayInputStream(sb.toString().getBytes());
+ }
+
+ private List<UiView> getViewsForCat(List<UiExtension> extensions,
+ UiView.Category cat) {
+ List<UiView> views = new ArrayList<>();
+ for (UiExtension extension : extensions) {
+ views.addAll(extension.views().stream().filter(
+ view -> cat.equals(view.category())
+ ).collect(Collectors.toList()));
+ }
+ return views;
+ }
+
+ private void addCatHeader(StringBuilder sb, UiView.Category cat) {
+ sb.append(String.format(HDR_FORMAT, cat.label()));
+ }
+
+ private void addCatItems(StringBuilder sb, List<UiView> catViews) {
+ for (UiView view : catViews) {
+ sb.append(String.format(NAV_FORMAT, view.id(), icon(view), view.label()));
+ }
+ }
+
+ private String icon(UiView view) {
+ String gid = view.iconId() == null ? BLANK_GLYPH : view.iconId();
+ return "<div icon icon-id=\"" + gid + "\"></div>";
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainViewResource.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainViewResource.java
new file mode 100644
index 00000000..130a7eab
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/MainViewResource.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import org.onosproject.rest.AbstractInjectionResource;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM;
+import static javax.ws.rs.core.MediaType.TEXT_HTML;
+
+/**
+ * Resource for serving the dynamically composed onos.js.
+ */
+@Path("/")
+public class MainViewResource extends AbstractInjectionResource {
+
+ static final String CONTENT_TYPE = "Content-Type";
+ static final String STYLESHEET = "text/css";
+ static final String SCRIPT = "text/javascript";
+
+ @Path("{view}/{resource}")
+ @GET
+ public Response getViewResource(@PathParam("view") String viewId,
+ @PathParam("resource") String resource) throws IOException {
+ UiExtensionService service = get(UiExtensionService.class);
+ UiExtension extension = service.getViewExtension(viewId);
+ return extension != null ?
+ Response.ok(extension.resource(viewId, resource))
+ .header(CONTENT_TYPE, contentType(resource)).build() :
+ Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+ static String contentType(String resource) {
+ return resource.endsWith(".html") ? TEXT_HTML :
+ resource.endsWith(".css") ? STYLESHEET :
+ resource.endsWith(".js") ? SCRIPT :
+ APPLICATION_OCTET_STREAM;
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
new file mode 100644
index 00000000..92ebcf75
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+
+import java.util.Collection;
+
+
+/**
+ * Message handler for port view related messages.
+ */
+public class PortViewMessageHandler extends UiMessageHandler {
+
+ private static final String PORT_DATA_REQ = "portDataRequest";
+ private static final String PORT_DATA_RESP = "portDataResponse";
+ private static final String PORTS = "ports";
+
+ private static final String ID = "id";
+ private static final String PKT_RX = "pkt_rx";
+ private static final String PKT_TX = "pkt_tx";
+ private static final String BYTES_RX = "bytes_rx";
+ private static final String BYTES_TX = "bytes_tx";
+ private static final String PKT_RX_DRP = "pkt_rx_drp";
+ private static final String PKT_TX_DRP = "pkt_tx_drp";
+ private static final String DURATION = "duration";
+
+ private static final String[] COL_IDS = {
+ ID, PKT_RX, PKT_TX, BYTES_RX, BYTES_TX,
+ PKT_RX_DRP, PKT_TX_DRP, DURATION
+ };
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new PortDataRequest());
+ }
+
+ // handler for port table requests
+ private final class PortDataRequest extends TableRequestHandler {
+
+ private PortDataRequest() {
+ super(PORT_DATA_REQ, PORT_DATA_RESP, PORTS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ String uri = string(payload, "devId");
+ if (!Strings.isNullOrEmpty(uri)) {
+ DeviceId deviceId = DeviceId.deviceId(uri);
+ DeviceService ds = get(DeviceService.class);
+ for (PortStatistics stat : ds.getPortStatistics(deviceId)) {
+ populateRow(tm.addRow(), stat);
+ }
+ }
+ }
+
+ private void populateRow(TableModel.Row row, PortStatistics stat) {
+ row.cell(ID, stat.port())
+ .cell(PKT_RX, stat.packetsReceived())
+ .cell(PKT_TX, stat.packetsSent())
+ .cell(BYTES_RX, stat.bytesReceived())
+ .cell(BYTES_TX, stat.bytesSent())
+ .cell(PKT_RX_DRP, stat.packetsRxDropped())
+ .cell(PKT_TX_DRP, stat.packetsTxDropped())
+ .cell(DURATION, stat.durationSec());
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java
new file mode 100644
index 00000000..ed413fab
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cfg.ConfigProperty;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+
+import java.util.Collection;
+
+/**
+ * Message handler for component configuration view related messages.
+ */
+public class SettingsViewMessageHandler extends UiMessageHandler {
+
+ private static final String DATA_REQUEST = "settingDataRequest";
+ private static final String DATA_RESPONSE = "settingDataResponse";
+ private static final String SETTINGS = "settings";
+
+ private static final String COMPONENT = "component";
+ private static final String ID = "id";
+ private static final String TYPE = "type";
+ private static final String VALUE = "value";
+ private static final String DEFAULT = "defValue";
+ private static final String DESC = "desc";
+
+ private static final String[] COL_IDS = {
+ COMPONENT, ID, TYPE, VALUE, DEFAULT, DESC
+ };
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new SettingsRequest());
+ }
+
+ // handler for host table requests
+ private final class SettingsRequest extends TableRequestHandler {
+ private SettingsRequest() {
+ super(DATA_REQUEST, DATA_RESPONSE, SETTINGS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected String defaultColumnId() {
+ return COMPONENT;
+ }
+
+ @Override
+ protected TableModel createTableModel() {
+ TableModel tm = super.createTableModel();
+ return tm;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ ComponentConfigService ccs = get(ComponentConfigService.class);
+ for (String component : ccs.getComponentNames()) {
+ for (ConfigProperty prop : ccs.getProperties(component)) {
+ populateRow(tm.addRow(), component, prop);
+ }
+ }
+ }
+
+ private void populateRow(TableModel.Row row, String component, ConfigProperty prop) {
+ row.cell(COMPONENT, component)
+ .cell(ID, prop.name())
+ .cell(TYPE, prop.type().toString().toLowerCase())
+ .cell(VALUE, prop.value())
+ .cell(DEFAULT, prop.defaultValue())
+ .cell(DESC, prop.description());
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/SpriteService.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/SpriteService.java
new file mode 100644
index 00000000..7312329c
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/SpriteService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.util.Set;
+
+/**
+ * Provisional service to keep track of the topology view sprite definitions.
+ */
+public interface SpriteService {
+
+ /**
+ * Returns set of registered sprite definition names.
+ *
+ * @return set of sprite definition names
+ */
+ Set<String> getNames();
+
+ /**
+ * Registers sprite data under the specified name.
+ *
+ * @param name sprite definition name
+ * @param spriteData sprite data
+ */
+ void put(String name, JsonNode spriteData);
+
+ /**
+ * Returns the sprite definition registered under the given name.
+ *
+ * @param name sprite definition name
+ * @return sprite data
+ */
+ JsonNode get(String name);
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopoOverlayCache.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopoOverlayCache.java
new file mode 100644
index 00000000..99557c2d
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopoOverlayCache.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import org.onosproject.ui.UiTopoOverlay;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * A cache of {@link org.onosproject.ui.UiTopoOverlay}'s that were registered
+ * at the time the UI connection was established.
+ */
+public class TopoOverlayCache {
+
+ private static final String EMPTY = "";
+ private static final UiTopoOverlay NONE = new NullOverlay();
+
+ private final Map<String, UiTopoOverlay> overlays = new HashMap<>();
+ private UiTopoOverlay current = NONE;
+
+ public TopoOverlayCache() {
+ overlays.put(null, NONE);
+ }
+
+ /**
+ * Adds a topology overlay to the cache.
+ *
+ * @param overlay a topology overlay
+ */
+ public void add(UiTopoOverlay overlay) {
+ overlays.put(overlay.id(), overlay);
+ }
+
+ /**
+ * Invoked when the cache is no longer needed.
+ */
+ public void destroy() {
+ overlays.clear();
+ }
+
+ /**
+ * Switching currently selected overlay.
+ *
+ * @param deact identity of overlay to deactivate
+ * @param act identity of overlay to activate
+ */
+ public void switchOverlay(String deact, String act) {
+ UiTopoOverlay toDeactivate = getOverlay(deact);
+ UiTopoOverlay toActivate = getOverlay(act);
+
+ toDeactivate.deactivate();
+ current = toActivate;
+ current.activate();
+ }
+
+ private UiTopoOverlay getOverlay(String id) {
+ return isNullOrEmpty(id) ? NONE : overlays.get(id);
+ }
+
+ /**
+ * Returns the current overlay instance.
+ * Note that this method always returns a reference; when there is no
+ * overlay selected the "NULL" overlay instance is returned.
+ *
+ * @return the current overlay
+ */
+ public UiTopoOverlay currentOverlay() {
+ return current;
+ }
+
+ /**
+ * Returns the number of overlays in the cache. Remember that this
+ * includes the "NULL" overlay, representing "no overlay selected".
+ *
+ * @return number of overlays
+ */
+ public int size() {
+ return overlays.size();
+ }
+
+ /**
+ * Returns true if the identifier of the currently active overlay
+ * matches the given parameter.
+ *
+ * @param overlayId overlay identifier
+ * @return true if this matches the ID of currently active overlay
+ */
+ public boolean isActive(String overlayId) {
+ return currentOverlay().id().equals(overlayId);
+ }
+
+ // overlay instance representing "no overlay selected"
+ private static class NullOverlay extends UiTopoOverlay {
+ public NullOverlay() {
+ super(EMPTY);
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyResource.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyResource.java
new file mode 100644
index 00000000..30960911
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyResource.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014-2015 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.onosproject.ui.impl;
+
+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.onlab.rest.BaseResource;
+import org.slf4j.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Topology viewer resource.
+ */
+@Path("topology")
+public class TopologyResource extends BaseResource {
+
+ private static final Logger log = getLogger(TopologyResource.class);
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ @Path("geoloc")
+ @GET
+ @Produces("application/json")
+ public Response getGeoLocations() {
+ ObjectNode rootNode = mapper.createObjectNode();
+ ArrayNode devices = mapper.createArrayNode();
+ ArrayNode hosts = mapper.createArrayNode();
+
+ Map<String, ObjectNode> metaUi = TopologyViewMessageHandler.getMetaUi();
+ for (String id : metaUi.keySet()) {
+ ObjectNode memento = metaUi.get(id);
+ if (id.length() > 17 && id.charAt(17) == '/') {
+ addGeoData(hosts, "id", id, memento);
+ } else {
+ addGeoData(devices, "uri", id, memento);
+ }
+ }
+
+ rootNode.set("devices", devices);
+ rootNode.set("hosts", hosts);
+ return Response.ok(rootNode.toString()).build();
+ }
+
+ private void addGeoData(ArrayNode array, String idField, String id,
+ ObjectNode memento) {
+ ObjectNode node = mapper.createObjectNode().put(idField, id);
+ ObjectNode annot = mapper.createObjectNode();
+ node.set("annotations", annot);
+ try {
+ annot.put("latitude", memento.get("lat").asDouble())
+ .put("longitude", memento.get("lng").asDouble());
+ array.add(node);
+ } catch (Exception e) {
+ log.debug("Skipping geo entry");
+ }
+ }
+
+ @Path("sprites")
+ @POST
+ @Consumes("application/json")
+ public Response setSprites(InputStream stream) throws IOException {
+ JsonNode root = mapper.readTree(stream);
+ String name = root.path("defn_name").asText("sprites");
+ get(SpriteService.class).put(name, root);
+ return Response.ok().build();
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
new file mode 100644
index 00000000..8acdc2cf
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -0,0 +1,781 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.util.AbstractAccumulator;
+import org.onlab.util.Accumulator;
+import org.onosproject.cluster.ClusterEvent;
+import org.onosproject.cluster.ClusterEventListener;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.Event;
+import org.onosproject.mastership.MastershipAdminService;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkListener;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiConnection;
+import org.onosproject.ui.impl.TrafficMonitor.Mode;
+import org.onosproject.ui.topo.Highlights;
+import org.onosproject.ui.topo.NodeSelection;
+import org.onosproject.ui.topo.PropertyPanel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.HostId.hostId;
+import static org.onosproject.net.device.DeviceEvent.Type.*;
+import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
+import static org.onosproject.ui.JsonUtils.envelope;
+import static org.onosproject.ui.topo.TopoJson.highlightsMessage;
+import static org.onosproject.ui.topo.TopoJson.json;
+
+/**
+ * Web socket capable of interacting with the GUI topology view.
+ */
+public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
+
+ // incoming event types
+ private static final String REQ_DETAILS = "requestDetails";
+ private static final String UPDATE_META = "updateMeta";
+ private static final String ADD_HOST_INTENT = "addHostIntent";
+ private static final String ADD_MULTI_SRC_INTENT = "addMultiSourceIntent";
+ private static final String REQ_RELATED_INTENTS = "requestRelatedIntents";
+ private static final String REQ_NEXT_INTENT = "requestNextRelatedIntent";
+ private static final String REQ_PREV_INTENT = "requestPrevRelatedIntent";
+ private static final String REQ_SEL_INTENT_TRAFFIC = "requestSelectedIntentTraffic";
+ private static final String REQ_ALL_FLOW_TRAFFIC = "requestAllFlowTraffic";
+ private static final String REQ_ALL_PORT_TRAFFIC = "requestAllPortTraffic";
+ private static final String REQ_DEV_LINK_FLOWS = "requestDeviceLinkFlows";
+ private static final String CANCEL_TRAFFIC = "cancelTraffic";
+ private static final String REQ_SUMMARY = "requestSummary";
+ private static final String CANCEL_SUMMARY = "cancelSummary";
+ private static final String EQ_MASTERS = "equalizeMasters";
+ private static final String SPRITE_LIST_REQ = "spriteListRequest";
+ private static final String SPRITE_DATA_REQ = "spriteDataRequest";
+ private static final String TOPO_START = "topoStart";
+ private static final String TOPO_HEARTBEAT = "topoHeartbeat";
+ private static final String TOPO_SELECT_OVERLAY = "topoSelectOverlay";
+ private static final String TOPO_STOP = "topoStop";
+
+ // outgoing event types
+ private static final String SHOW_SUMMARY = "showSummary";
+ private static final String SHOW_DETAILS = "showDetails";
+ private static final String SPRITE_LIST_RESPONSE = "spriteListResponse";
+ private static final String SPRITE_DATA_RESPONSE = "spriteDataResponse";
+ private static final String UPDATE_INSTANCE = "updateInstance";
+
+ // fields
+ private static final String ID = "id";
+ private static final String DEVICE = "device";
+ private static final String HOST = "host";
+ private static final String CLASS = "class";
+ private static final String UNKNOWN = "unknown";
+ private static final String ONE = "one";
+ private static final String TWO = "two";
+ private static final String SRC = "src";
+ private static final String DST = "dst";
+ private static final String DATA = "data";
+ private static final String NAME = "name";
+ private static final String NAMES = "names";
+ private static final String ACTIVATE = "activate";
+ private static final String DEACTIVATE = "deactivate";
+
+
+ private static final String APP_ID = "org.onosproject.gui";
+
+ private static final long TRAFFIC_PERIOD = 5000;
+ private static final long SUMMARY_PERIOD = 30000;
+
+ private static final Comparator<? super ControllerNode> NODE_COMPARATOR =
+ (o1, o2) -> o1.id().toString().compareTo(o2.id().toString());
+
+
+ private final Timer timer = new Timer("onos-topology-view");
+
+ private static final int MAX_EVENTS = 1000;
+ private static final int MAX_BATCH_MS = 5000;
+ private static final int MAX_IDLE_MS = 1000;
+
+ private ApplicationId appId;
+
+ private final ClusterEventListener clusterListener = new InternalClusterListener();
+ private final MastershipListener mastershipListener = new InternalMastershipListener();
+ private final DeviceListener deviceListener = new InternalDeviceListener();
+ private final LinkListener linkListener = new InternalLinkListener();
+ private final HostListener hostListener = new InternalHostListener();
+ private final IntentListener intentListener = new InternalIntentListener();
+ private final FlowRuleListener flowListener = new InternalFlowListener();
+
+ private final Accumulator<Event> eventAccummulator = new InternalEventAccummulator();
+ private final ExecutorService msgSender =
+ newSingleThreadExecutor(groupedThreads("onos/gui", "msg-sender"));
+
+ private TopoOverlayCache overlayCache;
+ private TrafficMonitor traffic;
+
+ private TimerTask summaryTask = null;
+ private boolean summaryRunning = false;
+
+ private boolean listenersRemoved = false;
+
+
+ @Override
+ public void init(UiConnection connection, ServiceDirectory directory) {
+ super.init(connection, directory);
+ appId = directory.get(CoreService.class).registerApplication(APP_ID);
+ traffic = new TrafficMonitor(TRAFFIC_PERIOD, servicesBundle, this);
+ }
+
+ @Override
+ public void destroy() {
+ cancelAllRequests();
+ removeListeners();
+ super.destroy();
+ }
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(
+ new TopoStart(),
+ new TopoHeartbeat(),
+ new TopoSelectOverlay(),
+ new TopoStop(),
+ new ReqSummary(),
+ new CancelSummary(),
+ new SpriteListReq(),
+ new SpriteDataReq(),
+ new RequestDetails(),
+ new UpdateMeta(),
+ new EqMasters(),
+
+ // TODO: migrate traffic related to separate app
+ new AddHostIntent(),
+ new AddMultiSourceIntent(),
+
+ new ReqAllFlowTraffic(),
+ new ReqAllPortTraffic(),
+ new ReqDevLinkFlows(),
+ new ReqRelatedIntents(),
+ new ReqNextIntent(),
+ new ReqPrevIntent(),
+ new ReqSelectedIntentTraffic(),
+
+ new CancelTraffic()
+ );
+ }
+
+ /**
+ * Injects the topology overlay cache.
+ *
+ * @param overlayCache injected cache
+ */
+ void setOverlayCache(TopoOverlayCache overlayCache) {
+ this.overlayCache = overlayCache;
+ }
+
+ // ==================================================================
+
+ private final class TopoStart extends RequestHandler {
+ private TopoStart() {
+ super(TOPO_START);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ addListeners();
+ sendAllInstances(null);
+ sendAllDevices();
+ sendAllLinks();
+ sendAllHosts();
+ }
+ }
+
+ private final class TopoHeartbeat extends RequestHandler {
+ private TopoHeartbeat() {
+ super(TOPO_HEARTBEAT);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ // place holder for now
+ }
+ }
+
+ private final class TopoSelectOverlay extends RequestHandler {
+ private TopoSelectOverlay() {
+ super(TOPO_SELECT_OVERLAY);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ String deact = string(payload, DEACTIVATE);
+ String act = string(payload, ACTIVATE);
+ overlayCache.switchOverlay(deact, act);
+ }
+ }
+
+ private final class TopoStop extends RequestHandler {
+ private TopoStop() {
+ super(TOPO_STOP);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ stopSummaryMonitoring();
+ traffic.stopMonitoring();
+ }
+ }
+
+ private final class ReqSummary extends RequestHandler {
+ private ReqSummary() {
+ super(REQ_SUMMARY);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ requestSummary(sid);
+ startSummaryMonitoring();
+ }
+ }
+
+ private final class CancelSummary extends RequestHandler {
+ private CancelSummary() {
+ super(CANCEL_SUMMARY);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ stopSummaryMonitoring();
+ }
+ }
+
+ private final class SpriteListReq extends RequestHandler {
+ private SpriteListReq() {
+ super(SPRITE_LIST_REQ);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ ObjectNode root = objectNode();
+ ArrayNode names = arrayNode();
+ get(SpriteService.class).getNames().forEach(names::add);
+ root.set(NAMES, names);
+ sendMessage(SPRITE_LIST_RESPONSE, sid, root);
+ }
+ }
+
+ private final class SpriteDataReq extends RequestHandler {
+ private SpriteDataReq() {
+ super(SPRITE_DATA_REQ);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ String name = string(payload, NAME);
+ ObjectNode root = objectNode();
+ root.set(DATA, get(SpriteService.class).get(name));
+ sendMessage(SPRITE_DATA_RESPONSE, sid, root);
+ }
+ }
+
+ private final class RequestDetails extends RequestHandler {
+ private RequestDetails() {
+ super(REQ_DETAILS);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ String type = string(payload, CLASS, UNKNOWN);
+ String id = string(payload, ID);
+ PropertyPanel pp = null;
+
+ if (type.equals(DEVICE)) {
+ pp = deviceDetails(deviceId(id), sid);
+ overlayCache.currentOverlay().modifyDeviceDetails(pp);
+ } else if (type.equals(HOST)) {
+ pp = hostDetails(hostId(id), sid);
+ overlayCache.currentOverlay().modifyHostDetails(pp);
+ }
+
+ sendMessage(envelope(SHOW_DETAILS, sid, json(pp)));
+ }
+ }
+
+ private final class UpdateMeta extends RequestHandler {
+ private UpdateMeta() {
+ super(UPDATE_META);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ updateMetaUi(payload);
+ }
+ }
+
+ private final class EqMasters extends RequestHandler {
+ private EqMasters() {
+ super(EQ_MASTERS);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ directory.get(MastershipAdminService.class).balanceRoles();
+ }
+ }
+
+
+ // ========= -----------------------------------------------------------------
+
+ // === TODO: move traffic related classes to traffic app
+
+ private final class AddHostIntent extends RequestHandler {
+ private AddHostIntent() {
+ super(ADD_HOST_INTENT);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ // TODO: add protection against device ids and non-existent hosts.
+ HostId one = hostId(string(payload, ONE));
+ HostId two = hostId(string(payload, TWO));
+
+ HostToHostIntent intent = HostToHostIntent.builder()
+ .appId(appId)
+ .one(one)
+ .two(two)
+ .build();
+
+ intentService.submit(intent);
+ if (overlayCache.isActive(TrafficOverlay.TRAFFIC_ID)) {
+ traffic.monitor(intent);
+ }
+ }
+ }
+
+ private final class AddMultiSourceIntent extends RequestHandler {
+ private AddMultiSourceIntent() {
+ super(ADD_MULTI_SRC_INTENT);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ // TODO: add protection against device ids and non-existent hosts.
+ Set<HostId> src = getHostIds((ArrayNode) payload.path(SRC));
+ HostId dst = hostId(string(payload, DST));
+ Host dstHost = hostService.getHost(dst);
+
+ Set<ConnectPoint> ingressPoints = getHostLocations(src);
+
+ // FIXME: clearly, this is not enough
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthDst(dstHost.mac()).build();
+ TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+
+ MultiPointToSinglePointIntent intent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoints(ingressPoints)
+ .egressPoint(dstHost.location())
+ .build();
+
+ intentService.submit(intent);
+ if (overlayCache.isActive(TrafficOverlay.TRAFFIC_ID)) {
+ traffic.monitor(intent);
+ }
+ }
+ }
+
+ // ========= -----------------------------------------------------------------
+
+ private final class ReqAllFlowTraffic extends RequestHandler {
+ private ReqAllFlowTraffic() {
+ super(REQ_ALL_FLOW_TRAFFIC);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ traffic.monitor(Mode.ALL_FLOW_TRAFFIC);
+ }
+ }
+
+ private final class ReqAllPortTraffic extends RequestHandler {
+ private ReqAllPortTraffic() {
+ super(REQ_ALL_PORT_TRAFFIC);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ traffic.monitor(Mode.ALL_PORT_TRAFFIC);
+ }
+ }
+
+ private final class ReqDevLinkFlows extends RequestHandler {
+ private ReqDevLinkFlows() {
+ super(REQ_DEV_LINK_FLOWS);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ NodeSelection nodeSelection =
+ new NodeSelection(payload, deviceService, hostService);
+ traffic.monitor(Mode.DEV_LINK_FLOWS, nodeSelection);
+ }
+ }
+
+ private final class ReqRelatedIntents extends RequestHandler {
+ private ReqRelatedIntents() {
+ super(REQ_RELATED_INTENTS);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ NodeSelection nodeSelection =
+ new NodeSelection(payload, deviceService, hostService);
+ traffic.monitor(Mode.RELATED_INTENTS, nodeSelection);
+ }
+ }
+
+ private final class ReqNextIntent extends RequestHandler {
+ private ReqNextIntent() {
+ super(REQ_NEXT_INTENT);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ traffic.selectNextIntent();
+ }
+ }
+
+ private final class ReqPrevIntent extends RequestHandler {
+ private ReqPrevIntent() {
+ super(REQ_PREV_INTENT);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ traffic.selectPreviousIntent();
+ }
+ }
+
+ private final class ReqSelectedIntentTraffic extends RequestHandler {
+ private ReqSelectedIntentTraffic() {
+ super(REQ_SEL_INTENT_TRAFFIC);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ traffic.monitor(Mode.SELECTED_INTENT);
+ }
+ }
+
+ private final class CancelTraffic extends RequestHandler {
+ private CancelTraffic() {
+ super(CANCEL_TRAFFIC);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ traffic.stopMonitoring();
+ }
+ }
+
+ //=======================================================================
+
+ // Converts highlights to JSON format and sends the message to the client
+ protected void sendHighlights(Highlights highlights) {
+ sendMessage(highlightsMessage(highlights));
+ }
+
+ // Subscribes for summary messages.
+ private synchronized void requestSummary(long sid) {
+ PropertyPanel pp = summmaryMessage(sid);
+ overlayCache.currentOverlay().modifySummary(pp);
+ sendMessage(envelope(SHOW_SUMMARY, sid, json(pp)));
+ }
+
+
+ private void cancelAllRequests() {
+ stopSummaryMonitoring();
+ traffic.stopMonitoring();
+ }
+
+ // Sends all controller nodes to the client as node-added messages.
+ private void sendAllInstances(String messageType) {
+ List<ControllerNode> nodes = new ArrayList<>(clusterService.getNodes());
+ Collections.sort(nodes, NODE_COMPARATOR);
+ for (ControllerNode node : nodes) {
+ sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node),
+ messageType));
+ }
+ }
+
+ // Sends all devices to the client as device-added messages.
+ private void sendAllDevices() {
+ // Send optical first, others later for layered rendering
+ for (Device device : deviceService.getDevices()) {
+ if (device.type() == Device.Type.ROADM) {
+ sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
+ }
+ }
+ for (Device device : deviceService.getDevices()) {
+ if (device.type() != Device.Type.ROADM) {
+ sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
+ }
+ }
+ }
+
+ // Sends all links to the client as link-added messages.
+ private void sendAllLinks() {
+ // Send optical first, others later for layered rendering
+ for (Link link : linkService.getLinks()) {
+ if (link.type() == Link.Type.OPTICAL) {
+ sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
+ }
+ }
+ for (Link link : linkService.getLinks()) {
+ if (link.type() != Link.Type.OPTICAL) {
+ sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
+ }
+ }
+ }
+
+ // Sends all hosts to the client as host-added messages.
+ private void sendAllHosts() {
+ for (Host host : hostService.getHosts()) {
+ sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
+ }
+ }
+
+ private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) {
+ Set<ConnectPoint> points = new HashSet<>();
+ for (HostId hostId : hostIds) {
+ points.add(getHostLocation(hostId));
+ }
+ return points;
+ }
+
+ private HostLocation getHostLocation(HostId hostId) {
+ return hostService.getHost(hostId).location();
+ }
+
+ // Produces a list of host ids from the specified JSON array.
+ private Set<HostId> getHostIds(ArrayNode ids) {
+ Set<HostId> hostIds = new HashSet<>();
+ for (JsonNode id : ids) {
+ hostIds.add(hostId(id.asText()));
+ }
+ return hostIds;
+ }
+
+
+ private synchronized void startSummaryMonitoring() {
+ stopSummaryMonitoring();
+ summaryTask = new SummaryMonitor();
+ timer.schedule(summaryTask, SUMMARY_PERIOD, SUMMARY_PERIOD);
+ summaryRunning = true;
+ }
+
+ private synchronized void stopSummaryMonitoring() {
+ if (summaryTask != null) {
+ summaryTask.cancel();
+ summaryTask = null;
+ }
+ summaryRunning = false;
+ }
+
+
+ // Adds all internal listeners.
+ private synchronized void addListeners() {
+ listenersRemoved = false;
+ clusterService.addListener(clusterListener);
+ mastershipService.addListener(mastershipListener);
+ deviceService.addListener(deviceListener);
+ linkService.addListener(linkListener);
+ hostService.addListener(hostListener);
+ intentService.addListener(intentListener);
+ flowService.addListener(flowListener);
+ }
+
+ // Removes all internal listeners.
+ private synchronized void removeListeners() {
+ if (!listenersRemoved) {
+ listenersRemoved = true;
+ clusterService.removeListener(clusterListener);
+ mastershipService.removeListener(mastershipListener);
+ deviceService.removeListener(deviceListener);
+ linkService.removeListener(linkListener);
+ hostService.removeListener(hostListener);
+ intentService.removeListener(intentListener);
+ flowService.removeListener(flowListener);
+ }
+ }
+
+ // Cluster event listener.
+ private class InternalClusterListener implements ClusterEventListener {
+ @Override
+ public void event(ClusterEvent event) {
+ msgSender.execute(() -> sendMessage(instanceMessage(event, null)));
+ }
+ }
+
+ // Mastership change listener
+ private class InternalMastershipListener implements MastershipListener {
+ @Override
+ public void event(MastershipEvent event) {
+ msgSender.execute(() -> {
+ sendAllInstances(UPDATE_INSTANCE);
+ Device device = deviceService.getDevice(event.subject());
+ if (device != null) {
+ sendMessage(deviceMessage(new DeviceEvent(DEVICE_UPDATED, device)));
+ }
+ });
+ }
+ }
+
+ // Device event listener.
+ private class InternalDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ if (event.type() != PORT_STATS_UPDATED) {
+ msgSender.execute(() -> sendMessage(deviceMessage(event)));
+ eventAccummulator.add(event);
+ }
+ }
+ }
+
+ // Link event listener.
+ private class InternalLinkListener implements LinkListener {
+ @Override
+ public void event(LinkEvent event) {
+ msgSender.execute(() -> sendMessage(linkMessage(event)));
+ eventAccummulator.add(event);
+ }
+ }
+
+ // Host event listener.
+ private class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ msgSender.execute(() -> sendMessage(hostMessage(event)));
+ eventAccummulator.add(event);
+ }
+ }
+
+ // Intent event listener.
+ private class InternalIntentListener implements IntentListener {
+ @Override
+ public void event(IntentEvent event) {
+ msgSender.execute(traffic::pokeIntent);
+ eventAccummulator.add(event);
+ }
+ }
+
+ // Intent event listener.
+ private class InternalFlowListener implements FlowRuleListener {
+ @Override
+ public void event(FlowRuleEvent event) {
+ eventAccummulator.add(event);
+ }
+ }
+
+
+ // === SUMMARY MONITORING
+
+ // Periodic update of the summary information
+ private class SummaryMonitor extends TimerTask {
+ @Override
+ public void run() {
+ try {
+ if (summaryRunning) {
+ msgSender.execute(() -> requestSummary(0));
+ }
+ } catch (Exception e) {
+ log.warn("Unable to handle summary request due to {}", e.getMessage());
+ log.warn("Boom!", e);
+ }
+ }
+ }
+
+ // Accumulates events to drive methodic update of the summary pane.
+ private class InternalEventAccummulator extends AbstractAccumulator<Event> {
+ protected InternalEventAccummulator() {
+ super(new Timer("topo-summary"), MAX_EVENTS, MAX_BATCH_MS, MAX_IDLE_MS);
+ }
+
+ @Override
+ public void processItems(List<Event> items) {
+ // Start-of-Debugging -- Keep in until ONOS-2572 is fixed for reals
+ long now = System.currentTimeMillis();
+ String me = this.toString();
+ String miniMe = me.replaceAll("^.*@", "me@");
+ log.debug("Time: {}; this: {}, processing items ({} events)",
+ now, miniMe, items.size());
+ // End-of-Debugging
+
+ try {
+ if (summaryRunning) {
+ msgSender.execute(() -> requestSummary(0));
+ }
+ } catch (Exception e) {
+ log.warn("Unable to handle summary request due to {}", e.getMessage());
+ log.debug("Boom!", e);
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
new file mode 100644
index 00000000..840e89f3
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterEvent;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.PortStatisticsService;
+import org.onosproject.incubator.net.tunnel.OpticalTunnelEndPoint;
+import org.onosproject.incubator.net.tunnel.Tunnel;
+import org.onosproject.incubator.net.tunnel.TunnelService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.Annotated;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultEdgeLink;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.EdgeLink;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.statistic.StatisticService;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.ui.JsonUtils;
+import org.onosproject.ui.UiConnection;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.impl.topo.ServicesBundle;
+import org.onosproject.ui.topo.PropertyPanel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+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 com.google.common.base.Strings.isNullOrEmpty;
+import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
+import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
+import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
+import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
+import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
+import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
+import static org.onosproject.ui.topo.TopoConstants.CoreButtons;
+import static org.onosproject.ui.topo.TopoConstants.Properties;
+import static org.onosproject.ui.topo.TopoUtils.compactLinkString;
+
+/**
+ * Facility for creating messages bound for the topology viewer.
+ */
+public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
+
+ protected static final Logger log =
+ LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
+
+ private static final ProviderId PID =
+ new ProviderId("core", "org.onosproject.core", true);
+
+ protected static final String SHOW_HIGHLIGHTS = "showHighlights";
+
+ protected ServiceDirectory directory;
+ protected ClusterService clusterService;
+ protected DeviceService deviceService;
+ protected LinkService linkService;
+ protected HostService hostService;
+ protected MastershipService mastershipService;
+ protected IntentService intentService;
+ protected FlowRuleService flowService;
+ protected StatisticService flowStatsService;
+ protected PortStatisticsService portStatsService;
+ protected TopologyService topologyService;
+ protected TunnelService tunnelService;
+
+ protected ServicesBundle servicesBundle;
+
+ private String version;
+
+ // TODO: extract into an external & durable state; good enough for now and demo
+ private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
+
+ /**
+ * Returns read-only view of the meta-ui information.
+ *
+ * @return map of id to meta-ui mementos
+ */
+ static Map<String, ObjectNode> getMetaUi() {
+ return Collections.unmodifiableMap(metaUi);
+ }
+
+ @Override
+ public void init(UiConnection connection, ServiceDirectory directory) {
+ super.init(connection, directory);
+ this.directory = checkNotNull(directory, "Directory cannot be null");
+ clusterService = directory.get(ClusterService.class);
+ 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);
+ flowService = directory.get(FlowRuleService.class);
+ flowStatsService = directory.get(StatisticService.class);
+ portStatsService = directory.get(PortStatisticsService.class);
+ topologyService = directory.get(TopologyService.class);
+ tunnelService = directory.get(TunnelService.class);
+
+ servicesBundle = new ServicesBundle(intentService, deviceService,
+ hostService, linkService,
+ flowService,
+ flowStatsService, portStatsService);
+
+ String ver = directory.get(CoreService.class).version().toString();
+ version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
+ }
+
+ // 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";
+ }
+
+ // Produces JSON structure from annotations.
+ private JsonNode props(Annotations annotations) {
+ ObjectNode props = objectNode();
+ if (annotations != null) {
+ for (String key : annotations.keys()) {
+ props.put(key, annotations.value(key));
+ }
+ }
+ return props;
+ }
+
+ // Produces an informational log message event bound to the client.
+ protected ObjectNode info(long id, String message) {
+ return message("info", id, message);
+ }
+
+ // Produces a warning log message event bound to the client.
+ protected ObjectNode warning(long id, String message) {
+ return message("warning", id, message);
+ }
+
+ // Produces an error log message event bound to the client.
+ protected ObjectNode error(long id, String message) {
+ return message("error", id, message);
+ }
+
+ // Produces a log message event bound to the client.
+ private ObjectNode message(String severity, long id, String message) {
+ ObjectNode payload = objectNode()
+ .put("severity", severity)
+ .put("message", message);
+
+ return JsonUtils.envelope("message", id, payload);
+ }
+
+ // Produces a cluster instance message to the client.
+ protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
+ ControllerNode node = event.subject();
+ int switchCount = mastershipService.getDevicesOf(node.id()).size();
+ ObjectNode payload = objectNode()
+ .put("id", node.id().toString())
+ .put("ip", node.ip().toString())
+ .put("online", clusterService.getState(node.id()) == ACTIVE)
+ .put("uiAttached", node.equals(clusterService.getLocalNode()))
+ .put("switches", switchCount);
+
+ ArrayNode labels = arrayNode();
+ labels.add(node.id().toString());
+ labels.add(node.ip().toString());
+
+ // Add labels, props and stuff the payload into envelope.
+ payload.set("labels", labels);
+ addMetaUi(node.id().toString(), payload);
+
+ String type = messageType != null ? messageType :
+ ((event.type() == INSTANCE_ADDED) ? "addInstance" :
+ ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
+ "addInstance")));
+ return JsonUtils.envelope(type, 0, payload);
+ }
+
+ // Produces a device event message to the client.
+ protected ObjectNode deviceMessage(DeviceEvent event) {
+ Device device = event.subject();
+ ObjectNode payload = objectNode()
+ .put("id", device.id().toString())
+ .put("type", device.type().toString().toLowerCase())
+ .put("online", deviceService.isAvailable(device.id()))
+ .put("master", master(device.id()));
+
+ // Generate labels: id, chassis id, no-label, optional-name
+ String name = device.annotations().value(AnnotationKeys.NAME);
+ ArrayNode labels = arrayNode();
+ labels.add("");
+ labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
+ labels.add(device.id().toString());
+
+ // Add labels, props and stuff the payload into envelope.
+ payload.set("labels", labels);
+ payload.set("props", props(device.annotations()));
+ addGeoLocation(device, payload);
+ addMetaUi(device.id().toString(), payload);
+
+ String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
+ ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
+ return JsonUtils.envelope(type, 0, payload);
+ }
+
+ // Produces a link event message to the client.
+ protected ObjectNode linkMessage(LinkEvent event) {
+ Link link = event.subject();
+ ObjectNode payload = objectNode()
+ .put("id", compactLinkString(link))
+ .put("type", link.type().toString().toLowerCase())
+ .put("online", link.state() == Link.State.ACTIVE)
+ .put("linkWidth", 1.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" : "updateLink");
+ return JsonUtils.envelope(type, 0, payload);
+ }
+
+ // Produces a host event message to the client.
+ protected ObjectNode hostMessage(HostEvent event) {
+ Host host = event.subject();
+ String hostType = host.annotations().value(AnnotationKeys.TYPE);
+ ObjectNode payload = objectNode()
+ .put("id", host.id().toString())
+ .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
+ .put("ingress", compactLinkString(edgeLink(host, true)))
+ .put("egress", compactLinkString(edgeLink(host, false)));
+ payload.set("cp", hostConnect(host.location()));
+ payload.set("labels", labels(ip(host.ipAddresses()),
+ host.mac().toString()));
+ payload.set("props", props(host.annotations()));
+ addGeoLocation(host, payload);
+ addMetaUi(host.id().toString(), payload);
+
+ String type = (event.type() == HOST_ADDED) ? "addHost" :
+ ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
+ return JsonUtils.envelope(type, 0, payload);
+ }
+
+ // Encodes the specified host location into a JSON object.
+ private ObjectNode hostConnect(HostLocation location) {
+ return objectNode()
+ .put("device", location.deviceId().toString())
+ .put("port", location.port().toLong());
+ }
+
+ // Encodes the specified list of labels a JSON array.
+ private ArrayNode labels(String... labels) {
+ ArrayNode json = arrayNode();
+ for (String label : labels) {
+ json.add(label);
+ }
+ return json;
+ }
+
+ // Returns the name of the master node for the specified device id.
+ private String master(DeviceId deviceId) {
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ return master != null ? master.toString() : "";
+ }
+
+ // Generates an edge link from the specified host location.
+ private EdgeLink edgeLink(Host host, boolean ingress) {
+ return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
+ host.location(), ingress);
+ }
+
+ // Adds meta UI information for the specified object.
+ private void addMetaUi(String id, ObjectNode payload) {
+ ObjectNode meta = metaUi.get(id);
+ if (meta != null) {
+ payload.set("metaUi", meta);
+ }
+ }
+
+ // Adds a geo location JSON to the specified payload object.
+ private void addGeoLocation(Annotated annotated, ObjectNode payload) {
+ Annotations annotations = annotated.annotations();
+ if (annotations == null) {
+ return;
+ }
+
+ String slat = annotations.value(AnnotationKeys.LATITUDE);
+ String slng = annotations.value(AnnotationKeys.LONGITUDE);
+ try {
+ if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
+ double lat = Double.parseDouble(slat);
+ double lng = Double.parseDouble(slng);
+ ObjectNode loc = objectNode()
+ .put("type", "latlng").put("lat", lat).put("lng", lng);
+ payload.set("location", loc);
+ }
+ } catch (NumberFormatException e) {
+ log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
+ }
+ }
+
+ // Updates meta UI information for the specified object.
+ protected void updateMetaUi(ObjectNode payload) {
+ metaUi.put(JsonUtils.string(payload, "id"),
+ JsonUtils.node(payload, "memento"));
+ }
+
+
+ // -----------------------------------------------------------------------
+ // Create models of the data to return, that overlays can adjust / augment
+
+ // Returns property panel model for summary response.
+ protected PropertyPanel summmaryMessage(long sid) {
+ Topology topology = topologyService.currentTopology();
+
+ return new PropertyPanel("ONOS Summary", "node")
+ .addProp(Properties.DEVICES, topology.deviceCount())
+ .addProp(Properties.LINKS, topology.linkCount())
+ .addProp(Properties.HOSTS, hostService.getHostCount())
+ .addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
+ .addSeparator()
+ .addProp(Properties.INTENTS, intentService.getIntentCount())
+ .addProp(Properties.TUNNELS, tunnelService.tunnelCount())
+ .addProp(Properties.FLOWS, flowService.getFlowRuleCount())
+ .addProp(Properties.VERSION, version);
+ }
+
+ // Returns property panel model for device details response.
+ protected PropertyPanel deviceDetails(DeviceId deviceId, long sid) {
+ Device device = deviceService.getDevice(deviceId);
+ Annotations annot = device.annotations();
+ String name = annot.value(AnnotationKeys.NAME);
+ int portCount = deviceService.getPorts(deviceId).size();
+ int flowCount = getFlowCount(deviceId);
+ int tunnelCount = getTunnelCount(deviceId);
+
+ String title = isNullOrEmpty(name) ? deviceId.toString() : name;
+ String typeId = device.type().toString().toLowerCase();
+
+ PropertyPanel pp = new PropertyPanel(title, typeId)
+ .id(deviceId.toString())
+
+ .addProp(Properties.URI, deviceId.toString())
+ .addProp(Properties.VENDOR, device.manufacturer())
+ .addProp(Properties.HW_VERSION, device.hwVersion())
+ .addProp(Properties.SW_VERSION, device.swVersion())
+ .addProp(Properties.SERIAL_NUMBER, device.serialNumber())
+ .addProp(Properties.PROTOCOL, annot.value(AnnotationKeys.PROTOCOL))
+ .addSeparator()
+
+ .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
+ .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE))
+ .addSeparator()
+
+ .addProp(Properties.PORTS, portCount)
+ .addProp(Properties.FLOWS, flowCount)
+ .addProp(Properties.TUNNELS, tunnelCount)
+
+ .addButton(CoreButtons.SHOW_DEVICE_VIEW)
+ .addButton(CoreButtons.SHOW_FLOW_VIEW)
+ .addButton(CoreButtons.SHOW_PORT_VIEW)
+ .addButton(CoreButtons.SHOW_GROUP_VIEW);
+
+ return pp;
+ }
+
+ protected int getFlowCount(DeviceId deviceId) {
+ int count = 0;
+ for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
+ count++;
+ }
+ return count;
+ }
+
+ protected int getTunnelCount(DeviceId deviceId) {
+ int count = 0;
+ Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
+ for (Tunnel tunnel : tunnels) {
+ OpticalTunnelEndPoint src = (OpticalTunnelEndPoint) tunnel.src();
+ OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst();
+ DeviceId srcDevice = (DeviceId) src.elementId().get();
+ DeviceId dstDevice = (DeviceId) dst.elementId().get();
+ if (srcDevice.toString().equals(deviceId.toString()) ||
+ dstDevice.toString().equals(deviceId.toString())) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ // Counts all flow entries that egress on the links of the given device.
+ private Map<Link, Integer> getLinkFlowCounts(DeviceId deviceId) {
+ // get the flows for the device
+ List<FlowEntry> entries = new ArrayList<>();
+ for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
+ entries.add(flowEntry);
+ }
+
+ // get egress links from device, and include edge links
+ Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
+ Set<Host> hosts = hostService.getConnectedHosts(deviceId);
+ if (hosts != null) {
+ for (Host host : hosts) {
+ links.add(createEdgeLink(host, false));
+ }
+ }
+
+ // compile flow counts per link
+ Map<Link, Integer> counts = new HashMap<>();
+ for (Link link : links) {
+ counts.put(link, getEgressFlows(link, entries));
+ }
+ return counts;
+ }
+
+ // Counts all entries that egress on the link source port.
+ private int getEgressFlows(Link link, List<FlowEntry> entries) {
+ int count = 0;
+ PortNumber out = link.src().port();
+ for (FlowEntry entry : entries) {
+ TrafficTreatment treatment = entry.treatment();
+ for (Instruction instruction : treatment.allInstructions()) {
+ if (instruction.type() == Instruction.Type.OUTPUT &&
+ ((OutputInstruction) instruction).port().equals(out)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ // Returns host details response.
+ protected PropertyPanel hostDetails(HostId hostId, long sid) {
+ Host host = hostService.getHost(hostId);
+ Annotations annot = host.annotations();
+ String type = annot.value(AnnotationKeys.TYPE);
+ String name = annot.value(AnnotationKeys.NAME);
+ String vlan = host.vlan().toString();
+
+ String title = isNullOrEmpty(name) ? hostId.toString() : name;
+ String typeId = isNullOrEmpty(type) ? "endstation" : type;
+
+ PropertyPanel pp = new PropertyPanel(title, typeId)
+ .id(hostId.toString())
+ .addProp(Properties.MAC, host.mac())
+ .addProp(Properties.IP, host.ipAddresses(), "[\\[\\]]")
+ .addProp(Properties.VLAN, vlan.equals("-1") ? "none" : vlan)
+ .addSeparator()
+ .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
+ .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
+
+ // Potentially add button descriptors here
+ return pp;
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
new file mode 100644
index 00000000..3d733f9e
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
@@ -0,0 +1,675 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.LinkCollectionIntent;
+import org.onosproject.net.intent.OpticalConnectivityIntent;
+import org.onosproject.net.intent.OpticalPathIntent;
+import org.onosproject.net.intent.PathIntent;
+import org.onosproject.net.statistic.Load;
+import org.onosproject.ui.impl.topo.IntentSelection;
+import org.onosproject.ui.impl.topo.ServicesBundle;
+import org.onosproject.ui.impl.topo.TopoIntentFilter;
+import org.onosproject.ui.impl.topo.TrafficLink;
+import org.onosproject.ui.impl.topo.TrafficLink.StatsType;
+import org.onosproject.ui.impl.topo.TrafficLinkMap;
+import org.onosproject.ui.topo.DeviceHighlight;
+import org.onosproject.ui.topo.Highlights;
+import org.onosproject.ui.topo.Highlights.Amount;
+import org.onosproject.ui.topo.HostHighlight;
+import org.onosproject.ui.topo.LinkHighlight.Flavor;
+import org.onosproject.ui.topo.NodeHighlight;
+import org.onosproject.ui.topo.NodeSelection;
+import org.onosproject.ui.topo.TopoUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
+import static org.onosproject.ui.impl.TrafficMonitor.Mode.*;
+
+/**
+ * Encapsulates the behavior of monitoring specific traffic patterns.
+ */
+public class TrafficMonitor {
+
+ // 4 Kilo Bytes as threshold
+ private static final double BPS_THRESHOLD = 4 * TopoUtils.KILO;
+
+ private static final Logger log =
+ LoggerFactory.getLogger(TrafficMonitor.class);
+
+ /**
+ * Designates the different modes of operation.
+ */
+ public enum Mode {
+ IDLE,
+ ALL_FLOW_TRAFFIC,
+ ALL_PORT_TRAFFIC,
+ DEV_LINK_FLOWS,
+ RELATED_INTENTS,
+ SELECTED_INTENT
+ }
+
+ private final long trafficPeriod;
+ private final ServicesBundle servicesBundle;
+ private final TopologyViewMessageHandler msgHandler;
+ private final TopoIntentFilter intentFilter;
+
+ private final Timer timer = new Timer("topo-traffic");
+
+ private TimerTask trafficTask = null;
+ private Mode mode = IDLE;
+ private NodeSelection selectedNodes = null;
+ private IntentSelection selectedIntents = null;
+
+
+ /**
+ * Constructs a traffic monitor.
+ *
+ * @param trafficPeriod traffic task period in ms
+ * @param servicesBundle bundle of services
+ * @param msgHandler our message handler
+ */
+ public TrafficMonitor(long trafficPeriod, ServicesBundle servicesBundle,
+ TopologyViewMessageHandler msgHandler) {
+ this.trafficPeriod = trafficPeriod;
+ this.servicesBundle = servicesBundle;
+ this.msgHandler = msgHandler;
+
+ intentFilter = new TopoIntentFilter(servicesBundle);
+ }
+
+ // =======================================================================
+ // === API ===
+
+ /**
+ * Monitor for traffic data to be sent back to the web client, under
+ * the given mode. This causes a background traffic task to be
+ * scheduled to repeatedly compute and transmit the appropriate traffic
+ * data to the client.
+ * <p>
+ * The monitoring mode is expected to be one of:
+ * <ul>
+ * <li>ALL_FLOW_TRAFFIC</li>
+ * <li>ALL_PORT_TRAFFIC</li>
+ * <li>SELECTED_INTENT</li>
+ * </ul>
+ *
+ * @param mode monitoring mode
+ */
+ public synchronized void monitor(Mode mode) {
+ log.debug("monitor: {}", mode);
+ this.mode = mode;
+
+ switch (mode) {
+ case ALL_FLOW_TRAFFIC:
+ clearSelection();
+ scheduleTask();
+ sendAllFlowTraffic();
+ break;
+
+ case ALL_PORT_TRAFFIC:
+ clearSelection();
+ scheduleTask();
+ sendAllPortTraffic();
+ break;
+
+ case SELECTED_INTENT:
+ scheduleTask();
+ sendSelectedIntentTraffic();
+ break;
+
+ default:
+ log.debug("Unexpected call to monitor({})", mode);
+ clearAll();
+ break;
+ }
+ }
+
+ /**
+ * Monitor for traffic data to be sent back to the web client, under
+ * the given mode, using the given selection of devices and hosts.
+ * In the case of "device link flows", this causes a background traffic
+ * task to be scheduled to repeatedly compute and transmit the appropriate
+ * traffic data to the client. In the case of "related intents", no
+ * repeating task is scheduled.
+ * <p>
+ * The monitoring mode is expected to be one of:
+ * <ul>
+ * <li>DEV_LINK_FLOWS</li>
+ * <li>RELATED_INTENTS</li>
+ * </ul>
+ *
+ * @param mode monitoring mode
+ * @param nodeSelection how to select a node
+ */
+ public synchronized void monitor(Mode mode, NodeSelection nodeSelection) {
+ log.debug("monitor: {} -- {}", mode, nodeSelection);
+ this.mode = mode;
+ this.selectedNodes = nodeSelection;
+
+ switch (mode) {
+ case DEV_LINK_FLOWS:
+ // only care about devices (not hosts)
+ if (selectedNodes.devicesWithHover().isEmpty()) {
+ sendClearAll();
+ } else {
+ scheduleTask();
+ sendDeviceLinkFlows();
+ }
+ break;
+
+ case RELATED_INTENTS:
+ if (selectedNodes.none()) {
+ sendClearAll();
+ } else {
+ selectedIntents = new IntentSelection(selectedNodes, intentFilter);
+ if (selectedIntents.none()) {
+ sendClearAll();
+ } else {
+ sendSelectedIntents();
+ }
+ }
+ break;
+
+ default:
+ log.debug("Unexpected call to monitor({}, {})", mode, nodeSelection);
+ clearAll();
+ break;
+ }
+ }
+
+ // TODO: move this out to the "h2h/multi-intent app"
+ /**
+ * Monitor for traffic data to be sent back to the web client, for the
+ * given intent.
+ *
+ * @param intent the intent to monitor
+ */
+ public synchronized void monitor(Intent intent) {
+ log.debug("monitor intent: {}", intent.id());
+ selectedNodes = null;
+ selectedIntents = new IntentSelection(intent);
+ mode = SELECTED_INTENT;
+ scheduleTask();
+ sendSelectedIntentTraffic();
+ }
+
+ /**
+ * Selects the next intent in the select group (if there is one),
+ * and sends highlighting data back to the web client to display
+ * which path is selected.
+ */
+ public synchronized void selectNextIntent() {
+ if (selectedIntents != null) {
+ selectedIntents.next();
+ sendSelectedIntents();
+ if (mode == SELECTED_INTENT) {
+ mode = RELATED_INTENTS;
+ }
+ }
+ }
+
+ /**
+ * Selects the previous intent in the select group (if there is one),
+ * and sends highlighting data back to the web client to display
+ * which path is selected.
+ */
+ public synchronized void selectPreviousIntent() {
+ if (selectedIntents != null) {
+ selectedIntents.prev();
+ sendSelectedIntents();
+ if (mode == SELECTED_INTENT) {
+ mode = RELATED_INTENTS;
+ }
+ }
+ }
+
+ /**
+ * Resends selected intent traffic data. This is called, for example,
+ * when the system detects an intent update happened.
+ */
+ public synchronized void pokeIntent() {
+ if (mode == SELECTED_INTENT) {
+ sendSelectedIntentTraffic();
+ }
+ }
+
+ /**
+ * Stop all traffic monitoring.
+ */
+ public synchronized void stopMonitoring() {
+ log.debug("STOP monitoring");
+ if (mode != IDLE) {
+ sendClearAll();
+ }
+ }
+
+
+ // =======================================================================
+ // === Helper methods ===
+
+ private void sendClearAll() {
+ clearAll();
+ sendClearHighlights();
+ }
+
+ private void clearAll() {
+ this.mode = IDLE;
+ clearSelection();
+ cancelTask();
+ }
+
+ private void clearSelection() {
+ selectedNodes = null;
+ selectedIntents = null;
+ }
+
+ private synchronized void scheduleTask() {
+ if (trafficTask == null) {
+ log.debug("Starting up background traffic task...");
+ trafficTask = new TrafficUpdateTask();
+ timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
+ } else {
+ log.debug("(traffic task already running)");
+ }
+ }
+
+ private synchronized void cancelTask() {
+ if (trafficTask != null) {
+ trafficTask.cancel();
+ trafficTask = null;
+ }
+ }
+
+ private void sendAllFlowTraffic() {
+ log.debug("sendAllFlowTraffic");
+ msgHandler.sendHighlights(trafficSummary(StatsType.FLOW_STATS));
+ }
+
+ private void sendAllPortTraffic() {
+ log.debug("sendAllPortTraffic");
+ msgHandler.sendHighlights(trafficSummary(StatsType.PORT_STATS));
+ }
+
+ private void sendDeviceLinkFlows() {
+ log.debug("sendDeviceLinkFlows: {}", selectedNodes);
+ msgHandler.sendHighlights(deviceLinkFlows());
+ }
+
+ private void sendSelectedIntents() {
+ log.debug("sendSelectedIntents: {}", selectedIntents);
+ msgHandler.sendHighlights(intentGroup());
+ }
+
+ private void sendSelectedIntentTraffic() {
+ log.debug("sendSelectedIntentTraffic: {}", selectedIntents);
+ msgHandler.sendHighlights(intentTraffic());
+ }
+
+ private void sendClearHighlights() {
+ log.debug("sendClearHighlights");
+ msgHandler.sendHighlights(new Highlights());
+ }
+
+ // =======================================================================
+ // === Generate messages in JSON object node format
+
+ private Highlights trafficSummary(StatsType type) {
+ Highlights highlights = new Highlights();
+
+ TrafficLinkMap linkMap = new TrafficLinkMap();
+ compileLinks(linkMap);
+ addEdgeLinks(linkMap);
+
+ for (TrafficLink tlink : linkMap.biLinks()) {
+ if (type == StatsType.FLOW_STATS) {
+ attachFlowLoad(tlink);
+ } else if (type == StatsType.PORT_STATS) {
+ attachPortLoad(tlink);
+ }
+
+ // we only want to report on links deemed to have traffic
+ if (tlink.hasTraffic()) {
+ highlights.add(tlink.highlight(type));
+ }
+ }
+ return highlights;
+ }
+
+ // create highlights for links, showing flows for selected devices.
+ private Highlights deviceLinkFlows() {
+ Highlights highlights = new Highlights();
+
+ if (selectedNodes != null && !selectedNodes.devicesWithHover().isEmpty()) {
+ // capture flow counts on bilinks
+ TrafficLinkMap linkMap = new TrafficLinkMap();
+
+ for (Device device : selectedNodes.devicesWithHover()) {
+ Map<Link, Integer> counts = getLinkFlowCounts(device.id());
+ for (Link link : counts.keySet()) {
+ TrafficLink tlink = linkMap.add(link);
+ tlink.addFlows(counts.get(link));
+ }
+ }
+
+ // now report on our collated links
+ for (TrafficLink tlink : linkMap.biLinks()) {
+ highlights.add(tlink.highlight(StatsType.FLOW_COUNT));
+ }
+
+ }
+ return highlights;
+ }
+
+ private Highlights intentGroup() {
+ Highlights highlights = new Highlights();
+
+ if (selectedIntents != null && !selectedIntents.none()) {
+ // If 'all' intents are selected, they will all have primary
+ // highlighting; otherwise, the specifically selected intent will
+ // have primary highlighting, and the remainder will have secondary
+ // highlighting.
+ Set<Intent> primary;
+ Set<Intent> secondary;
+ int count = selectedIntents.size();
+
+ Set<Intent> allBut = new HashSet<>(selectedIntents.intents());
+ Intent current;
+
+ if (selectedIntents.all()) {
+ primary = allBut;
+ secondary = Collections.emptySet();
+ log.debug("Highlight all intents ({})", count);
+ } else {
+ current = selectedIntents.current();
+ primary = new HashSet<>();
+ primary.add(current);
+ allBut.remove(current);
+ secondary = allBut;
+ log.debug("Highlight intent: {} ([{}] of {})",
+ current.id(), selectedIntents.index(), count);
+ }
+
+ highlightIntentLinks(highlights, primary, secondary);
+ }
+ return highlights;
+ }
+
+ private Highlights intentTraffic() {
+ Highlights highlights = new Highlights();
+
+ if (selectedIntents != null && selectedIntents.single()) {
+ Intent current = selectedIntents.current();
+ Set<Intent> primary = new HashSet<>();
+ primary.add(current);
+ log.debug("Highlight traffic for intent: {} ([{}] of {})",
+ current.id(), selectedIntents.index(), selectedIntents.size());
+
+ highlightIntentLinksWithTraffic(highlights, primary);
+ highlights.subdueAllElse(Amount.MINIMALLY);
+ }
+ return highlights;
+ }
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ private void compileLinks(TrafficLinkMap linkMap) {
+ servicesBundle.linkService().getLinks().forEach(linkMap::add);
+ }
+
+ private void addEdgeLinks(TrafficLinkMap linkMap) {
+ servicesBundle.hostService().getHosts().forEach(host -> {
+ linkMap.add(createEdgeLink(host, true));
+ linkMap.add(createEdgeLink(host, false));
+ });
+ }
+
+ private Load getLinkFlowLoad(Link link) {
+ if (link != null && link.src().elementId() instanceof DeviceId) {
+ return servicesBundle.flowStatsService().load(link);
+ }
+ return null;
+ }
+
+ private void attachFlowLoad(TrafficLink link) {
+ link.addLoad(getLinkFlowLoad(link.one()));
+ link.addLoad(getLinkFlowLoad(link.two()));
+ }
+
+ private void attachPortLoad(TrafficLink link) {
+ // For bi-directional traffic links, use
+ // the max link rate of either direction
+ // (we choose 'one' since we know that is never null)
+ Link one = link.one();
+ Load egressSrc = servicesBundle.portStatsService().load(one.src());
+ Load egressDst = servicesBundle.portStatsService().load(one.dst());
+ link.addLoad(maxLoad(egressSrc, egressDst), BPS_THRESHOLD);
+// link.addLoad(maxLoad(egressSrc, egressDst), 10); // DEBUG ONLY!!
+ }
+
+ private Load maxLoad(Load a, Load b) {
+ if (a == null) {
+ return b;
+ }
+ if (b == null) {
+ return a;
+ }
+ return a.rate() > b.rate() ? a : b;
+ }
+
+ // Counts all flow entries that egress on the links of the given device.
+ private Map<Link, Integer> getLinkFlowCounts(DeviceId deviceId) {
+ // get the flows for the device
+ List<FlowEntry> entries = new ArrayList<>();
+ for (FlowEntry flowEntry : servicesBundle.flowService()
+ .getFlowEntries(deviceId)) {
+ entries.add(flowEntry);
+ }
+
+ // get egress links from device, and include edge links
+ Set<Link> links = new HashSet<>(servicesBundle.linkService()
+ .getDeviceEgressLinks(deviceId));
+ Set<Host> hosts = servicesBundle.hostService().getConnectedHosts(deviceId);
+ if (hosts != null) {
+ for (Host host : hosts) {
+ links.add(createEdgeLink(host, false));
+ }
+ }
+
+ // compile flow counts per link
+ Map<Link, Integer> counts = new HashMap<>();
+ for (Link link : links) {
+ counts.put(link, getEgressFlows(link, entries));
+ }
+ return counts;
+ }
+
+ // Counts all entries that egress on the link source port.
+ private int getEgressFlows(Link link, List<FlowEntry> entries) {
+ int count = 0;
+ PortNumber out = link.src().port();
+ for (FlowEntry entry : entries) {
+ TrafficTreatment treatment = entry.treatment();
+ for (Instruction instruction : treatment.allInstructions()) {
+ if (instruction.type() == Instruction.Type.OUTPUT &&
+ ((OutputInstruction) instruction).port().equals(out)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ private void highlightIntentLinks(Highlights highlights,
+ Set<Intent> primary, Set<Intent> secondary) {
+ TrafficLinkMap linkMap = new TrafficLinkMap();
+ // NOTE: highlight secondary first, then primary, so that links shared
+ // by intents are colored correctly ("last man wins")
+ createTrafficLinks(highlights, linkMap, secondary, Flavor.SECONDARY_HIGHLIGHT, false);
+ createTrafficLinks(highlights, linkMap, primary, Flavor.PRIMARY_HIGHLIGHT, false);
+ colorLinks(highlights, linkMap);
+ }
+
+ private void highlightIntentLinksWithTraffic(Highlights highlights,
+ Set<Intent> primary) {
+ TrafficLinkMap linkMap = new TrafficLinkMap();
+ createTrafficLinks(highlights, linkMap, primary, Flavor.PRIMARY_HIGHLIGHT, true);
+ colorLinks(highlights, linkMap);
+ }
+
+ private void createTrafficLinks(Highlights highlights,
+ TrafficLinkMap linkMap, Set<Intent> intents,
+ Flavor flavor, boolean showTraffic) {
+ for (Intent intent : intents) {
+ List<Intent> installables = servicesBundle.intentService()
+ .getInstallableIntents(intent.key());
+ Iterable<Link> links = null;
+ if (installables != null) {
+ for (Intent installable : installables) {
+
+ if (installable instanceof PathIntent) {
+ links = ((PathIntent) installable).path().links();
+ } else if (installable instanceof FlowRuleIntent) {
+ links = linkResources(installable);
+ } else if (installable instanceof LinkCollectionIntent) {
+ links = ((LinkCollectionIntent) installable).links();
+ } else if (installable instanceof OpticalPathIntent) {
+ links = ((OpticalPathIntent) installable).path().links();
+ }
+
+ boolean isOptical = intent instanceof OpticalConnectivityIntent;
+ processLinks(linkMap, links, flavor, isOptical, showTraffic);
+ updateHighlights(highlights, links);
+ }
+ }
+ }
+ }
+
+ private void updateHighlights(Highlights highlights, Iterable<Link> links) {
+ for (Link link : links) {
+ ensureNodePresent(highlights, link.src().elementId());
+ ensureNodePresent(highlights, link.dst().elementId());
+ }
+ }
+
+ private void ensureNodePresent(Highlights highlights, ElementId eid) {
+ String id = eid.toString();
+ NodeHighlight nh = highlights.getNode(id);
+ if (nh == null) {
+ if (eid instanceof DeviceId) {
+ nh = new DeviceHighlight(id);
+ highlights.add((DeviceHighlight) nh);
+ } else if (eid instanceof HostId) {
+ nh = new HostHighlight(id);
+ highlights.add((HostHighlight) nh);
+ }
+ }
+ }
+
+ // Extracts links from the specified flow rule intent resources
+ private Collection<Link> linkResources(Intent installable) {
+ ImmutableList.Builder<Link> builder = ImmutableList.builder();
+ installable.resources().stream().filter(r -> r instanceof Link)
+ .forEach(r -> builder.add((Link) r));
+ return builder.build();
+ }
+
+ private void processLinks(TrafficLinkMap linkMap, Iterable<Link> links,
+ Flavor flavor, boolean isOptical,
+ boolean showTraffic) {
+ if (links != null) {
+ for (Link link : links) {
+ TrafficLink tlink = linkMap.add(link);
+ tlink.tagFlavor(flavor);
+ tlink.optical(isOptical);
+ if (showTraffic) {
+ tlink.addLoad(getLinkFlowLoad(link));
+ tlink.antMarch(true);
+ }
+ }
+ }
+ }
+
+ private void colorLinks(Highlights highlights, TrafficLinkMap linkMap) {
+ for (TrafficLink tlink : linkMap.biLinks()) {
+ highlights.add(tlink.highlight(StatsType.TAGGED));
+ }
+ }
+
+ // =======================================================================
+ // === Background Task
+
+ // Provides periodic update of traffic information to the client
+ private class TrafficUpdateTask extends TimerTask {
+ @Override
+ public void run() {
+ try {
+ switch (mode) {
+ case ALL_FLOW_TRAFFIC:
+ sendAllFlowTraffic();
+ break;
+ case ALL_PORT_TRAFFIC:
+ sendAllPortTraffic();
+ break;
+ case DEV_LINK_FLOWS:
+ sendDeviceLinkFlows();
+ break;
+ case SELECTED_INTENT:
+ sendSelectedIntentTraffic();
+ break;
+
+ default:
+ // RELATED_INTENTS and IDLE modes should never invoke
+ // the background task, but if they do, they have
+ // nothing to do
+ break;
+ }
+
+ } catch (Exception e) {
+ log.warn("Unable to process traffic task due to {}", e.getMessage());
+ log.warn("Boom!", e);
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TrafficOverlay.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TrafficOverlay.java
new file mode 100644
index 00000000..ea8ca3ea
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TrafficOverlay.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import org.onosproject.ui.UiTopoOverlay;
+import org.onosproject.ui.topo.ButtonId;
+import org.onosproject.ui.topo.PropertyPanel;
+
+/**
+ * Topology Overlay for network traffic.
+ */
+public class TrafficOverlay extends UiTopoOverlay {
+ /**
+ * Traffic Overlay identifier.
+ */
+ public static final String TRAFFIC_ID = "traffic";
+
+ private static final String SDF_ID = "showDeviceFlows";
+ private static final String SRT_ID = "showRelatedTraffic";
+
+ private static final ButtonId SHOW_DEVICE_FLOWS = new ButtonId(SDF_ID);
+ private static final ButtonId SHOW_RELATED_TRAFFIC = new ButtonId(SRT_ID);
+
+
+ public TrafficOverlay() {
+ super(TRAFFIC_ID);
+ }
+
+ // override activate and deactivate, to write log messages
+ @Override
+ public void activate() {
+ super.activate();
+ log.debug("TrafficOverlay Activated");
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ log.debug("TrafficOverlay Deactivated");
+ }
+
+ @Override
+ public void modifyDeviceDetails(PropertyPanel pp) {
+ pp.addButton(SHOW_DEVICE_FLOWS)
+ .addButton(SHOW_RELATED_TRAFFIC);
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TunnelViewMessageHandler.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TunnelViewMessageHandler.java
new file mode 100644
index 00000000..13ce9398
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TunnelViewMessageHandler.java
@@ -0,0 +1,80 @@
+package org.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.incubator.net.tunnel.Tunnel;
+import org.onosproject.incubator.net.tunnel.TunnelEndPointFormatter;
+import org.onosproject.incubator.net.tunnel.TunnelService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.EnumFormatter;
+
+import java.util.Collection;
+
+public class TunnelViewMessageHandler extends UiMessageHandler {
+ private static final String TUNNEL_DATA_REQ = "tunnelDataRequest";
+ private static final String TUNNEL_DATA_RESP = "tunnelDataResponse";
+ private static final String TUNNELS = "tunnels";
+ private static final String ID = "id";
+ private static final String NAME = "name";
+ private static final String ONE = "one";
+ private static final String TWO = "two";
+ private static final String TYPE = "type";
+ private static final String GROUP_ID = "group_id";
+
+ private static final String BANDWIDTH = "bandwidth";
+ private static final String PATH = "path";
+
+
+ private static final String[] COL_IDS = {
+ ID, NAME, ONE, TWO, TYPE, GROUP_ID,
+ BANDWIDTH, PATH
+ };
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new TunnelDataRequestHandler());
+ }
+
+ private final class TunnelDataRequestHandler extends TableRequestHandler {
+
+ public TunnelDataRequestHandler() {
+ super(TUNNEL_DATA_REQ, TUNNEL_DATA_RESP, TUNNELS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COL_IDS;
+ }
+
+ @Override
+ protected TableModel createTableModel() {
+ TableModel tm = super.createTableModel();
+ //TODO add more formater class so that we can get a more readable table
+ tm.setFormatter(ONE, TunnelEndPointFormatter.INSTANCE);
+ tm.setFormatter(TWO, TunnelEndPointFormatter.INSTANCE);
+ tm.setFormatter(TYPE, EnumFormatter.INSTANCE);
+ return tm;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ TunnelService ts = get(TunnelService.class);
+ ts.queryAllTunnels().forEach(tunnel -> populateRow(tm.addRow(), tunnel));
+ }
+
+ }
+
+ private void populateRow(TableModel.Row row, Tunnel tunnel) {
+ row.cell(ID, tunnel.tunnelId().id())
+ .cell(NAME, tunnel.tunnelName().value())
+ .cell(ONE, tunnel.src())
+ .cell(TWO, tunnel.dst())
+ .cell(TYPE, tunnel.type())
+ .cell(GROUP_ID, tunnel.groupId().id())
+ .cell(BANDWIDTH, tunnel.annotations().value(BANDWIDTH))
+ .cell(PATH, tunnel.path());
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
new file mode 100644
index 00000000..2bd0bb61
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiTopoOverlayFactory;
+import org.onosproject.ui.UiView;
+import org.onosproject.ui.UiViewHidden;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableList.of;
+import static java.util.stream.Collectors.toSet;
+import static org.onosproject.ui.UiView.Category.NETWORK;
+import static org.onosproject.ui.UiView.Category.PLATFORM;
+
+/**
+ * Manages the user interface extensions.
+ */
+@Component(immediate = true)
+@Service
+public class UiExtensionManager implements UiExtensionService, SpriteService {
+
+ private static final ClassLoader CL =
+ UiExtensionManager.class.getClassLoader();
+ private static final String CORE = "core";
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ // List of all extensions
+ private final List<UiExtension> extensions = Lists.newArrayList();
+
+ // Map of views to extensions
+ private final Map<String, UiExtension> views = Maps.newHashMap();
+
+ // Core views & core extension
+ private final UiExtension core = createCoreExtension();
+
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ // Creates core UI extension
+ private UiExtension createCoreExtension() {
+ List<UiView> coreViews = of(
+ new UiView(PLATFORM, "app", "Applications", "nav_apps"),
+ new UiView(PLATFORM, "settings", "Settings", "nav_settings"),
+ new UiView(PLATFORM, "cluster", "Cluster Nodes", "nav_cluster"),
+ new UiView(NETWORK, "topo", "Topology", "nav_topo"),
+ new UiView(NETWORK, "device", "Devices", "nav_devs"),
+ new UiViewHidden("flow"),
+ new UiViewHidden("port"),
+ new UiViewHidden("group"),
+ new UiView(NETWORK, "link", "Links", "nav_links"),
+ new UiView(NETWORK, "host", "Hosts", "nav_hosts"),
+ new UiView(NETWORK, "intent", "Intents", "nav_intents"),
+ //TODO add a new type of icon for tunnel
+ new UiView(NETWORK, "tunnel", "Tunnels", "nav_links")
+ );
+
+ UiMessageHandlerFactory messageHandlerFactory =
+ () -> ImmutableList.of(
+ new TopologyViewMessageHandler(),
+ new DeviceViewMessageHandler(),
+ new LinkViewMessageHandler(),
+ new HostViewMessageHandler(),
+ new FlowViewMessageHandler(),
+ new PortViewMessageHandler(),
+ new GroupViewMessageHandler(),
+ new IntentViewMessageHandler(),
+ new ApplicationViewMessageHandler(),
+ new SettingsViewMessageHandler(),
+ new ClusterViewMessageHandler(),
+ new TunnelViewMessageHandler()
+ );
+
+ UiTopoOverlayFactory topoOverlayFactory =
+ () -> ImmutableList.of(
+ new TrafficOverlay()
+ );
+
+ return new UiExtension.Builder(CL, coreViews)
+ .messageHandlerFactory(messageHandlerFactory)
+ .topoOverlayFactory(topoOverlayFactory)
+ .resourcePath(CORE)
+ .build();
+ }
+
+ @Activate
+ public void activate() {
+ register(core);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ UiWebSocketServlet.closeAll();
+ unregister(core);
+ log.info("Stopped");
+ }
+
+ @Override
+ public synchronized void register(UiExtension extension) {
+ if (!extensions.contains(extension)) {
+ extensions.add(extension);
+ for (UiView view : extension.views()) {
+ views.put(view.id(), extension);
+ }
+ }
+ }
+
+ @Override
+ public synchronized void unregister(UiExtension extension) {
+ extensions.remove(extension);
+ extension.views().stream()
+ .map(UiView::id).collect(toSet()).forEach(views::remove);
+ }
+
+ @Override
+ public synchronized List<UiExtension> getExtensions() {
+ return ImmutableList.copyOf(extensions);
+ }
+
+ @Override
+ public synchronized UiExtension getViewExtension(String viewId) {
+ return views.get(viewId);
+ }
+
+ // =====================================================================
+ // Provisional tracking of sprite definitions
+
+ private final Map<String, JsonNode> sprites = Maps.newHashMap();
+
+ @Override
+ public Set<String> getNames() {
+ return ImmutableSet.copyOf(sprites.keySet());
+ }
+
+ @Override
+ public void put(String name, JsonNode spriteData) {
+ log.info("Registered sprite definition [{}]", name);
+ sprites.put(name, spriteData);
+ }
+
+ @Override
+ public JsonNode get(String name) {
+ return sprites.get(name);
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
new file mode 100644
index 00000000..1b6f7f99
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+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.osgi.ServiceDirectory;
+import org.onlab.osgi.ServiceNotFoundException;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.ui.UiConnection;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.UiTopoOverlayFactory;
+import org.onosproject.ui.topo.TopoConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Web socket capable of interacting with the GUI.
+ */
+public class UiWebSocket
+ implements UiConnection, WebSocket.OnTextMessage, WebSocket.OnControl {
+
+ private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class);
+
+ private static final long MAX_AGE_MS = 30_000;
+
+ private static final byte PING = 0x9;
+ private static final byte PONG = 0xA;
+ private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
+
+ private final ServiceDirectory directory;
+
+ private Connection connection;
+ private FrameConnection control;
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private long lastActive = System.currentTimeMillis();
+
+ private Map<String, UiMessageHandler> handlers;
+ private TopoOverlayCache overlayCache;
+
+ /**
+ * Creates a new web-socket for serving data to GUI.
+ *
+ * @param directory service directory
+ */
+ public UiWebSocket(ServiceDirectory directory) {
+ this.directory = directory;
+ }
+
+ /**
+ * Issues a close on the connection.
+ */
+ synchronized void close() {
+ destroyHandlersAndOverlays();
+ if (connection.isOpen()) {
+ connection.close();
+ }
+ }
+
+ /**
+ * Indicates if this connection is idle.
+ *
+ * @return true if idle or closed
+ */
+ synchronized boolean isIdle() {
+ long quietFor = System.currentTimeMillis() - lastActive;
+ boolean idle = quietFor > MAX_AGE_MS;
+ if (idle || (connection != null && !connection.isOpen())) {
+ log.debug("IDLE (or closed) websocket [{} ms]", quietFor);
+ return true;
+ } else if (connection != null) {
+ try {
+ control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
+ } catch (IOException e) {
+ log.warn("Unable to send ping message due to: ", e);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onOpen(Connection connection) {
+ this.connection = connection;
+ this.control = (FrameConnection) connection;
+ try {
+ createHandlersAndOverlays();
+ sendInstanceData();
+ log.info("GUI client connected");
+
+ } catch (ServiceNotFoundException e) {
+ log.warn("Unable to open GUI connection; services have been shut-down", e);
+ this.connection.close();
+ this.connection = null;
+ this.control = null;
+ }
+ }
+
+ @Override
+ public synchronized void onClose(int closeCode, String message) {
+ destroyHandlersAndOverlays();
+ log.info("GUI client disconnected [close-code={}, message={}]",
+ closeCode, message);
+ }
+
+ @Override
+ public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
+ lastActive = System.currentTimeMillis();
+ return true;
+ }
+
+ @Override
+ public void onMessage(String data) {
+ log.debug("onMessage: {}", data);
+ lastActive = System.currentTimeMillis();
+ try {
+ ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
+ String type = message.path("event").asText("unknown");
+ UiMessageHandler handler = handlers.get(type);
+ if (handler != null) {
+ handler.process(message);
+ } else {
+ log.warn("No GUI message handler for type {}", type);
+ }
+ } catch (Exception e) {
+ log.warn("Unable to parse GUI message {} due to {}", data, e);
+ log.debug("Boom!!!", e);
+ }
+ }
+
+ @Override
+ public synchronized void sendMessage(ObjectNode message) {
+ try {
+ if (connection.isOpen()) {
+ connection.sendMessage(message.toString());
+ }
+ } catch (IOException e) {
+ log.warn("Unable to send message {} to GUI due to {}", message, e);
+ log.debug("Boom!!!", e);
+ }
+ }
+
+ @Override
+ public synchronized void sendMessage(String type, long sid, ObjectNode payload) {
+ ObjectNode message = mapper.createObjectNode();
+ message.put("event", type);
+ if (sid > 0) {
+ message.put("sid", sid);
+ }
+ message.set("payload", payload);
+ sendMessage(message);
+
+ }
+
+ // Creates new message handlers.
+ private synchronized void createHandlersAndOverlays() {
+ log.debug("creating handlers and overlays...");
+ handlers = new HashMap<>();
+ overlayCache = new TopoOverlayCache();
+
+ UiExtensionService service = directory.get(UiExtensionService.class);
+ service.getExtensions().forEach(ext -> {
+ UiMessageHandlerFactory factory = ext.messageHandlerFactory();
+ if (factory != null) {
+ factory.newHandlers().forEach(handler -> {
+ handler.init(this, directory);
+ handler.messageTypes().forEach(type -> handlers.put(type, handler));
+
+ // need to inject the overlay cache into topology message handler
+ if (handler instanceof TopologyViewMessageHandler) {
+ ((TopologyViewMessageHandler) handler).setOverlayCache(overlayCache);
+ }
+ });
+ }
+
+ UiTopoOverlayFactory overlayFactory = ext.topoOverlayFactory();
+ if (overlayFactory != null) {
+ overlayFactory.newOverlays().forEach(overlayCache::add);
+ }
+ });
+ log.debug("#handlers = {}, #overlays = {}", handlers.size(),
+ overlayCache.size());
+ }
+
+ // Destroys message handlers.
+ private synchronized void destroyHandlersAndOverlays() {
+ log.debug("destroying handlers and overlays...");
+ handlers.forEach((type, handler) -> handler.destroy());
+ handlers.clear();
+
+ if (overlayCache != null) {
+ overlayCache.destroy();
+ overlayCache = null;
+ }
+ }
+
+ // Sends cluster node/instance information to allow GUI to fail-over.
+ private void sendInstanceData() {
+ ClusterService service = directory.get(ClusterService.class);
+ ArrayNode instances = mapper.createArrayNode();
+
+ for (ControllerNode node : service.getNodes()) {
+ ObjectNode instance = mapper.createObjectNode()
+ .put("id", node.id().toString())
+ .put("ip", node.ip().toString())
+ .put(TopoConstants.Glyphs.UI_ATTACHED,
+ node.equals(service.getLocalNode()));
+ instances.add(instance);
+ }
+
+ ObjectNode payload = mapper.createObjectNode();
+ payload.set("clusterNodes", instances);
+ sendMessage("bootstrap", 0, payload);
+ }
+
+}
+
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocketServlet.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocketServlet.java
new file mode 100644
index 00000000..ffc558da
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocketServlet.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl;
+
+import org.eclipse.jetty.websocket.WebSocket;
+import org.eclipse.jetty.websocket.WebSocketServlet;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceDirectory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Web socket servlet capable of creating web sockets for the user interface.
+ */
+public class UiWebSocketServlet extends WebSocketServlet {
+
+ private static final long PING_DELAY_MS = 5000;
+
+ private static UiWebSocketServlet instance;
+
+ private ServiceDirectory directory = new DefaultServiceDirectory();
+
+ private final Set<UiWebSocket> sockets = new HashSet<>();
+ private final Timer timer = new Timer();
+ private final TimerTask pruner = new Pruner();
+ private boolean isStopped = false;
+
+ /**
+ * Closes all currently open UI web-sockets.
+ */
+ public static void closeAll() {
+ if (instance != null) {
+ instance.isStopped = true;
+ instance.sockets.forEach(UiWebSocket::close);
+ instance.sockets.clear();
+ instance.pruner.cancel();
+ instance.timer.cancel();
+ }
+ }
+
+ @Override
+ public void init() throws ServletException {
+ super.init();
+ instance = this;
+ timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
+ }
+
+ @Override
+ public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
+ if (isStopped) {
+ return null;
+ }
+ UiWebSocket socket = new UiWebSocket(directory);
+ synchronized (sockets) {
+ sockets.add(socket);
+ }
+ return socket;
+ }
+
+ // Task for pruning web-sockets that are idle.
+ private class Pruner extends TimerTask {
+ @Override
+ public void run() {
+ synchronized (sockets) {
+ Iterator<UiWebSocket> it = sockets.iterator();
+ while (it.hasNext()) {
+ UiWebSocket socket = it.next();
+ if (socket.isIdle()) {
+ it.remove();
+ socket.close();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/package-info.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/package-info.java
new file mode 100644
index 00000000..0f228039
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2015 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.
+ */
+
+/**
+ * Set of resources providing data for the ONOS GUI.
+ */
+package org.onosproject.ui.impl;
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java
new file mode 100644
index 00000000..151e6131
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl.topo;
+
+import org.onosproject.net.intent.Intent;
+import org.onosproject.ui.topo.NodeSelection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Encapsulates a selection of intents (paths) inferred from a selection
+ * of devices and/or hosts from the topology view.
+ */
+public class IntentSelection {
+
+ private static final int ALL = -1;
+
+ protected static final Logger log =
+ LoggerFactory.getLogger(IntentSelection.class);
+
+ private final NodeSelection nodes;
+
+ private final List<Intent> intents;
+ private int index = ALL;
+
+ /**
+ * Creates an intent selection group, based on selected nodes.
+ *
+ * @param nodes node selection
+ * @param filter intent filter
+ */
+ public IntentSelection(NodeSelection nodes, TopoIntentFilter filter) {
+ this.nodes = nodes;
+ intents = filter.findPathIntents(nodes.hostsWithHover(), nodes.devicesWithHover());
+ if (intents.size() == 1) {
+ index = 0; // pre-select a single intent
+ }
+ }
+
+ /**
+ * Creates an intent selection group, for a single intent.
+ *
+ * @param intent the intent
+ */
+ public IntentSelection(Intent intent) {
+ nodes = null;
+ intents = new ArrayList<>(1);
+ intents.add(intent);
+ index = 0;
+ }
+
+ /**
+ * Returns true if no intents are selected.
+ *
+ * @return true if nothing selected
+ */
+ public boolean none() {
+ return intents.isEmpty();
+ }
+
+ /**
+ * Returns true if all intents in this select group are currently selected.
+ * This is the initial state, so that all intents are shown on the
+ * topology view with primary highlighting.
+ *
+ * @return true if all selected
+ */
+ public boolean all() {
+ return index == ALL;
+ }
+
+ /**
+ * Returns true if there is a single intent in this select group, or if
+ * a specific intent has been marked (index != ALL).
+ *
+ * @return true if single intent marked
+ */
+ public boolean single() {
+ return !all();
+ }
+
+ /**
+ * Returns the number of intents in this selection group.
+ *
+ * @return number of intents
+ */
+ public int size() {
+ return intents.size();
+ }
+
+ /**
+ * Returns the index of the currently selected intent.
+ *
+ * @return the current index
+ */
+ public int index() {
+ return index;
+ }
+
+ /**
+ * The list of intents in this selection group.
+ *
+ * @return list of intents
+ */
+ public List<Intent> intents() {
+ return Collections.unmodifiableList(intents);
+ }
+
+ /**
+ * Marks and returns the next intent in this group. Note that the
+ * selection wraps around to the beginning again, if necessary.
+ *
+ * @return the next intent in the group
+ */
+ public Intent next() {
+ index += 1;
+ if (index >= intents.size()) {
+ index = 0;
+ }
+ return intents.get(index);
+ }
+
+ /**
+ * Marks and returns the previous intent in this group. Note that the
+ * selection wraps around to the end again, if necessary.
+ *
+ * @return the previous intent in the group
+ */
+ public Intent prev() {
+ index -= 1;
+ if (index < 0) {
+ index = intents.size() - 1;
+ }
+ return intents.get(index);
+ }
+
+ /**
+ * Returns the currently marked intent, or null if "all" intents
+ * are marked.
+ *
+ * @return the currently marked intent
+ */
+ public Intent current() {
+ return all() ? null : intents.get(index);
+ }
+
+ @Override
+ public String toString() {
+ return "IntentSelection{" +
+ "nodes=" + nodes +
+ ", #intents=" + intents.size() +
+ ", index=" + index +
+ '}';
+ }
+
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java
new file mode 100644
index 00000000..bcc4ad8b
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl.topo;
+
+import org.onosproject.incubator.net.PortStatisticsService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.statistic.StatisticService;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A bundle of services that the topology view requires to get its job done.
+ */
+public class ServicesBundle {
+
+ private final IntentService intentService;
+ private final DeviceService deviceService;
+ private final HostService hostService;
+ private final LinkService linkService;
+ private final FlowRuleService flowService;
+ private final StatisticService flowStatsService;
+ private final PortStatisticsService portStatsService;
+
+ /**
+ * Creates the services bundle.
+ *
+ * @param intentService intent service reference
+ * @param deviceService device service reference
+ * @param hostService host service reference
+ * @param linkService link service reference
+ * @param flowService flow service reference
+ * @param flowStatsService flow statistics service reference
+ * @param portStatsService port statistics service reference
+ */
+ public ServicesBundle(IntentService intentService,
+ DeviceService deviceService,
+ HostService hostService,
+ LinkService linkService,
+ FlowRuleService flowService,
+ StatisticService flowStatsService,
+ PortStatisticsService portStatsService) {
+ this.intentService = checkNotNull(intentService);
+ this.deviceService = checkNotNull(deviceService);
+ this.hostService = checkNotNull(hostService);
+ this.linkService = checkNotNull(linkService);
+ this.flowService = checkNotNull(flowService);
+ this.flowStatsService = checkNotNull(flowStatsService);
+ this.portStatsService = checkNotNull(portStatsService);
+ }
+
+ /**
+ * Returns a reference to the intent service.
+ *
+ * @return intent service reference
+ */
+ public IntentService intentService() {
+ return intentService;
+ }
+
+ /**
+ * Returns a reference to the device service.
+ *
+ * @return device service reference
+ */
+ public DeviceService deviceService() {
+ return deviceService;
+ }
+
+ /**
+ * Returns a reference to the host service.
+ *
+ * @return host service reference
+ */
+ public HostService hostService() {
+ return hostService;
+ }
+
+ /**
+ * Returns a reference to the link service.
+ *
+ * @return link service reference
+ */
+ public LinkService linkService() {
+ return linkService;
+ }
+
+ /**
+ * Returns a reference to the flow rule service.
+ *
+ * @return flow service reference
+ */
+ public FlowRuleService flowService() {
+ return flowService;
+ }
+
+ /**
+ * Returns a reference to the flow statistics service.
+ *
+ * @return flow statistics service reference
+ */
+ public StatisticService flowStatsService() {
+ return flowStatsService;
+ }
+
+ /**
+ * Returns a reference to the port statistics service.
+ *
+ * @return port statistics service reference
+ */
+ public PortStatisticsService portStatsService() {
+ return portStatsService;
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoIntentFilter.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoIntentFilter.java
new file mode 100644
index 00000000..8372dede
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoIntentFilter.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2014-2015 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.onosproject.ui.impl.topo;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.Link;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.LinkCollectionIntent;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.OpticalConnectivityIntent;
+import org.onosproject.net.intent.PathIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.link.LinkService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import static org.onosproject.net.intent.IntentState.INSTALLED;
+
+/**
+ * Auxiliary facility to query the intent service based on the specified
+ * set of end-station hosts, edge points or infrastructure devices.
+ */
+public class TopoIntentFilter {
+
+ private final IntentService intentService;
+ private final DeviceService deviceService;
+ private final HostService hostService;
+ private final LinkService linkService;
+
+ /**
+ * Creates an intent filter.
+ *
+ * @param services service references bundle
+ */
+ public TopoIntentFilter(ServicesBundle services) {
+ this.intentService = services.intentService();
+ this.deviceService = services.deviceService();
+ this.hostService = services.hostService();
+ this.linkService = services.linkService();
+ }
+
+ /**
+ * Finds all path (host-to-host or point-to-point) intents that pertain
+ * to the given hosts and devices.
+ *
+ * @param hosts set of hosts to query by
+ * @param devices set of devices to query by
+ * @return set of intents that 'match' all hosts and devices given
+ */
+ public List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices) {
+ // start with all intents
+ Iterable<Intent> sourceIntents = intentService.getIntents();
+
+ // 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, devices, edgePoints, sourceIntents);
+ }
+
+
+ // 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;
+ }
+
+ // Produces a list of intents that target all selected hosts, devices or connect points.
+ private List<Intent> getIntents(Set<Host> hosts, Set<Device> devices,
+ Set<ConnectPoint> edgePoints,
+ Iterable<Intent> sourceIntents) {
+ List<Intent> intents = new ArrayList<>();
+ if (hosts.isEmpty() && devices.isEmpty()) {
+ return intents;
+ }
+
+ Set<OpticalConnectivityIntent> opticalIntents = new HashSet<>();
+
+ // Search through all intents and see if they are relevant to our search.
+ for (Intent intent : sourceIntents) {
+ if (intentService.getIntentState(intent.key()) == INSTALLED) {
+ boolean isRelevant = false;
+ if (intent instanceof HostToHostIntent) {
+ isRelevant = isIntentRelevantToHosts((HostToHostIntent) intent, hosts) &&
+ isIntentRelevantToDevices(intent, devices);
+ } else if (intent instanceof PointToPointIntent) {
+ isRelevant = isIntentRelevant((PointToPointIntent) intent, edgePoints) &&
+ isIntentRelevantToDevices(intent, devices);
+ } else if (intent instanceof MultiPointToSinglePointIntent) {
+ isRelevant = isIntentRelevant((MultiPointToSinglePointIntent) intent, edgePoints) &&
+ isIntentRelevantToDevices(intent, devices);
+ } else if (intent instanceof OpticalConnectivityIntent) {
+ opticalIntents.add((OpticalConnectivityIntent) intent);
+ }
+ // TODO: add other intents, e.g. SinglePointToMultiPointIntent
+
+ if (isRelevant) {
+ intents.add(intent);
+ }
+ }
+ }
+
+ // As a second pass, try to link up any optical intents with the
+ // packet-level ones.
+ for (OpticalConnectivityIntent intent : opticalIntents) {
+ if (isIntentRelevant(intent, intents) &&
+ isIntentRelevantToDevices(intent, devices)) {
+ intents.add(intent);
+ }
+ }
+ return intents;
+ }
+
+ // Indicates whether the specified intent involves all of the given hosts.
+ private boolean isIntentRelevantToHosts(HostToHostIntent intent, Iterable<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 devices.
+ private boolean isIntentRelevantToDevices(Intent intent, Iterable<Device> devices) {
+ List<Intent> installables = intentService.getInstallableIntents(intent.key());
+ for (Device device : devices) {
+ if (!isIntentRelevantToDevice(installables, device)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Indicates whether the specified intent involves the given device.
+ private boolean isIntentRelevantToDevice(List<Intent> installables, Device device) {
+ if (installables != null) {
+ for (Intent installable : installables) {
+ if (installable instanceof PathIntent) {
+ PathIntent pathIntent = (PathIntent) installable;
+ if (pathContainsDevice(pathIntent.path().links(), device.id())) {
+ return true;
+ }
+ } else if (installable instanceof FlowRuleIntent) {
+ FlowRuleIntent flowRuleIntent = (FlowRuleIntent) installable;
+ if (rulesContainDevice(flowRuleIntent.flowRules(), device.id())) {
+ return true;
+ }
+ } else if (installable instanceof LinkCollectionIntent) {
+ LinkCollectionIntent linksIntent = (LinkCollectionIntent) installable;
+ if (pathContainsDevice(linksIntent.links(), device.id())) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // Indicates whether the specified links involve the given device.
+ private boolean pathContainsDevice(Iterable<Link> links, DeviceId id) {
+ for (Link link : links) {
+ if (link.src().elementId().equals(id) || link.dst().elementId().equals(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Indicates whether the specified flow rules involvesthe given device.
+ private boolean rulesContainDevice(Collection<FlowRule> flowRules, DeviceId id) {
+ for (FlowRule rule : flowRules) {
+ if (rule.deviceId().equals(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isIntentRelevant(PointToPointIntent intent,
+ Iterable<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,
+ Iterable<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;
+ }
+
+ // Indicates whether the specified intent involves all of the given edge points.
+ private boolean isIntentRelevant(OpticalConnectivityIntent opticalIntent,
+ Iterable<Intent> intents) {
+ Link ccSrc = getFirstLink(opticalIntent.getSrc(), false);
+ Link ccDst = getFirstLink(opticalIntent.getDst(), true);
+ if (ccSrc == null || ccDst == null) {
+ return false;
+ }
+
+ for (Intent intent : intents) {
+ List<Intent> installables = intentService.getInstallableIntents(intent.key());
+ for (Intent installable : installables) {
+ if (installable instanceof PathIntent) {
+ List<Link> links = ((PathIntent) installable).path().links();
+ if (links.size() == 3) {
+ Link tunnel = links.get(1);
+ if (Objects.equals(tunnel.src(), ccSrc.src()) &&
+ Objects.equals(tunnel.dst(), ccDst.dst())) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private Link getFirstLink(ConnectPoint point, boolean ingress) {
+ for (Link link : linkService.getLinks(point)) {
+ if (point.equals(ingress ? link.src() : link.dst())) {
+ return link;
+ }
+ }
+ return null;
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLink.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLink.java
new file mode 100644
index 00000000..a0e16620
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLink.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.statistic.Load;
+import org.onosproject.ui.topo.BiLink;
+import org.onosproject.ui.topo.LinkHighlight;
+import org.onosproject.ui.topo.LinkHighlight.Flavor;
+import org.onosproject.ui.topo.TopoUtils;
+
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.NO_HIGHLIGHT;
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
+
+/**
+ * Representation of a link and its inverse, and associated traffic data.
+ * This class understands how to generate the appropriate
+ * {@link LinkHighlight}s for showing traffic data on the topology view.
+ */
+public class TrafficLink extends BiLink {
+
+ private static final String EMPTY = "";
+ private static final String QUE = "?";
+
+ private long bytes = 0;
+ private long rate = 0;
+ private long flows = 0;
+ private Flavor taggedFlavor = NO_HIGHLIGHT;
+ private boolean hasTraffic = false;
+ private boolean isOptical = false;
+ private boolean antMarch = false;
+
+ /**
+ * Constructs a traffic link for the given key and initial link.
+ *
+ * @param key canonical key for this traffic link
+ * @param link first link
+ */
+ public TrafficLink(LinkKey key, Link link) {
+ super(key, link);
+ }
+
+ /**
+ * Sets the optical flag to the given value.
+ *
+ * @param b true if an optical link
+ * @return self, for chaining
+ */
+ public TrafficLink optical(boolean b) {
+ isOptical = b;
+ return this;
+ }
+
+ /**
+ * Sets the ant march flag to the given value.
+ *
+ * @param b true if marching ants required
+ * @return self, for chaining
+ */
+ public TrafficLink antMarch(boolean b) {
+ antMarch = b;
+ return this;
+ }
+
+ /**
+ * Tags this traffic link with the flavor to be used in visual rendering.
+ *
+ * @param flavor the flavor to tag
+ * @return self, for chaining
+ */
+ public TrafficLink tagFlavor(Flavor flavor) {
+ this.taggedFlavor = flavor;
+ return this;
+ }
+
+ /**
+ * Adds load statistics, marks the traffic link as having traffic.
+ *
+ * @param load load to add
+ */
+ public void addLoad(Load load) {
+ addLoad(load, 0);
+ }
+
+ /**
+ * Adds load statistics, marks the traffic link as having traffic, if the
+ * load {@link Load#rate rate} is greater than the given threshold
+ * (expressed in bytes per second).
+ *
+ * @param load load to add
+ * @param threshold threshold to register traffic
+ */
+ public void addLoad(Load load, double threshold) {
+ if (load != null) {
+ this.hasTraffic = hasTraffic || load.rate() > threshold;
+ this.bytes += load.latest();
+ this.rate += load.rate();
+ }
+ }
+
+ /**
+ * Adds the given count of flows to this traffic link.
+ *
+ * @param count count of flows
+ */
+ public void addFlows(int count) {
+ this.flows += count;
+ }
+
+ @Override
+ public LinkHighlight highlight(Enum<?> type) {
+ StatsType statsType = (StatsType) type;
+ switch (statsType) {
+ case FLOW_COUNT:
+ return highlightForFlowCount(statsType);
+
+ case FLOW_STATS:
+ case PORT_STATS:
+ return highlightForStats(statsType);
+
+ case TAGGED:
+ return highlightForTagging(statsType);
+
+ default:
+ throw new IllegalStateException("unexpected case: " + statsType);
+ }
+ }
+
+ private LinkHighlight highlightForStats(StatsType type) {
+ return new LinkHighlight(linkId(), SECONDARY_HIGHLIGHT)
+ .setLabel(generateLabel(type));
+ }
+
+ private LinkHighlight highlightForFlowCount(StatsType type) {
+ Flavor flavor = flows > 0 ? PRIMARY_HIGHLIGHT : SECONDARY_HIGHLIGHT;
+ return new LinkHighlight(linkId(), flavor)
+ .setLabel(generateLabel(type));
+ }
+
+ private LinkHighlight highlightForTagging(StatsType type) {
+ LinkHighlight hlite = new LinkHighlight(linkId(), taggedFlavor)
+ .setLabel(generateLabel(type));
+ if (isOptical) {
+ hlite.addMod(LinkHighlight.MOD_OPTICAL);
+ }
+ if (antMarch) {
+ hlite.addMod(LinkHighlight.MOD_ANIMATED);
+ }
+ return hlite;
+ }
+
+ // Generates a string representation of the load, to be used as a label
+ private String generateLabel(StatsType type) {
+ switch (type) {
+ case FLOW_COUNT:
+ return TopoUtils.formatFlows(flows);
+
+ case FLOW_STATS:
+ return TopoUtils.formatBytes(bytes);
+
+ case PORT_STATS:
+ return TopoUtils.formatBitRate(rate);
+
+ case TAGGED:
+ return hasTraffic ? TopoUtils.formatBytes(bytes) : EMPTY;
+
+ default:
+ return QUE;
+ }
+ }
+
+ /**
+ * Returns true if this link has been deemed to have enough traffic
+ * to register on the topology view in the web UI.
+ *
+ * @return true if this link has displayable traffic
+ */
+ public boolean hasTraffic() {
+ return hasTraffic;
+ }
+
+ /**
+ * Designates type of traffic statistics to report on a highlighted link.
+ */
+ public enum StatsType {
+ /**
+ * Number of flows.
+ */
+ FLOW_COUNT,
+
+ /**
+ * Number of bytes.
+ */
+ FLOW_STATS,
+
+ /**
+ * Number of bits per second.
+ */
+ PORT_STATS,
+
+ /**
+ * Custom tagged information.
+ */
+ TAGGED
+ }
+}
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLinkMap.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLinkMap.java
new file mode 100644
index 00000000..081964f8
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLinkMap.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.ui.topo.BiLinkMap;
+
+/**
+ * Collection of {@link TrafficLink}s.
+ */
+public class TrafficLinkMap extends BiLinkMap<TrafficLink> {
+
+ @Override
+ public TrafficLink create(LinkKey key, Link link) {
+ return new TrafficLink(key, link);
+ }
+}