blob: 2deabc0bf5dd6ccb117168e41857c60fd1b41819 [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
443 // Returns summary response.
Simon Hunt0af1ec32015-07-24 12:17:55 -0700444 protected PropertyPanel summmaryMessage(long sid) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700445 Topology topology = topologyService.currentTopology();
Simon Hunt0af1ec32015-07-24 12:17:55 -0700446 PropertyPanel pp = new PropertyPanel("ONOS Summary", "node")
447 .add(new PropertyPanel.Prop("Devices", format(topology.deviceCount())))
448 .add(new PropertyPanel.Prop("Links", format(topology.linkCount())))
449 .add(new PropertyPanel.Prop("Hosts", format(hostService.getHostCount())))
450 .add(new PropertyPanel.Prop("Topology SCCs", format(topology.clusterCount())))
451 .add(new PropertyPanel.Separator())
452 .add(new PropertyPanel.Prop("Intents", format(intentService.getIntentCount())))
453 .add(new PropertyPanel.Prop("Tunnels", format(tunnelService.tunnelCount())))
454 .add(new PropertyPanel.Prop("Flows", format(flowService.getFlowRuleCount())))
455 .add(new PropertyPanel.Prop("Version", version));
456
457 return pp;
Thomas Vachuska329af532015-03-10 02:08:33 -0700458 }
459
460 // Returns device details response.
461 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
462 Device device = deviceService.getDevice(deviceId);
463 Annotations annot = device.annotations();
464 String name = annot.value(AnnotationKeys.NAME);
465 int portCount = deviceService.getPorts(deviceId).size();
466 int flowCount = getFlowCount(deviceId);
cheng fan35dc0f22015-06-10 06:02:47 +0800467 int tunnelCount = getTunnelCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700468 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700469 json(isNullOrEmpty(name) ? deviceId.toString() : name,
470 device.type().toString().toLowerCase(),
471 new Prop("URI", deviceId.toString()),
472 new Prop("Vendor", device.manufacturer()),
473 new Prop("H/W Version", device.hwVersion()),
474 new Prop("S/W Version", device.swVersion()),
475 new Prop("Serial Number", device.serialNumber()),
476 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
477 new Separator(),
478 new Prop("Master", master(deviceId)),
479 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
480 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
481 new Separator(),
482 new Prop("Ports", Integer.toString(portCount)),
cheng fan35dc0f22015-06-10 06:02:47 +0800483 new Prop("Flows", Integer.toString(flowCount)),
484 new Prop("Tunnels", Integer.toString(tunnelCount))
485 ));
Thomas Vachuska329af532015-03-10 02:08:33 -0700486 }
487
488 protected int getFlowCount(DeviceId deviceId) {
489 int count = 0;
490 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
491 while (it.hasNext()) {
492 count++;
493 it.next();
494 }
495 return count;
496 }
497
cheng fan35dc0f22015-06-10 06:02:47 +0800498 protected int getTunnelCount(DeviceId deviceId) {
499 int count = 0;
500 Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
501 for (Tunnel tunnel : tunnels) {
502 OpticalTunnelEndPoint src = (OpticalTunnelEndPoint) tunnel.src();
503 OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst();
504 DeviceId srcDevice = (DeviceId) src.elementId().get();
505 DeviceId dstDevice = (DeviceId) dst.elementId().get();
506 if (srcDevice.toString().equals(deviceId.toString())
507 || dstDevice.toString().equals(deviceId.toString())) {
508 count++;
509 }
510 }
511 return count;
512 }
513
Thomas Vachuska329af532015-03-10 02:08:33 -0700514 // Counts all entries that egress on the given device links.
515 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
516 List<FlowEntry> entries = new ArrayList<>();
517 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
518 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
519 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
520 while (it.hasNext()) {
521 entries.add(it.next());
522 }
523
524 // Add all edge links to the set
525 if (hosts != null) {
526 for (Host host : hosts) {
527 links.add(new DefaultEdgeLink(host.providerId(),
528 new ConnectPoint(host.id(), P0),
529 host.location(), false));
530 }
531 }
532
533 Map<Link, Integer> counts = new HashMap<>();
534 for (Link link : links) {
535 counts.put(link, getEgressFlows(link, entries));
536 }
537 return counts;
538 }
539
540 // Counts all entries that egress on the link source port.
541 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
542 int count = 0;
543 PortNumber out = link.src().port();
544 for (FlowEntry entry : entries) {
545 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700546 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700547 if (instruction.type() == Instruction.Type.OUTPUT &&
548 ((OutputInstruction) instruction).port().equals(out)) {
549 count++;
550 }
551 }
552 }
553 return count;
554 }
555
556
557 // Returns host details response.
558 protected ObjectNode hostDetails(HostId hostId, long sid) {
559 Host host = hostService.getHost(hostId);
560 Annotations annot = host.annotations();
561 String type = annot.value(AnnotationKeys.TYPE);
562 String name = annot.value(AnnotationKeys.NAME);
563 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700564 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700565 json(isNullOrEmpty(name) ? hostId.toString() : name,
566 isNullOrEmpty(type) ? "endstation" : type,
567 new Prop("MAC", host.mac().toString()),
568 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
569 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
570 new Separator(),
571 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
572 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700573 }
574
575
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700576 // Produces JSON message to trigger flow traffic overview visualization
577 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700578 ObjectNode payload = objectNode();
579 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700580 payload.set("paths", paths);
581
Simon Huntda580882015-05-12 20:58:18 -0700582 ObjectNode pathNodeN = objectNode();
583 ArrayNode linksNodeN = arrayNode();
584 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700585
586 pathNodeN.put("class", "plain").put("traffic", false);
587 pathNodeN.set("links", linksNodeN);
588 pathNodeN.set("labels", labelsN);
589 paths.add(pathNodeN);
590
Simon Huntda580882015-05-12 20:58:18 -0700591 ObjectNode pathNodeT = objectNode();
592 ArrayNode linksNodeT = arrayNode();
593 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700594
595 pathNodeT.put("class", "secondary").put("traffic", true);
596 pathNodeT.set("links", linksNodeT);
597 pathNodeT.set("labels", labelsT);
598 paths.add(pathNodeT);
599
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700600 Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
601 addEdgeLinks(biLinks);
602 for (BiLink link : biLinks.values()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700603 boolean bi = link.two != null;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700604 if (type == FLOW) {
605 link.addLoad(getLinkLoad(link.one));
606 link.addLoad(bi ? getLinkLoad(link.two) : null);
607 } else if (type == PORT) {
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700608 //For a bi-directional traffic links, use
609 //the max link rate of either direction
610 link.addLoad(portStatsService.load(link.one.src()),
611 BPS_THRESHOLD,
612 portStatsService.load(link.one.dst()),
613 BPS_THRESHOLD);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700614 }
615 if (link.hasTraffic) {
616 linksNodeT.add(compactLinkString(link.one));
617 labelsT.add(type == PORT ?
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700618 formatBitRate(link.rate) + "ps" :
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700619 formatBytes(link.bytes));
620 } else {
621 linksNodeN.add(compactLinkString(link.one));
622 labelsN.add("");
Thomas Vachuska329af532015-03-10 02:08:33 -0700623 }
624 }
Simon Huntd2747a02015-04-30 22:41:16 -0700625 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700626 }
627
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700628 private Load getLinkLoad(Link link) {
629 if (link.src().elementId() instanceof DeviceId) {
630 return flowStatsService.load(link);
631 }
632 return null;
633 }
634
635 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
636 hostService.getHosts().forEach(host -> {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700637 addLink(biLinks, createEdgeLink(host, true));
638 addLink(biLinks, createEdgeLink(host, false));
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700639 });
640 }
641
642 private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700643 Map<LinkKey, BiLink> biLinks = new HashMap<>();
644 for (Link link : links) {
645 addLink(biLinks, link);
646 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700647 return biLinks;
Thomas Vachuska329af532015-03-10 02:08:33 -0700648 }
649
650 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700651 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700652 ObjectNode payload = objectNode();
653 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700654 payload.set("paths", paths);
655
656 for (Device device : devices) {
657 Map<Link, Integer> counts = getFlowCounts(device.id());
658 for (Link link : counts.keySet()) {
659 addLinkFlows(link, paths, counts.get(link));
660 }
661 }
Simon Huntd2747a02015-04-30 22:41:16 -0700662 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700663 }
664
665 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700666 ObjectNode pathNode = objectNode();
667 ArrayNode linksNode = arrayNode();
668 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700669 boolean noFlows = count == null || count == 0;
670 pathNode.put("class", noFlows ? "secondary" : "primary");
671 pathNode.put("traffic", false);
672 pathNode.set("links", linksNode.add(compactLinkString(link)));
673 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
674 (count == 1 ? " flow" : " flows"))));
675 paths.add(pathNode);
676 }
677
678
679 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700680 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700681 ObjectNode payload = objectNode();
682 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700683 payload.set("paths", paths);
684
685 // Classify links based on their traffic traffic first...
686 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
687
688 // Then separate the links into their respective classes and send them out.
689 Map<String, ObjectNode> pathNodes = new HashMap<>();
690 for (BiLink biLink : biLinks.values()) {
691 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700692 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700693 ObjectNode pathNode = pathNodes.get(tc);
694 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700695 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700696 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700697 pathNode.set("links", arrayNode());
698 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700699 pathNodes.put(tc, pathNode);
700 paths.add(pathNode);
701 }
702 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
703 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
704 }
705
Simon Huntd2747a02015-04-30 22:41:16 -0700706 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700707 }
708
709 // Classifies the link traffic according to the specified classes.
710 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
711 Map<LinkKey, BiLink> biLinks = new HashMap<>();
712 for (TrafficClass trafficClass : trafficClasses) {
713 for (Intent intent : trafficClass.intents) {
714 boolean isOptical = intent instanceof OpticalConnectivityIntent;
715 List<Intent> installables = intentService.getInstallableIntents(intent.key());
716 if (installables != null) {
717 for (Intent installable : installables) {
718 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
719 if (installable instanceof PathIntent) {
720 classifyLinks(type, biLinks, trafficClass.showTraffic,
721 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700722 } else if (installable instanceof FlowRuleIntent) {
723 classifyLinks(type, biLinks, trafficClass.showTraffic,
724 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700725 } else if (installable instanceof LinkCollectionIntent) {
726 classifyLinks(type, biLinks, trafficClass.showTraffic,
727 ((LinkCollectionIntent) installable).links());
728 } else if (installable instanceof OpticalPathIntent) {
729 classifyLinks(type, biLinks, trafficClass.showTraffic,
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700730 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska329af532015-03-10 02:08:33 -0700731 }
732 }
733 }
734 }
735 }
736 return biLinks;
737 }
738
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700739 // Extracts links from the specified flow rule intent resources
740 private Collection<Link> linkResources(Intent installable) {
741 ImmutableList.Builder<Link> builder = ImmutableList.builder();
742 for (NetworkResource r : installable.resources()) {
743 if (r instanceof Link) {
744 builder.add((Link) r);
745 }
746 }
747 return builder.build();
748 }
749
Thomas Vachuska329af532015-03-10 02:08:33 -0700750
751 // Adds the link segments (path or tree) associated with the specified
752 // connectivity intent
753 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
754 boolean showTraffic, Iterable<Link> links) {
755 if (links != null) {
756 for (Link link : links) {
757 BiLink biLink = addLink(biLinks, link);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700758 if (showTraffic) {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700759 biLink.addLoad(getLinkLoad(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700760 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700761 biLink.addClass(type);
Thomas Vachuska329af532015-03-10 02:08:33 -0700762 }
763 }
764 }
765
766
Thomas Vachuska583bc632015-04-14 10:10:57 -0700767 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700768 LinkKey key = canonicalLinkKey(link);
769 BiLink biLink = biLinks.get(key);
770 if (biLink != null) {
771 biLink.setOther(link);
772 } else {
773 biLink = new BiLink(key, link);
774 biLinks.put(key, biLink);
775 }
776 return biLink;
777 }
778
Thomas Vachuska329af532015-03-10 02:08:33 -0700779 // Poor-mans formatting to get the labels with byte counts looking nice.
780 private String formatBytes(long bytes) {
781 String unit;
782 double value;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700783 if (bytes > GIGA) {
784 value = bytes / GIGA;
785 unit = GBYTES_UNIT;
786 } else if (bytes > MEGA) {
787 value = bytes / MEGA;
788 unit = MBYTES_UNIT;
789 } else if (bytes > KILO) {
790 value = bytes / KILO;
791 unit = KBYTES_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700792 } else {
793 value = bytes;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700794 unit = BYTES_UNIT;
795 }
796 DecimalFormat format = new DecimalFormat("#,###.##");
797 return format.format(value) + " " + unit;
798 }
799
Simon Hunt80170a72015-06-10 19:49:57 -0700800 // Poor-mans formatting to get the labels with bit rate looking nice.
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700801 private String formatBitRate(long bytes) {
802 String unit;
803 double value;
804 //Convert to bits
805 long bits = bytes * 8;
806 if (bits > GIGA) {
807 value = bits / GIGA;
808 unit = GBITS_UNIT;
Simon Hunt80170a72015-06-10 19:49:57 -0700809
810 // NOTE: temporary hack to clip rate at 10.0 Gbps
811 // Added for the CORD Fabric demo at ONS 2015
812 if (value > 10.0) {
813 value = 10.0;
814 }
815
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700816 } else if (bits > MEGA) {
817 value = bits / MEGA;
818 unit = MBITS_UNIT;
819 } else if (bits > KILO) {
820 value = bits / KILO;
821 unit = KBITS_UNIT;
822 } else {
823 value = bits;
824 unit = BITS_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700825 }
826 DecimalFormat format = new DecimalFormat("#,###.##");
827 return format.format(value) + " " + unit;
828 }
829
830 // Formats the given number into a string.
831 private String format(Number number) {
832 DecimalFormat format = new DecimalFormat("#,###");
833 return format.format(number);
834 }
835
Thomas Vachuska329af532015-03-10 02:08:33 -0700836 // Produces compact string representation of a link.
837 private static String compactLinkString(Link link) {
838 return String.format(COMPACT, link.src().elementId(), link.src().port(),
839 link.dst().elementId(), link.dst().port());
840 }
841
Simon Hunt0af1ec32015-07-24 12:17:55 -0700842 protected ObjectNode json(PropertyPanel pp) {
843 ObjectNode result = objectNode()
844 .put("title", pp.title()).put("type", pp.typeId());
845 ObjectNode pnode = objectNode();
846 ArrayNode porder = arrayNode();
847 for (PropertyPanel.Prop p : pp.properties()) {
848 porder.add(p.key());
849 pnode.put(p.key(), p.value());
850 }
851 result.set("propOrder", porder);
852 result.set("props", pnode);
853 return result;
854 }
855
Thomas Vachuska329af532015-03-10 02:08:33 -0700856 // Produces JSON property details.
857 private ObjectNode json(String id, String type, Prop... props) {
Simon Huntda580882015-05-12 20:58:18 -0700858 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700859 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700860 ObjectNode pnode = objectNode();
861 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700862 for (Prop p : props) {
863 porder.add(p.key);
864 pnode.put(p.key, p.value);
865 }
866 result.set("propOrder", porder);
867 result.set("props", pnode);
868 return result;
869 }
870
871 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700872 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700873 String sn = link.src().elementId().toString();
874 String dn = link.dst().elementId().toString();
875 return sn.compareTo(dn) < 0 ?
876 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
877 }
878
879 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700880 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700881 public final LinkKey key;
882 public final Link one;
883 public Link two;
884 public boolean hasTraffic = false;
885 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700886
887 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700888 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700889
890 BiLink(LinkKey key, Link link) {
891 this.key = key;
892 this.one = link;
893 }
894
895 void setOther(Link link) {
896 this.two = link;
897 }
898
899 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700900 addLoad(load, 0);
901 }
902
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700903 void addLoad(Load load, double threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700904 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700905 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700906 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700907 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700908 }
909 }
910
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700911 void addLoad(Load srcLinkLoad,
912 double srcLinkThreshold,
913 Load dstLinkLoad,
914 double dstLinkThreshold) {
915 //use the max of link load at source or destination
916 if (srcLinkLoad != null) {
917 this.hasTraffic = hasTraffic || srcLinkLoad.rate() > srcLinkThreshold;
918 this.bytes = srcLinkLoad.latest();
919 this.rate = srcLinkLoad.rate();
920 }
921
922 if (dstLinkLoad != null) {
923 if (dstLinkLoad.rate() > this.rate) {
924 this.bytes = dstLinkLoad.latest();
925 this.rate = dstLinkLoad.rate();
926 this.hasTraffic = hasTraffic || dstLinkLoad.rate() > dstLinkThreshold;
927 }
928 }
929 }
930
Thomas Vachuska329af532015-03-10 02:08:33 -0700931 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700932 classes.add(trafficClass);
933 }
934
935 String classes() {
936 StringBuilder sb = new StringBuilder();
937 classes.forEach(c -> sb.append(c).append(" "));
938 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700939 }
940 }
941
942 // Auxiliary key/value carrier.
Simon Hunt93735af2015-07-28 12:10:30 -0700943 @Deprecated
Thomas Vachuska583bc632015-04-14 10:10:57 -0700944 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700945 public final String key;
946 public final String value;
947
948 protected Prop(String key, String value) {
949 this.key = key;
950 this.value = value;
951 }
952 }
953
954 // Auxiliary properties separator
Simon Hunt93735af2015-07-28 12:10:30 -0700955 @Deprecated
Thomas Vachuska583bc632015-04-14 10:10:57 -0700956 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700957 protected Separator() {
958 super("-", "");
959 }
960 }
961
Simon Hunt93735af2015-07-28 12:10:30 -0700962 // TODO: move this to traffic overlay component
Thomas Vachuska329af532015-03-10 02:08:33 -0700963 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700964 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700965 public final boolean showTraffic;
966 public final String type;
967 public final Iterable<Intent> intents;
968
969 TrafficClass(String type, Iterable<Intent> intents) {
970 this(type, intents, false);
971 }
972
973 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
974 this.type = type;
975 this.intents = intents;
976 this.showTraffic = showTraffic;
977 }
978 }
979
980}