blob: 7981dabba28e4438644c391515b663dc3bdebb38 [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()),
Thomas Vachuska82041f52014-11-30 22:14:02 -0800478 new Prop("Protocol", annot.value("protocol")),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800479 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800480 new Prop("Master", master(deviceId)),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800481 new Prop("Latitude", annot.value("latitude")),
482 new Prop("Longitude", annot.value("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();
Thomas Vachuska89543292014-11-19 11:28:33 -0800545 String type = annot.value("type");
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800546 String name = annot.value("name");
547 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(),
555 new Prop("Latitude", annot.value("latitude")),
556 new Prop("Longitude", annot.value("longitude"))));
557 }
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;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800675 List<Intent> installables = intentService.getInstallableIntents(intent.id());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800676 if (installables != null) {
677 for (Intent installable : installables) {
678 String cls = isOptical ? trafficClass.type + " optical" : trafficClass.type;
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800679 if (installable instanceof PathIntent) {
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800680 classifyLinks(cls, biLinks, ((PathIntent) installable).path().links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800681 } else if (installable instanceof LinkCollectionIntent) {
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800682 classifyLinks(cls, biLinks, ((LinkCollectionIntent) installable).links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800683 } else if (installable instanceof OpticalPathIntent) {
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800684 classifyLinks(cls, biLinks, ((OpticalPathIntent) installable).path().links());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800685 }
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800686 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800687 }
688 }
689 }
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800690 return biLinks;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800691 }
692
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800693
694 // Adds the link segments (path or tree) associated with the specified
695 // connectivity intent
696 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
697 Iterable<Link> links) {
698 if (links != null) {
699 for (Link link : links) {
700 BiLink biLink = addLink(biLinks, link);
701 if (isInfrastructureEgress(link)) {
702 biLink.addLoad(statService.load(link));
703 biLink.addClass(type);
704 }
705 }
706 }
707 }
708
709
710 private BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
711 LinkKey key = canonicalLinkKey(link);
712 BiLink biLink = biLinks.get(key);
713 if (biLink != null) {
714 biLink.setOther(link);
715 } else {
716 biLink = new BiLink(key, link);
717 biLinks.put(key, biLink);
718 }
719 return biLink;
720 }
721
722
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800723 // Adds the link segments (path or tree) associated with the specified
724 // connectivity intent
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800725 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
726 Iterable<Link> links) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800727 ObjectNode pathNode = mapper.createObjectNode();
728 ArrayNode linksNode = mapper.createArrayNode();
729
Thomas Vachuska22e34922014-11-14 00:40:55 -0800730 if (links != null) {
731 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800732 boolean hasTraffic = false;
Thomas Vachuska22e34922014-11-14 00:40:55 -0800733 for (Link link : links) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800734 if (isInfrastructureEgress(link)) {
735 linksNode.add(compactLinkString(link));
736 Load load = statService.load(link);
737 String label = "";
738 if (load.rate() > 0) {
739 hasTraffic = true;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800740 label = formatBytes(load.latest());
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800741 }
742 labels.add(label);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800743 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800744 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800745 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
Thomas Vachuska22e34922014-11-14 00:40:55 -0800746 pathNode.put("traffic", hasTraffic);
747 pathNode.set("links", linksNode);
748 pathNode.set("labels", labels);
749 paths.add(pathNode);
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800750 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800751 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800752
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800753 // Poor-mans formatting to get the labels with byte counts looking nice.
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800754 private String formatBytes(long bytes) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800755 String unit;
756 double value;
757 if (bytes > GB) {
758 value = bytes / GB;
759 unit = GB_UNIT;
760 } else if (bytes > MB) {
761 value = bytes / MB;
762 unit = MB_UNIT;
763 } else if (bytes > KB) {
764 value = bytes / KB;
765 unit = KB_UNIT;
766 } else {
767 value = bytes;
768 unit = B_UNIT;
769 }
770 DecimalFormat format = new DecimalFormat("#,###.##");
Thomas Vachuska29617e52014-11-20 03:17:46 -0800771 return format.format(value) + " " + unit;
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800772 }
773
Thomas Vachuska47635c62014-11-22 01:21:36 -0800774 // Formats the given number into a string.
775 private String format(Number number) {
776 DecimalFormat format = new DecimalFormat("#,###");
777 return format.format(number);
778 }
779
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800780 private boolean isInfrastructureEgress(Link link) {
781 return link.src().elementId() instanceof DeviceId;
782 }
783
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800784 // Produces compact string representation of a link.
785 private static String compactLinkString(Link link) {
786 return String.format(COMPACT, link.src().elementId(), link.src().port(),
787 link.dst().elementId(), link.dst().port());
788 }
789
790 // Produces JSON property details.
791 private ObjectNode json(String id, String type, Prop... props) {
792 ObjectMapper mapper = new ObjectMapper();
793 ObjectNode result = mapper.createObjectNode()
794 .put("id", id).put("type", type);
795 ObjectNode pnode = mapper.createObjectNode();
796 ArrayNode porder = mapper.createArrayNode();
797 for (Prop p : props) {
798 porder.add(p.key);
799 pnode.put(p.key, p.value);
800 }
801 result.set("propOrder", porder);
802 result.set("props", pnode);
803 return result;
804 }
805
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800806 // Produces canonical link key, i.e. one that will match link and its inverse.
807 private LinkKey canonicalLinkKey(Link link) {
808 String sn = link.src().elementId().toString();
809 String dn = link.dst().elementId().toString();
810 return sn.compareTo(dn) < 0 ?
811 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
812 }
813
814 // Representation of link and its inverse and any traffic data.
815 private class BiLink {
816 public final LinkKey key;
817 public final Link one;
818 public Link two;
819 public boolean hasTraffic = false;
820 public long bytes = 0;
821 public String classes = "";
822
823 BiLink(LinkKey key, Link link) {
824 this.key = key;
825 this.one = link;
826 }
827
828 void setOther(Link link) {
829 this.two = link;
830 }
831
832 void addLoad(Load load) {
833 if (load != null) {
834 this.hasTraffic = hasTraffic || load.rate() > 0;
835 this.bytes += load.latest();
836 }
837 }
838
839 void addClass(String trafficClass) {
840 classes = classes + " " + trafficClass;
841 }
842 }
843
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800844 // Auxiliary key/value carrier.
845 private class Prop {
846 public final String key;
847 public final String value;
848
849 protected Prop(String key, String value) {
850 this.key = key;
851 this.value = value;
852 }
853 }
854
855 // Auxiliary properties separator
856 private class Separator extends Prop {
857 protected Separator() {
858 super("-", "");
859 }
860 }
861
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800862 // Auxiliary carrier of data for requesting traffic message.
863 protected class TrafficClass {
864 public final String type;
865 public final Set<Intent> intents;
866
867 TrafficClass(String type, Set<Intent> intents) {
868 this.type = type;
869 this.intents = intents;
870 }
871 }
872
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800873}