blob: 336d6f2164b9210ec90ad87869ea3c16da3f8045 [file] [log] [blame]
Thomas Vachuska329af532015-03-10 02:08:33 -07001/*
2 * Copyright 2015 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.onosproject.ui.impl;
17
18import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuska329af532015-03-10 02:08:33 -070019import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070021import com.google.common.collect.ImmutableList;
Thomas Vachuska329af532015-03-10 02:08:33 -070022import org.onlab.osgi.ServiceDirectory;
23import org.onlab.packet.IpAddress;
24import org.onosproject.cluster.ClusterEvent;
25import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.ControllerNode;
27import org.onosproject.cluster.NodeId;
28import org.onosproject.core.CoreService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -070029import org.onosproject.incubator.net.PortStatisticsService;
cheng fan35dc0f22015-06-10 06:02:47 +080030import org.onosproject.incubator.net.tunnel.OpticalTunnelEndPoint;
31import org.onosproject.incubator.net.tunnel.Tunnel;
32import org.onosproject.incubator.net.tunnel.TunnelService;
Thomas Vachuska329af532015-03-10 02:08:33 -070033import org.onosproject.mastership.MastershipService;
34import org.onosproject.net.Annotated;
35import org.onosproject.net.AnnotationKeys;
36import org.onosproject.net.Annotations;
37import org.onosproject.net.ConnectPoint;
38import org.onosproject.net.DefaultEdgeLink;
39import org.onosproject.net.Device;
40import org.onosproject.net.DeviceId;
41import org.onosproject.net.EdgeLink;
42import org.onosproject.net.Host;
43import org.onosproject.net.HostId;
44import org.onosproject.net.HostLocation;
45import org.onosproject.net.Link;
46import org.onosproject.net.LinkKey;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070047import org.onosproject.net.NetworkResource;
Thomas Vachuska329af532015-03-10 02:08:33 -070048import org.onosproject.net.PortNumber;
49import org.onosproject.net.device.DeviceEvent;
50import org.onosproject.net.device.DeviceService;
51import org.onosproject.net.flow.FlowEntry;
52import org.onosproject.net.flow.FlowRuleService;
53import org.onosproject.net.flow.TrafficTreatment;
54import org.onosproject.net.flow.instructions.Instruction;
55import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
56import org.onosproject.net.host.HostEvent;
57import org.onosproject.net.host.HostService;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070058import org.onosproject.net.intent.FlowRuleIntent;
Thomas Vachuska329af532015-03-10 02:08:33 -070059import org.onosproject.net.intent.Intent;
60import org.onosproject.net.intent.IntentService;
61import org.onosproject.net.intent.LinkCollectionIntent;
62import org.onosproject.net.intent.OpticalConnectivityIntent;
63import org.onosproject.net.intent.OpticalPathIntent;
64import org.onosproject.net.intent.PathIntent;
65import org.onosproject.net.link.LinkEvent;
66import org.onosproject.net.link.LinkService;
67import org.onosproject.net.provider.ProviderId;
68import org.onosproject.net.statistic.Load;
69import org.onosproject.net.statistic.StatisticService;
70import org.onosproject.net.topology.Topology;
71import org.onosproject.net.topology.TopologyService;
Simon Huntd2747a02015-04-30 22:41:16 -070072import org.onosproject.ui.JsonUtils;
Thomas Vachuska329af532015-03-10 02:08:33 -070073import org.onosproject.ui.UiConnection;
Simon Hunta0ddb022015-05-01 09:53:01 -070074import org.onosproject.ui.UiMessageHandler;
Thomas Vachuska329af532015-03-10 02:08:33 -070075import org.slf4j.Logger;
76import org.slf4j.LoggerFactory;
77
78import java.text.DecimalFormat;
79import java.util.ArrayList;
80import java.util.Collection;
81import java.util.Collections;
82import java.util.HashMap;
83import java.util.HashSet;
84import java.util.Iterator;
85import java.util.List;
86import java.util.Map;
87import java.util.Set;
88import java.util.concurrent.ConcurrentHashMap;
89
90import static com.google.common.base.Preconditions.checkNotNull;
91import static com.google.common.base.Strings.isNullOrEmpty;
92import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
93import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
94import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -070095import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
Thomas Vachuska329af532015-03-10 02:08:33 -070096import static org.onosproject.net.DeviceId.deviceId;
97import static org.onosproject.net.HostId.hostId;
98import static org.onosproject.net.LinkKey.linkKey;
99import static org.onosproject.net.PortNumber.P0;
100import static org.onosproject.net.PortNumber.portNumber;
101import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
102import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
103import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
104import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
105import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
106import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700107import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
108import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700109
110/**
111 * Facility for creating messages bound for the topology viewer.
Sho SHIMIZUbe63b232015-06-30 10:57:58 -0700112 *
113 * @deprecated in Cardinal Release
Thomas Vachuska329af532015-03-10 02:08:33 -0700114 */
Simon Huntd7f7bcc2015-05-08 14:13:17 -0700115@Deprecated
Simon Hunta0ddb022015-05-01 09:53:01 -0700116public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700117
Simon Huntd2747a02015-04-30 22:41:16 -0700118 protected static final Logger log =
119 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700120
Simon Huntd2747a02015-04-30 22:41:16 -0700121 private static final ProviderId PID =
122 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700123 private static final String COMPACT = "%s/%s-%s/%s";
124
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700125 private static final double KILO = 1024;
126 private static final double MEGA = 1024 * KILO;
127 private static final double GIGA = 1024 * MEGA;
Thomas Vachuska329af532015-03-10 02:08:33 -0700128
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700129 private static final String GBITS_UNIT = "Gb";
130 private static final String MBITS_UNIT = "Mb";
131 private static final String KBITS_UNIT = "Kb";
132 private static final String BITS_UNIT = "b";
133 private static final String GBYTES_UNIT = "GB";
134 private static final String MBYTES_UNIT = "MB";
135 private static final String KBYTES_UNIT = "KB";
136 private static final String BYTES_UNIT = "B";
137 //4 Kilo Bytes as threshold
138 private static final double BPS_THRESHOLD = 4 * KILO;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700139
Thomas Vachuska329af532015-03-10 02:08:33 -0700140 protected ServiceDirectory directory;
141 protected ClusterService clusterService;
142 protected DeviceService deviceService;
143 protected LinkService linkService;
144 protected HostService hostService;
145 protected MastershipService mastershipService;
146 protected IntentService intentService;
147 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700148 protected StatisticService flowStatsService;
149 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700150 protected TopologyService topologyService;
cheng fan35dc0f22015-06-10 06:02:47 +0800151 protected TunnelService tunnelService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700152
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700153 protected enum StatsType {
154 FLOW, PORT
155 }
156
Thomas Vachuska329af532015-03-10 02:08:33 -0700157 private String version;
158
159 // TODO: extract into an external & durable state; good enough for now and demo
160 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
161
162 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700163 * Returns read-only view of the meta-ui information.
164 *
165 * @return map of id to meta-ui mementos
166 */
167 static Map<String, ObjectNode> getMetaUi() {
168 return Collections.unmodifiableMap(metaUi);
169 }
170
171 @Override
172 public void init(UiConnection connection, ServiceDirectory directory) {
173 super.init(connection, directory);
174 this.directory = checkNotNull(directory, "Directory cannot be null");
175 clusterService = directory.get(ClusterService.class);
176 deviceService = directory.get(DeviceService.class);
177 linkService = directory.get(LinkService.class);
178 hostService = directory.get(HostService.class);
179 mastershipService = directory.get(MastershipService.class);
180 intentService = directory.get(IntentService.class);
181 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700182 flowStatsService = directory.get(StatisticService.class);
183 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700184 topologyService = directory.get(TopologyService.class);
cheng fan35dc0f22015-06-10 06:02:47 +0800185 tunnelService = directory.get(TunnelService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700186
187 String ver = directory.get(CoreService.class).version().toString();
188 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
189 }
190
Thomas Vachuska329af532015-03-10 02:08:33 -0700191 // Returns the specified set of IP addresses as a string.
192 private String ip(Set<IpAddress> ipAddresses) {
193 Iterator<IpAddress> it = ipAddresses.iterator();
194 return it.hasNext() ? it.next().toString() : "unknown";
195 }
196
197 // Produces JSON structure from annotations.
198 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700199 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700200 if (annotations != null) {
201 for (String key : annotations.keys()) {
202 props.put(key, annotations.value(key));
203 }
204 }
205 return props;
206 }
207
208 // Produces an informational log message event bound to the client.
209 protected ObjectNode info(long id, String message) {
210 return message("info", id, message);
211 }
212
213 // Produces a warning log message event bound to the client.
214 protected ObjectNode warning(long id, String message) {
215 return message("warning", id, message);
216 }
217
218 // Produces an error log message event bound to the client.
219 protected ObjectNode error(long id, String message) {
220 return message("error", id, message);
221 }
222
223 // Produces a log message event bound to the client.
224 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700225 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700226 .put("severity", severity)
227 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700228
Simon Huntd2747a02015-04-30 22:41:16 -0700229 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700230 }
231
232 // Produces a set of all hosts listed in the specified JSON array.
233 protected Set<Host> getHosts(ArrayNode array) {
234 Set<Host> hosts = new HashSet<>();
235 if (array != null) {
236 for (JsonNode node : array) {
237 try {
238 addHost(hosts, hostId(node.asText()));
239 } catch (IllegalArgumentException e) {
240 log.debug("Skipping ID {}", node.asText());
241 }
242 }
243 }
244 return hosts;
245 }
246
247 // Adds the specified host to the set of hosts.
248 private void addHost(Set<Host> hosts, HostId hostId) {
249 Host host = hostService.getHost(hostId);
250 if (host != null) {
251 hosts.add(host);
252 }
253 }
254
255
256 // Produces a set of all devices listed in the specified JSON array.
257 protected Set<Device> getDevices(ArrayNode array) {
258 Set<Device> devices = new HashSet<>();
259 if (array != null) {
260 for (JsonNode node : array) {
261 try {
262 addDevice(devices, deviceId(node.asText()));
263 } catch (IllegalArgumentException e) {
264 log.debug("Skipping ID {}", node.asText());
265 }
266 }
267 }
268 return devices;
269 }
270
271 private void addDevice(Set<Device> devices, DeviceId deviceId) {
272 Device device = deviceService.getDevice(deviceId);
273 if (device != null) {
274 devices.add(device);
275 }
276 }
277
278 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
279 try {
280 addHost(hosts, hostId(hover));
281 } catch (IllegalArgumentException e) {
282 try {
283 addDevice(devices, deviceId(hover));
284 } catch (IllegalArgumentException ne) {
285 log.debug("Skipping ID {}", hover);
286 }
287 }
288 }
289
290 // Produces a cluster instance message to the client.
291 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
292 ControllerNode node = event.subject();
293 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700294 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700295 .put("id", node.id().toString())
296 .put("ip", node.ip().toString())
297 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700298 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700299 .put("switches", switchCount);
300
Simon Huntda580882015-05-12 20:58:18 -0700301 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700302 labels.add(node.id().toString());
303 labels.add(node.ip().toString());
304
305 // Add labels, props and stuff the payload into envelope.
306 payload.set("labels", labels);
307 addMetaUi(node.id().toString(), payload);
308
309 String type = messageType != null ? messageType :
310 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
311 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
312 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700313 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700314 }
315
316 // Produces a device event message to the client.
317 protected ObjectNode deviceMessage(DeviceEvent event) {
318 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700319 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700320 .put("id", device.id().toString())
321 .put("type", device.type().toString().toLowerCase())
322 .put("online", deviceService.isAvailable(device.id()))
323 .put("master", master(device.id()));
324
325 // Generate labels: id, chassis id, no-label, optional-name
326 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700327 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700328 labels.add("");
329 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
330 labels.add(device.id().toString());
331
332 // Add labels, props and stuff the payload into envelope.
333 payload.set("labels", labels);
334 payload.set("props", props(device.annotations()));
335 addGeoLocation(device, payload);
336 addMetaUi(device.id().toString(), payload);
337
338 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
339 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700340 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700341 }
342
343 // Produces a link event message to the client.
344 protected ObjectNode linkMessage(LinkEvent event) {
345 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700346 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700347 .put("id", compactLinkString(link))
348 .put("type", link.type().toString().toLowerCase())
349 .put("online", link.state() == Link.State.ACTIVE)
350 .put("linkWidth", 1.2)
351 .put("src", link.src().deviceId().toString())
352 .put("srcPort", link.src().port().toString())
353 .put("dst", link.dst().deviceId().toString())
354 .put("dstPort", link.dst().port().toString());
355 String type = (event.type() == LINK_ADDED) ? "addLink" :
356 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700357 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700358 }
359
360 // Produces a host event message to the client.
361 protected ObjectNode hostMessage(HostEvent event) {
362 Host host = event.subject();
363 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700364 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700365 .put("id", host.id().toString())
366 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
367 .put("ingress", compactLinkString(edgeLink(host, true)))
368 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700369 payload.set("cp", hostConnect(host.location()));
370 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700371 host.mac().toString()));
372 payload.set("props", props(host.annotations()));
373 addGeoLocation(host, payload);
374 addMetaUi(host.id().toString(), payload);
375
376 String type = (event.type() == HOST_ADDED) ? "addHost" :
377 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700378 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700379 }
380
381 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700382 private ObjectNode hostConnect(HostLocation location) {
383 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700384 .put("device", location.deviceId().toString())
385 .put("port", location.port().toLong());
386 }
387
388 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700389 private ArrayNode labels(String... labels) {
390 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700391 for (String label : labels) {
392 json.add(label);
393 }
394 return json;
395 }
396
397 // Returns the name of the master node for the specified device id.
398 private String master(DeviceId deviceId) {
399 NodeId master = mastershipService.getMasterFor(deviceId);
400 return master != null ? master.toString() : "";
401 }
402
403 // Generates an edge link from the specified host location.
404 private EdgeLink edgeLink(Host host, boolean ingress) {
405 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
406 host.location(), ingress);
407 }
408
409 // Adds meta UI information for the specified object.
410 private void addMetaUi(String id, ObjectNode payload) {
411 ObjectNode meta = metaUi.get(id);
412 if (meta != null) {
413 payload.set("metaUi", meta);
414 }
415 }
416
417 // Adds a geo location JSON to the specified payload object.
418 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
419 Annotations annotations = annotated.annotations();
420 if (annotations == null) {
421 return;
422 }
423
424 String slat = annotations.value(AnnotationKeys.LATITUDE);
425 String slng = annotations.value(AnnotationKeys.LONGITUDE);
426 try {
427 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
428 double lat = Double.parseDouble(slat);
429 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700430 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700431 .put("type", "latlng").put("lat", lat).put("lng", lng);
432 payload.set("location", loc);
433 }
434 } catch (NumberFormatException e) {
435 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
436 }
437 }
438
439 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700440 protected void updateMetaUi(ObjectNode payload) {
441 metaUi.put(JsonUtils.string(payload, "id"),
442 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700443 }
444
445 // Returns summary response.
446 protected ObjectNode summmaryMessage(long sid) {
447 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700448 return JsonUtils.envelope("showSummary", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700449 json("ONOS Summary", "node",
450 new Prop("Devices", format(topology.deviceCount())),
451 new Prop("Links", format(topology.linkCount())),
452 new Prop("Hosts", format(hostService.getHostCount())),
453 new Prop("Topology SCCs", format(topology.clusterCount())),
454 new Separator(),
455 new Prop("Intents", format(intentService.getIntentCount())),
cheng fan35dc0f22015-06-10 06:02:47 +0800456 new Prop("Tunnels", format(tunnelService.tunnelCount())),
Simon Hunta0ddb022015-05-01 09:53:01 -0700457 new Prop("Flows", format(flowService.getFlowRuleCount())),
458 new Prop("Version", version)));
Thomas Vachuska329af532015-03-10 02:08:33 -0700459 }
460
461 // Returns device details response.
462 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
463 Device device = deviceService.getDevice(deviceId);
464 Annotations annot = device.annotations();
465 String name = annot.value(AnnotationKeys.NAME);
466 int portCount = deviceService.getPorts(deviceId).size();
467 int flowCount = getFlowCount(deviceId);
cheng fan35dc0f22015-06-10 06:02:47 +0800468 int tunnelCount = getTunnelCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700469 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700470 json(isNullOrEmpty(name) ? deviceId.toString() : name,
471 device.type().toString().toLowerCase(),
472 new Prop("URI", deviceId.toString()),
473 new Prop("Vendor", device.manufacturer()),
474 new Prop("H/W Version", device.hwVersion()),
475 new Prop("S/W Version", device.swVersion()),
476 new Prop("Serial Number", device.serialNumber()),
477 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
478 new Separator(),
479 new Prop("Master", master(deviceId)),
480 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
481 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
482 new Separator(),
483 new Prop("Ports", Integer.toString(portCount)),
cheng fan35dc0f22015-06-10 06:02:47 +0800484 new Prop("Flows", Integer.toString(flowCount)),
485 new Prop("Tunnels", Integer.toString(tunnelCount))
486 ));
Thomas Vachuska329af532015-03-10 02:08:33 -0700487 }
488
489 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
cheng fan35dc0f22015-06-10 06:02:47 +0800499 protected int getTunnelCount(DeviceId deviceId) {
500 int count = 0;
501 Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
502 for (Tunnel tunnel : tunnels) {
503 OpticalTunnelEndPoint src = (OpticalTunnelEndPoint) tunnel.src();
504 OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst();
505 DeviceId srcDevice = (DeviceId) src.elementId().get();
506 DeviceId dstDevice = (DeviceId) dst.elementId().get();
507 if (srcDevice.toString().equals(deviceId.toString())
508 || dstDevice.toString().equals(deviceId.toString())) {
509 count++;
510 }
511 }
512 return count;
513 }
514
Thomas Vachuska329af532015-03-10 02:08:33 -0700515 // Counts all entries that egress on the given device links.
516 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
517 List<FlowEntry> entries = new ArrayList<>();
518 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
519 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
520 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
521 while (it.hasNext()) {
522 entries.add(it.next());
523 }
524
525 // Add all edge links to the set
526 if (hosts != null) {
527 for (Host host : hosts) {
528 links.add(new DefaultEdgeLink(host.providerId(),
529 new ConnectPoint(host.id(), P0),
530 host.location(), false));
531 }
532 }
533
534 Map<Link, Integer> counts = new HashMap<>();
535 for (Link link : links) {
536 counts.put(link, getEgressFlows(link, entries));
537 }
538 return counts;
539 }
540
541 // Counts all entries that egress on the link source port.
542 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
543 int count = 0;
544 PortNumber out = link.src().port();
545 for (FlowEntry entry : entries) {
546 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700547 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700548 if (instruction.type() == Instruction.Type.OUTPUT &&
549 ((OutputInstruction) instruction).port().equals(out)) {
550 count++;
551 }
552 }
553 }
554 return count;
555 }
556
557
558 // Returns host details response.
559 protected ObjectNode hostDetails(HostId hostId, long sid) {
560 Host host = hostService.getHost(hostId);
561 Annotations annot = host.annotations();
562 String type = annot.value(AnnotationKeys.TYPE);
563 String name = annot.value(AnnotationKeys.NAME);
564 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700565 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700566 json(isNullOrEmpty(name) ? hostId.toString() : name,
567 isNullOrEmpty(type) ? "endstation" : type,
568 new Prop("MAC", host.mac().toString()),
569 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
570 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
571 new Separator(),
572 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
573 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700574 }
575
576
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700577 // Produces JSON message to trigger flow traffic overview visualization
578 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700579 ObjectNode payload = objectNode();
580 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700581 payload.set("paths", paths);
582
Simon Huntda580882015-05-12 20:58:18 -0700583 ObjectNode pathNodeN = objectNode();
584 ArrayNode linksNodeN = arrayNode();
585 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700586
587 pathNodeN.put("class", "plain").put("traffic", false);
588 pathNodeN.set("links", linksNodeN);
589 pathNodeN.set("labels", labelsN);
590 paths.add(pathNodeN);
591
Simon Huntda580882015-05-12 20:58:18 -0700592 ObjectNode pathNodeT = objectNode();
593 ArrayNode linksNodeT = arrayNode();
594 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700595
596 pathNodeT.put("class", "secondary").put("traffic", true);
597 pathNodeT.set("links", linksNodeT);
598 pathNodeT.set("labels", labelsT);
599 paths.add(pathNodeT);
600
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700601 Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
602 addEdgeLinks(biLinks);
603 for (BiLink link : biLinks.values()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700604 boolean bi = link.two != null;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700605 if (type == FLOW) {
606 link.addLoad(getLinkLoad(link.one));
607 link.addLoad(bi ? getLinkLoad(link.two) : null);
608 } else if (type == PORT) {
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700609 //For a bi-directional traffic links, use
610 //the max link rate of either direction
611 link.addLoad(portStatsService.load(link.one.src()),
612 BPS_THRESHOLD,
613 portStatsService.load(link.one.dst()),
614 BPS_THRESHOLD);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700615 }
616 if (link.hasTraffic) {
617 linksNodeT.add(compactLinkString(link.one));
618 labelsT.add(type == PORT ?
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700619 formatBitRate(link.rate) + "ps" :
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700620 formatBytes(link.bytes));
621 } else {
622 linksNodeN.add(compactLinkString(link.one));
623 labelsN.add("");
Thomas Vachuska329af532015-03-10 02:08:33 -0700624 }
625 }
Simon Huntd2747a02015-04-30 22:41:16 -0700626 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700627 }
628
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700629 private Load getLinkLoad(Link link) {
630 if (link.src().elementId() instanceof DeviceId) {
631 return flowStatsService.load(link);
632 }
633 return null;
634 }
635
636 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
637 hostService.getHosts().forEach(host -> {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700638 addLink(biLinks, createEdgeLink(host, true));
639 addLink(biLinks, createEdgeLink(host, false));
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700640 });
641 }
642
643 private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700644 Map<LinkKey, BiLink> biLinks = new HashMap<>();
645 for (Link link : links) {
646 addLink(biLinks, link);
647 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700648 return biLinks;
Thomas Vachuska329af532015-03-10 02:08:33 -0700649 }
650
651 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700652 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700653 ObjectNode payload = objectNode();
654 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700655 payload.set("paths", paths);
656
657 for (Device device : devices) {
658 Map<Link, Integer> counts = getFlowCounts(device.id());
659 for (Link link : counts.keySet()) {
660 addLinkFlows(link, paths, counts.get(link));
661 }
662 }
Simon Huntd2747a02015-04-30 22:41:16 -0700663 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700664 }
665
666 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700667 ObjectNode pathNode = objectNode();
668 ArrayNode linksNode = arrayNode();
669 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700670 boolean noFlows = count == null || count == 0;
671 pathNode.put("class", noFlows ? "secondary" : "primary");
672 pathNode.put("traffic", false);
673 pathNode.set("links", linksNode.add(compactLinkString(link)));
674 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
675 (count == 1 ? " flow" : " flows"))));
676 paths.add(pathNode);
677 }
678
679
680 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700681 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700682 ObjectNode payload = objectNode();
683 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700684 payload.set("paths", paths);
685
686 // Classify links based on their traffic traffic first...
687 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
688
689 // Then separate the links into their respective classes and send them out.
690 Map<String, ObjectNode> pathNodes = new HashMap<>();
691 for (BiLink biLink : biLinks.values()) {
692 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700693 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700694 ObjectNode pathNode = pathNodes.get(tc);
695 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700696 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700697 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700698 pathNode.set("links", arrayNode());
699 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700700 pathNodes.put(tc, pathNode);
701 paths.add(pathNode);
702 }
703 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
704 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
705 }
706
Simon Huntd2747a02015-04-30 22:41:16 -0700707 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700708 }
709
710 // Classifies the link traffic according to the specified classes.
711 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
712 Map<LinkKey, BiLink> biLinks = new HashMap<>();
713 for (TrafficClass trafficClass : trafficClasses) {
714 for (Intent intent : trafficClass.intents) {
715 boolean isOptical = intent instanceof OpticalConnectivityIntent;
716 List<Intent> installables = intentService.getInstallableIntents(intent.key());
717 if (installables != null) {
718 for (Intent installable : installables) {
719 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
720 if (installable instanceof PathIntent) {
721 classifyLinks(type, biLinks, trafficClass.showTraffic,
722 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700723 } else if (installable instanceof FlowRuleIntent) {
724 classifyLinks(type, biLinks, trafficClass.showTraffic,
725 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700726 } else if (installable instanceof LinkCollectionIntent) {
727 classifyLinks(type, biLinks, trafficClass.showTraffic,
728 ((LinkCollectionIntent) installable).links());
729 } else if (installable instanceof OpticalPathIntent) {
730 classifyLinks(type, biLinks, trafficClass.showTraffic,
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700731 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska329af532015-03-10 02:08:33 -0700732 }
733 }
734 }
735 }
736 }
737 return biLinks;
738 }
739
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700740 // Extracts links from the specified flow rule intent resources
741 private Collection<Link> linkResources(Intent installable) {
742 ImmutableList.Builder<Link> builder = ImmutableList.builder();
743 for (NetworkResource r : installable.resources()) {
744 if (r instanceof Link) {
745 builder.add((Link) r);
746 }
747 }
748 return builder.build();
749 }
750
Thomas Vachuska329af532015-03-10 02:08:33 -0700751
752 // Adds the link segments (path or tree) associated with the specified
753 // connectivity intent
754 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
755 boolean showTraffic, Iterable<Link> links) {
756 if (links != null) {
757 for (Link link : links) {
758 BiLink biLink = addLink(biLinks, link);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700759 if (showTraffic) {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700760 biLink.addLoad(getLinkLoad(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700761 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700762 biLink.addClass(type);
Thomas Vachuska329af532015-03-10 02:08:33 -0700763 }
764 }
765 }
766
767
Thomas Vachuska583bc632015-04-14 10:10:57 -0700768 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700769 LinkKey key = canonicalLinkKey(link);
770 BiLink biLink = biLinks.get(key);
771 if (biLink != null) {
772 biLink.setOther(link);
773 } else {
774 biLink = new BiLink(key, link);
775 biLinks.put(key, biLink);
776 }
777 return biLink;
778 }
779
Thomas Vachuska329af532015-03-10 02:08:33 -0700780 // Poor-mans formatting to get the labels with byte counts looking nice.
781 private String formatBytes(long bytes) {
782 String unit;
783 double value;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700784 if (bytes > GIGA) {
785 value = bytes / GIGA;
786 unit = GBYTES_UNIT;
787 } else if (bytes > MEGA) {
788 value = bytes / MEGA;
789 unit = MBYTES_UNIT;
790 } else if (bytes > KILO) {
791 value = bytes / KILO;
792 unit = KBYTES_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700793 } else {
794 value = bytes;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700795 unit = BYTES_UNIT;
796 }
797 DecimalFormat format = new DecimalFormat("#,###.##");
798 return format.format(value) + " " + unit;
799 }
800
Simon Hunt80170a72015-06-10 19:49:57 -0700801 // Poor-mans formatting to get the labels with bit rate looking nice.
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700802 private String formatBitRate(long bytes) {
803 String unit;
804 double value;
805 //Convert to bits
806 long bits = bytes * 8;
807 if (bits > GIGA) {
808 value = bits / GIGA;
809 unit = GBITS_UNIT;
Simon Hunt80170a72015-06-10 19:49:57 -0700810
811 // NOTE: temporary hack to clip rate at 10.0 Gbps
812 // Added for the CORD Fabric demo at ONS 2015
813 if (value > 10.0) {
814 value = 10.0;
815 }
816
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700817 } else if (bits > MEGA) {
818 value = bits / MEGA;
819 unit = MBITS_UNIT;
820 } else if (bits > KILO) {
821 value = bits / KILO;
822 unit = KBITS_UNIT;
823 } else {
824 value = bits;
825 unit = BITS_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700826 }
827 DecimalFormat format = new DecimalFormat("#,###.##");
828 return format.format(value) + " " + unit;
829 }
830
831 // Formats the given number into a string.
832 private String format(Number number) {
833 DecimalFormat format = new DecimalFormat("#,###");
834 return format.format(number);
835 }
836
Thomas Vachuska329af532015-03-10 02:08:33 -0700837 // Produces compact string representation of a link.
838 private static String compactLinkString(Link link) {
839 return String.format(COMPACT, link.src().elementId(), link.src().port(),
840 link.dst().elementId(), link.dst().port());
841 }
842
843 // Produces JSON property details.
844 private ObjectNode json(String id, String type, Prop... props) {
Simon Huntda580882015-05-12 20:58:18 -0700845 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700846 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700847 ObjectNode pnode = objectNode();
848 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700849 for (Prop p : props) {
850 porder.add(p.key);
851 pnode.put(p.key, p.value);
852 }
853 result.set("propOrder", porder);
854 result.set("props", pnode);
855 return result;
856 }
857
858 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700859 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700860 String sn = link.src().elementId().toString();
861 String dn = link.dst().elementId().toString();
862 return sn.compareTo(dn) < 0 ?
863 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
864 }
865
866 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700867 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700868 public final LinkKey key;
869 public final Link one;
870 public Link two;
871 public boolean hasTraffic = false;
872 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700873
874 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700875 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700876
877 BiLink(LinkKey key, Link link) {
878 this.key = key;
879 this.one = link;
880 }
881
882 void setOther(Link link) {
883 this.two = link;
884 }
885
886 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700887 addLoad(load, 0);
888 }
889
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700890 void addLoad(Load load, double threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700891 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700892 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700893 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700894 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700895 }
896 }
897
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700898 void addLoad(Load srcLinkLoad,
899 double srcLinkThreshold,
900 Load dstLinkLoad,
901 double dstLinkThreshold) {
902 //use the max of link load at source or destination
903 if (srcLinkLoad != null) {
904 this.hasTraffic = hasTraffic || srcLinkLoad.rate() > srcLinkThreshold;
905 this.bytes = srcLinkLoad.latest();
906 this.rate = srcLinkLoad.rate();
907 }
908
909 if (dstLinkLoad != null) {
910 if (dstLinkLoad.rate() > this.rate) {
911 this.bytes = dstLinkLoad.latest();
912 this.rate = dstLinkLoad.rate();
913 this.hasTraffic = hasTraffic || dstLinkLoad.rate() > dstLinkThreshold;
914 }
915 }
916 }
917
Thomas Vachuska329af532015-03-10 02:08:33 -0700918 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700919 classes.add(trafficClass);
920 }
921
922 String classes() {
923 StringBuilder sb = new StringBuilder();
924 classes.forEach(c -> sb.append(c).append(" "));
925 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700926 }
927 }
928
929 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700930 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700931 public final String key;
932 public final String value;
933
934 protected Prop(String key, String value) {
935 this.key = key;
936 this.value = value;
937 }
938 }
939
940 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700941 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700942 protected Separator() {
943 super("-", "");
944 }
945 }
946
947 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700948 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700949 public final boolean showTraffic;
950 public final String type;
951 public final Iterable<Intent> intents;
952
953 TrafficClass(String type, Iterable<Intent> intents) {
954 this(type, intents, false);
955 }
956
957 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
958 this.type = type;
959 this.intents = intents;
960 this.showTraffic = showTraffic;
961 }
962 }
963
964}