blob: 486e375f7e378b63d61c8fafb6ccde79e1b9d22b [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;
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -080029import org.onlab.onos.net.AnnotationKeys;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080030import org.onlab.onos.net.Annotations;
31import org.onlab.onos.net.ConnectPoint;
32import org.onlab.onos.net.DefaultEdgeLink;
33import org.onlab.onos.net.Device;
34import org.onlab.onos.net.DeviceId;
35import org.onlab.onos.net.EdgeLink;
36import org.onlab.onos.net.Host;
37import org.onlab.onos.net.HostId;
38import org.onlab.onos.net.HostLocation;
39import org.onlab.onos.net.Link;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -080040import org.onlab.onos.net.LinkKey;
Thomas Vachuska29617e52014-11-20 03:17:46 -080041import org.onlab.onos.net.PortNumber;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080042import org.onlab.onos.net.device.DeviceEvent;
43import org.onlab.onos.net.device.DeviceService;
Thomas Vachuska87d3db12014-11-20 01:18:50 -080044import org.onlab.onos.net.flow.FlowEntry;
45import org.onlab.onos.net.flow.FlowRuleService;
Thomas Vachuska29617e52014-11-20 03:17:46 -080046import org.onlab.onos.net.flow.TrafficTreatment;
47import org.onlab.onos.net.flow.instructions.Instruction;
48import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080049import org.onlab.onos.net.host.HostEvent;
50import org.onlab.onos.net.host.HostService;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080051import org.onlab.onos.net.intent.Intent;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080052import org.onlab.onos.net.intent.IntentService;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080053import org.onlab.onos.net.intent.LinkCollectionIntent;
Thomas Vachuska22e34922014-11-14 00:40:55 -080054import org.onlab.onos.net.intent.OpticalConnectivityIntent;
Thomas Vachuskadea4cb32014-11-14 12:14:30 -080055import org.onlab.onos.net.intent.OpticalPathIntent;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080056import org.onlab.onos.net.intent.PathIntent;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080057import org.onlab.onos.net.link.LinkEvent;
58import org.onlab.onos.net.link.LinkService;
59import org.onlab.onos.net.provider.ProviderId;
Thomas Vachuska7c27ad72014-11-14 16:20:10 -080060import org.onlab.onos.net.statistic.Load;
61import org.onlab.onos.net.statistic.StatisticService;
Thomas Vachuska47635c62014-11-22 01:21:36 -080062import org.onlab.onos.net.topology.Topology;
63import org.onlab.onos.net.topology.TopologyService;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080064import org.onlab.osgi.ServiceDirectory;
65import org.onlab.packet.IpAddress;
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;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080083import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
84import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
85import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -080086import static org.onlab.onos.net.DeviceId.deviceId;
87import static org.onlab.onos.net.HostId.hostId;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -080088import static org.onlab.onos.net.LinkKey.linkKey;
Thomas Vachuska29617e52014-11-20 03:17:46 -080089import static org.onlab.onos.net.PortNumber.P0;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080090import static org.onlab.onos.net.PortNumber.portNumber;
91import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
92import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
93import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
94import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
95import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
96import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
97
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
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800105 private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
106 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())),
457 new Prop("Paths", format(topology.pathCount())),
458 new Separator(),
459 new Prop("Intents", format(intentService.getIntentCount())),
460 new Prop("Flows", format(flowService.getFlowRuleCount())),
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800461 new Prop("Version", version)));
Thomas Vachuska47635c62014-11-22 01:21:36 -0800462 }
463
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800464 // Returns device details response.
465 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
466 Device device = deviceService.getDevice(deviceId);
467 Annotations annot = device.annotations();
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800468 String name = annot.value(AnnotationKeys.NAME);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800469 int portCount = deviceService.getPorts(deviceId).size();
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800470 int flowCount = getFlowCount(deviceId);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800471 return envelope("showDetails", sid,
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800472 json(isNullOrEmpty(name) ? deviceId.toString() : name,
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800473 device.type().toString().toLowerCase(),
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800474 new Prop("URI", deviceId.toString()),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800475 new Prop("Vendor", device.manufacturer()),
476 new Prop("H/W Version", device.hwVersion()),
477 new Prop("S/W Version", device.swVersion()),
478 new Prop("Serial Number", device.serialNumber()),
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800479 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800480 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800481 new Prop("Master", master(deviceId)),
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800482 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
483 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800484 new Separator(),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800485 new Prop("Ports", Integer.toString(portCount)),
486 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800487 }
488
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800489 protected int getFlowCount(DeviceId deviceId) {
490 int count = 0;
491 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
492 while (it.hasNext()) {
493 count++;
494 it.next();
495 }
496 return count;
497 }
498
Thomas Vachuska29617e52014-11-20 03:17:46 -0800499 // Counts all entries that egress on the given device links.
500 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
501 List<FlowEntry> entries = new ArrayList<>();
502 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
503 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
504 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
505 while (it.hasNext()) {
506 entries.add(it.next());
507 }
508
509 // Add all edge links to the set
510 if (hosts != null) {
511 for (Host host : hosts) {
512 links.add(new DefaultEdgeLink(host.providerId(),
513 new ConnectPoint(host.id(), P0),
514 host.location(), false));
515 }
516 }
517
518 Map<Link, Integer> counts = new HashMap<>();
519 for (Link link : links) {
520 counts.put(link, getEgressFlows(link, entries));
521 }
522 return counts;
523 }
524
525 // Counts all entries that egress on the link source port.
526 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
527 int count = 0;
528 PortNumber out = link.src().port();
529 for (FlowEntry entry : entries) {
530 TrafficTreatment treatment = entry.treatment();
531 for (Instruction instruction : treatment.instructions()) {
532 if (instruction.type() == Instruction.Type.OUTPUT &&
533 ((OutputInstruction) instruction).port().equals(out)) {
534 count++;
535 }
536 }
537 }
538 return count;
539 }
540
541
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800542 // Returns host details response.
543 protected ObjectNode hostDetails(HostId hostId, long sid) {
544 Host host = hostService.getHost(hostId);
545 Annotations annot = host.annotations();
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800546 String type = annot.value(AnnotationKeys.TYPE);
547 String name = annot.value(AnnotationKeys.NAME);
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800548 String vlan = host.vlan().toString();
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800549 return envelope("showDetails", sid,
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800550 json(isNullOrEmpty(name) ? hostId.toString() : name,
Simon Huntb33b40b2014-12-01 16:12:47 -0800551 isNullOrEmpty(type) ? "endstation" : type,
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800552 new Prop("MAC", host.mac().toString()),
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800553 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
554 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800555 new Separator(),
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800556 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
557 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800558 }
559
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800560
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800561 // Produces JSON message to trigger traffic overview visualization
562 protected ObjectNode trafficSummaryMessage(long sid) {
563 ObjectNode payload = mapper.createObjectNode();
564 ArrayNode paths = mapper.createArrayNode();
565 payload.set("paths", paths);
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800566
567 ObjectNode pathNodeN = mapper.createObjectNode();
568 ArrayNode linksNodeN = mapper.createArrayNode();
569 ArrayNode labelsN = mapper.createArrayNode();
570
571 pathNodeN.put("class", "plain").put("traffic", false);
572 pathNodeN.set("links", linksNodeN);
573 pathNodeN.set("labels", labelsN);
574 paths.add(pathNodeN);
575
576 ObjectNode pathNodeT = mapper.createObjectNode();
577 ArrayNode linksNodeT = mapper.createArrayNode();
578 ArrayNode labelsT = mapper.createArrayNode();
579
580 pathNodeT.put("class", "secondary").put("traffic", true);
581 pathNodeT.set("links", linksNodeT);
582 pathNodeT.set("labels", labelsT);
583 paths.add(pathNodeT);
584
585 for (BiLink link : consolidateLinks(linkService.getLinks())) {
586 boolean bi = link.two != null;
587 if (isInfrastructureEgress(link.one) ||
588 (bi && isInfrastructureEgress(link.two))) {
589 link.addLoad(statService.load(link.one));
590 link.addLoad(bi ? statService.load(link.two) : null);
591 if (link.hasTraffic) {
592 linksNodeT.add(compactLinkString(link.one));
593 labelsT.add(formatBytes(link.bytes));
594 } else {
595 linksNodeN.add(compactLinkString(link.one));
596 labelsN.add("");
597 }
598 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800599 }
600 return envelope("showTraffic", sid, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800601 }
602
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800603 private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
604 Map<LinkKey, BiLink> biLinks = new HashMap<>();
605 for (Link link : links) {
606 addLink(biLinks, link);
607 }
608 return biLinks.values();
609 }
610
Thomas Vachuska29617e52014-11-20 03:17:46 -0800611 // Produces JSON message to trigger flow overview visualization
612 protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
613 ObjectNode payload = mapper.createObjectNode();
614 ArrayNode paths = mapper.createArrayNode();
615 payload.set("paths", paths);
616
617 for (Device device : devices) {
618 Map<Link, Integer> counts = getFlowCounts(device.id());
619 for (Link link : counts.keySet()) {
620 addLinkFlows(link, paths, counts.get(link));
621 }
622 }
623 return envelope("showTraffic", sid, payload);
624 }
625
626 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
627 ObjectNode pathNode = mapper.createObjectNode();
628 ArrayNode linksNode = mapper.createArrayNode();
629 ArrayNode labels = mapper.createArrayNode();
630 boolean noFlows = count == null || count == 0;
631 pathNode.put("class", noFlows ? "secondary" : "primary");
632 pathNode.put("traffic", false);
633 pathNode.set("links", linksNode.add(compactLinkString(link)));
634 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
635 (count == 1 ? " flow" : " flows"))));
636 paths.add(pathNode);
637 }
638
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800639
640 // Produces JSON message to trigger traffic visualization
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800641 protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800642 ObjectNode payload = mapper.createObjectNode();
643 ArrayNode paths = mapper.createArrayNode();
644 payload.set("paths", paths);
645
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800646 // Classify links based on their traffic traffic first...
647 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
648
649 // Then separate the links into their respective classes and send them out.
650 Map<String, ObjectNode> pathNodes = new HashMap<>();
651 for (BiLink biLink : biLinks.values()) {
652 boolean hasTraffic = biLink.hasTraffic;
653 String tc = (biLink.classes + (hasTraffic ? " animated" : "")).trim();
654 ObjectNode pathNode = pathNodes.get(tc);
655 if (pathNode == null) {
656 pathNode = mapper.createObjectNode()
657 .put("class", tc).put("traffic", hasTraffic);
658 pathNode.set("links", mapper.createArrayNode());
659 pathNode.set("labels", mapper.createArrayNode());
660 pathNodes.put(tc, pathNode);
661 paths.add(pathNode);
662 }
663 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
664 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
665 }
666
667 return envelope("showTraffic", sid, payload);
668 }
669
670 // Classifies the link traffic according to the specified classes.
671 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
672 Map<LinkKey, BiLink> biLinks = new HashMap<>();
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800673 for (TrafficClass trafficClass : trafficClasses) {
674 for (Intent intent : trafficClass.intents) {
Thomas Vachuska22e34922014-11-14 00:40:55 -0800675 boolean isOptical = intent instanceof OpticalConnectivityIntent;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800676 List<Intent> installables = intentService.getInstallableIntents(intent.id());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800677 if (installables != null) {
678 for (Intent installable : installables) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800679 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800680 if (installable instanceof PathIntent) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800681 classifyLinks(type, biLinks, trafficClass.showTraffic,
682 ((PathIntent) installable).path().links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800683 } else if (installable instanceof LinkCollectionIntent) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800684 classifyLinks(type, biLinks, trafficClass.showTraffic,
685 ((LinkCollectionIntent) installable).links());
Thomas Vachuskadea4cb32014-11-14 12:14:30 -0800686 } else if (installable instanceof OpticalPathIntent) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800687 classifyLinks(type, biLinks, trafficClass.showTraffic,
688 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800689 }
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800690 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800691 }
692 }
693 }
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800694 return biLinks;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800695 }
696
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800697
698 // Adds the link segments (path or tree) associated with the specified
699 // connectivity intent
700 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800701 boolean showTraffic, Iterable<Link> links) {
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800702 if (links != null) {
703 for (Link link : links) {
704 BiLink biLink = addLink(biLinks, link);
705 if (isInfrastructureEgress(link)) {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800706 if (showTraffic) {
707 biLink.addLoad(statService.load(link));
708 }
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800709 biLink.addClass(type);
710 }
711 }
712 }
713 }
714
715
716 private BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
717 LinkKey key = canonicalLinkKey(link);
718 BiLink biLink = biLinks.get(key);
719 if (biLink != null) {
720 biLink.setOther(link);
721 } else {
722 biLink = new BiLink(key, link);
723 biLinks.put(key, biLink);
724 }
725 return biLink;
726 }
727
728
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800729 // Adds the link segments (path or tree) associated with the specified
730 // connectivity intent
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800731 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
732 Iterable<Link> links) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800733 ObjectNode pathNode = mapper.createObjectNode();
734 ArrayNode linksNode = mapper.createArrayNode();
735
Thomas Vachuska22e34922014-11-14 00:40:55 -0800736 if (links != null) {
737 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800738 boolean hasTraffic = false;
Thomas Vachuska22e34922014-11-14 00:40:55 -0800739 for (Link link : links) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800740 if (isInfrastructureEgress(link)) {
741 linksNode.add(compactLinkString(link));
742 Load load = statService.load(link);
743 String label = "";
744 if (load.rate() > 0) {
745 hasTraffic = true;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800746 label = formatBytes(load.latest());
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800747 }
748 labels.add(label);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800749 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800750 }
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800751 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
Thomas Vachuska22e34922014-11-14 00:40:55 -0800752 pathNode.put("traffic", hasTraffic);
753 pathNode.set("links", linksNode);
754 pathNode.set("labels", labels);
755 paths.add(pathNode);
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800756 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800757 }
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800758
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800759 // Poor-mans formatting to get the labels with byte counts looking nice.
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800760 private String formatBytes(long bytes) {
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800761 String unit;
762 double value;
763 if (bytes > GB) {
764 value = bytes / GB;
765 unit = GB_UNIT;
766 } else if (bytes > MB) {
767 value = bytes / MB;
768 unit = MB_UNIT;
769 } else if (bytes > KB) {
770 value = bytes / KB;
771 unit = KB_UNIT;
772 } else {
773 value = bytes;
774 unit = B_UNIT;
775 }
776 DecimalFormat format = new DecimalFormat("#,###.##");
Thomas Vachuska29617e52014-11-20 03:17:46 -0800777 return format.format(value) + " " + unit;
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800778 }
779
Thomas Vachuska47635c62014-11-22 01:21:36 -0800780 // Formats the given number into a string.
781 private String format(Number number) {
782 DecimalFormat format = new DecimalFormat("#,###");
783 return format.format(number);
784 }
785
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800786 private boolean isInfrastructureEgress(Link link) {
787 return link.src().elementId() instanceof DeviceId;
788 }
789
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800790 // Produces compact string representation of a link.
791 private static String compactLinkString(Link link) {
792 return String.format(COMPACT, link.src().elementId(), link.src().port(),
793 link.dst().elementId(), link.dst().port());
794 }
795
796 // Produces JSON property details.
797 private ObjectNode json(String id, String type, Prop... props) {
798 ObjectMapper mapper = new ObjectMapper();
799 ObjectNode result = mapper.createObjectNode()
800 .put("id", id).put("type", type);
801 ObjectNode pnode = mapper.createObjectNode();
802 ArrayNode porder = mapper.createArrayNode();
803 for (Prop p : props) {
804 porder.add(p.key);
805 pnode.put(p.key, p.value);
806 }
807 result.set("propOrder", porder);
808 result.set("props", pnode);
809 return result;
810 }
811
Thomas Vachuska5dd52f72014-11-28 19:27:45 -0800812 // Produces canonical link key, i.e. one that will match link and its inverse.
813 private LinkKey canonicalLinkKey(Link link) {
814 String sn = link.src().elementId().toString();
815 String dn = link.dst().elementId().toString();
816 return sn.compareTo(dn) < 0 ?
817 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
818 }
819
820 // Representation of link and its inverse and any traffic data.
821 private class BiLink {
822 public final LinkKey key;
823 public final Link one;
824 public Link two;
825 public boolean hasTraffic = false;
826 public long bytes = 0;
827 public String classes = "";
828
829 BiLink(LinkKey key, Link link) {
830 this.key = key;
831 this.one = link;
832 }
833
834 void setOther(Link link) {
835 this.two = link;
836 }
837
838 void addLoad(Load load) {
839 if (load != null) {
840 this.hasTraffic = hasTraffic || load.rate() > 0;
841 this.bytes += load.latest();
842 }
843 }
844
845 void addClass(String trafficClass) {
846 classes = classes + " " + trafficClass;
847 }
848 }
849
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800850 // Auxiliary key/value carrier.
851 private class Prop {
852 public final String key;
853 public final String value;
854
855 protected Prop(String key, String value) {
856 this.key = key;
857 this.value = value;
858 }
859 }
860
861 // Auxiliary properties separator
862 private class Separator extends Prop {
863 protected Separator() {
864 super("-", "");
865 }
866 }
867
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800868 // Auxiliary carrier of data for requesting traffic message.
869 protected class TrafficClass {
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800870 public final boolean showTraffic;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800871 public final String type;
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800872 public final Iterable<Intent> intents;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800873
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800874 TrafficClass(String type, Iterable<Intent> intents) {
875 this(type, intents, false);
876 }
877
878 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800879 this.type = type;
880 this.intents = intents;
Thomas Vachuska164fa5c2014-12-02 21:59:41 -0800881 this.showTraffic = showTraffic;
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800882 }
883 }
884
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800885}