blob: 017925b3205f49822963303b75648116d429c7ed [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;
Simon Hunt0af1ec32015-07-24 12:17:55 -070075import org.onosproject.ui.topo.PropertyPanel;
Thomas Vachuska329af532015-03-10 02:08:33 -070076import org.slf4j.Logger;
77import org.slf4j.LoggerFactory;
78
79import java.text.DecimalFormat;
80import java.util.ArrayList;
81import java.util.Collection;
82import java.util.Collections;
83import java.util.HashMap;
84import java.util.HashSet;
85import java.util.Iterator;
86import java.util.List;
87import java.util.Map;
88import java.util.Set;
89import java.util.concurrent.ConcurrentHashMap;
90
91import static com.google.common.base.Preconditions.checkNotNull;
92import static com.google.common.base.Strings.isNullOrEmpty;
93import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
94import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
95import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -070096import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
Thomas Vachuska329af532015-03-10 02:08:33 -070097import static org.onosproject.net.DeviceId.deviceId;
98import static org.onosproject.net.HostId.hostId;
99import static org.onosproject.net.LinkKey.linkKey;
100import static org.onosproject.net.PortNumber.P0;
101import static org.onosproject.net.PortNumber.portNumber;
102import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
103import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
104import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
105import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
106import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
107import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700108import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
109import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700110
111/**
112 * Facility for creating messages bound for the topology viewer.
113 */
Simon Hunta0ddb022015-05-01 09:53:01 -0700114public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700115
Simon Huntd2747a02015-04-30 22:41:16 -0700116 protected static final Logger log =
117 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700118
Simon Huntd2747a02015-04-30 22:41:16 -0700119 private static final ProviderId PID =
120 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700121 private static final String COMPACT = "%s/%s-%s/%s";
122
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700123 private static final double KILO = 1024;
124 private static final double MEGA = 1024 * KILO;
125 private static final double GIGA = 1024 * MEGA;
Thomas Vachuska329af532015-03-10 02:08:33 -0700126
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700127 private static final String GBITS_UNIT = "Gb";
128 private static final String MBITS_UNIT = "Mb";
129 private static final String KBITS_UNIT = "Kb";
130 private static final String BITS_UNIT = "b";
131 private static final String GBYTES_UNIT = "GB";
132 private static final String MBYTES_UNIT = "MB";
133 private static final String KBYTES_UNIT = "KB";
134 private static final String BYTES_UNIT = "B";
135 //4 Kilo Bytes as threshold
136 private static final double BPS_THRESHOLD = 4 * KILO;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700137
Thomas Vachuska329af532015-03-10 02:08:33 -0700138 protected ServiceDirectory directory;
139 protected ClusterService clusterService;
140 protected DeviceService deviceService;
141 protected LinkService linkService;
142 protected HostService hostService;
143 protected MastershipService mastershipService;
144 protected IntentService intentService;
145 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700146 protected StatisticService flowStatsService;
147 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700148 protected TopologyService topologyService;
cheng fan35dc0f22015-06-10 06:02:47 +0800149 protected TunnelService tunnelService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700150
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700151 protected enum StatsType {
152 FLOW, PORT
153 }
154
Thomas Vachuska329af532015-03-10 02:08:33 -0700155 private String version;
156
157 // TODO: extract into an external & durable state; good enough for now and demo
158 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
159
160 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700161 * Returns read-only view of the meta-ui information.
162 *
163 * @return map of id to meta-ui mementos
164 */
165 static Map<String, ObjectNode> getMetaUi() {
166 return Collections.unmodifiableMap(metaUi);
167 }
168
169 @Override
170 public void init(UiConnection connection, ServiceDirectory directory) {
171 super.init(connection, directory);
172 this.directory = checkNotNull(directory, "Directory cannot be null");
173 clusterService = directory.get(ClusterService.class);
174 deviceService = directory.get(DeviceService.class);
175 linkService = directory.get(LinkService.class);
176 hostService = directory.get(HostService.class);
177 mastershipService = directory.get(MastershipService.class);
178 intentService = directory.get(IntentService.class);
179 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700180 flowStatsService = directory.get(StatisticService.class);
181 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700182 topologyService = directory.get(TopologyService.class);
cheng fan35dc0f22015-06-10 06:02:47 +0800183 tunnelService = directory.get(TunnelService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700184
185 String ver = directory.get(CoreService.class).version().toString();
186 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
187 }
188
Thomas Vachuska329af532015-03-10 02:08:33 -0700189 // Returns the specified set of IP addresses as a string.
190 private String ip(Set<IpAddress> ipAddresses) {
191 Iterator<IpAddress> it = ipAddresses.iterator();
192 return it.hasNext() ? it.next().toString() : "unknown";
193 }
194
195 // Produces JSON structure from annotations.
196 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700197 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700198 if (annotations != null) {
199 for (String key : annotations.keys()) {
200 props.put(key, annotations.value(key));
201 }
202 }
203 return props;
204 }
205
206 // Produces an informational log message event bound to the client.
207 protected ObjectNode info(long id, String message) {
208 return message("info", id, message);
209 }
210
211 // Produces a warning log message event bound to the client.
212 protected ObjectNode warning(long id, String message) {
213 return message("warning", id, message);
214 }
215
216 // Produces an error log message event bound to the client.
217 protected ObjectNode error(long id, String message) {
218 return message("error", id, message);
219 }
220
221 // Produces a log message event bound to the client.
222 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700223 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700224 .put("severity", severity)
225 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700226
Simon Huntd2747a02015-04-30 22:41:16 -0700227 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700228 }
229
230 // Produces a set of all hosts listed in the specified JSON array.
231 protected Set<Host> getHosts(ArrayNode array) {
232 Set<Host> hosts = new HashSet<>();
233 if (array != null) {
234 for (JsonNode node : array) {
235 try {
236 addHost(hosts, hostId(node.asText()));
237 } catch (IllegalArgumentException e) {
238 log.debug("Skipping ID {}", node.asText());
239 }
240 }
241 }
242 return hosts;
243 }
244
245 // Adds the specified host to the set of hosts.
246 private void addHost(Set<Host> hosts, HostId hostId) {
247 Host host = hostService.getHost(hostId);
248 if (host != null) {
249 hosts.add(host);
250 }
251 }
252
253
254 // Produces a set of all devices listed in the specified JSON array.
255 protected Set<Device> getDevices(ArrayNode array) {
256 Set<Device> devices = new HashSet<>();
257 if (array != null) {
258 for (JsonNode node : array) {
259 try {
260 addDevice(devices, deviceId(node.asText()));
261 } catch (IllegalArgumentException e) {
262 log.debug("Skipping ID {}", node.asText());
263 }
264 }
265 }
266 return devices;
267 }
268
269 private void addDevice(Set<Device> devices, DeviceId deviceId) {
270 Device device = deviceService.getDevice(deviceId);
271 if (device != null) {
272 devices.add(device);
273 }
274 }
275
276 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
277 try {
278 addHost(hosts, hostId(hover));
279 } catch (IllegalArgumentException e) {
280 try {
281 addDevice(devices, deviceId(hover));
282 } catch (IllegalArgumentException ne) {
283 log.debug("Skipping ID {}", hover);
284 }
285 }
286 }
287
288 // Produces a cluster instance message to the client.
289 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
290 ControllerNode node = event.subject();
291 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700292 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700293 .put("id", node.id().toString())
294 .put("ip", node.ip().toString())
295 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700296 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700297 .put("switches", switchCount);
298
Simon Huntda580882015-05-12 20:58:18 -0700299 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700300 labels.add(node.id().toString());
301 labels.add(node.ip().toString());
302
303 // Add labels, props and stuff the payload into envelope.
304 payload.set("labels", labels);
305 addMetaUi(node.id().toString(), payload);
306
307 String type = messageType != null ? messageType :
308 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
309 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
310 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700311 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700312 }
313
314 // Produces a device event message to the client.
315 protected ObjectNode deviceMessage(DeviceEvent event) {
316 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700317 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700318 .put("id", device.id().toString())
319 .put("type", device.type().toString().toLowerCase())
320 .put("online", deviceService.isAvailable(device.id()))
321 .put("master", master(device.id()));
322
323 // Generate labels: id, chassis id, no-label, optional-name
324 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700325 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700326 labels.add("");
327 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
328 labels.add(device.id().toString());
329
330 // Add labels, props and stuff the payload into envelope.
331 payload.set("labels", labels);
332 payload.set("props", props(device.annotations()));
333 addGeoLocation(device, payload);
334 addMetaUi(device.id().toString(), payload);
335
336 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
337 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700338 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700339 }
340
341 // Produces a link event message to the client.
342 protected ObjectNode linkMessage(LinkEvent event) {
343 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700344 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700345 .put("id", compactLinkString(link))
346 .put("type", link.type().toString().toLowerCase())
347 .put("online", link.state() == Link.State.ACTIVE)
348 .put("linkWidth", 1.2)
349 .put("src", link.src().deviceId().toString())
350 .put("srcPort", link.src().port().toString())
351 .put("dst", link.dst().deviceId().toString())
352 .put("dstPort", link.dst().port().toString());
353 String type = (event.type() == LINK_ADDED) ? "addLink" :
354 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700355 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700356 }
357
358 // Produces a host event message to the client.
359 protected ObjectNode hostMessage(HostEvent event) {
360 Host host = event.subject();
361 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700362 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700363 .put("id", host.id().toString())
364 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
365 .put("ingress", compactLinkString(edgeLink(host, true)))
366 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700367 payload.set("cp", hostConnect(host.location()));
368 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700369 host.mac().toString()));
370 payload.set("props", props(host.annotations()));
371 addGeoLocation(host, payload);
372 addMetaUi(host.id().toString(), payload);
373
374 String type = (event.type() == HOST_ADDED) ? "addHost" :
375 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700376 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700377 }
378
379 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700380 private ObjectNode hostConnect(HostLocation location) {
381 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700382 .put("device", location.deviceId().toString())
383 .put("port", location.port().toLong());
384 }
385
386 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700387 private ArrayNode labels(String... labels) {
388 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700389 for (String label : labels) {
390 json.add(label);
391 }
392 return json;
393 }
394
395 // Returns the name of the master node for the specified device id.
396 private String master(DeviceId deviceId) {
397 NodeId master = mastershipService.getMasterFor(deviceId);
398 return master != null ? master.toString() : "";
399 }
400
401 // Generates an edge link from the specified host location.
402 private EdgeLink edgeLink(Host host, boolean ingress) {
403 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
404 host.location(), ingress);
405 }
406
407 // Adds meta UI information for the specified object.
408 private void addMetaUi(String id, ObjectNode payload) {
409 ObjectNode meta = metaUi.get(id);
410 if (meta != null) {
411 payload.set("metaUi", meta);
412 }
413 }
414
415 // Adds a geo location JSON to the specified payload object.
416 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
417 Annotations annotations = annotated.annotations();
418 if (annotations == null) {
419 return;
420 }
421
422 String slat = annotations.value(AnnotationKeys.LATITUDE);
423 String slng = annotations.value(AnnotationKeys.LONGITUDE);
424 try {
425 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
426 double lat = Double.parseDouble(slat);
427 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700428 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700429 .put("type", "latlng").put("lat", lat).put("lng", lng);
430 payload.set("location", loc);
431 }
432 } catch (NumberFormatException e) {
433 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
434 }
435 }
436
437 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700438 protected void updateMetaUi(ObjectNode payload) {
439 metaUi.put(JsonUtils.string(payload, "id"),
440 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700441 }
442
Simon Huntb745ca62015-07-28 15:37:11 -0700443 // -----------------------------------------------------------------------
444 // Create models of the data to return, that overlays can adjust / augment
445
446 // Returns property panel model for summary response.
Simon Hunt0af1ec32015-07-24 12:17:55 -0700447 protected PropertyPanel summmaryMessage(long sid) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700448 Topology topology = topologyService.currentTopology();
Simon Hunt0af1ec32015-07-24 12:17:55 -0700449
Simon Hunt00a27ff2015-07-28 18:53:40 -0700450 return new PropertyPanel("ONOS Summary", "node")
451 .addProp("Devices", topology.deviceCount())
452 .addProp("Links", topology.linkCount())
453 .addProp("Hosts", hostService.getHostCount())
454 .addProp("Topology SCCs", topology.clusterCount())
455 .addSeparator()
456 .addProp("Intents", intentService.getIntentCount())
457 .addProp("Tunnels", tunnelService.tunnelCount())
458 .addProp("Flows", flowService.getFlowRuleCount())
459 .addProp("Version", version);
Thomas Vachuska329af532015-03-10 02:08:33 -0700460 }
461
Simon Huntb745ca62015-07-28 15:37:11 -0700462 // Returns property panel model for device details response.
463 protected PropertyPanel deviceDetails(DeviceId deviceId, long sid) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700464 Device device = deviceService.getDevice(deviceId);
465 Annotations annot = device.annotations();
466 String name = annot.value(AnnotationKeys.NAME);
467 int portCount = deviceService.getPorts(deviceId).size();
468 int flowCount = getFlowCount(deviceId);
cheng fan35dc0f22015-06-10 06:02:47 +0800469 int tunnelCount = getTunnelCount(deviceId);
Simon Huntb745ca62015-07-28 15:37:11 -0700470
471 String title = isNullOrEmpty(name) ? deviceId.toString() : name;
472 String typeId = device.type().toString().toLowerCase();
473
474 PropertyPanel pp = new PropertyPanel(title, typeId)
475 .id(deviceId.toString())
Simon Hunt00a27ff2015-07-28 18:53:40 -0700476 .addProp("URI", deviceId.toString())
477 .addProp("Vendor", device.manufacturer())
478 .addProp("H/W Version", device.hwVersion())
479 .addProp("S/W Version", device.swVersion())
480 .addProp("Serial Number", device.serialNumber())
481 .addProp("Protocol", annot.value(AnnotationKeys.PROTOCOL))
482 .addSeparator()
483 .addProp("Latitude", annot.value(AnnotationKeys.LATITUDE))
484 .addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE))
485 .addSeparator()
486 .addProp("Ports", portCount)
487 .addProp("Flows", flowCount)
488 .addProp("Tunnels", tunnelCount);
489
490 // TODO: add button descriptors
Simon Huntb745ca62015-07-28 15:37:11 -0700491
492 return pp;
Thomas Vachuska329af532015-03-10 02:08:33 -0700493 }
494
495 protected int getFlowCount(DeviceId deviceId) {
496 int count = 0;
Simon Huntb745ca62015-07-28 15:37:11 -0700497 for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700498 count++;
Thomas Vachuska329af532015-03-10 02:08:33 -0700499 }
500 return count;
501 }
502
cheng fan35dc0f22015-06-10 06:02:47 +0800503 protected int getTunnelCount(DeviceId deviceId) {
504 int count = 0;
505 Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
506 for (Tunnel tunnel : tunnels) {
507 OpticalTunnelEndPoint src = (OpticalTunnelEndPoint) tunnel.src();
508 OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst();
509 DeviceId srcDevice = (DeviceId) src.elementId().get();
510 DeviceId dstDevice = (DeviceId) dst.elementId().get();
Simon Huntb745ca62015-07-28 15:37:11 -0700511 if (srcDevice.toString().equals(deviceId.toString()) ||
512 dstDevice.toString().equals(deviceId.toString())) {
cheng fan35dc0f22015-06-10 06:02:47 +0800513 count++;
514 }
515 }
516 return count;
517 }
518
Thomas Vachuska329af532015-03-10 02:08:33 -0700519 // Counts all entries that egress on the given device links.
520 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
521 List<FlowEntry> entries = new ArrayList<>();
522 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
523 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
Simon Huntb745ca62015-07-28 15:37:11 -0700524 for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
525 entries.add(flowEntry);
Thomas Vachuska329af532015-03-10 02:08:33 -0700526 }
527
528 // Add all edge links to the set
529 if (hosts != null) {
530 for (Host host : hosts) {
531 links.add(new DefaultEdgeLink(host.providerId(),
532 new ConnectPoint(host.id(), P0),
533 host.location(), false));
534 }
535 }
536
537 Map<Link, Integer> counts = new HashMap<>();
538 for (Link link : links) {
539 counts.put(link, getEgressFlows(link, entries));
540 }
541 return counts;
542 }
543
544 // Counts all entries that egress on the link source port.
545 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
546 int count = 0;
547 PortNumber out = link.src().port();
548 for (FlowEntry entry : entries) {
549 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700550 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700551 if (instruction.type() == Instruction.Type.OUTPUT &&
552 ((OutputInstruction) instruction).port().equals(out)) {
553 count++;
554 }
555 }
556 }
557 return count;
558 }
559
560
561 // Returns host details response.
Simon Huntb745ca62015-07-28 15:37:11 -0700562 protected PropertyPanel hostDetails(HostId hostId, long sid) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700563 Host host = hostService.getHost(hostId);
564 Annotations annot = host.annotations();
565 String type = annot.value(AnnotationKeys.TYPE);
566 String name = annot.value(AnnotationKeys.NAME);
567 String vlan = host.vlan().toString();
Simon Huntb745ca62015-07-28 15:37:11 -0700568
569 String title = isNullOrEmpty(name) ? hostId.toString() : name;
570 String typeId = isNullOrEmpty(type) ? "endstation" : type;
571
572 PropertyPanel pp = new PropertyPanel(title, typeId)
573 .id(hostId.toString())
Simon Hunt00a27ff2015-07-28 18:53:40 -0700574 .addProp("MAC", host.mac())
575 .addProp("IP", host.ipAddresses(), "[\\[\\]]")
576 .addProp("VLAN", vlan.equals("-1") ? "none" : vlan)
577 .addSeparator()
578 .addProp("Latitude", annot.value(AnnotationKeys.LATITUDE))
579 .addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE));
Simon Huntb745ca62015-07-28 15:37:11 -0700580
Simon Hunt00a27ff2015-07-28 18:53:40 -0700581 // TODO: add button descriptors
Simon Huntb745ca62015-07-28 15:37:11 -0700582 return pp;
Thomas Vachuska329af532015-03-10 02:08:33 -0700583 }
584
585
Simon Huntb745ca62015-07-28 15:37:11 -0700586 // TODO: migrate to Traffic overlay
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700587 // Produces JSON message to trigger flow traffic overview visualization
588 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700589 ObjectNode payload = objectNode();
590 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700591 payload.set("paths", paths);
592
Simon Huntda580882015-05-12 20:58:18 -0700593 ObjectNode pathNodeN = objectNode();
594 ArrayNode linksNodeN = arrayNode();
595 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700596
597 pathNodeN.put("class", "plain").put("traffic", false);
598 pathNodeN.set("links", linksNodeN);
599 pathNodeN.set("labels", labelsN);
600 paths.add(pathNodeN);
601
Simon Huntda580882015-05-12 20:58:18 -0700602 ObjectNode pathNodeT = objectNode();
603 ArrayNode linksNodeT = arrayNode();
604 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700605
606 pathNodeT.put("class", "secondary").put("traffic", true);
607 pathNodeT.set("links", linksNodeT);
608 pathNodeT.set("labels", labelsT);
609 paths.add(pathNodeT);
610
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700611 Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
612 addEdgeLinks(biLinks);
613 for (BiLink link : biLinks.values()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700614 boolean bi = link.two != null;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700615 if (type == FLOW) {
616 link.addLoad(getLinkLoad(link.one));
617 link.addLoad(bi ? getLinkLoad(link.two) : null);
618 } else if (type == PORT) {
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700619 //For a bi-directional traffic links, use
620 //the max link rate of either direction
621 link.addLoad(portStatsService.load(link.one.src()),
622 BPS_THRESHOLD,
623 portStatsService.load(link.one.dst()),
624 BPS_THRESHOLD);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700625 }
626 if (link.hasTraffic) {
627 linksNodeT.add(compactLinkString(link.one));
628 labelsT.add(type == PORT ?
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700629 formatBitRate(link.rate) + "ps" :
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700630 formatBytes(link.bytes));
631 } else {
632 linksNodeN.add(compactLinkString(link.one));
633 labelsN.add("");
Thomas Vachuska329af532015-03-10 02:08:33 -0700634 }
635 }
Simon Huntd2747a02015-04-30 22:41:16 -0700636 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700637 }
638
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700639 private Load getLinkLoad(Link link) {
640 if (link.src().elementId() instanceof DeviceId) {
641 return flowStatsService.load(link);
642 }
643 return null;
644 }
645
646 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
647 hostService.getHosts().forEach(host -> {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700648 addLink(biLinks, createEdgeLink(host, true));
649 addLink(biLinks, createEdgeLink(host, false));
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700650 });
651 }
652
653 private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700654 Map<LinkKey, BiLink> biLinks = new HashMap<>();
655 for (Link link : links) {
656 addLink(biLinks, link);
657 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700658 return biLinks;
Thomas Vachuska329af532015-03-10 02:08:33 -0700659 }
660
661 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700662 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700663 ObjectNode payload = objectNode();
664 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700665 payload.set("paths", paths);
666
667 for (Device device : devices) {
668 Map<Link, Integer> counts = getFlowCounts(device.id());
669 for (Link link : counts.keySet()) {
670 addLinkFlows(link, paths, counts.get(link));
671 }
672 }
Simon Huntd2747a02015-04-30 22:41:16 -0700673 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700674 }
675
676 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700677 ObjectNode pathNode = objectNode();
678 ArrayNode linksNode = arrayNode();
679 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700680 boolean noFlows = count == null || count == 0;
681 pathNode.put("class", noFlows ? "secondary" : "primary");
682 pathNode.put("traffic", false);
683 pathNode.set("links", linksNode.add(compactLinkString(link)));
684 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
685 (count == 1 ? " flow" : " flows"))));
686 paths.add(pathNode);
687 }
688
689
690 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700691 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700692 ObjectNode payload = objectNode();
693 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700694 payload.set("paths", paths);
695
696 // Classify links based on their traffic traffic first...
697 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
698
699 // Then separate the links into their respective classes and send them out.
700 Map<String, ObjectNode> pathNodes = new HashMap<>();
701 for (BiLink biLink : biLinks.values()) {
702 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700703 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700704 ObjectNode pathNode = pathNodes.get(tc);
705 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700706 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700707 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700708 pathNode.set("links", arrayNode());
709 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700710 pathNodes.put(tc, pathNode);
711 paths.add(pathNode);
712 }
713 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
714 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
715 }
716
Simon Huntd2747a02015-04-30 22:41:16 -0700717 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700718 }
719
720 // Classifies the link traffic according to the specified classes.
721 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
722 Map<LinkKey, BiLink> biLinks = new HashMap<>();
723 for (TrafficClass trafficClass : trafficClasses) {
724 for (Intent intent : trafficClass.intents) {
725 boolean isOptical = intent instanceof OpticalConnectivityIntent;
726 List<Intent> installables = intentService.getInstallableIntents(intent.key());
727 if (installables != null) {
728 for (Intent installable : installables) {
729 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
730 if (installable instanceof PathIntent) {
731 classifyLinks(type, biLinks, trafficClass.showTraffic,
732 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700733 } else if (installable instanceof FlowRuleIntent) {
734 classifyLinks(type, biLinks, trafficClass.showTraffic,
735 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700736 } else if (installable instanceof LinkCollectionIntent) {
737 classifyLinks(type, biLinks, trafficClass.showTraffic,
738 ((LinkCollectionIntent) installable).links());
739 } else if (installable instanceof OpticalPathIntent) {
740 classifyLinks(type, biLinks, trafficClass.showTraffic,
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700741 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska329af532015-03-10 02:08:33 -0700742 }
743 }
744 }
745 }
746 }
747 return biLinks;
748 }
749
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700750 // Extracts links from the specified flow rule intent resources
751 private Collection<Link> linkResources(Intent installable) {
752 ImmutableList.Builder<Link> builder = ImmutableList.builder();
753 for (NetworkResource r : installable.resources()) {
754 if (r instanceof Link) {
755 builder.add((Link) r);
756 }
757 }
758 return builder.build();
759 }
760
Thomas Vachuska329af532015-03-10 02:08:33 -0700761
762 // Adds the link segments (path or tree) associated with the specified
763 // connectivity intent
764 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
765 boolean showTraffic, Iterable<Link> links) {
766 if (links != null) {
767 for (Link link : links) {
768 BiLink biLink = addLink(biLinks, link);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700769 if (showTraffic) {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700770 biLink.addLoad(getLinkLoad(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700771 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700772 biLink.addClass(type);
Thomas Vachuska329af532015-03-10 02:08:33 -0700773 }
774 }
775 }
776
777
Thomas Vachuska583bc632015-04-14 10:10:57 -0700778 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700779 LinkKey key = canonicalLinkKey(link);
780 BiLink biLink = biLinks.get(key);
781 if (biLink != null) {
782 biLink.setOther(link);
783 } else {
784 biLink = new BiLink(key, link);
785 biLinks.put(key, biLink);
786 }
787 return biLink;
788 }
789
Thomas Vachuska329af532015-03-10 02:08:33 -0700790 // Poor-mans formatting to get the labels with byte counts looking nice.
791 private String formatBytes(long bytes) {
792 String unit;
793 double value;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700794 if (bytes > GIGA) {
795 value = bytes / GIGA;
796 unit = GBYTES_UNIT;
797 } else if (bytes > MEGA) {
798 value = bytes / MEGA;
799 unit = MBYTES_UNIT;
800 } else if (bytes > KILO) {
801 value = bytes / KILO;
802 unit = KBYTES_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700803 } else {
804 value = bytes;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700805 unit = BYTES_UNIT;
806 }
807 DecimalFormat format = new DecimalFormat("#,###.##");
808 return format.format(value) + " " + unit;
809 }
810
Simon Hunt80170a72015-06-10 19:49:57 -0700811 // Poor-mans formatting to get the labels with bit rate looking nice.
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700812 private String formatBitRate(long bytes) {
813 String unit;
814 double value;
815 //Convert to bits
816 long bits = bytes * 8;
817 if (bits > GIGA) {
818 value = bits / GIGA;
819 unit = GBITS_UNIT;
Simon Hunt80170a72015-06-10 19:49:57 -0700820
821 // NOTE: temporary hack to clip rate at 10.0 Gbps
822 // Added for the CORD Fabric demo at ONS 2015
823 if (value > 10.0) {
824 value = 10.0;
825 }
826
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700827 } else if (bits > MEGA) {
828 value = bits / MEGA;
829 unit = MBITS_UNIT;
830 } else if (bits > KILO) {
831 value = bits / KILO;
832 unit = KBITS_UNIT;
833 } else {
834 value = bits;
835 unit = BITS_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700836 }
837 DecimalFormat format = new DecimalFormat("#,###.##");
838 return format.format(value) + " " + unit;
839 }
840
Thomas Vachuska329af532015-03-10 02:08:33 -0700841 // Produces compact string representation of a link.
842 private static String compactLinkString(Link link) {
843 return String.format(COMPACT, link.src().elementId(), link.src().port(),
844 link.dst().elementId(), link.dst().port());
845 }
846
Simon Huntb745ca62015-07-28 15:37:11 -0700847 // translates the property panel into JSON, for returning to the client
Simon Hunt0af1ec32015-07-24 12:17:55 -0700848 protected ObjectNode json(PropertyPanel pp) {
849 ObjectNode result = objectNode()
Simon Huntb745ca62015-07-28 15:37:11 -0700850 .put("title", pp.title())
851 .put("type", pp.typeId())
852 .put("id", pp.id());
853
Simon Hunt0af1ec32015-07-24 12:17:55 -0700854 ObjectNode pnode = objectNode();
855 ArrayNode porder = arrayNode();
856 for (PropertyPanel.Prop p : pp.properties()) {
857 porder.add(p.key());
858 pnode.put(p.key(), p.value());
859 }
860 result.set("propOrder", porder);
861 result.set("props", pnode);
862 return result;
863 }
864
Thomas Vachuska329af532015-03-10 02:08:33 -0700865 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700866 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700867 String sn = link.src().elementId().toString();
868 String dn = link.dst().elementId().toString();
869 return sn.compareTo(dn) < 0 ?
870 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
871 }
872
873 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700874 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700875 public final LinkKey key;
876 public final Link one;
877 public Link two;
878 public boolean hasTraffic = false;
879 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700880
881 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700882 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700883
884 BiLink(LinkKey key, Link link) {
885 this.key = key;
886 this.one = link;
887 }
888
889 void setOther(Link link) {
890 this.two = link;
891 }
892
893 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700894 addLoad(load, 0);
895 }
896
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700897 void addLoad(Load load, double threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700898 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700899 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700900 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700901 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700902 }
903 }
904
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700905 void addLoad(Load srcLinkLoad,
906 double srcLinkThreshold,
907 Load dstLinkLoad,
908 double dstLinkThreshold) {
909 //use the max of link load at source or destination
910 if (srcLinkLoad != null) {
911 this.hasTraffic = hasTraffic || srcLinkLoad.rate() > srcLinkThreshold;
912 this.bytes = srcLinkLoad.latest();
913 this.rate = srcLinkLoad.rate();
914 }
915
916 if (dstLinkLoad != null) {
917 if (dstLinkLoad.rate() > this.rate) {
918 this.bytes = dstLinkLoad.latest();
919 this.rate = dstLinkLoad.rate();
920 this.hasTraffic = hasTraffic || dstLinkLoad.rate() > dstLinkThreshold;
921 }
922 }
923 }
924
Thomas Vachuska329af532015-03-10 02:08:33 -0700925 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700926 classes.add(trafficClass);
927 }
928
929 String classes() {
930 StringBuilder sb = new StringBuilder();
931 classes.forEach(c -> sb.append(c).append(" "));
932 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700933 }
934 }
935
Thomas Vachuska329af532015-03-10 02:08:33 -0700936
Simon Hunt93735af2015-07-28 12:10:30 -0700937 // TODO: move this to traffic overlay component
Thomas Vachuska329af532015-03-10 02:08:33 -0700938 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700939 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700940 public final boolean showTraffic;
941 public final String type;
942 public final Iterable<Intent> intents;
943
944 TrafficClass(String type, Iterable<Intent> intents) {
945 this(type, intents, false);
946 }
947
948 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
949 this.type = type;
950 this.intents = intents;
951 this.showTraffic = showTraffic;
952 }
953 }
954
955}