blob: 91a820c378ec0b950264cfe6b29bb963ffcf8a7c [file] [log] [blame]
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -08001/*
2 * Copyright 2014 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.onlab.onos.gui;
17
18import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.node.ArrayNode;
21import com.fasterxml.jackson.databind.node.ObjectNode;
22import org.onlab.onos.cluster.ClusterEvent;
23import org.onlab.onos.cluster.ClusterService;
24import org.onlab.onos.cluster.ControllerNode;
25import org.onlab.onos.cluster.NodeId;
Thomas Vachuska47635c62014-11-22 01:21:36 -080026import org.onlab.onos.core.CoreService;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080027import org.onlab.onos.mastership.MastershipService;
Thomas Vachuska0f6baee2014-11-11 15:02:32 -080028import org.onlab.onos.net.Annotated;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080029import org.onlab.onos.net.Annotations;
30import org.onlab.onos.net.ConnectPoint;
31import org.onlab.onos.net.DefaultEdgeLink;
32import org.onlab.onos.net.Device;
33import org.onlab.onos.net.DeviceId;
34import org.onlab.onos.net.EdgeLink;
35import org.onlab.onos.net.Host;
36import org.onlab.onos.net.HostId;
37import org.onlab.onos.net.HostLocation;
38import org.onlab.onos.net.Link;
Thomas Vachuska29617e52014-11-20 03:17:46 -080039import org.onlab.onos.net.PortNumber;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080040import org.onlab.onos.net.device.DeviceEvent;
41import org.onlab.onos.net.device.DeviceService;
Thomas Vachuska87d3db12014-11-20 01:18:50 -080042import org.onlab.onos.net.flow.FlowEntry;
43import org.onlab.onos.net.flow.FlowRuleService;
Thomas Vachuska29617e52014-11-20 03:17:46 -080044import org.onlab.onos.net.flow.TrafficTreatment;
45import org.onlab.onos.net.flow.instructions.Instruction;
46import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080047import org.onlab.onos.net.host.HostEvent;
48import org.onlab.onos.net.host.HostService;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080049import org.onlab.onos.net.intent.Intent;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080050import org.onlab.onos.net.intent.IntentService;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080051import org.onlab.onos.net.intent.LinkCollectionIntent;
Thomas Vachuska22e34922014-11-14 00:40:55 -080052import org.onlab.onos.net.intent.OpticalConnectivityIntent;
Thomas Vachuskadea4cb32014-11-14 12:14:30 -080053import org.onlab.onos.net.intent.OpticalPathIntent;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080054import org.onlab.onos.net.intent.PathIntent;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080055import org.onlab.onos.net.link.LinkEvent;
56import org.onlab.onos.net.link.LinkService;
57import org.onlab.onos.net.provider.ProviderId;
Thomas Vachuska7c27ad72014-11-14 16:20:10 -080058import org.onlab.onos.net.statistic.Load;
59import org.onlab.onos.net.statistic.StatisticService;
Thomas Vachuska47635c62014-11-22 01:21:36 -080060import org.onlab.onos.net.topology.Topology;
61import org.onlab.onos.net.topology.TopologyService;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080062import org.onlab.osgi.ServiceDirectory;
63import org.onlab.packet.IpAddress;
Thomas Vachuska0f6baee2014-11-11 15:02:32 -080064import org.slf4j.Logger;
65import org.slf4j.LoggerFactory;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080066
Thomas Vachuska20322ff2014-11-19 16:22:25 -080067import java.text.DecimalFormat;
Thomas Vachuska29617e52014-11-20 03:17:46 -080068import java.util.ArrayList;
69import java.util.HashMap;
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -080070import java.util.HashSet;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080071import java.util.Iterator;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080072import java.util.List;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080073import java.util.Map;
74import java.util.Set;
75import java.util.concurrent.ConcurrentHashMap;
76
77import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska89543292014-11-19 11:28:33 -080078import static com.google.common.base.Strings.isNullOrEmpty;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080079import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
80import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
81import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -080082import static org.onlab.onos.net.DeviceId.deviceId;
83import static org.onlab.onos.net.HostId.hostId;
Thomas Vachuska29617e52014-11-20 03:17:46 -080084import static org.onlab.onos.net.PortNumber.P0;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080085import static org.onlab.onos.net.PortNumber.portNumber;
86import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
87import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
88import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
89import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
90import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
91import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
92
93/**
94 * Facility for creating messages bound for the topology viewer.
95 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -080096public abstract class TopologyViewMessages {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080097
Thomas Vachuska7c27ad72014-11-14 16:20:10 -080098 protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class);
Thomas Vachuska0f6baee2014-11-11 15:02:32 -080099
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800100 private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
101 private static final String COMPACT = "%s/%s-%s/%s";
102
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800103 private static final double KB = 1024;
104 private static final double MB = 1024 * KB;
105 private static final double GB = 1024 * MB;
106
107 private static final String GB_UNIT = "GB";
108 private static final String MB_UNIT = "MB";
109 private static final String KB_UNIT = "KB";
110 private static final String B_UNIT = "B";
111
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800112 private static final String ANIMATED = "animated";
113
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800114 protected final ServiceDirectory directory;
115 protected final ClusterService clusterService;
116 protected final DeviceService deviceService;
117 protected final LinkService linkService;
118 protected final HostService hostService;
119 protected final MastershipService mastershipService;
120 protected final IntentService intentService;
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800121 protected final FlowRuleService flowService;
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800122 protected final StatisticService statService;
Thomas Vachuska47635c62014-11-22 01:21:36 -0800123 protected final TopologyService topologyService;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800124
125 protected final ObjectMapper mapper = new ObjectMapper();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800126 private final String version;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800127
128 // TODO: extract into an external & durable state; good enough for now and demo
129 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
130
131 /**
132 * Creates a messaging facility for creating messages for topology viewer.
133 *
134 * @param directory service directory
135 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800136 protected TopologyViewMessages(ServiceDirectory directory) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800137 this.directory = checkNotNull(directory, "Directory cannot be null");
138 clusterService = directory.get(ClusterService.class);
139 deviceService = directory.get(DeviceService.class);
140 linkService = directory.get(LinkService.class);
141 hostService = directory.get(HostService.class);
142 mastershipService = directory.get(MastershipService.class);
143 intentService = directory.get(IntentService.class);
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800144 flowService = directory.get(FlowRuleService.class);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800145 statService = directory.get(StatisticService.class);
Thomas Vachuska47635c62014-11-22 01:21:36 -0800146 topologyService = directory.get(TopologyService.class);
147
148 version = directory.get(CoreService.class).version().toString();
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800149 }
150
151 // Retrieves the payload from the specified event.
152 protected ObjectNode payload(ObjectNode event) {
153 return (ObjectNode) event.path("payload");
154 }
155
156 // Returns the specified node property as a number
157 protected long number(ObjectNode node, String name) {
158 return node.path(name).asLong();
159 }
160
161 // Returns the specified node property as a string.
162 protected String string(ObjectNode node, String name) {
163 return node.path(name).asText();
164 }
165
166 // Returns the specified node property as a string.
167 protected String string(ObjectNode node, String name, String defaultValue) {
168 return node.path(name).asText(defaultValue);
169 }
170
171 // Returns the specified set of IP addresses as a string.
172 private String ip(Set<IpAddress> ipAddresses) {
173 Iterator<IpAddress> it = ipAddresses.iterator();
174 return it.hasNext() ? it.next().toString() : "unknown";
175 }
176
177 // Produces JSON structure from annotations.
178 private JsonNode props(Annotations annotations) {
179 ObjectNode props = mapper.createObjectNode();
Thomas Vachuska16582df2014-11-15 20:12:17 -0800180 if (annotations != null) {
181 for (String key : annotations.keys()) {
182 props.put(key, annotations.value(key));
183 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800184 }
185 return props;
186 }
187
188 // Produces an informational log message event bound to the client.
189 protected ObjectNode info(long id, String message) {
190 return message("info", id, message);
191 }
192
193 // Produces a warning log message event bound to the client.
194 protected ObjectNode warning(long id, String message) {
195 return message("warning", id, message);
196 }
197
198 // Produces an error log message event bound to the client.
199 protected ObjectNode error(long id, String message) {
200 return message("error", id, message);
201 }
202
203 // Produces a log message event bound to the client.
204 private ObjectNode message(String severity, long id, String message) {
205 return envelope("message", id,
206 mapper.createObjectNode()
207 .put("severity", severity)
208 .put("message", message));
209 }
210
211 // Puts the payload into an envelope and returns it.
212 protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
213 ObjectNode event = mapper.createObjectNode();
214 event.put("event", type);
215 if (sid > 0) {
216 event.put("sid", sid);
217 }
218 event.set("payload", payload);
219 return event;
220 }
221
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800222 // Produces a set of all hosts listed in the specified JSON array.
223 protected Set<Host> getHosts(ArrayNode array) {
224 Set<Host> hosts = new HashSet<>();
225 if (array != null) {
226 for (JsonNode node : array) {
227 try {
228 addHost(hosts, hostId(node.asText()));
229 } catch (IllegalArgumentException e) {
230 log.debug("Skipping ID {}", node.asText());
231 }
232 }
233 }
234 return hosts;
235 }
236
237 // Adds the specified host to the set of hosts.
238 private void addHost(Set<Host> hosts, HostId hostId) {
239 Host host = hostService.getHost(hostId);
240 if (host != null) {
241 hosts.add(host);
242 }
243 }
244
245
246 // Produces a set of all devices listed in the specified JSON array.
247 protected Set<Device> getDevices(ArrayNode array) {
248 Set<Device> devices = new HashSet<>();
249 if (array != null) {
250 for (JsonNode node : array) {
251 try {
252 addDevice(devices, deviceId(node.asText()));
253 } catch (IllegalArgumentException e) {
254 log.debug("Skipping ID {}", node.asText());
255 }
256 }
257 }
258 return devices;
259 }
260
261 private void addDevice(Set<Device> devices, DeviceId deviceId) {
262 Device device = deviceService.getDevice(deviceId);
263 if (device != null) {
264 devices.add(device);
265 }
266 }
267
268 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
269 try {
270 addHost(hosts, hostId(hover));
271 } catch (IllegalArgumentException e) {
272 try {
273 addDevice(devices, deviceId(hover));
274 } catch (IllegalArgumentException ne) {
275 log.debug("Skipping ID {}", hover);
276 }
277 }
278 }
279
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800280 // Produces a cluster instance message to the client.
281 protected ObjectNode instanceMessage(ClusterEvent event) {
282 ControllerNode node = event.subject();
283 ObjectNode payload = mapper.createObjectNode()
284 .put("id", node.id().toString())
Thomas Vachuskae4cebaf2014-11-15 18:49:34 -0800285 .put("online", clusterService.getState(node.id()) == ACTIVE)
286 .put("uiAttached", event.subject().equals(clusterService.getLocalNode()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800287
288 ArrayNode labels = mapper.createArrayNode();
289 labels.add(node.id().toString());
290 labels.add(node.ip().toString());
291
292 // Add labels, props and stuff the payload into envelope.
293 payload.set("labels", labels);
294 addMetaUi(node.id().toString(), payload);
295
296 String type = (event.type() == INSTANCE_ADDED) ? "addInstance" :
297 ((event.type() == INSTANCE_REMOVED) ? "removeInstance" : "updateInstance");
298 return envelope(type, 0, payload);
299 }
300
301 // Produces a device event message to the client.
302 protected ObjectNode deviceMessage(DeviceEvent event) {
303 Device device = event.subject();
304 ObjectNode payload = mapper.createObjectNode()
305 .put("id", device.id().toString())
306 .put("type", device.type().toString().toLowerCase())
307 .put("online", deviceService.isAvailable(device.id()))
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800308 .put("master", master(device.id()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800309
310 // Generate labels: id, chassis id, no-label, optional-name
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800311 String name = device.annotations().value("name");
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800312 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800313 labels.add("");
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800314 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800315 labels.add(device.id().toString());
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800316
317 // Add labels, props and stuff the payload into envelope.
318 payload.set("labels", labels);
319 payload.set("props", props(device.annotations()));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800320 addGeoLocation(device, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800321 addMetaUi(device.id().toString(), payload);
322
323 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
324 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
325 return envelope(type, 0, payload);
326 }
327
328 // Produces a link event message to the client.
329 protected ObjectNode linkMessage(LinkEvent event) {
330 Link link = event.subject();
331 ObjectNode payload = mapper.createObjectNode()
332 .put("id", compactLinkString(link))
333 .put("type", link.type().toString().toLowerCase())
Thomas Vachuskae4cebaf2014-11-15 18:49:34 -0800334 .put("online", link.state() == Link.State.ACTIVE)
Thomas Vachuskacd2920c2014-11-19 14:49:55 -0800335 .put("linkWidth", 1.2)
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800336 .put("src", link.src().deviceId().toString())
337 .put("srcPort", link.src().port().toString())
338 .put("dst", link.dst().deviceId().toString())
339 .put("dstPort", link.dst().port().toString());
340 String type = (event.type() == LINK_ADDED) ? "addLink" :
341 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
342 return envelope(type, 0, payload);
343 }
344
345 // Produces a host event message to the client.
346 protected ObjectNode hostMessage(HostEvent event) {
347 Host host = event.subject();
Thomas Vachuska89543292014-11-19 11:28:33 -0800348 String hostType = host.annotations().value("type");
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800349 ObjectNode payload = mapper.createObjectNode()
350 .put("id", host.id().toString())
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800351 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800352 .put("ingress", compactLinkString(edgeLink(host, true)))
353 .put("egress", compactLinkString(edgeLink(host, false)));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800354 payload.set("cp", hostConnect(mapper, host.location()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800355 payload.set("labels", labels(mapper, ip(host.ipAddresses()),
356 host.mac().toString()));
357 payload.set("props", props(host.annotations()));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800358 addGeoLocation(host, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800359 addMetaUi(host.id().toString(), payload);
360
361 String type = (event.type() == HOST_ADDED) ? "addHost" :
362 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
363 return envelope(type, 0, payload);
364 }
365
366 // Encodes the specified host location into a JSON object.
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800367 private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800368 return mapper.createObjectNode()
369 .put("device", location.deviceId().toString())
370 .put("port", location.port().toLong());
371 }
372
373 // Encodes the specified list of labels a JSON array.
374 private ArrayNode labels(ObjectMapper mapper, String... labels) {
375 ArrayNode json = mapper.createArrayNode();
376 for (String label : labels) {
377 json.add(label);
378 }
379 return json;
380 }
381
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800382 // Returns the name of the master node for the specified device id.
383 private String master(DeviceId deviceId) {
384 NodeId master = mastershipService.getMasterFor(deviceId);
385 return master != null ? master.toString() : "";
386 }
387
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800388 // Generates an edge link from the specified host location.
389 private EdgeLink edgeLink(Host host, boolean ingress) {
390 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
391 host.location(), ingress);
392 }
393
394 // Adds meta UI information for the specified object.
395 private void addMetaUi(String id, ObjectNode payload) {
396 ObjectNode meta = metaUi.get(id);
397 if (meta != null) {
398 payload.set("metaUi", meta);
399 }
400 }
401
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800402 // Adds a geo location JSON to the specified payload object.
403 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
404 Annotations annotations = annotated.annotations();
Thomas Vachuska16582df2014-11-15 20:12:17 -0800405 if (annotations == null) {
406 return;
407 }
408
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800409 String slat = annotations.value("latitude");
410 String slng = annotations.value("longitude");
411 try {
412 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
413 double lat = Double.parseDouble(slat);
414 double lng = Double.parseDouble(slng);
415 ObjectNode loc = mapper.createObjectNode()
416 .put("type", "latlng").put("lat", lat).put("lng", lng);
417 payload.set("location", loc);
418 }
419 } catch (NumberFormatException e) {
420 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
421 }
422 }
423
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800424 // Updates meta UI information for the specified object.
425 protected void updateMetaUi(ObjectNode event) {
426 ObjectNode payload = payload(event);
Simon Hunt3b9cddb2014-11-11 20:50:04 -0800427 metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800428 }
429
Thomas Vachuska47635c62014-11-22 01:21:36 -0800430 // Returns summary response.
431 protected ObjectNode summmaryMessage(long sid) {
432 Topology topology = topologyService.currentTopology();
433 return envelope("showSummary", sid,
434 json("ONOS Summary", "node",
435 new Prop("Devices", format(topology.deviceCount())),
436 new Prop("Links", format(topology.linkCount())),
437 new Prop("Hosts", format(hostService.getHostCount())),
438 new Prop("Topology SCCs", format(topology.clusterCount())),
439 new Prop("Paths", format(topology.pathCount())),
440 new Separator(),
441 new Prop("Intents", format(intentService.getIntentCount())),
442 new Prop("Flows", format(flowService.getFlowRuleCount())),
443 new Prop("Version", version.replace(".SNAPSHOT", "*"))));
444 }
445
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800446 // Returns device details response.
447 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
448 Device device = deviceService.getDevice(deviceId);
449 Annotations annot = device.annotations();
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800450 String name = annot.value("name");
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800451 int portCount = deviceService.getPorts(deviceId).size();
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800452 int flowCount = getFlowCount(deviceId);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800453 return envelope("showDetails", sid,
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800454 json(isNullOrEmpty(name) ? deviceId.toString() : name,
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800455 device.type().toString().toLowerCase(),
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800456 new Prop("URI", deviceId.toString()),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800457 new Prop("Vendor", device.manufacturer()),
458 new Prop("H/W Version", device.hwVersion()),
459 new Prop("S/W Version", device.swVersion()),
460 new Prop("Serial Number", device.serialNumber()),
461 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800462 new Prop("Master", master(deviceId)),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800463 new Prop("Latitude", annot.value("latitude")),
464 new Prop("Longitude", annot.value("longitude")),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800465 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800466 new Prop("Ports", Integer.toString(portCount)),
467 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800468 }
469
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800470 protected int getFlowCount(DeviceId deviceId) {
471 int count = 0;
472 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
473 while (it.hasNext()) {
474 count++;
475 it.next();
476 }
477 return count;
478 }
479
Thomas Vachuska29617e52014-11-20 03:17:46 -0800480 // Counts all entries that egress on the given device links.
481 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
482 List<FlowEntry> entries = new ArrayList<>();
483 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
484 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
485 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
486 while (it.hasNext()) {
487 entries.add(it.next());
488 }
489
490 // Add all edge links to the set
491 if (hosts != null) {
492 for (Host host : hosts) {
493 links.add(new DefaultEdgeLink(host.providerId(),
494 new ConnectPoint(host.id(), P0),
495 host.location(), false));
496 }
497 }
498
499 Map<Link, Integer> counts = new HashMap<>();
500 for (Link link : links) {
501 counts.put(link, getEgressFlows(link, entries));
502 }
503 return counts;
504 }
505
506 // Counts all entries that egress on the link source port.
507 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
508 int count = 0;
509 PortNumber out = link.src().port();
510 for (FlowEntry entry : entries) {
511 TrafficTreatment treatment = entry.treatment();
512 for (Instruction instruction : treatment.instructions()) {
513 if (instruction.type() == Instruction.Type.OUTPUT &&
514 ((OutputInstruction) instruction).port().equals(out)) {
515 count++;
516 }
517 }
518 }
519 return count;
520 }
521
522
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800523 // Returns host details response.
524 protected ObjectNode hostDetails(HostId hostId, long sid) {
525 Host host = hostService.getHost(hostId);
526 Annotations annot = host.annotations();
Thomas Vachuska89543292014-11-19 11:28:33 -0800527 String type = annot.value("type");
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800528 String name = annot.value("name");
529 String vlan = host.vlan().toString();
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800530 return envelope("showDetails", sid,
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800531 json(isNullOrEmpty(name) ? hostId.toString() : name,
532 isNullOrEmpty(type) ? "host" : type,
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800533 new Prop("MAC", host.mac().toString()),
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800534 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
535 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800536 new Separator(),
537 new Prop("Latitude", annot.value("latitude")),
538 new Prop("Longitude", annot.value("longitude"))));
539 }
540
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800541
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800542 // Produces JSON message to trigger traffic overview visualization
543 protected ObjectNode trafficSummaryMessage(long sid) {
544 ObjectNode payload = mapper.createObjectNode();
545 ArrayNode paths = mapper.createArrayNode();
546 payload.set("paths", paths);
547 for (Link link : linkService.getLinks()) {
548 Set<Link> links = new HashSet<>();
549 links.add(link);
550 addPathTraffic(paths, "plain", "secondary", links);
551 }
552 return envelope("showTraffic", sid, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800553 }
554
Thomas Vachuska29617e52014-11-20 03:17:46 -0800555 // Produces JSON message to trigger flow overview visualization
556 protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
557 ObjectNode payload = mapper.createObjectNode();
558 ArrayNode paths = mapper.createArrayNode();
559 payload.set("paths", paths);
560
561 for (Device device : devices) {
562 Map<Link, Integer> counts = getFlowCounts(device.id());
563 for (Link link : counts.keySet()) {
564 addLinkFlows(link, paths, counts.get(link));
565 }
566 }
567 return envelope("showTraffic", sid, payload);
568 }
569
570 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
571 ObjectNode pathNode = mapper.createObjectNode();
572 ArrayNode linksNode = mapper.createArrayNode();
573 ArrayNode labels = mapper.createArrayNode();
574 boolean noFlows = count == null || count == 0;
575 pathNode.put("class", noFlows ? "secondary" : "primary");
576 pathNode.put("traffic", false);
577 pathNode.set("links", linksNode.add(compactLinkString(link)));
578 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
579 (count == 1 ? " flow" : " flows"))));
580 paths.add(pathNode);
581 }
582
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800583
584 // Produces JSON message to trigger traffic visualization
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800585 protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800586 ObjectNode payload = mapper.createObjectNode();
587 ArrayNode paths = mapper.createArrayNode();
588 payload.set("paths", paths);
589
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800590 for (TrafficClass trafficClass : trafficClasses) {
591 for (Intent intent : trafficClass.intents) {
Thomas Vachuska22e34922014-11-14 00:40:55 -0800592 boolean isOptical = intent instanceof OpticalConnectivityIntent;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800593 List<Intent> installables = intentService.getInstallableIntents(intent.id());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800594 if (installables != null) {
595 for (Intent installable : installables) {
596 String cls = isOptical ? trafficClass.type + " optical" : trafficClass.type;
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800597 if (installable instanceof PathIntent) {
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800598 addPathTraffic(paths, cls, ANIMATED,
599 ((PathIntent) installable).path().links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800600 } else if (installable instanceof LinkCollectionIntent) {
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800601 addPathTraffic(paths, cls, ANIMATED,
602 ((LinkCollectionIntent) installable).links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800603 } else if (installable instanceof OpticalPathIntent) {
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800604 addPathTraffic(paths, cls, ANIMATED,
605 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800606 }
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800607
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800608 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800609 }
610 }
611 }
612
613 return envelope("showTraffic", sid, payload);
614 }
615
616 // Adds the link segments (path or tree) associated with the specified
617 // connectivity intent
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800618 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
619 Iterable<Link> links) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800620 ObjectNode pathNode = mapper.createObjectNode();
621 ArrayNode linksNode = mapper.createArrayNode();
622
Thomas Vachuska22e34922014-11-14 00:40:55 -0800623 if (links != null) {
624 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800625 boolean hasTraffic = false;
Thomas Vachuska22e34922014-11-14 00:40:55 -0800626 for (Link link : links) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800627 if (isInfrastructureEgress(link)) {
628 linksNode.add(compactLinkString(link));
629 Load load = statService.load(link);
630 String label = "";
631 if (load.rate() > 0) {
632 hasTraffic = true;
633 label = format(load);
634 }
635 labels.add(label);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800636 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800637 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800638 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
Thomas Vachuska22e34922014-11-14 00:40:55 -0800639 pathNode.put("traffic", hasTraffic);
640 pathNode.set("links", linksNode);
641 pathNode.set("labels", labels);
642 paths.add(pathNode);
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800643 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800644 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800645
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800646 // Poor-mans formatting to get the labels with byte counts looking nice.
647 private String format(Load load) {
648 long bytes = load.latest();
649 String unit;
650 double value;
651 if (bytes > GB) {
652 value = bytes / GB;
653 unit = GB_UNIT;
654 } else if (bytes > MB) {
655 value = bytes / MB;
656 unit = MB_UNIT;
657 } else if (bytes > KB) {
658 value = bytes / KB;
659 unit = KB_UNIT;
660 } else {
661 value = bytes;
662 unit = B_UNIT;
663 }
664 DecimalFormat format = new DecimalFormat("#,###.##");
Thomas Vachuska29617e52014-11-20 03:17:46 -0800665 return format.format(value) + " " + unit;
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800666 }
667
Thomas Vachuska47635c62014-11-22 01:21:36 -0800668 // Formats the given number into a string.
669 private String format(Number number) {
670 DecimalFormat format = new DecimalFormat("#,###");
671 return format.format(number);
672 }
673
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800674 private boolean isInfrastructureEgress(Link link) {
675 return link.src().elementId() instanceof DeviceId;
676 }
677
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800678 // Produces compact string representation of a link.
679 private static String compactLinkString(Link link) {
680 return String.format(COMPACT, link.src().elementId(), link.src().port(),
681 link.dst().elementId(), link.dst().port());
682 }
683
684 // Produces JSON property details.
685 private ObjectNode json(String id, String type, Prop... props) {
686 ObjectMapper mapper = new ObjectMapper();
687 ObjectNode result = mapper.createObjectNode()
688 .put("id", id).put("type", type);
689 ObjectNode pnode = mapper.createObjectNode();
690 ArrayNode porder = mapper.createArrayNode();
691 for (Prop p : props) {
692 porder.add(p.key);
693 pnode.put(p.key, p.value);
694 }
695 result.set("propOrder", porder);
696 result.set("props", pnode);
697 return result;
698 }
699
700 // Auxiliary key/value carrier.
701 private class Prop {
702 public final String key;
703 public final String value;
704
705 protected Prop(String key, String value) {
706 this.key = key;
707 this.value = value;
708 }
709 }
710
711 // Auxiliary properties separator
712 private class Separator extends Prop {
713 protected Separator() {
714 super("-", "");
715 }
716 }
717
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800718 // Auxiliary carrier of data for requesting traffic message.
719 protected class TrafficClass {
720 public final String type;
721 public final Set<Intent> intents;
722
723 TrafficClass(String type, Set<Intent> intents) {
724 this.type = type;
725 this.intents = intents;
726 }
727 }
728
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800729}