blob: e4f0aedf0f919a936c2590e41ae67c7a2c80b48d [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 Vachuska5dd52f72014-11-28 19:27:45 -080039import org.onlab.onos.net.LinkKey;
Thomas Vachuska29617e52014-11-20 03:17:46 -080040import org.onlab.onos.net.PortNumber;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080041import org.onlab.onos.net.device.DeviceEvent;
42import org.onlab.onos.net.device.DeviceService;
Thomas Vachuska87d3db12014-11-20 01:18:50 -080043import org.onlab.onos.net.flow.FlowEntry;
44import org.onlab.onos.net.flow.FlowRuleService;
Thomas Vachuska29617e52014-11-20 03:17:46 -080045import org.onlab.onos.net.flow.TrafficTreatment;
46import org.onlab.onos.net.flow.instructions.Instruction;
47import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080048import org.onlab.onos.net.host.HostEvent;
49import org.onlab.onos.net.host.HostService;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080050import org.onlab.onos.net.intent.Intent;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080051import org.onlab.onos.net.intent.IntentService;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080052import org.onlab.onos.net.intent.LinkCollectionIntent;
Thomas Vachuska22e34922014-11-14 00:40:55 -080053import org.onlab.onos.net.intent.OpticalConnectivityIntent;
Thomas Vachuskadea4cb32014-11-14 12:14:30 -080054import org.onlab.onos.net.intent.OpticalPathIntent;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080055import org.onlab.onos.net.intent.PathIntent;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080056import org.onlab.onos.net.link.LinkEvent;
57import org.onlab.onos.net.link.LinkService;
58import org.onlab.onos.net.provider.ProviderId;
Thomas Vachuska7c27ad72014-11-14 16:20:10 -080059import org.onlab.onos.net.statistic.Load;
60import org.onlab.onos.net.statistic.StatisticService;
Thomas Vachuska47635c62014-11-22 01:21:36 -080061import org.onlab.onos.net.topology.Topology;
62import org.onlab.onos.net.topology.TopologyService;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080063import org.onlab.osgi.ServiceDirectory;
64import org.onlab.packet.IpAddress;
Thomas Vachuska0f6baee2014-11-11 15:02:32 -080065import org.slf4j.Logger;
66import org.slf4j.LoggerFactory;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080067
Thomas Vachuska20322ff2014-11-19 16:22:25 -080068import java.text.DecimalFormat;
Thomas Vachuska29617e52014-11-20 03:17:46 -080069import java.util.ArrayList;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -080070import java.util.Collection;
Thomas Vachuska998b1412014-11-23 02:42:49 -080071import java.util.Collections;
Thomas Vachuska29617e52014-11-20 03:17:46 -080072import java.util.HashMap;
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -080073import java.util.HashSet;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080074import java.util.Iterator;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080075import java.util.List;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080076import java.util.Map;
77import java.util.Set;
78import java.util.concurrent.ConcurrentHashMap;
79
80import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska89543292014-11-19 11:28:33 -080081import static com.google.common.base.Strings.isNullOrEmpty;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080082import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
83import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
84import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -080085import static org.onlab.onos.net.DeviceId.deviceId;
86import static org.onlab.onos.net.HostId.hostId;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -080087import static org.onlab.onos.net.LinkKey.linkKey;
Thomas Vachuska29617e52014-11-20 03:17:46 -080088import static org.onlab.onos.net.PortNumber.P0;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080089import static org.onlab.onos.net.PortNumber.portNumber;
90import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
91import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
92import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
93import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
94import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
95import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
96
97/**
98 * Facility for creating messages bound for the topology viewer.
99 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800100public abstract class TopologyViewMessages {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800101
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800102 protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class);
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800103
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800104 private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
105 private static final String COMPACT = "%s/%s-%s/%s";
106
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800107 private static final double KB = 1024;
108 private static final double MB = 1024 * KB;
109 private static final double GB = 1024 * MB;
110
111 private static final String GB_UNIT = "GB";
112 private static final String MB_UNIT = "MB";
113 private static final String KB_UNIT = "KB";
114 private static final String B_UNIT = "B";
115
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800116 protected final ServiceDirectory directory;
117 protected final ClusterService clusterService;
118 protected final DeviceService deviceService;
119 protected final LinkService linkService;
120 protected final HostService hostService;
121 protected final MastershipService mastershipService;
122 protected final IntentService intentService;
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800123 protected final FlowRuleService flowService;
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800124 protected final StatisticService statService;
Thomas Vachuska47635c62014-11-22 01:21:36 -0800125 protected final TopologyService topologyService;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800126
127 protected final ObjectMapper mapper = new ObjectMapper();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800128 private final String version;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800129
130 // TODO: extract into an external & durable state; good enough for now and demo
131 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
132
133 /**
Thomas Vachuska998b1412014-11-23 02:42:49 -0800134 * Returns read-only view of the meta-ui information.
135 *
136 * @return map of id to meta-ui mementos
137 */
138 static Map<String, ObjectNode> getMetaUi() {
139 return Collections.unmodifiableMap(metaUi);
140 }
141
142 /**
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800143 * Creates a messaging facility for creating messages for topology viewer.
144 *
145 * @param directory service directory
146 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800147 protected TopologyViewMessages(ServiceDirectory directory) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800148 this.directory = checkNotNull(directory, "Directory cannot be null");
149 clusterService = directory.get(ClusterService.class);
150 deviceService = directory.get(DeviceService.class);
151 linkService = directory.get(LinkService.class);
152 hostService = directory.get(HostService.class);
153 mastershipService = directory.get(MastershipService.class);
154 intentService = directory.get(IntentService.class);
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800155 flowService = directory.get(FlowRuleService.class);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800156 statService = directory.get(StatisticService.class);
Thomas Vachuska47635c62014-11-22 01:21:36 -0800157 topologyService = directory.get(TopologyService.class);
158
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800159 String ver = directory.get(CoreService.class).version().toString();
160 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800161 }
162
163 // Retrieves the payload from the specified event.
164 protected ObjectNode payload(ObjectNode event) {
165 return (ObjectNode) event.path("payload");
166 }
167
168 // Returns the specified node property as a number
169 protected long number(ObjectNode node, String name) {
170 return node.path(name).asLong();
171 }
172
173 // Returns the specified node property as a string.
174 protected String string(ObjectNode node, String name) {
175 return node.path(name).asText();
176 }
177
178 // Returns the specified node property as a string.
179 protected String string(ObjectNode node, String name, String defaultValue) {
180 return node.path(name).asText(defaultValue);
181 }
182
183 // Returns the specified set of IP addresses as a string.
184 private String ip(Set<IpAddress> ipAddresses) {
185 Iterator<IpAddress> it = ipAddresses.iterator();
186 return it.hasNext() ? it.next().toString() : "unknown";
187 }
188
189 // Produces JSON structure from annotations.
190 private JsonNode props(Annotations annotations) {
191 ObjectNode props = mapper.createObjectNode();
Thomas Vachuska16582df2014-11-15 20:12:17 -0800192 if (annotations != null) {
193 for (String key : annotations.keys()) {
194 props.put(key, annotations.value(key));
195 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800196 }
197 return props;
198 }
199
200 // Produces an informational log message event bound to the client.
201 protected ObjectNode info(long id, String message) {
202 return message("info", id, message);
203 }
204
205 // Produces a warning log message event bound to the client.
206 protected ObjectNode warning(long id, String message) {
207 return message("warning", id, message);
208 }
209
210 // Produces an error log message event bound to the client.
211 protected ObjectNode error(long id, String message) {
212 return message("error", id, message);
213 }
214
215 // Produces a log message event bound to the client.
216 private ObjectNode message(String severity, long id, String message) {
217 return envelope("message", id,
218 mapper.createObjectNode()
219 .put("severity", severity)
220 .put("message", message));
221 }
222
223 // Puts the payload into an envelope and returns it.
224 protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
225 ObjectNode event = mapper.createObjectNode();
226 event.put("event", type);
227 if (sid > 0) {
228 event.put("sid", sid);
229 }
230 event.set("payload", payload);
231 return event;
232 }
233
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800234 // Produces a set of all hosts listed in the specified JSON array.
235 protected Set<Host> getHosts(ArrayNode array) {
236 Set<Host> hosts = new HashSet<>();
237 if (array != null) {
238 for (JsonNode node : array) {
239 try {
240 addHost(hosts, hostId(node.asText()));
241 } catch (IllegalArgumentException e) {
242 log.debug("Skipping ID {}", node.asText());
243 }
244 }
245 }
246 return hosts;
247 }
248
249 // Adds the specified host to the set of hosts.
250 private void addHost(Set<Host> hosts, HostId hostId) {
251 Host host = hostService.getHost(hostId);
252 if (host != null) {
253 hosts.add(host);
254 }
255 }
256
257
258 // Produces a set of all devices listed in the specified JSON array.
259 protected Set<Device> getDevices(ArrayNode array) {
260 Set<Device> devices = new HashSet<>();
261 if (array != null) {
262 for (JsonNode node : array) {
263 try {
264 addDevice(devices, deviceId(node.asText()));
265 } catch (IllegalArgumentException e) {
266 log.debug("Skipping ID {}", node.asText());
267 }
268 }
269 }
270 return devices;
271 }
272
273 private void addDevice(Set<Device> devices, DeviceId deviceId) {
274 Device device = deviceService.getDevice(deviceId);
275 if (device != null) {
276 devices.add(device);
277 }
278 }
279
280 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
281 try {
282 addHost(hosts, hostId(hover));
283 } catch (IllegalArgumentException e) {
284 try {
285 addDevice(devices, deviceId(hover));
286 } catch (IllegalArgumentException ne) {
287 log.debug("Skipping ID {}", hover);
288 }
289 }
290 }
291
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800292 // Produces a cluster instance message to the client.
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800293 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800294 ControllerNode node = event.subject();
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800295 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800296 ObjectNode payload = mapper.createObjectNode()
297 .put("id", node.id().toString())
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800298 .put("ip", node.ip().toString())
Thomas Vachuskae4cebaf2014-11-15 18:49:34 -0800299 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800300 .put("uiAttached", event.subject().equals(clusterService.getLocalNode()))
301 .put("switches", switchCount);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800302
303 ArrayNode labels = mapper.createArrayNode();
304 labels.add(node.id().toString());
305 labels.add(node.ip().toString());
306
307 // Add labels, props and stuff the payload into envelope.
308 payload.set("labels", labels);
309 addMetaUi(node.id().toString(), payload);
310
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800311 String type = messageType != null ? messageType :
312 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
313 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
Thomas Vachuskad1c25cd2014-11-29 17:47:58 -0800314 "addInstance")));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800315 return envelope(type, 0, payload);
316 }
317
318 // Produces a device event message to the client.
319 protected ObjectNode deviceMessage(DeviceEvent event) {
320 Device device = event.subject();
321 ObjectNode payload = mapper.createObjectNode()
322 .put("id", device.id().toString())
323 .put("type", device.type().toString().toLowerCase())
324 .put("online", deviceService.isAvailable(device.id()))
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800325 .put("master", master(device.id()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800326
327 // Generate labels: id, chassis id, no-label, optional-name
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800328 String name = device.annotations().value("name");
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800329 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800330 labels.add("");
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800331 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800332 labels.add(device.id().toString());
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800333
334 // Add labels, props and stuff the payload into envelope.
335 payload.set("labels", labels);
336 payload.set("props", props(device.annotations()));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800337 addGeoLocation(device, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800338 addMetaUi(device.id().toString(), payload);
339
340 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
341 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
342 return envelope(type, 0, payload);
343 }
344
345 // Produces a link event message to the client.
346 protected ObjectNode linkMessage(LinkEvent event) {
347 Link link = event.subject();
348 ObjectNode payload = mapper.createObjectNode()
349 .put("id", compactLinkString(link))
350 .put("type", link.type().toString().toLowerCase())
Thomas Vachuskae4cebaf2014-11-15 18:49:34 -0800351 .put("online", link.state() == Link.State.ACTIVE)
Thomas Vachuskacd2920c2014-11-19 14:49:55 -0800352 .put("linkWidth", 1.2)
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800353 .put("src", link.src().deviceId().toString())
354 .put("srcPort", link.src().port().toString())
355 .put("dst", link.dst().deviceId().toString())
356 .put("dstPort", link.dst().port().toString());
357 String type = (event.type() == LINK_ADDED) ? "addLink" :
358 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
359 return envelope(type, 0, payload);
360 }
361
362 // Produces a host event message to the client.
363 protected ObjectNode hostMessage(HostEvent event) {
364 Host host = event.subject();
Thomas Vachuska89543292014-11-19 11:28:33 -0800365 String hostType = host.annotations().value("type");
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800366 ObjectNode payload = mapper.createObjectNode()
367 .put("id", host.id().toString())
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800368 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800369 .put("ingress", compactLinkString(edgeLink(host, true)))
370 .put("egress", compactLinkString(edgeLink(host, false)));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800371 payload.set("cp", hostConnect(mapper, host.location()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800372 payload.set("labels", labels(mapper, ip(host.ipAddresses()),
373 host.mac().toString()));
374 payload.set("props", props(host.annotations()));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800375 addGeoLocation(host, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800376 addMetaUi(host.id().toString(), payload);
377
378 String type = (event.type() == HOST_ADDED) ? "addHost" :
379 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
380 return envelope(type, 0, payload);
381 }
382
383 // Encodes the specified host location into a JSON object.
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800384 private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800385 return mapper.createObjectNode()
386 .put("device", location.deviceId().toString())
387 .put("port", location.port().toLong());
388 }
389
390 // Encodes the specified list of labels a JSON array.
391 private ArrayNode labels(ObjectMapper mapper, String... labels) {
392 ArrayNode json = mapper.createArrayNode();
393 for (String label : labels) {
394 json.add(label);
395 }
396 return json;
397 }
398
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800399 // Returns the name of the master node for the specified device id.
400 private String master(DeviceId deviceId) {
401 NodeId master = mastershipService.getMasterFor(deviceId);
402 return master != null ? master.toString() : "";
403 }
404
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800405 // Generates an edge link from the specified host location.
406 private EdgeLink edgeLink(Host host, boolean ingress) {
407 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
408 host.location(), ingress);
409 }
410
411 // Adds meta UI information for the specified object.
412 private void addMetaUi(String id, ObjectNode payload) {
413 ObjectNode meta = metaUi.get(id);
414 if (meta != null) {
415 payload.set("metaUi", meta);
416 }
417 }
418
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800419 // Adds a geo location JSON to the specified payload object.
420 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
421 Annotations annotations = annotated.annotations();
Thomas Vachuska16582df2014-11-15 20:12:17 -0800422 if (annotations == null) {
423 return;
424 }
425
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800426 String slat = annotations.value("latitude");
427 String slng = annotations.value("longitude");
428 try {
429 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
430 double lat = Double.parseDouble(slat);
431 double lng = Double.parseDouble(slng);
432 ObjectNode loc = mapper.createObjectNode()
433 .put("type", "latlng").put("lat", lat).put("lng", lng);
434 payload.set("location", loc);
435 }
436 } catch (NumberFormatException e) {
437 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
438 }
439 }
440
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800441 // Updates meta UI information for the specified object.
442 protected void updateMetaUi(ObjectNode event) {
443 ObjectNode payload = payload(event);
Simon Hunt3b9cddb2014-11-11 20:50:04 -0800444 metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800445 }
446
Thomas Vachuska47635c62014-11-22 01:21:36 -0800447 // Returns summary response.
448 protected ObjectNode summmaryMessage(long sid) {
449 Topology topology = topologyService.currentTopology();
450 return envelope("showSummary", sid,
451 json("ONOS Summary", "node",
452 new Prop("Devices", format(topology.deviceCount())),
453 new Prop("Links", format(topology.linkCount())),
454 new Prop("Hosts", format(hostService.getHostCount())),
455 new Prop("Topology SCCs", format(topology.clusterCount())),
456 new Prop("Paths", format(topology.pathCount())),
457 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();
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800467 String name = annot.value("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()),
478 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800479 new Prop("Master", master(deviceId)),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800480 new Prop("Latitude", annot.value("latitude")),
481 new Prop("Longitude", annot.value("longitude")),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800482 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800483 new Prop("Ports", Integer.toString(portCount)),
484 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800485 }
486
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800487 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
Thomas Vachuska29617e52014-11-20 03:17:46 -0800497 // Counts all entries that egress on the given device links.
498 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
499 List<FlowEntry> entries = new ArrayList<>();
500 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
501 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
502 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
503 while (it.hasNext()) {
504 entries.add(it.next());
505 }
506
507 // Add all edge links to the set
508 if (hosts != null) {
509 for (Host host : hosts) {
510 links.add(new DefaultEdgeLink(host.providerId(),
511 new ConnectPoint(host.id(), P0),
512 host.location(), false));
513 }
514 }
515
516 Map<Link, Integer> counts = new HashMap<>();
517 for (Link link : links) {
518 counts.put(link, getEgressFlows(link, entries));
519 }
520 return counts;
521 }
522
523 // Counts all entries that egress on the link source port.
524 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
525 int count = 0;
526 PortNumber out = link.src().port();
527 for (FlowEntry entry : entries) {
528 TrafficTreatment treatment = entry.treatment();
529 for (Instruction instruction : treatment.instructions()) {
530 if (instruction.type() == Instruction.Type.OUTPUT &&
531 ((OutputInstruction) instruction).port().equals(out)) {
532 count++;
533 }
534 }
535 }
536 return count;
537 }
538
539
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800540 // Returns host details response.
541 protected ObjectNode hostDetails(HostId hostId, long sid) {
542 Host host = hostService.getHost(hostId);
543 Annotations annot = host.annotations();
Thomas Vachuska89543292014-11-19 11:28:33 -0800544 String type = annot.value("type");
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800545 String name = annot.value("name");
546 String vlan = host.vlan().toString();
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800547 return envelope("showDetails", sid,
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800548 json(isNullOrEmpty(name) ? hostId.toString() : name,
549 isNullOrEmpty(type) ? "host" : type,
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800550 new Prop("MAC", host.mac().toString()),
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800551 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
552 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800553 new Separator(),
554 new Prop("Latitude", annot.value("latitude")),
555 new Prop("Longitude", annot.value("longitude"))));
556 }
557
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800558
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800559 // Produces JSON message to trigger traffic overview visualization
560 protected ObjectNode trafficSummaryMessage(long sid) {
561 ObjectNode payload = mapper.createObjectNode();
562 ArrayNode paths = mapper.createArrayNode();
563 payload.set("paths", paths);
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800564
565 ObjectNode pathNodeN = mapper.createObjectNode();
566 ArrayNode linksNodeN = mapper.createArrayNode();
567 ArrayNode labelsN = mapper.createArrayNode();
568
569 pathNodeN.put("class", "plain").put("traffic", false);
570 pathNodeN.set("links", linksNodeN);
571 pathNodeN.set("labels", labelsN);
572 paths.add(pathNodeN);
573
574 ObjectNode pathNodeT = mapper.createObjectNode();
575 ArrayNode linksNodeT = mapper.createArrayNode();
576 ArrayNode labelsT = mapper.createArrayNode();
577
578 pathNodeT.put("class", "secondary").put("traffic", true);
579 pathNodeT.set("links", linksNodeT);
580 pathNodeT.set("labels", labelsT);
581 paths.add(pathNodeT);
582
583 for (BiLink link : consolidateLinks(linkService.getLinks())) {
584 boolean bi = link.two != null;
585 if (isInfrastructureEgress(link.one) ||
586 (bi && isInfrastructureEgress(link.two))) {
587 link.addLoad(statService.load(link.one));
588 link.addLoad(bi ? statService.load(link.two) : null);
589 if (link.hasTraffic) {
590 linksNodeT.add(compactLinkString(link.one));
591 labelsT.add(formatBytes(link.bytes));
592 } else {
593 linksNodeN.add(compactLinkString(link.one));
594 labelsN.add("");
595 }
596 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800597 }
598 return envelope("showTraffic", sid, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800599 }
600
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800601 private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
602 Map<LinkKey, BiLink> biLinks = new HashMap<>();
603 for (Link link : links) {
604 addLink(biLinks, link);
605 }
606 return biLinks.values();
607 }
608
Thomas Vachuska29617e52014-11-20 03:17:46 -0800609 // Produces JSON message to trigger flow overview visualization
610 protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
611 ObjectNode payload = mapper.createObjectNode();
612 ArrayNode paths = mapper.createArrayNode();
613 payload.set("paths", paths);
614
615 for (Device device : devices) {
616 Map<Link, Integer> counts = getFlowCounts(device.id());
617 for (Link link : counts.keySet()) {
618 addLinkFlows(link, paths, counts.get(link));
619 }
620 }
621 return envelope("showTraffic", sid, payload);
622 }
623
624 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
625 ObjectNode pathNode = mapper.createObjectNode();
626 ArrayNode linksNode = mapper.createArrayNode();
627 ArrayNode labels = mapper.createArrayNode();
628 boolean noFlows = count == null || count == 0;
629 pathNode.put("class", noFlows ? "secondary" : "primary");
630 pathNode.put("traffic", false);
631 pathNode.set("links", linksNode.add(compactLinkString(link)));
632 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
633 (count == 1 ? " flow" : " flows"))));
634 paths.add(pathNode);
635 }
636
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800637
638 // Produces JSON message to trigger traffic visualization
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800639 protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800640 ObjectNode payload = mapper.createObjectNode();
641 ArrayNode paths = mapper.createArrayNode();
642 payload.set("paths", paths);
643
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800644 // Classify links based on their traffic traffic first...
645 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
646
647 // Then separate the links into their respective classes and send them out.
648 Map<String, ObjectNode> pathNodes = new HashMap<>();
649 for (BiLink biLink : biLinks.values()) {
650 boolean hasTraffic = biLink.hasTraffic;
651 String tc = (biLink.classes + (hasTraffic ? " animated" : "")).trim();
652 ObjectNode pathNode = pathNodes.get(tc);
653 if (pathNode == null) {
654 pathNode = mapper.createObjectNode()
655 .put("class", tc).put("traffic", hasTraffic);
656 pathNode.set("links", mapper.createArrayNode());
657 pathNode.set("labels", mapper.createArrayNode());
658 pathNodes.put(tc, pathNode);
659 paths.add(pathNode);
660 }
661 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
662 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
663 }
664
665 return envelope("showTraffic", sid, payload);
666 }
667
668 // Classifies the link traffic according to the specified classes.
669 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
670 Map<LinkKey, BiLink> biLinks = new HashMap<>();
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800671 for (TrafficClass trafficClass : trafficClasses) {
672 for (Intent intent : trafficClass.intents) {
Thomas Vachuska22e34922014-11-14 00:40:55 -0800673 boolean isOptical = intent instanceof OpticalConnectivityIntent;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800674 List<Intent> installables = intentService.getInstallableIntents(intent.id());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800675 if (installables != null) {
676 for (Intent installable : installables) {
677 String cls = isOptical ? trafficClass.type + " optical" : trafficClass.type;
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800678 if (installable instanceof PathIntent) {
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800679 classifyLinks(cls, biLinks, ((PathIntent) installable).path().links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800680 } else if (installable instanceof LinkCollectionIntent) {
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800681 classifyLinks(cls, biLinks, ((LinkCollectionIntent) installable).links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800682 } else if (installable instanceof OpticalPathIntent) {
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800683 classifyLinks(cls, biLinks, ((OpticalPathIntent) installable).path().links());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800684 }
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800685 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800686 }
687 }
688 }
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800689 return biLinks;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800690 }
691
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800692
693 // Adds the link segments (path or tree) associated with the specified
694 // connectivity intent
695 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
696 Iterable<Link> links) {
697 if (links != null) {
698 for (Link link : links) {
699 BiLink biLink = addLink(biLinks, link);
700 if (isInfrastructureEgress(link)) {
701 biLink.addLoad(statService.load(link));
702 biLink.addClass(type);
703 }
704 }
705 }
706 }
707
708
709 private BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
710 LinkKey key = canonicalLinkKey(link);
711 BiLink biLink = biLinks.get(key);
712 if (biLink != null) {
713 biLink.setOther(link);
714 } else {
715 biLink = new BiLink(key, link);
716 biLinks.put(key, biLink);
717 }
718 return biLink;
719 }
720
721
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800722 // Adds the link segments (path or tree) associated with the specified
723 // connectivity intent
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800724 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
725 Iterable<Link> links) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800726 ObjectNode pathNode = mapper.createObjectNode();
727 ArrayNode linksNode = mapper.createArrayNode();
728
Thomas Vachuska22e34922014-11-14 00:40:55 -0800729 if (links != null) {
730 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800731 boolean hasTraffic = false;
Thomas Vachuska22e34922014-11-14 00:40:55 -0800732 for (Link link : links) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800733 if (isInfrastructureEgress(link)) {
734 linksNode.add(compactLinkString(link));
735 Load load = statService.load(link);
736 String label = "";
737 if (load.rate() > 0) {
738 hasTraffic = true;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800739 label = formatBytes(load.latest());
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800740 }
741 labels.add(label);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800742 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800743 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800744 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
Thomas Vachuska22e34922014-11-14 00:40:55 -0800745 pathNode.put("traffic", hasTraffic);
746 pathNode.set("links", linksNode);
747 pathNode.set("labels", labels);
748 paths.add(pathNode);
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800749 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800750 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800751
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800752 // Poor-mans formatting to get the labels with byte counts looking nice.
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800753 private String formatBytes(long bytes) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800754 String unit;
755 double value;
756 if (bytes > GB) {
757 value = bytes / GB;
758 unit = GB_UNIT;
759 } else if (bytes > MB) {
760 value = bytes / MB;
761 unit = MB_UNIT;
762 } else if (bytes > KB) {
763 value = bytes / KB;
764 unit = KB_UNIT;
765 } else {
766 value = bytes;
767 unit = B_UNIT;
768 }
769 DecimalFormat format = new DecimalFormat("#,###.##");
Thomas Vachuska29617e52014-11-20 03:17:46 -0800770 return format.format(value) + " " + unit;
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800771 }
772
Thomas Vachuska47635c62014-11-22 01:21:36 -0800773 // Formats the given number into a string.
774 private String format(Number number) {
775 DecimalFormat format = new DecimalFormat("#,###");
776 return format.format(number);
777 }
778
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800779 private boolean isInfrastructureEgress(Link link) {
780 return link.src().elementId() instanceof DeviceId;
781 }
782
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800783 // Produces compact string representation of a link.
784 private static String compactLinkString(Link link) {
785 return String.format(COMPACT, link.src().elementId(), link.src().port(),
786 link.dst().elementId(), link.dst().port());
787 }
788
789 // Produces JSON property details.
790 private ObjectNode json(String id, String type, Prop... props) {
791 ObjectMapper mapper = new ObjectMapper();
792 ObjectNode result = mapper.createObjectNode()
793 .put("id", id).put("type", type);
794 ObjectNode pnode = mapper.createObjectNode();
795 ArrayNode porder = mapper.createArrayNode();
796 for (Prop p : props) {
797 porder.add(p.key);
798 pnode.put(p.key, p.value);
799 }
800 result.set("propOrder", porder);
801 result.set("props", pnode);
802 return result;
803 }
804
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800805 // Produces canonical link key, i.e. one that will match link and its inverse.
806 private LinkKey canonicalLinkKey(Link link) {
807 String sn = link.src().elementId().toString();
808 String dn = link.dst().elementId().toString();
809 return sn.compareTo(dn) < 0 ?
810 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
811 }
812
813 // Representation of link and its inverse and any traffic data.
814 private class BiLink {
815 public final LinkKey key;
816 public final Link one;
817 public Link two;
818 public boolean hasTraffic = false;
819 public long bytes = 0;
820 public String classes = "";
821
822 BiLink(LinkKey key, Link link) {
823 this.key = key;
824 this.one = link;
825 }
826
827 void setOther(Link link) {
828 this.two = link;
829 }
830
831 void addLoad(Load load) {
832 if (load != null) {
833 this.hasTraffic = hasTraffic || load.rate() > 0;
834 this.bytes += load.latest();
835 }
836 }
837
838 void addClass(String trafficClass) {
839 classes = classes + " " + trafficClass;
840 }
841 }
842
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800843 // Auxiliary key/value carrier.
844 private class Prop {
845 public final String key;
846 public final String value;
847
848 protected Prop(String key, String value) {
849 this.key = key;
850 this.value = value;
851 }
852 }
853
854 // Auxiliary properties separator
855 private class Separator extends Prop {
856 protected Separator() {
857 super("-", "");
858 }
859 }
860
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800861 // Auxiliary carrier of data for requesting traffic message.
862 protected class TrafficClass {
863 public final String type;
864 public final Set<Intent> intents;
865
866 TrafficClass(String type, Set<Intent> intents) {
867 this.type = type;
868 this.intents = intents;
869 }
870 }
871
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800872}