blob: 534c8bc9e4438c61884793e1a7e25ab98abbf114 [file] [log] [blame]
Thomas Vachuska329af532015-03-10 02:08:33 -07001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.ui.impl;
17
18import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuska329af532015-03-10 02:08:33 -070019import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070021import com.google.common.collect.ImmutableList;
Thomas Vachuska329af532015-03-10 02:08:33 -070022import org.onlab.osgi.ServiceDirectory;
23import org.onlab.packet.IpAddress;
24import org.onosproject.cluster.ClusterEvent;
25import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.ControllerNode;
27import org.onosproject.cluster.NodeId;
28import org.onosproject.core.CoreService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -070029import org.onosproject.incubator.net.PortStatisticsService;
cheng fan35dc0f22015-06-10 06:02:47 +080030import org.onosproject.incubator.net.tunnel.OpticalTunnelEndPoint;
31import org.onosproject.incubator.net.tunnel.Tunnel;
32import org.onosproject.incubator.net.tunnel.TunnelService;
Thomas Vachuska329af532015-03-10 02:08:33 -070033import org.onosproject.mastership.MastershipService;
34import org.onosproject.net.Annotated;
35import org.onosproject.net.AnnotationKeys;
36import org.onosproject.net.Annotations;
37import org.onosproject.net.ConnectPoint;
38import org.onosproject.net.DefaultEdgeLink;
39import org.onosproject.net.Device;
40import org.onosproject.net.DeviceId;
41import org.onosproject.net.EdgeLink;
42import org.onosproject.net.Host;
43import org.onosproject.net.HostId;
44import org.onosproject.net.HostLocation;
45import org.onosproject.net.Link;
46import org.onosproject.net.LinkKey;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070047import org.onosproject.net.NetworkResource;
Thomas Vachuska329af532015-03-10 02:08:33 -070048import org.onosproject.net.PortNumber;
49import org.onosproject.net.device.DeviceEvent;
50import org.onosproject.net.device.DeviceService;
51import org.onosproject.net.flow.FlowEntry;
52import org.onosproject.net.flow.FlowRuleService;
53import org.onosproject.net.flow.TrafficTreatment;
54import org.onosproject.net.flow.instructions.Instruction;
55import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
56import org.onosproject.net.host.HostEvent;
57import org.onosproject.net.host.HostService;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070058import org.onosproject.net.intent.FlowRuleIntent;
Thomas Vachuska329af532015-03-10 02:08:33 -070059import org.onosproject.net.intent.Intent;
60import org.onosproject.net.intent.IntentService;
61import org.onosproject.net.intent.LinkCollectionIntent;
62import org.onosproject.net.intent.OpticalConnectivityIntent;
63import org.onosproject.net.intent.OpticalPathIntent;
64import org.onosproject.net.intent.PathIntent;
65import org.onosproject.net.link.LinkEvent;
66import org.onosproject.net.link.LinkService;
67import org.onosproject.net.provider.ProviderId;
68import org.onosproject.net.statistic.Load;
69import org.onosproject.net.statistic.StatisticService;
70import org.onosproject.net.topology.Topology;
71import org.onosproject.net.topology.TopologyService;
Simon Huntd2747a02015-04-30 22:41:16 -070072import org.onosproject.ui.JsonUtils;
Thomas Vachuska329af532015-03-10 02:08:33 -070073import org.onosproject.ui.UiConnection;
Simon Hunta0ddb022015-05-01 09:53:01 -070074import org.onosproject.ui.UiMessageHandler;
Thomas Vachuska329af532015-03-10 02:08:33 -070075import org.slf4j.Logger;
76import org.slf4j.LoggerFactory;
77
78import java.text.DecimalFormat;
79import java.util.ArrayList;
80import java.util.Collection;
81import java.util.Collections;
82import java.util.HashMap;
83import java.util.HashSet;
84import java.util.Iterator;
85import java.util.List;
86import java.util.Map;
87import java.util.Set;
88import java.util.concurrent.ConcurrentHashMap;
89
90import static com.google.common.base.Preconditions.checkNotNull;
91import static com.google.common.base.Strings.isNullOrEmpty;
92import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
93import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
94import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -070095import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
Thomas Vachuska329af532015-03-10 02:08:33 -070096import static org.onosproject.net.DeviceId.deviceId;
97import static org.onosproject.net.HostId.hostId;
98import static org.onosproject.net.LinkKey.linkKey;
99import static org.onosproject.net.PortNumber.P0;
100import static org.onosproject.net.PortNumber.portNumber;
101import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
102import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
103import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
104import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
105import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
106import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700107import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
108import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700109
110/**
111 * Facility for creating messages bound for the topology viewer.
112 */
Simon Huntd7f7bcc2015-05-08 14:13:17 -0700113@Deprecated
Simon Hunta0ddb022015-05-01 09:53:01 -0700114public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700115
Simon Huntd2747a02015-04-30 22:41:16 -0700116 protected static final Logger log =
117 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700118
Simon Huntd2747a02015-04-30 22:41:16 -0700119 private static final ProviderId PID =
120 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700121 private static final String COMPACT = "%s/%s-%s/%s";
122
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700123 private static final double KILO = 1024;
124 private static final double MEGA = 1024 * KILO;
125 private static final double GIGA = 1024 * MEGA;
Thomas Vachuska329af532015-03-10 02:08:33 -0700126
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700127 private static final String GBITS_UNIT = "Gb";
128 private static final String MBITS_UNIT = "Mb";
129 private static final String KBITS_UNIT = "Kb";
130 private static final String BITS_UNIT = "b";
131 private static final String GBYTES_UNIT = "GB";
132 private static final String MBYTES_UNIT = "MB";
133 private static final String KBYTES_UNIT = "KB";
134 private static final String BYTES_UNIT = "B";
135 //4 Kilo Bytes as threshold
136 private static final double BPS_THRESHOLD = 4 * KILO;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700137
Thomas Vachuska329af532015-03-10 02:08:33 -0700138 protected ServiceDirectory directory;
139 protected ClusterService clusterService;
140 protected DeviceService deviceService;
141 protected LinkService linkService;
142 protected HostService hostService;
143 protected MastershipService mastershipService;
144 protected IntentService intentService;
145 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700146 protected StatisticService flowStatsService;
147 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700148 protected TopologyService topologyService;
cheng fan35dc0f22015-06-10 06:02:47 +0800149 protected TunnelService tunnelService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700150
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700151 protected enum StatsType {
152 FLOW, PORT
153 }
154
Thomas Vachuska329af532015-03-10 02:08:33 -0700155 private String version;
156
157 // TODO: extract into an external & durable state; good enough for now and demo
158 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
159
160 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700161 * Returns read-only view of the meta-ui information.
162 *
163 * @return map of id to meta-ui mementos
164 */
165 static Map<String, ObjectNode> getMetaUi() {
166 return Collections.unmodifiableMap(metaUi);
167 }
168
169 @Override
170 public void init(UiConnection connection, ServiceDirectory directory) {
171 super.init(connection, directory);
172 this.directory = checkNotNull(directory, "Directory cannot be null");
173 clusterService = directory.get(ClusterService.class);
174 deviceService = directory.get(DeviceService.class);
175 linkService = directory.get(LinkService.class);
176 hostService = directory.get(HostService.class);
177 mastershipService = directory.get(MastershipService.class);
178 intentService = directory.get(IntentService.class);
179 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700180 flowStatsService = directory.get(StatisticService.class);
181 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700182 topologyService = directory.get(TopologyService.class);
cheng fan35dc0f22015-06-10 06:02:47 +0800183 tunnelService = directory.get(TunnelService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700184
185 String ver = directory.get(CoreService.class).version().toString();
186 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
187 }
188
Thomas Vachuska329af532015-03-10 02:08:33 -0700189 // Returns the specified set of IP addresses as a string.
190 private String ip(Set<IpAddress> ipAddresses) {
191 Iterator<IpAddress> it = ipAddresses.iterator();
192 return it.hasNext() ? it.next().toString() : "unknown";
193 }
194
195 // Produces JSON structure from annotations.
196 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700197 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700198 if (annotations != null) {
199 for (String key : annotations.keys()) {
200 props.put(key, annotations.value(key));
201 }
202 }
203 return props;
204 }
205
206 // Produces an informational log message event bound to the client.
207 protected ObjectNode info(long id, String message) {
208 return message("info", id, message);
209 }
210
211 // Produces a warning log message event bound to the client.
212 protected ObjectNode warning(long id, String message) {
213 return message("warning", id, message);
214 }
215
216 // Produces an error log message event bound to the client.
217 protected ObjectNode error(long id, String message) {
218 return message("error", id, message);
219 }
220
221 // Produces a log message event bound to the client.
222 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700223 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700224 .put("severity", severity)
225 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700226
Simon Huntd2747a02015-04-30 22:41:16 -0700227 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700228 }
229
230 // Produces a set of all hosts listed in the specified JSON array.
231 protected Set<Host> getHosts(ArrayNode array) {
232 Set<Host> hosts = new HashSet<>();
233 if (array != null) {
234 for (JsonNode node : array) {
235 try {
236 addHost(hosts, hostId(node.asText()));
237 } catch (IllegalArgumentException e) {
238 log.debug("Skipping ID {}", node.asText());
239 }
240 }
241 }
242 return hosts;
243 }
244
245 // Adds the specified host to the set of hosts.
246 private void addHost(Set<Host> hosts, HostId hostId) {
247 Host host = hostService.getHost(hostId);
248 if (host != null) {
249 hosts.add(host);
250 }
251 }
252
253
254 // Produces a set of all devices listed in the specified JSON array.
255 protected Set<Device> getDevices(ArrayNode array) {
256 Set<Device> devices = new HashSet<>();
257 if (array != null) {
258 for (JsonNode node : array) {
259 try {
260 addDevice(devices, deviceId(node.asText()));
261 } catch (IllegalArgumentException e) {
262 log.debug("Skipping ID {}", node.asText());
263 }
264 }
265 }
266 return devices;
267 }
268
269 private void addDevice(Set<Device> devices, DeviceId deviceId) {
270 Device device = deviceService.getDevice(deviceId);
271 if (device != null) {
272 devices.add(device);
273 }
274 }
275
276 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
277 try {
278 addHost(hosts, hostId(hover));
279 } catch (IllegalArgumentException e) {
280 try {
281 addDevice(devices, deviceId(hover));
282 } catch (IllegalArgumentException ne) {
283 log.debug("Skipping ID {}", hover);
284 }
285 }
286 }
287
288 // Produces a cluster instance message to the client.
289 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
290 ControllerNode node = event.subject();
291 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700292 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700293 .put("id", node.id().toString())
294 .put("ip", node.ip().toString())
295 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700296 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700297 .put("switches", switchCount);
298
Simon Huntda580882015-05-12 20:58:18 -0700299 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700300 labels.add(node.id().toString());
301 labels.add(node.ip().toString());
302
303 // Add labels, props and stuff the payload into envelope.
304 payload.set("labels", labels);
305 addMetaUi(node.id().toString(), payload);
306
307 String type = messageType != null ? messageType :
308 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
309 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
310 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700311 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700312 }
313
314 // Produces a device event message to the client.
315 protected ObjectNode deviceMessage(DeviceEvent event) {
316 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700317 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700318 .put("id", device.id().toString())
319 .put("type", device.type().toString().toLowerCase())
320 .put("online", deviceService.isAvailable(device.id()))
321 .put("master", master(device.id()));
322
323 // Generate labels: id, chassis id, no-label, optional-name
324 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700325 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700326 labels.add("");
327 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
328 labels.add(device.id().toString());
329
330 // Add labels, props and stuff the payload into envelope.
331 payload.set("labels", labels);
332 payload.set("props", props(device.annotations()));
333 addGeoLocation(device, payload);
334 addMetaUi(device.id().toString(), payload);
335
336 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
337 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700338 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700339 }
340
341 // Produces a link event message to the client.
342 protected ObjectNode linkMessage(LinkEvent event) {
343 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700344 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700345 .put("id", compactLinkString(link))
346 .put("type", link.type().toString().toLowerCase())
347 .put("online", link.state() == Link.State.ACTIVE)
348 .put("linkWidth", 1.2)
349 .put("src", link.src().deviceId().toString())
350 .put("srcPort", link.src().port().toString())
351 .put("dst", link.dst().deviceId().toString())
352 .put("dstPort", link.dst().port().toString());
353 String type = (event.type() == LINK_ADDED) ? "addLink" :
354 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700355 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700356 }
357
358 // Produces a host event message to the client.
359 protected ObjectNode hostMessage(HostEvent event) {
360 Host host = event.subject();
361 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700362 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700363 .put("id", host.id().toString())
364 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
365 .put("ingress", compactLinkString(edgeLink(host, true)))
366 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700367 payload.set("cp", hostConnect(host.location()));
368 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700369 host.mac().toString()));
370 payload.set("props", props(host.annotations()));
371 addGeoLocation(host, payload);
372 addMetaUi(host.id().toString(), payload);
373
374 String type = (event.type() == HOST_ADDED) ? "addHost" :
375 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700376 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700377 }
378
379 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700380 private ObjectNode hostConnect(HostLocation location) {
381 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700382 .put("device", location.deviceId().toString())
383 .put("port", location.port().toLong());
384 }
385
386 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700387 private ArrayNode labels(String... labels) {
388 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700389 for (String label : labels) {
390 json.add(label);
391 }
392 return json;
393 }
394
395 // Returns the name of the master node for the specified device id.
396 private String master(DeviceId deviceId) {
397 NodeId master = mastershipService.getMasterFor(deviceId);
398 return master != null ? master.toString() : "";
399 }
400
401 // Generates an edge link from the specified host location.
402 private EdgeLink edgeLink(Host host, boolean ingress) {
403 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
404 host.location(), ingress);
405 }
406
407 // Adds meta UI information for the specified object.
408 private void addMetaUi(String id, ObjectNode payload) {
409 ObjectNode meta = metaUi.get(id);
410 if (meta != null) {
411 payload.set("metaUi", meta);
412 }
413 }
414
415 // Adds a geo location JSON to the specified payload object.
416 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
417 Annotations annotations = annotated.annotations();
418 if (annotations == null) {
419 return;
420 }
421
422 String slat = annotations.value(AnnotationKeys.LATITUDE);
423 String slng = annotations.value(AnnotationKeys.LONGITUDE);
424 try {
425 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
426 double lat = Double.parseDouble(slat);
427 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700428 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700429 .put("type", "latlng").put("lat", lat).put("lng", lng);
430 payload.set("location", loc);
431 }
432 } catch (NumberFormatException e) {
433 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
434 }
435 }
436
437 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700438 protected void updateMetaUi(ObjectNode payload) {
439 metaUi.put(JsonUtils.string(payload, "id"),
440 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700441 }
442
443 // Returns summary response.
444 protected ObjectNode summmaryMessage(long sid) {
445 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700446 return JsonUtils.envelope("showSummary", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700447 json("ONOS Summary", "node",
448 new Prop("Devices", format(topology.deviceCount())),
449 new Prop("Links", format(topology.linkCount())),
450 new Prop("Hosts", format(hostService.getHostCount())),
451 new Prop("Topology SCCs", format(topology.clusterCount())),
452 new Separator(),
453 new Prop("Intents", format(intentService.getIntentCount())),
cheng fan35dc0f22015-06-10 06:02:47 +0800454 new Prop("Tunnels", format(tunnelService.tunnelCount())),
Simon Hunta0ddb022015-05-01 09:53:01 -0700455 new Prop("Flows", format(flowService.getFlowRuleCount())),
456 new Prop("Version", version)));
Thomas Vachuska329af532015-03-10 02:08:33 -0700457 }
458
459 // Returns device details response.
460 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
461 Device device = deviceService.getDevice(deviceId);
462 Annotations annot = device.annotations();
463 String name = annot.value(AnnotationKeys.NAME);
464 int portCount = deviceService.getPorts(deviceId).size();
465 int flowCount = getFlowCount(deviceId);
cheng fan35dc0f22015-06-10 06:02:47 +0800466 int tunnelCount = getTunnelCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700467 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700468 json(isNullOrEmpty(name) ? deviceId.toString() : name,
469 device.type().toString().toLowerCase(),
470 new Prop("URI", deviceId.toString()),
471 new Prop("Vendor", device.manufacturer()),
472 new Prop("H/W Version", device.hwVersion()),
473 new Prop("S/W Version", device.swVersion()),
474 new Prop("Serial Number", device.serialNumber()),
475 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
476 new Separator(),
477 new Prop("Master", master(deviceId)),
478 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
479 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
480 new Separator(),
481 new Prop("Ports", Integer.toString(portCount)),
cheng fan35dc0f22015-06-10 06:02:47 +0800482 new Prop("Flows", Integer.toString(flowCount)),
483 new Prop("Tunnels", Integer.toString(tunnelCount))
484 ));
Thomas Vachuska329af532015-03-10 02:08:33 -0700485 }
486
487 protected int getFlowCount(DeviceId deviceId) {
488 int count = 0;
489 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
490 while (it.hasNext()) {
491 count++;
492 it.next();
493 }
494 return count;
495 }
496
cheng fan35dc0f22015-06-10 06:02:47 +0800497 protected int getTunnelCount(DeviceId deviceId) {
498 int count = 0;
499 Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
500 for (Tunnel tunnel : tunnels) {
501 OpticalTunnelEndPoint src = (OpticalTunnelEndPoint) tunnel.src();
502 OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst();
503 DeviceId srcDevice = (DeviceId) src.elementId().get();
504 DeviceId dstDevice = (DeviceId) dst.elementId().get();
505 if (srcDevice.toString().equals(deviceId.toString())
506 || dstDevice.toString().equals(deviceId.toString())) {
507 count++;
508 }
509 }
510 return count;
511 }
512
Thomas Vachuska329af532015-03-10 02:08:33 -0700513 // Counts all entries that egress on the given device links.
514 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
515 List<FlowEntry> entries = new ArrayList<>();
516 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
517 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
518 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
519 while (it.hasNext()) {
520 entries.add(it.next());
521 }
522
523 // Add all edge links to the set
524 if (hosts != null) {
525 for (Host host : hosts) {
526 links.add(new DefaultEdgeLink(host.providerId(),
527 new ConnectPoint(host.id(), P0),
528 host.location(), false));
529 }
530 }
531
532 Map<Link, Integer> counts = new HashMap<>();
533 for (Link link : links) {
534 counts.put(link, getEgressFlows(link, entries));
535 }
536 return counts;
537 }
538
539 // Counts all entries that egress on the link source port.
540 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
541 int count = 0;
542 PortNumber out = link.src().port();
543 for (FlowEntry entry : entries) {
544 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700545 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700546 if (instruction.type() == Instruction.Type.OUTPUT &&
547 ((OutputInstruction) instruction).port().equals(out)) {
548 count++;
549 }
550 }
551 }
552 return count;
553 }
554
555
556 // Returns host details response.
557 protected ObjectNode hostDetails(HostId hostId, long sid) {
558 Host host = hostService.getHost(hostId);
559 Annotations annot = host.annotations();
560 String type = annot.value(AnnotationKeys.TYPE);
561 String name = annot.value(AnnotationKeys.NAME);
562 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700563 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700564 json(isNullOrEmpty(name) ? hostId.toString() : name,
565 isNullOrEmpty(type) ? "endstation" : type,
566 new Prop("MAC", host.mac().toString()),
567 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
568 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
569 new Separator(),
570 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
571 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700572 }
573
574
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700575 // Produces JSON message to trigger flow traffic overview visualization
576 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700577 ObjectNode payload = objectNode();
578 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700579 payload.set("paths", paths);
580
Simon Huntda580882015-05-12 20:58:18 -0700581 ObjectNode pathNodeN = objectNode();
582 ArrayNode linksNodeN = arrayNode();
583 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700584
585 pathNodeN.put("class", "plain").put("traffic", false);
586 pathNodeN.set("links", linksNodeN);
587 pathNodeN.set("labels", labelsN);
588 paths.add(pathNodeN);
589
Simon Huntda580882015-05-12 20:58:18 -0700590 ObjectNode pathNodeT = objectNode();
591 ArrayNode linksNodeT = arrayNode();
592 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700593
594 pathNodeT.put("class", "secondary").put("traffic", true);
595 pathNodeT.set("links", linksNodeT);
596 pathNodeT.set("labels", labelsT);
597 paths.add(pathNodeT);
598
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700599 Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
600 addEdgeLinks(biLinks);
601 for (BiLink link : biLinks.values()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700602 boolean bi = link.two != null;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700603 if (type == FLOW) {
604 link.addLoad(getLinkLoad(link.one));
605 link.addLoad(bi ? getLinkLoad(link.two) : null);
606 } else if (type == PORT) {
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700607 //For a bi-directional traffic links, use
608 //the max link rate of either direction
609 link.addLoad(portStatsService.load(link.one.src()),
610 BPS_THRESHOLD,
611 portStatsService.load(link.one.dst()),
612 BPS_THRESHOLD);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700613 }
614 if (link.hasTraffic) {
615 linksNodeT.add(compactLinkString(link.one));
616 labelsT.add(type == PORT ?
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700617 formatBitRate(link.rate) + "ps" :
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700618 formatBytes(link.bytes));
619 } else {
620 linksNodeN.add(compactLinkString(link.one));
621 labelsN.add("");
Thomas Vachuska329af532015-03-10 02:08:33 -0700622 }
623 }
Simon Huntd2747a02015-04-30 22:41:16 -0700624 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700625 }
626
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700627 private Load getLinkLoad(Link link) {
628 if (link.src().elementId() instanceof DeviceId) {
629 return flowStatsService.load(link);
630 }
631 return null;
632 }
633
634 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
635 hostService.getHosts().forEach(host -> {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700636 addLink(biLinks, createEdgeLink(host, true));
637 addLink(biLinks, createEdgeLink(host, false));
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700638 });
639 }
640
641 private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700642 Map<LinkKey, BiLink> biLinks = new HashMap<>();
643 for (Link link : links) {
644 addLink(biLinks, link);
645 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700646 return biLinks;
Thomas Vachuska329af532015-03-10 02:08:33 -0700647 }
648
649 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700650 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700651 ObjectNode payload = objectNode();
652 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700653 payload.set("paths", paths);
654
655 for (Device device : devices) {
656 Map<Link, Integer> counts = getFlowCounts(device.id());
657 for (Link link : counts.keySet()) {
658 addLinkFlows(link, paths, counts.get(link));
659 }
660 }
Simon Huntd2747a02015-04-30 22:41:16 -0700661 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700662 }
663
664 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700665 ObjectNode pathNode = objectNode();
666 ArrayNode linksNode = arrayNode();
667 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700668 boolean noFlows = count == null || count == 0;
669 pathNode.put("class", noFlows ? "secondary" : "primary");
670 pathNode.put("traffic", false);
671 pathNode.set("links", linksNode.add(compactLinkString(link)));
672 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
673 (count == 1 ? " flow" : " flows"))));
674 paths.add(pathNode);
675 }
676
677
678 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700679 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700680 ObjectNode payload = objectNode();
681 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700682 payload.set("paths", paths);
683
684 // Classify links based on their traffic traffic first...
685 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
686
687 // Then separate the links into their respective classes and send them out.
688 Map<String, ObjectNode> pathNodes = new HashMap<>();
689 for (BiLink biLink : biLinks.values()) {
690 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700691 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700692 ObjectNode pathNode = pathNodes.get(tc);
693 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700694 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700695 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700696 pathNode.set("links", arrayNode());
697 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700698 pathNodes.put(tc, pathNode);
699 paths.add(pathNode);
700 }
701 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
702 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
703 }
704
Simon Huntd2747a02015-04-30 22:41:16 -0700705 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700706 }
707
708 // Classifies the link traffic according to the specified classes.
709 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
710 Map<LinkKey, BiLink> biLinks = new HashMap<>();
711 for (TrafficClass trafficClass : trafficClasses) {
712 for (Intent intent : trafficClass.intents) {
713 boolean isOptical = intent instanceof OpticalConnectivityIntent;
714 List<Intent> installables = intentService.getInstallableIntents(intent.key());
715 if (installables != null) {
716 for (Intent installable : installables) {
717 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
718 if (installable instanceof PathIntent) {
719 classifyLinks(type, biLinks, trafficClass.showTraffic,
720 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700721 } else if (installable instanceof FlowRuleIntent) {
722 classifyLinks(type, biLinks, trafficClass.showTraffic,
723 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700724 } else if (installable instanceof LinkCollectionIntent) {
725 classifyLinks(type, biLinks, trafficClass.showTraffic,
726 ((LinkCollectionIntent) installable).links());
727 } else if (installable instanceof OpticalPathIntent) {
728 classifyLinks(type, biLinks, trafficClass.showTraffic,
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700729 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska329af532015-03-10 02:08:33 -0700730 }
731 }
732 }
733 }
734 }
735 return biLinks;
736 }
737
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700738 // Extracts links from the specified flow rule intent resources
739 private Collection<Link> linkResources(Intent installable) {
740 ImmutableList.Builder<Link> builder = ImmutableList.builder();
741 for (NetworkResource r : installable.resources()) {
742 if (r instanceof Link) {
743 builder.add((Link) r);
744 }
745 }
746 return builder.build();
747 }
748
Thomas Vachuska329af532015-03-10 02:08:33 -0700749
750 // Adds the link segments (path or tree) associated with the specified
751 // connectivity intent
752 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
753 boolean showTraffic, Iterable<Link> links) {
754 if (links != null) {
755 for (Link link : links) {
756 BiLink biLink = addLink(biLinks, link);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700757 if (showTraffic) {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700758 biLink.addLoad(getLinkLoad(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700759 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700760 biLink.addClass(type);
Thomas Vachuska329af532015-03-10 02:08:33 -0700761 }
762 }
763 }
764
765
Thomas Vachuska583bc632015-04-14 10:10:57 -0700766 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700767 LinkKey key = canonicalLinkKey(link);
768 BiLink biLink = biLinks.get(key);
769 if (biLink != null) {
770 biLink.setOther(link);
771 } else {
772 biLink = new BiLink(key, link);
773 biLinks.put(key, biLink);
774 }
775 return biLink;
776 }
777
Thomas Vachuska329af532015-03-10 02:08:33 -0700778 // Poor-mans formatting to get the labels with byte counts looking nice.
779 private String formatBytes(long bytes) {
780 String unit;
781 double value;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700782 if (bytes > GIGA) {
783 value = bytes / GIGA;
784 unit = GBYTES_UNIT;
785 } else if (bytes > MEGA) {
786 value = bytes / MEGA;
787 unit = MBYTES_UNIT;
788 } else if (bytes > KILO) {
789 value = bytes / KILO;
790 unit = KBYTES_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700791 } else {
792 value = bytes;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700793 unit = BYTES_UNIT;
794 }
795 DecimalFormat format = new DecimalFormat("#,###.##");
796 return format.format(value) + " " + unit;
797 }
798
799 // Poor-mans formatting to get the labels with byte counts looking nice.
800 private String formatBitRate(long bytes) {
801 String unit;
802 double value;
803 //Convert to bits
804 long bits = bytes * 8;
805 if (bits > GIGA) {
806 value = bits / GIGA;
807 unit = GBITS_UNIT;
808 } else if (bits > MEGA) {
809 value = bits / MEGA;
810 unit = MBITS_UNIT;
811 } else if (bits > KILO) {
812 value = bits / KILO;
813 unit = KBITS_UNIT;
814 } else {
815 value = bits;
816 unit = BITS_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700817 }
818 DecimalFormat format = new DecimalFormat("#,###.##");
819 return format.format(value) + " " + unit;
820 }
821
822 // Formats the given number into a string.
823 private String format(Number number) {
824 DecimalFormat format = new DecimalFormat("#,###");
825 return format.format(number);
826 }
827
Thomas Vachuska329af532015-03-10 02:08:33 -0700828 // Produces compact string representation of a link.
829 private static String compactLinkString(Link link) {
830 return String.format(COMPACT, link.src().elementId(), link.src().port(),
831 link.dst().elementId(), link.dst().port());
832 }
833
834 // Produces JSON property details.
835 private ObjectNode json(String id, String type, Prop... props) {
Simon Huntda580882015-05-12 20:58:18 -0700836 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700837 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700838 ObjectNode pnode = objectNode();
839 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700840 for (Prop p : props) {
841 porder.add(p.key);
842 pnode.put(p.key, p.value);
843 }
844 result.set("propOrder", porder);
845 result.set("props", pnode);
846 return result;
847 }
848
849 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700850 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700851 String sn = link.src().elementId().toString();
852 String dn = link.dst().elementId().toString();
853 return sn.compareTo(dn) < 0 ?
854 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
855 }
856
857 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700858 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700859 public final LinkKey key;
860 public final Link one;
861 public Link two;
862 public boolean hasTraffic = false;
863 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700864
865 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700866 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700867
868 BiLink(LinkKey key, Link link) {
869 this.key = key;
870 this.one = link;
871 }
872
873 void setOther(Link link) {
874 this.two = link;
875 }
876
877 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700878 addLoad(load, 0);
879 }
880
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700881 void addLoad(Load load, double threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700882 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700883 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700884 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700885 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700886 }
887 }
888
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700889 void addLoad(Load srcLinkLoad,
890 double srcLinkThreshold,
891 Load dstLinkLoad,
892 double dstLinkThreshold) {
893 //use the max of link load at source or destination
894 if (srcLinkLoad != null) {
895 this.hasTraffic = hasTraffic || srcLinkLoad.rate() > srcLinkThreshold;
896 this.bytes = srcLinkLoad.latest();
897 this.rate = srcLinkLoad.rate();
898 }
899
900 if (dstLinkLoad != null) {
901 if (dstLinkLoad.rate() > this.rate) {
902 this.bytes = dstLinkLoad.latest();
903 this.rate = dstLinkLoad.rate();
904 this.hasTraffic = hasTraffic || dstLinkLoad.rate() > dstLinkThreshold;
905 }
906 }
907 }
908
Thomas Vachuska329af532015-03-10 02:08:33 -0700909 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700910 classes.add(trafficClass);
911 }
912
913 String classes() {
914 StringBuilder sb = new StringBuilder();
915 classes.forEach(c -> sb.append(c).append(" "));
916 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700917 }
918 }
919
920 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700921 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700922 public final String key;
923 public final String value;
924
925 protected Prop(String key, String value) {
926 this.key = key;
927 this.value = value;
928 }
929 }
930
931 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700932 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700933 protected Separator() {
934 super("-", "");
935 }
936 }
937
938 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700939 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700940 public final boolean showTraffic;
941 public final String type;
942 public final Iterable<Intent> intents;
943
944 TrafficClass(String type, Iterable<Intent> intents) {
945 this(type, intents, false);
946 }
947
948 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
949 this.type = type;
950 this.intents = intents;
951 this.showTraffic = showTraffic;
952 }
953 }
954
955}