blob: b48231ea69d51bbcb6df1d358e2bbeaaf10e723e [file] [log] [blame]
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -08001/*
Simon Hunt8ead3a22015-01-06 11:00:15 -08002 * Copyright 2014,2015 Open Networking Laboratory
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080016package org.onosproject.ui.impl;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080017
18import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.node.ArrayNode;
21import com.fasterxml.jackson.databind.node.ObjectNode;
Simon Hunt8ead3a22015-01-06 11:00:15 -080022import org.onlab.osgi.ServiceDirectory;
23import org.onlab.packet.IpAddress;
Brian O'Connorabafb502014-12-02 22:26:20 -080024import org.onosproject.cluster.ClusterEvent;
25import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.ControllerNode;
27import org.onosproject.cluster.NodeId;
28import org.onosproject.core.CoreService;
29import org.onosproject.mastership.MastershipService;
30import org.onosproject.net.Annotated;
31import org.onosproject.net.AnnotationKeys;
32import org.onosproject.net.Annotations;
33import org.onosproject.net.ConnectPoint;
34import org.onosproject.net.DefaultEdgeLink;
35import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.EdgeLink;
38import org.onosproject.net.Host;
39import org.onosproject.net.HostId;
40import org.onosproject.net.HostLocation;
41import org.onosproject.net.Link;
42import org.onosproject.net.LinkKey;
43import org.onosproject.net.PortNumber;
44import org.onosproject.net.device.DeviceEvent;
45import org.onosproject.net.device.DeviceService;
46import org.onosproject.net.flow.FlowEntry;
47import org.onosproject.net.flow.FlowRuleService;
48import org.onosproject.net.flow.TrafficTreatment;
49import org.onosproject.net.flow.instructions.Instruction;
50import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
51import org.onosproject.net.host.HostEvent;
52import org.onosproject.net.host.HostService;
53import org.onosproject.net.intent.Intent;
54import org.onosproject.net.intent.IntentService;
55import org.onosproject.net.intent.LinkCollectionIntent;
56import org.onosproject.net.intent.OpticalConnectivityIntent;
57import org.onosproject.net.intent.OpticalPathIntent;
58import org.onosproject.net.intent.PathIntent;
59import org.onosproject.net.link.LinkEvent;
60import org.onosproject.net.link.LinkService;
61import org.onosproject.net.provider.ProviderId;
62import org.onosproject.net.statistic.Load;
63import org.onosproject.net.statistic.StatisticService;
64import org.onosproject.net.topology.Topology;
65import org.onosproject.net.topology.TopologyService;
Thomas Vachuska0f6baee2014-11-11 15:02:32 -080066import org.slf4j.Logger;
67import org.slf4j.LoggerFactory;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080068
Thomas Vachuska20322ff2014-11-19 16:22:25 -080069import java.text.DecimalFormat;
Thomas Vachuska29617e52014-11-20 03:17:46 -080070import java.util.ArrayList;
Thomas Vachuska5dd52f72014-11-28 19:27:45 -080071import java.util.Collection;
Thomas Vachuska998b1412014-11-23 02:42:49 -080072import java.util.Collections;
Thomas Vachuska29617e52014-11-20 03:17:46 -080073import java.util.HashMap;
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -080074import java.util.HashSet;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080075import java.util.Iterator;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080076import java.util.List;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080077import java.util.Map;
78import java.util.Set;
79import java.util.concurrent.ConcurrentHashMap;
80
81import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska89543292014-11-19 11:28:33 -080082import static com.google.common.base.Strings.isNullOrEmpty;
Brian O'Connorabafb502014-12-02 22:26:20 -080083import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
84import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
85import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
86import static org.onosproject.net.DeviceId.deviceId;
87import static org.onosproject.net.HostId.hostId;
88import static org.onosproject.net.LinkKey.linkKey;
89import static org.onosproject.net.PortNumber.P0;
90import static org.onosproject.net.PortNumber.portNumber;
91import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
92import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
93import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
94import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
95import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
96import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080097
98/**
99 * Facility for creating messages bound for the topology viewer.
100 */
Thomas Vachuska329af532015-03-10 02:08:33 -0700101@Deprecated
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800102public abstract class TopologyViewMessages {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800103
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800104 protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class);
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800105
Brian O'Connorabafb502014-12-02 22:26:20 -0800106 private static final ProviderId PID = new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800107 private static final String COMPACT = "%s/%s-%s/%s";
108
Thomas Vachuska20322ff2014-11-19 16:22:25 -0800109 private static final double KB = 1024;
110 private static final double MB = 1024 * KB;
111 private static final double GB = 1024 * MB;
112
113 private static final String GB_UNIT = "GB";
114 private static final String MB_UNIT = "MB";
115 private static final String KB_UNIT = "KB";
116 private static final String B_UNIT = "B";
117
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800118 protected final ServiceDirectory directory;
119 protected final ClusterService clusterService;
120 protected final DeviceService deviceService;
121 protected final LinkService linkService;
122 protected final HostService hostService;
123 protected final MastershipService mastershipService;
124 protected final IntentService intentService;
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800125 protected final FlowRuleService flowService;
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800126 protected final StatisticService statService;
Thomas Vachuska47635c62014-11-22 01:21:36 -0800127 protected final TopologyService topologyService;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800128
129 protected final ObjectMapper mapper = new ObjectMapper();
Thomas Vachuska47635c62014-11-22 01:21:36 -0800130 private final String version;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800131
132 // TODO: extract into an external & durable state; good enough for now and demo
133 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
134
135 /**
Thomas Vachuska998b1412014-11-23 02:42:49 -0800136 * Returns read-only view of the meta-ui information.
137 *
138 * @return map of id to meta-ui mementos
139 */
140 static Map<String, ObjectNode> getMetaUi() {
141 return Collections.unmodifiableMap(metaUi);
142 }
143
144 /**
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800145 * Creates a messaging facility for creating messages for topology viewer.
146 *
147 * @param directory service directory
148 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800149 protected TopologyViewMessages(ServiceDirectory directory) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800150 this.directory = checkNotNull(directory, "Directory cannot be null");
151 clusterService = directory.get(ClusterService.class);
152 deviceService = directory.get(DeviceService.class);
153 linkService = directory.get(LinkService.class);
154 hostService = directory.get(HostService.class);
155 mastershipService = directory.get(MastershipService.class);
156 intentService = directory.get(IntentService.class);
Thomas Vachuska87d3db12014-11-20 01:18:50 -0800157 flowService = directory.get(FlowRuleService.class);
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800158 statService = directory.get(StatisticService.class);
Thomas Vachuska47635c62014-11-22 01:21:36 -0800159 topologyService = directory.get(TopologyService.class);
160
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800161 String ver = directory.get(CoreService.class).version().toString();
162 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800163 }
164
165 // Retrieves the payload from the specified event.
166 protected ObjectNode payload(ObjectNode event) {
167 return (ObjectNode) event.path("payload");
168 }
169
170 // Returns the specified node property as a number
171 protected long number(ObjectNode node, String name) {
172 return node.path(name).asLong();
173 }
174
175 // Returns the specified node property as a string.
176 protected String string(ObjectNode node, String name) {
177 return node.path(name).asText();
178 }
179
180 // Returns the specified node property as a string.
181 protected String string(ObjectNode node, String name, String defaultValue) {
182 return node.path(name).asText(defaultValue);
183 }
184
185 // Returns the specified set of IP addresses as a string.
186 private String ip(Set<IpAddress> ipAddresses) {
187 Iterator<IpAddress> it = ipAddresses.iterator();
188 return it.hasNext() ? it.next().toString() : "unknown";
189 }
190
191 // Produces JSON structure from annotations.
192 private JsonNode props(Annotations annotations) {
193 ObjectNode props = mapper.createObjectNode();
Thomas Vachuska16582df2014-11-15 20:12:17 -0800194 if (annotations != null) {
195 for (String key : annotations.keys()) {
196 props.put(key, annotations.value(key));
197 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800198 }
199 return props;
200 }
201
202 // Produces an informational log message event bound to the client.
203 protected ObjectNode info(long id, String message) {
204 return message("info", id, message);
205 }
206
207 // Produces a warning log message event bound to the client.
208 protected ObjectNode warning(long id, String message) {
209 return message("warning", id, message);
210 }
211
212 // Produces an error log message event bound to the client.
213 protected ObjectNode error(long id, String message) {
214 return message("error", id, message);
215 }
216
217 // Produces a log message event bound to the client.
218 private ObjectNode message(String severity, long id, String message) {
219 return envelope("message", id,
220 mapper.createObjectNode()
221 .put("severity", severity)
222 .put("message", message));
223 }
224
225 // Puts the payload into an envelope and returns it.
226 protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
227 ObjectNode event = mapper.createObjectNode();
228 event.put("event", type);
229 if (sid > 0) {
230 event.put("sid", sid);
231 }
232 event.set("payload", payload);
233 return event;
234 }
235
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800236 // Produces a set of all hosts listed in the specified JSON array.
237 protected Set<Host> getHosts(ArrayNode array) {
238 Set<Host> hosts = new HashSet<>();
239 if (array != null) {
240 for (JsonNode node : array) {
241 try {
242 addHost(hosts, hostId(node.asText()));
243 } catch (IllegalArgumentException e) {
244 log.debug("Skipping ID {}", node.asText());
245 }
246 }
247 }
248 return hosts;
249 }
250
251 // Adds the specified host to the set of hosts.
252 private void addHost(Set<Host> hosts, HostId hostId) {
253 Host host = hostService.getHost(hostId);
254 if (host != null) {
255 hosts.add(host);
256 }
257 }
258
259
260 // Produces a set of all devices listed in the specified JSON array.
261 protected Set<Device> getDevices(ArrayNode array) {
262 Set<Device> devices = new HashSet<>();
263 if (array != null) {
264 for (JsonNode node : array) {
265 try {
266 addDevice(devices, deviceId(node.asText()));
267 } catch (IllegalArgumentException e) {
268 log.debug("Skipping ID {}", node.asText());
269 }
270 }
271 }
272 return devices;
273 }
274
275 private void addDevice(Set<Device> devices, DeviceId deviceId) {
276 Device device = deviceService.getDevice(deviceId);
277 if (device != null) {
278 devices.add(device);
279 }
280 }
281
282 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
283 try {
284 addHost(hosts, hostId(hover));
285 } catch (IllegalArgumentException e) {
286 try {
287 addDevice(devices, deviceId(hover));
288 } catch (IllegalArgumentException ne) {
289 log.debug("Skipping ID {}", hover);
290 }
291 }
292 }
293
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800294 // Produces a cluster instance message to the client.
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800295 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800296 ControllerNode node = event.subject();
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800297 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800298 ObjectNode payload = mapper.createObjectNode()
299 .put("id", node.id().toString())
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800300 .put("ip", node.ip().toString())
Thomas Vachuskae4cebaf2014-11-15 18:49:34 -0800301 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskaf84c3a22014-11-22 11:30:57 -0800302 .put("uiAttached", event.subject().equals(clusterService.getLocalNode()))
303 .put("switches", switchCount);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800304
305 ArrayNode labels = mapper.createArrayNode();
306 labels.add(node.id().toString());
307 labels.add(node.ip().toString());
308
309 // Add labels, props and stuff the payload into envelope.
310 payload.set("labels", labels);
311 addMetaUi(node.id().toString(), payload);
312
Thomas Vachuskae02e11c2014-11-24 16:13:52 -0800313 String type = messageType != null ? messageType :
314 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
315 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
Thomas Vachuskad1c25cd2014-11-29 17:47:58 -0800316 "addInstance")));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800317 return envelope(type, 0, payload);
318 }
319
320 // Produces a device event message to the client.
321 protected ObjectNode deviceMessage(DeviceEvent event) {
322 Device device = event.subject();
323 ObjectNode payload = mapper.createObjectNode()
324 .put("id", device.id().toString())
325 .put("type", device.type().toString().toLowerCase())
326 .put("online", deviceService.isAvailable(device.id()))
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800327 .put("master", master(device.id()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800328
329 // Generate labels: id, chassis id, no-label, optional-name
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800330 String name = device.annotations().value(AnnotationKeys.NAME);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800331 ArrayNode labels = mapper.createArrayNode();
Thomas Vachuska60d72bf2014-11-21 13:02:00 -0800332 labels.add("");
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800333 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800334 labels.add(device.id().toString());
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800335
336 // Add labels, props and stuff the payload into envelope.
337 payload.set("labels", labels);
338 payload.set("props", props(device.annotations()));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800339 addGeoLocation(device, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800340 addMetaUi(device.id().toString(), payload);
341
342 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
343 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
344 return envelope(type, 0, payload);
345 }
346
347 // Produces a link event message to the client.
348 protected ObjectNode linkMessage(LinkEvent event) {
349 Link link = event.subject();
350 ObjectNode payload = mapper.createObjectNode()
351 .put("id", compactLinkString(link))
352 .put("type", link.type().toString().toLowerCase())
Thomas Vachuskae4cebaf2014-11-15 18:49:34 -0800353 .put("online", link.state() == Link.State.ACTIVE)
Thomas Vachuskacd2920c2014-11-19 14:49:55 -0800354 .put("linkWidth", 1.2)
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800355 .put("src", link.src().deviceId().toString())
356 .put("srcPort", link.src().port().toString())
357 .put("dst", link.dst().deviceId().toString())
358 .put("dstPort", link.dst().port().toString());
359 String type = (event.type() == LINK_ADDED) ? "addLink" :
360 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
361 return envelope(type, 0, payload);
362 }
363
364 // Produces a host event message to the client.
365 protected ObjectNode hostMessage(HostEvent event) {
366 Host host = event.subject();
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800367 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800368 ObjectNode payload = mapper.createObjectNode()
369 .put("id", host.id().toString())
Thomas Vachuska3f06e082014-11-21 11:58:23 -0800370 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800371 .put("ingress", compactLinkString(edgeLink(host, true)))
372 .put("egress", compactLinkString(edgeLink(host, false)));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800373 payload.set("cp", hostConnect(mapper, host.location()));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800374 payload.set("labels", labels(mapper, ip(host.ipAddresses()),
375 host.mac().toString()));
376 payload.set("props", props(host.annotations()));
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800377 addGeoLocation(host, payload);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800378 addMetaUi(host.id().toString(), payload);
379
380 String type = (event.type() == HOST_ADDED) ? "addHost" :
381 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
382 return envelope(type, 0, payload);
383 }
384
385 // Encodes the specified host location into a JSON object.
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800386 private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800387 return mapper.createObjectNode()
388 .put("device", location.deviceId().toString())
389 .put("port", location.port().toLong());
390 }
391
392 // Encodes the specified list of labels a JSON array.
393 private ArrayNode labels(ObjectMapper mapper, String... labels) {
394 ArrayNode json = mapper.createArrayNode();
395 for (String label : labels) {
396 json.add(label);
397 }
398 return json;
399 }
400
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800401 // Returns the name of the master node for the specified device id.
402 private String master(DeviceId deviceId) {
403 NodeId master = mastershipService.getMasterFor(deviceId);
404 return master != null ? master.toString() : "";
405 }
406
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800407 // Generates an edge link from the specified host location.
408 private EdgeLink edgeLink(Host host, boolean ingress) {
409 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
410 host.location(), ingress);
411 }
412
413 // Adds meta UI information for the specified object.
414 private void addMetaUi(String id, ObjectNode payload) {
415 ObjectNode meta = metaUi.get(id);
416 if (meta != null) {
417 payload.set("metaUi", meta);
418 }
419 }
420
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800421 // Adds a geo location JSON to the specified payload object.
422 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
423 Annotations annotations = annotated.annotations();
Thomas Vachuska16582df2014-11-15 20:12:17 -0800424 if (annotations == null) {
425 return;
426 }
427
Sho SHIMIZUf5c3a2e2014-12-02 14:49:39 -0800428 String slat = annotations.value(AnnotationKeys.LATITUDE);
429 String slng = annotations.value(AnnotationKeys.LONGITUDE);
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800430 try {
431 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
432 double lat = Double.parseDouble(slat);
433 double lng = Double.parseDouble(slng);
434 ObjectNode loc = mapper.createObjectNode()
435 .put("type", "latlng").put("lat", lat).put("lng", lng);
436 payload.set("location", loc);
437 }
438 } catch (NumberFormatException e) {
439 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
440 }
441 }
442
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800443 // Updates meta UI information for the specified object.
444 protected void updateMetaUi(ObjectNode event) {
445 ObjectNode payload = payload(event);
Simon Hunt3b9cddb2014-11-11 20:50:04 -0800446 metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800447 }
448
Thomas Vachuska47635c62014-11-22 01:21:36 -0800449 // Returns summary response.
450 protected ObjectNode summmaryMessage(long sid) {
451 Topology topology = topologyService.currentTopology();
452 return envelope("showSummary", sid,
453 json("ONOS Summary", "node",
454 new Prop("Devices", format(topology.deviceCount())),
455 new Prop("Links", format(topology.linkCount())),
456 new Prop("Hosts", format(hostService.getHostCount())),
457 new Prop("Topology SCCs", format(topology.clusterCount())),
Thomas Vachuska47635c62014-11-22 01:21:36 -0800458 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();
Ray Milkey42507352015-03-20 15:16:10 -0700531 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska29617e52014-11-20 03:17:46 -0800532 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;
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800676 List<Intent> installables = intentService.getInstallableIntents(intent.key());
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}