blob: 746c37e71205f78872c3d8272f0e792b7d8d7c03 [file] [log] [blame]
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -08001/*
Simon Hunt8ead3a22015-01-06 11:00:15 -08002 * Copyright 2014,2015 Open Networking Laboratory
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -08003 *
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 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.gui;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080017
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;
Simon Hunt8ead3a22015-01-06 11:00:15 -080022import org.onlab.osgi.ServiceDirectory;
23import org.onlab.packet.IpAddress;
Brian O'Connorabafb502014-12-02 22:26:20 -080024import org.onosproject.cluster.ClusterEvent;
25import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.ControllerNode;
27import org.onosproject.cluster.NodeId;
28import org.onosproject.core.CoreService;
29import org.onosproject.mastership.MastershipService;
30import org.onosproject.net.Annotated;
31import org.onosproject.net.AnnotationKeys;
32import org.onosproject.net.Annotations;
33import org.onosproject.net.ConnectPoint;
34import org.onosproject.net.DefaultEdgeLink;
35import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.EdgeLink;
38import org.onosproject.net.Host;
39import org.onosproject.net.HostId;
40import org.onosproject.net.HostLocation;
41import org.onosproject.net.Link;
42import org.onosproject.net.LinkKey;
43import org.onosproject.net.PortNumber;
44import org.onosproject.net.device.DeviceEvent;
45import org.onosproject.net.device.DeviceService;
46import org.onosproject.net.flow.FlowEntry;
47import org.onosproject.net.flow.FlowRuleService;
48import org.onosproject.net.flow.TrafficTreatment;
49import org.onosproject.net.flow.instructions.Instruction;
50import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
51import org.onosproject.net.host.HostEvent;
52import org.onosproject.net.host.HostService;
53import org.onosproject.net.intent.Intent;
54import org.onosproject.net.intent.IntentService;
55import org.onosproject.net.intent.LinkCollectionIntent;
56import org.onosproject.net.intent.OpticalConnectivityIntent;
57import org.onosproject.net.intent.OpticalPathIntent;
58import org.onosproject.net.intent.PathIntent;
59import org.onosproject.net.link.LinkEvent;
60import org.onosproject.net.link.LinkService;
61import org.onosproject.net.provider.ProviderId;
62import org.onosproject.net.statistic.Load;
63import org.onosproject.net.statistic.StatisticService;
64import org.onosproject.net.topology.Topology;
65import org.onosproject.net.topology.TopologyService;
Thomas Vachuska0f6baee2014-11-11 15:02:32 -080066import org.slf4j.Logger;
67import org.slf4j.LoggerFactory;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080068
Thomas Vachuska20322ff2014-11-19 16:22:25 -080069import java.text.DecimalFormat;
Thomas Vachuska29617e52014-11-20 03:17:46 -080070import java.util.ArrayList;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -080071import java.util.Collection;
Thomas Vachuska998b1412014-11-23 02:42:49 -080072import java.util.Collections;
Thomas Vachuska29617e52014-11-20 03:17:46 -080073import java.util.HashMap;
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -080074import java.util.HashSet;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080075import java.util.Iterator;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080076import java.util.List;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080077import java.util.Map;
78import java.util.Set;
79import java.util.concurrent.ConcurrentHashMap;
80
81import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska89543292014-11-19 11:28:33 -080082import static com.google.common.base.Strings.isNullOrEmpty;
Brian O'Connorabafb502014-12-02 22:26:20 -080083import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
84import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
85import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
86import static org.onosproject.net.DeviceId.deviceId;
87import static org.onosproject.net.HostId.hostId;
88import static org.onosproject.net.LinkKey.linkKey;
89import static org.onosproject.net.PortNumber.P0;
90import static org.onosproject.net.PortNumber.portNumber;
91import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
92import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
93import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
94import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
95import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
96import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080097
98/**
99 * Facility for creating messages bound for the topology viewer.
100 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800101public abstract class TopologyViewMessages {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800102
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800103 protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class);
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800104
Brian O'Connorabafb502014-12-02 22:26:20 -0800105 private static final ProviderId PID = new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800106 private static final String COMPACT = "%s/%s-%s/%s";
107
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800108 private static final double KB = 1024;
109 private static final double MB = 1024 * KB;
110 private static final double GB = 1024 * MB;
111
112 private static final String GB_UNIT = "GB";
113 private static final String MB_UNIT = "MB";
114 private static final String KB_UNIT = "KB";
115 private static final String B_UNIT = "B";
116
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800117 protected final ServiceDirectory directory;
118 protected final ClusterService clusterService;
119 protected final DeviceService deviceService;
120 protected final LinkService linkService;
121 protected final HostService hostService;
122 protected final MastershipService mastershipService;
123 protected final IntentService intentService;
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800124 protected final FlowRuleService flowService;
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800125 protected final StatisticService statService;
Thomas Vachuska47635c62014-11-22 01:21:36 -0800126 protected final TopologyService topologyService;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800127
128 protected final ObjectMapper mapper = new ObjectMapper();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800129 private final String version;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800130
131 // TODO: extract into an external & durable state; good enough for now and demo
132 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
133
134 /**
Thomas Vachuska998b1412014-11-23 02:42:49 -0800135 * Returns read-only view of the meta-ui information.
136 *
137 * @return map of id to meta-ui mementos
138 */
139 static Map<String, ObjectNode> getMetaUi() {
140 return Collections.unmodifiableMap(metaUi);
141 }
142
143 /**
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800144 * Creates a messaging facility for creating messages for topology viewer.
145 *
146 * @param directory service directory
147 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800148 protected TopologyViewMessages(ServiceDirectory directory) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800149 this.directory = checkNotNull(directory, "Directory cannot be null");
150 clusterService = directory.get(ClusterService.class);
151 deviceService = directory.get(DeviceService.class);
152 linkService = directory.get(LinkService.class);
153 hostService = directory.get(HostService.class);
154 mastershipService = directory.get(MastershipService.class);
155 intentService = directory.get(IntentService.class);
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800156 flowService = directory.get(FlowRuleService.class);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800157 statService = directory.get(StatisticService.class);
Thomas Vachuska47635c62014-11-22 01:21:36 -0800158 topologyService = directory.get(TopologyService.class);
159
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800160 String ver = directory.get(CoreService.class).version().toString();
161 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800162 }
163
164 // Retrieves the payload from the specified event.
165 protected ObjectNode payload(ObjectNode event) {
166 return (ObjectNode) event.path("payload");
167 }
168
169 // Returns the specified node property as a number
170 protected long number(ObjectNode node, String name) {
171 return node.path(name).asLong();
172 }
173
174 // Returns the specified node property as a string.
175 protected String string(ObjectNode node, String name) {
176 return node.path(name).asText();
177 }
178
179 // Returns the specified node property as a string.
180 protected String string(ObjectNode node, String name, String defaultValue) {
181 return node.path(name).asText(defaultValue);
182 }
183
184 // Returns the specified set of IP addresses as a string.
185 private String ip(Set<IpAddress> ipAddresses) {
186 Iterator<IpAddress> it = ipAddresses.iterator();
187 return it.hasNext() ? it.next().toString() : "unknown";
188 }
189
190 // Produces JSON structure from annotations.
191 private JsonNode props(Annotations annotations) {
192 ObjectNode props = mapper.createObjectNode();
Thomas Vachuska16582df2014-11-15 20:12:17 -0800193 if (annotations != null) {
194 for (String key : annotations.keys()) {
195 props.put(key, annotations.value(key));
196 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800197 }
198 return props;
199 }
200
201 // Produces an informational log message event bound to the client.
202 protected ObjectNode info(long id, String message) {
203 return message("info", id, message);
204 }
205
206 // Produces a warning log message event bound to the client.
207 protected ObjectNode warning(long id, String message) {
208 return message("warning", id, message);
209 }
210
211 // Produces an error log message event bound to the client.
212 protected ObjectNode error(long id, String message) {
213 return message("error", id, message);
214 }
215
216 // Produces a log message event bound to the client.
217 private ObjectNode message(String severity, long id, String message) {
218 return envelope("message", id,
219 mapper.createObjectNode()
220 .put("severity", severity)
221 .put("message", message));
222 }
223
224 // Puts the payload into an envelope and returns it.
225 protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
226 ObjectNode event = mapper.createObjectNode();
227 event.put("event", type);
228 if (sid > 0) {
229 event.put("sid", sid);
230 }
231 event.set("payload", payload);
232 return event;
233 }
234
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800235 // Produces a set of all hosts listed in the specified JSON array.
236 protected Set<Host> getHosts(ArrayNode array) {
237 Set<Host> hosts = new HashSet<>();
238 if (array != null) {
239 for (JsonNode node : array) {
240 try {
241 addHost(hosts, hostId(node.asText()));
242 } catch (IllegalArgumentException e) {
243 log.debug("Skipping ID {}", node.asText());
244 }
245 }
246 }
247 return hosts;
248 }
249
250 // Adds the specified host to the set of hosts.
251 private void addHost(Set<Host> hosts, HostId hostId) {
252 Host host = hostService.getHost(hostId);
253 if (host != null) {
254 hosts.add(host);
255 }
256 }
257
258
259 // Produces a set of all devices listed in the specified JSON array.
260 protected Set<Device> getDevices(ArrayNode array) {
261 Set<Device> devices = new HashSet<>();
262 if (array != null) {
263 for (JsonNode node : array) {
264 try {
265 addDevice(devices, deviceId(node.asText()));
266 } catch (IllegalArgumentException e) {
267 log.debug("Skipping ID {}", node.asText());
268 }
269 }
270 }
271 return devices;
272 }
273
274 private void addDevice(Set<Device> devices, DeviceId deviceId) {
275 Device device = deviceService.getDevice(deviceId);
276 if (device != null) {
277 devices.add(device);
278 }
279 }
280
281 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
282 try {
283 addHost(hosts, hostId(hover));
284 } catch (IllegalArgumentException e) {
285 try {
286 addDevice(devices, deviceId(hover));
287 } catch (IllegalArgumentException ne) {
288 log.debug("Skipping ID {}", hover);
289 }
290 }
291 }
292
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800293 // Produces a cluster instance message to the client.
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800294 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800295 ControllerNode node = event.subject();
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800296 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800297 ObjectNode payload = mapper.createObjectNode()
298 .put("id", node.id().toString())
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800299 .put("ip", node.ip().toString())
Thomas Vachuskae4cebaf2014-11-15 18:49:34 -0800300 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800301 .put("uiAttached", event.subject().equals(clusterService.getLocalNode()))
302 .put("switches", switchCount);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800303
304 ArrayNode labels = mapper.createArrayNode();
305 labels.add(node.id().toString());
306 labels.add(node.ip().toString());
307
308 // Add labels, props and stuff the payload into envelope.
309 payload.set("labels", labels);
310 addMetaUi(node.id().toString(), payload);
311
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800312 String type = messageType != null ? messageType :
313 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
314 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
Thomas Vachuskad1c25cd2014-11-29 17:47:58 -0800315 "addInstance")));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800316 return envelope(type, 0, payload);
317 }
318
319 // Produces a device event message to the client.
320 protected ObjectNode deviceMessage(DeviceEvent event) {
321 Device device = event.subject();
322 ObjectNode payload = mapper.createObjectNode()
323 .put("id", device.id().toString())
324 .put("type", device.type().toString().toLowerCase())
325 .put("online", deviceService.isAvailable(device.id()))
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800326 .put("master", master(device.id()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800327
328 // Generate labels: id, chassis id, no-label, optional-name
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800329 String name = device.annotations().value(AnnotationKeys.NAME);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800330 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800331 labels.add("");
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800332 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800333 labels.add(device.id().toString());
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800334
335 // Add labels, props and stuff the payload into envelope.
336 payload.set("labels", labels);
337 payload.set("props", props(device.annotations()));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800338 addGeoLocation(device, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800339 addMetaUi(device.id().toString(), payload);
340
341 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
342 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
343 return envelope(type, 0, payload);
344 }
345
346 // Produces a link event message to the client.
347 protected ObjectNode linkMessage(LinkEvent event) {
348 Link link = event.subject();
349 ObjectNode payload = mapper.createObjectNode()
350 .put("id", compactLinkString(link))
351 .put("type", link.type().toString().toLowerCase())
Thomas Vachuskae4cebaf2014-11-15 18:49:34 -0800352 .put("online", link.state() == Link.State.ACTIVE)
Thomas Vachuskacd2920c2014-11-19 14:49:55 -0800353 .put("linkWidth", 1.2)
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800354 .put("src", link.src().deviceId().toString())
355 .put("srcPort", link.src().port().toString())
356 .put("dst", link.dst().deviceId().toString())
357 .put("dstPort", link.dst().port().toString());
358 String type = (event.type() == LINK_ADDED) ? "addLink" :
359 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
360 return envelope(type, 0, payload);
361 }
362
363 // Produces a host event message to the client.
364 protected ObjectNode hostMessage(HostEvent event) {
365 Host host = event.subject();
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800366 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800367 ObjectNode payload = mapper.createObjectNode()
368 .put("id", host.id().toString())
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800369 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800370 .put("ingress", compactLinkString(edgeLink(host, true)))
371 .put("egress", compactLinkString(edgeLink(host, false)));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800372 payload.set("cp", hostConnect(mapper, host.location()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800373 payload.set("labels", labels(mapper, ip(host.ipAddresses()),
374 host.mac().toString()));
375 payload.set("props", props(host.annotations()));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800376 addGeoLocation(host, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800377 addMetaUi(host.id().toString(), payload);
378
379 String type = (event.type() == HOST_ADDED) ? "addHost" :
380 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
381 return envelope(type, 0, payload);
382 }
383
384 // Encodes the specified host location into a JSON object.
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800385 private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800386 return mapper.createObjectNode()
387 .put("device", location.deviceId().toString())
388 .put("port", location.port().toLong());
389 }
390
391 // Encodes the specified list of labels a JSON array.
392 private ArrayNode labels(ObjectMapper mapper, String... labels) {
393 ArrayNode json = mapper.createArrayNode();
394 for (String label : labels) {
395 json.add(label);
396 }
397 return json;
398 }
399
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800400 // Returns the name of the master node for the specified device id.
401 private String master(DeviceId deviceId) {
402 NodeId master = mastershipService.getMasterFor(deviceId);
403 return master != null ? master.toString() : "";
404 }
405
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800406 // Generates an edge link from the specified host location.
407 private EdgeLink edgeLink(Host host, boolean ingress) {
408 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
409 host.location(), ingress);
410 }
411
412 // Adds meta UI information for the specified object.
413 private void addMetaUi(String id, ObjectNode payload) {
414 ObjectNode meta = metaUi.get(id);
415 if (meta != null) {
416 payload.set("metaUi", meta);
417 }
418 }
419
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800420 // Adds a geo location JSON to the specified payload object.
421 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
422 Annotations annotations = annotated.annotations();
Thomas Vachuska16582df2014-11-15 20:12:17 -0800423 if (annotations == null) {
424 return;
425 }
426
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800427 String slat = annotations.value(AnnotationKeys.LATITUDE);
428 String slng = annotations.value(AnnotationKeys.LONGITUDE);
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800429 try {
430 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
431 double lat = Double.parseDouble(slat);
432 double lng = Double.parseDouble(slng);
433 ObjectNode loc = mapper.createObjectNode()
434 .put("type", "latlng").put("lat", lat).put("lng", lng);
435 payload.set("location", loc);
436 }
437 } catch (NumberFormatException e) {
438 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
439 }
440 }
441
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800442 // Updates meta UI information for the specified object.
443 protected void updateMetaUi(ObjectNode event) {
444 ObjectNode payload = payload(event);
Simon Hunt3b9cddb2014-11-11 20:50:04 -0800445 metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800446 }
447
Thomas Vachuska47635c62014-11-22 01:21:36 -0800448 // Returns summary response.
449 protected ObjectNode summmaryMessage(long sid) {
450 Topology topology = topologyService.currentTopology();
451 return envelope("showSummary", sid,
452 json("ONOS Summary", "node",
453 new Prop("Devices", format(topology.deviceCount())),
454 new Prop("Links", format(topology.linkCount())),
455 new Prop("Hosts", format(hostService.getHostCount())),
456 new Prop("Topology SCCs", format(topology.clusterCount())),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800457 new Separator(),
458 new Prop("Intents", format(intentService.getIntentCount())),
459 new Prop("Flows", format(flowService.getFlowRuleCount())),
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800460 new Prop("Version", version)));
Thomas Vachuska47635c62014-11-22 01:21:36 -0800461 }
462
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800463 // Returns device details response.
464 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
465 Device device = deviceService.getDevice(deviceId);
466 Annotations annot = device.annotations();
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800467 String name = annot.value(AnnotationKeys.NAME);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800468 int portCount = deviceService.getPorts(deviceId).size();
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800469 int flowCount = getFlowCount(deviceId);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800470 return envelope("showDetails", sid,
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800471 json(isNullOrEmpty(name) ? deviceId.toString() : name,
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800472 device.type().toString().toLowerCase(),
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800473 new Prop("URI", deviceId.toString()),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800474 new Prop("Vendor", device.manufacturer()),
475 new Prop("H/W Version", device.hwVersion()),
476 new Prop("S/W Version", device.swVersion()),
477 new Prop("Serial Number", device.serialNumber()),
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800478 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800479 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800480 new Prop("Master", master(deviceId)),
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800481 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
482 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800483 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800484 new Prop("Ports", Integer.toString(portCount)),
485 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800486 }
487
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800488 protected int getFlowCount(DeviceId deviceId) {
489 int count = 0;
490 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
491 while (it.hasNext()) {
492 count++;
493 it.next();
494 }
495 return count;
496 }
497
Thomas Vachuska29617e52014-11-20 03:17:46 -0800498 // Counts all entries that egress on the given device links.
499 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
500 List<FlowEntry> entries = new ArrayList<>();
501 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
502 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
503 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
504 while (it.hasNext()) {
505 entries.add(it.next());
506 }
507
508 // Add all edge links to the set
509 if (hosts != null) {
510 for (Host host : hosts) {
511 links.add(new DefaultEdgeLink(host.providerId(),
512 new ConnectPoint(host.id(), P0),
513 host.location(), false));
514 }
515 }
516
517 Map<Link, Integer> counts = new HashMap<>();
518 for (Link link : links) {
519 counts.put(link, getEgressFlows(link, entries));
520 }
521 return counts;
522 }
523
524 // Counts all entries that egress on the link source port.
525 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
526 int count = 0;
527 PortNumber out = link.src().port();
528 for (FlowEntry entry : entries) {
529 TrafficTreatment treatment = entry.treatment();
530 for (Instruction instruction : treatment.instructions()) {
531 if (instruction.type() == Instruction.Type.OUTPUT &&
532 ((OutputInstruction) instruction).port().equals(out)) {
533 count++;
534 }
535 }
536 }
537 return count;
538 }
539
540
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800541 // Returns host details response.
542 protected ObjectNode hostDetails(HostId hostId, long sid) {
543 Host host = hostService.getHost(hostId);
544 Annotations annot = host.annotations();
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800545 String type = annot.value(AnnotationKeys.TYPE);
546 String name = annot.value(AnnotationKeys.NAME);
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800547 String vlan = host.vlan().toString();
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800548 return envelope("showDetails", sid,
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800549 json(isNullOrEmpty(name) ? hostId.toString() : name,
Simon Huntb33b40b2014-12-01 16:12:47 -0800550 isNullOrEmpty(type) ? "endstation" : type,
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800551 new Prop("MAC", host.mac().toString()),
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800552 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
553 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800554 new Separator(),
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800555 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
556 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800557 }
558
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800559
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800560 // Produces JSON message to trigger traffic overview visualization
561 protected ObjectNode trafficSummaryMessage(long sid) {
562 ObjectNode payload = mapper.createObjectNode();
563 ArrayNode paths = mapper.createArrayNode();
564 payload.set("paths", paths);
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800565
566 ObjectNode pathNodeN = mapper.createObjectNode();
567 ArrayNode linksNodeN = mapper.createArrayNode();
568 ArrayNode labelsN = mapper.createArrayNode();
569
570 pathNodeN.put("class", "plain").put("traffic", false);
571 pathNodeN.set("links", linksNodeN);
572 pathNodeN.set("labels", labelsN);
573 paths.add(pathNodeN);
574
575 ObjectNode pathNodeT = mapper.createObjectNode();
576 ArrayNode linksNodeT = mapper.createArrayNode();
577 ArrayNode labelsT = mapper.createArrayNode();
578
579 pathNodeT.put("class", "secondary").put("traffic", true);
580 pathNodeT.set("links", linksNodeT);
581 pathNodeT.set("labels", labelsT);
582 paths.add(pathNodeT);
583
584 for (BiLink link : consolidateLinks(linkService.getLinks())) {
585 boolean bi = link.two != null;
586 if (isInfrastructureEgress(link.one) ||
587 (bi && isInfrastructureEgress(link.two))) {
588 link.addLoad(statService.load(link.one));
589 link.addLoad(bi ? statService.load(link.two) : null);
590 if (link.hasTraffic) {
591 linksNodeT.add(compactLinkString(link.one));
592 labelsT.add(formatBytes(link.bytes));
593 } else {
594 linksNodeN.add(compactLinkString(link.one));
595 labelsN.add("");
596 }
597 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800598 }
599 return envelope("showTraffic", sid, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800600 }
601
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800602 private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
603 Map<LinkKey, BiLink> biLinks = new HashMap<>();
604 for (Link link : links) {
605 addLink(biLinks, link);
606 }
607 return biLinks.values();
608 }
609
Thomas Vachuska29617e52014-11-20 03:17:46 -0800610 // Produces JSON message to trigger flow overview visualization
611 protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
612 ObjectNode payload = mapper.createObjectNode();
613 ArrayNode paths = mapper.createArrayNode();
614 payload.set("paths", paths);
615
616 for (Device device : devices) {
617 Map<Link, Integer> counts = getFlowCounts(device.id());
618 for (Link link : counts.keySet()) {
619 addLinkFlows(link, paths, counts.get(link));
620 }
621 }
622 return envelope("showTraffic", sid, payload);
623 }
624
625 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
626 ObjectNode pathNode = mapper.createObjectNode();
627 ArrayNode linksNode = mapper.createArrayNode();
628 ArrayNode labels = mapper.createArrayNode();
629 boolean noFlows = count == null || count == 0;
630 pathNode.put("class", noFlows ? "secondary" : "primary");
631 pathNode.put("traffic", false);
632 pathNode.set("links", linksNode.add(compactLinkString(link)));
633 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
634 (count == 1 ? " flow" : " flows"))));
635 paths.add(pathNode);
636 }
637
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800638
639 // Produces JSON message to trigger traffic visualization
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800640 protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800641 ObjectNode payload = mapper.createObjectNode();
642 ArrayNode paths = mapper.createArrayNode();
643 payload.set("paths", paths);
644
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800645 // Classify links based on their traffic traffic first...
646 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
647
648 // Then separate the links into their respective classes and send them out.
649 Map<String, ObjectNode> pathNodes = new HashMap<>();
650 for (BiLink biLink : biLinks.values()) {
651 boolean hasTraffic = biLink.hasTraffic;
652 String tc = (biLink.classes + (hasTraffic ? " animated" : "")).trim();
653 ObjectNode pathNode = pathNodes.get(tc);
654 if (pathNode == null) {
655 pathNode = mapper.createObjectNode()
656 .put("class", tc).put("traffic", hasTraffic);
657 pathNode.set("links", mapper.createArrayNode());
658 pathNode.set("labels", mapper.createArrayNode());
659 pathNodes.put(tc, pathNode);
660 paths.add(pathNode);
661 }
662 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
663 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
664 }
665
666 return envelope("showTraffic", sid, payload);
667 }
668
669 // Classifies the link traffic according to the specified classes.
670 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
671 Map<LinkKey, BiLink> biLinks = new HashMap<>();
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800672 for (TrafficClass trafficClass : trafficClasses) {
673 for (Intent intent : trafficClass.intents) {
Thomas Vachuska22e34922014-11-14 00:40:55 -0800674 boolean isOptical = intent instanceof OpticalConnectivityIntent;
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800675 List<Intent> installables = intentService.getInstallableIntents(intent.key());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800676 if (installables != null) {
677 for (Intent installable : installables) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800678 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800679 if (installable instanceof PathIntent) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800680 classifyLinks(type, biLinks, trafficClass.showTraffic,
681 ((PathIntent) installable).path().links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800682 } else if (installable instanceof LinkCollectionIntent) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800683 classifyLinks(type, biLinks, trafficClass.showTraffic,
684 ((LinkCollectionIntent) installable).links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800685 } else if (installable instanceof OpticalPathIntent) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800686 classifyLinks(type, biLinks, trafficClass.showTraffic,
687 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800688 }
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800689 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800690 }
691 }
692 }
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800693 return biLinks;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800694 }
695
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800696
697 // Adds the link segments (path or tree) associated with the specified
698 // connectivity intent
699 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800700 boolean showTraffic, Iterable<Link> links) {
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800701 if (links != null) {
702 for (Link link : links) {
703 BiLink biLink = addLink(biLinks, link);
704 if (isInfrastructureEgress(link)) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800705 if (showTraffic) {
706 biLink.addLoad(statService.load(link));
707 }
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800708 biLink.addClass(type);
709 }
710 }
711 }
712 }
713
714
715 private BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
716 LinkKey key = canonicalLinkKey(link);
717 BiLink biLink = biLinks.get(key);
718 if (biLink != null) {
719 biLink.setOther(link);
720 } else {
721 biLink = new BiLink(key, link);
722 biLinks.put(key, biLink);
723 }
724 return biLink;
725 }
726
727
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800728 // Adds the link segments (path or tree) associated with the specified
729 // connectivity intent
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800730 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
731 Iterable<Link> links) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800732 ObjectNode pathNode = mapper.createObjectNode();
733 ArrayNode linksNode = mapper.createArrayNode();
734
Thomas Vachuska22e34922014-11-14 00:40:55 -0800735 if (links != null) {
736 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800737 boolean hasTraffic = false;
Thomas Vachuska22e34922014-11-14 00:40:55 -0800738 for (Link link : links) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800739 if (isInfrastructureEgress(link)) {
740 linksNode.add(compactLinkString(link));
741 Load load = statService.load(link);
742 String label = "";
743 if (load.rate() > 0) {
744 hasTraffic = true;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800745 label = formatBytes(load.latest());
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800746 }
747 labels.add(label);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800748 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800749 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800750 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
Thomas Vachuska22e34922014-11-14 00:40:55 -0800751 pathNode.put("traffic", hasTraffic);
752 pathNode.set("links", linksNode);
753 pathNode.set("labels", labels);
754 paths.add(pathNode);
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800755 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800756 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800757
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800758 // Poor-mans formatting to get the labels with byte counts looking nice.
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800759 private String formatBytes(long bytes) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800760 String unit;
761 double value;
762 if (bytes > GB) {
763 value = bytes / GB;
764 unit = GB_UNIT;
765 } else if (bytes > MB) {
766 value = bytes / MB;
767 unit = MB_UNIT;
768 } else if (bytes > KB) {
769 value = bytes / KB;
770 unit = KB_UNIT;
771 } else {
772 value = bytes;
773 unit = B_UNIT;
774 }
775 DecimalFormat format = new DecimalFormat("#,###.##");
Thomas Vachuska29617e52014-11-20 03:17:46 -0800776 return format.format(value) + " " + unit;
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800777 }
778
Thomas Vachuska47635c62014-11-22 01:21:36 -0800779 // Formats the given number into a string.
780 private String format(Number number) {
781 DecimalFormat format = new DecimalFormat("#,###");
782 return format.format(number);
783 }
784
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800785 private boolean isInfrastructureEgress(Link link) {
786 return link.src().elementId() instanceof DeviceId;
787 }
788
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800789 // Produces compact string representation of a link.
790 private static String compactLinkString(Link link) {
791 return String.format(COMPACT, link.src().elementId(), link.src().port(),
792 link.dst().elementId(), link.dst().port());
793 }
794
795 // Produces JSON property details.
796 private ObjectNode json(String id, String type, Prop... props) {
797 ObjectMapper mapper = new ObjectMapper();
798 ObjectNode result = mapper.createObjectNode()
799 .put("id", id).put("type", type);
800 ObjectNode pnode = mapper.createObjectNode();
801 ArrayNode porder = mapper.createArrayNode();
802 for (Prop p : props) {
803 porder.add(p.key);
804 pnode.put(p.key, p.value);
805 }
806 result.set("propOrder", porder);
807 result.set("props", pnode);
808 return result;
809 }
810
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800811 // Produces canonical link key, i.e. one that will match link and its inverse.
812 private LinkKey canonicalLinkKey(Link link) {
813 String sn = link.src().elementId().toString();
814 String dn = link.dst().elementId().toString();
815 return sn.compareTo(dn) < 0 ?
816 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
817 }
818
819 // Representation of link and its inverse and any traffic data.
820 private class BiLink {
821 public final LinkKey key;
822 public final Link one;
823 public Link two;
824 public boolean hasTraffic = false;
825 public long bytes = 0;
826 public String classes = "";
827
828 BiLink(LinkKey key, Link link) {
829 this.key = key;
830 this.one = link;
831 }
832
833 void setOther(Link link) {
834 this.two = link;
835 }
836
837 void addLoad(Load load) {
838 if (load != null) {
839 this.hasTraffic = hasTraffic || load.rate() > 0;
840 this.bytes += load.latest();
841 }
842 }
843
844 void addClass(String trafficClass) {
845 classes = classes + " " + trafficClass;
846 }
847 }
848
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800849 // Auxiliary key/value carrier.
850 private class Prop {
851 public final String key;
852 public final String value;
853
854 protected Prop(String key, String value) {
855 this.key = key;
856 this.value = value;
857 }
858 }
859
860 // Auxiliary properties separator
861 private class Separator extends Prop {
862 protected Separator() {
863 super("-", "");
864 }
865 }
866
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800867 // Auxiliary carrier of data for requesting traffic message.
868 protected class TrafficClass {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800869 public final boolean showTraffic;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800870 public final String type;
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800871 public final Iterable<Intent> intents;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800872
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800873 TrafficClass(String type, Iterable<Intent> intents) {
874 this(type, intents, false);
875 }
876
877 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800878 this.type = type;
879 this.intents = intents;
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800880 this.showTraffic = showTraffic;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800881 }
882 }
883
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800884}