blob: 42dcd9f6ee7e60d18d16429e544329942e46d7f8 [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.
Sho SHIMIZUbe63b232015-06-30 10:57:58 -0700113 *
114 * @deprecated in Cardinal Release
Thomas Vachuska329af532015-03-10 02:08:33 -0700115 */
Simon Huntd7f7bcc2015-05-08 14:13:17 -0700116@Deprecated
Simon Hunta0ddb022015-05-01 09:53:01 -0700117public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700118
Simon Huntd2747a02015-04-30 22:41:16 -0700119 protected static final Logger log =
120 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700121
Simon Huntd2747a02015-04-30 22:41:16 -0700122 private static final ProviderId PID =
123 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700124 private static final String COMPACT = "%s/%s-%s/%s";
125
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700126 private static final double KILO = 1024;
127 private static final double MEGA = 1024 * KILO;
128 private static final double GIGA = 1024 * MEGA;
Thomas Vachuska329af532015-03-10 02:08:33 -0700129
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700130 private static final String GBITS_UNIT = "Gb";
131 private static final String MBITS_UNIT = "Mb";
132 private static final String KBITS_UNIT = "Kb";
133 private static final String BITS_UNIT = "b";
134 private static final String GBYTES_UNIT = "GB";
135 private static final String MBYTES_UNIT = "MB";
136 private static final String KBYTES_UNIT = "KB";
137 private static final String BYTES_UNIT = "B";
138 //4 Kilo Bytes as threshold
139 private static final double BPS_THRESHOLD = 4 * KILO;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700140
Thomas Vachuska329af532015-03-10 02:08:33 -0700141 protected ServiceDirectory directory;
142 protected ClusterService clusterService;
143 protected DeviceService deviceService;
144 protected LinkService linkService;
145 protected HostService hostService;
146 protected MastershipService mastershipService;
147 protected IntentService intentService;
148 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700149 protected StatisticService flowStatsService;
150 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700151 protected TopologyService topologyService;
cheng fan35dc0f22015-06-10 06:02:47 +0800152 protected TunnelService tunnelService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700153
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700154 protected enum StatsType {
155 FLOW, PORT
156 }
157
Thomas Vachuska329af532015-03-10 02:08:33 -0700158 private String version;
159
160 // TODO: extract into an external & durable state; good enough for now and demo
161 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
162
163 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700164 * Returns read-only view of the meta-ui information.
165 *
166 * @return map of id to meta-ui mementos
167 */
168 static Map<String, ObjectNode> getMetaUi() {
169 return Collections.unmodifiableMap(metaUi);
170 }
171
172 @Override
173 public void init(UiConnection connection, ServiceDirectory directory) {
174 super.init(connection, directory);
175 this.directory = checkNotNull(directory, "Directory cannot be null");
176 clusterService = directory.get(ClusterService.class);
177 deviceService = directory.get(DeviceService.class);
178 linkService = directory.get(LinkService.class);
179 hostService = directory.get(HostService.class);
180 mastershipService = directory.get(MastershipService.class);
181 intentService = directory.get(IntentService.class);
182 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700183 flowStatsService = directory.get(StatisticService.class);
184 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700185 topologyService = directory.get(TopologyService.class);
cheng fan35dc0f22015-06-10 06:02:47 +0800186 tunnelService = directory.get(TunnelService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700187
188 String ver = directory.get(CoreService.class).version().toString();
189 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
190 }
191
Thomas Vachuska329af532015-03-10 02:08:33 -0700192 // Returns the specified set of IP addresses as a string.
193 private String ip(Set<IpAddress> ipAddresses) {
194 Iterator<IpAddress> it = ipAddresses.iterator();
195 return it.hasNext() ? it.next().toString() : "unknown";
196 }
197
198 // Produces JSON structure from annotations.
199 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700200 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700201 if (annotations != null) {
202 for (String key : annotations.keys()) {
203 props.put(key, annotations.value(key));
204 }
205 }
206 return props;
207 }
208
209 // Produces an informational log message event bound to the client.
210 protected ObjectNode info(long id, String message) {
211 return message("info", id, message);
212 }
213
214 // Produces a warning log message event bound to the client.
215 protected ObjectNode warning(long id, String message) {
216 return message("warning", id, message);
217 }
218
219 // Produces an error log message event bound to the client.
220 protected ObjectNode error(long id, String message) {
221 return message("error", id, message);
222 }
223
224 // Produces a log message event bound to the client.
225 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700226 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700227 .put("severity", severity)
228 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700229
Simon Huntd2747a02015-04-30 22:41:16 -0700230 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700231 }
232
233 // Produces a set of all hosts listed in the specified JSON array.
234 protected Set<Host> getHosts(ArrayNode array) {
235 Set<Host> hosts = new HashSet<>();
236 if (array != null) {
237 for (JsonNode node : array) {
238 try {
239 addHost(hosts, hostId(node.asText()));
240 } catch (IllegalArgumentException e) {
241 log.debug("Skipping ID {}", node.asText());
242 }
243 }
244 }
245 return hosts;
246 }
247
248 // Adds the specified host to the set of hosts.
249 private void addHost(Set<Host> hosts, HostId hostId) {
250 Host host = hostService.getHost(hostId);
251 if (host != null) {
252 hosts.add(host);
253 }
254 }
255
256
257 // Produces a set of all devices listed in the specified JSON array.
258 protected Set<Device> getDevices(ArrayNode array) {
259 Set<Device> devices = new HashSet<>();
260 if (array != null) {
261 for (JsonNode node : array) {
262 try {
263 addDevice(devices, deviceId(node.asText()));
264 } catch (IllegalArgumentException e) {
265 log.debug("Skipping ID {}", node.asText());
266 }
267 }
268 }
269 return devices;
270 }
271
272 private void addDevice(Set<Device> devices, DeviceId deviceId) {
273 Device device = deviceService.getDevice(deviceId);
274 if (device != null) {
275 devices.add(device);
276 }
277 }
278
279 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
280 try {
281 addHost(hosts, hostId(hover));
282 } catch (IllegalArgumentException e) {
283 try {
284 addDevice(devices, deviceId(hover));
285 } catch (IllegalArgumentException ne) {
286 log.debug("Skipping ID {}", hover);
287 }
288 }
289 }
290
291 // Produces a cluster instance message to the client.
292 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
293 ControllerNode node = event.subject();
294 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700295 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700296 .put("id", node.id().toString())
297 .put("ip", node.ip().toString())
298 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700299 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700300 .put("switches", switchCount);
301
Simon Huntda580882015-05-12 20:58:18 -0700302 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700303 labels.add(node.id().toString());
304 labels.add(node.ip().toString());
305
306 // Add labels, props and stuff the payload into envelope.
307 payload.set("labels", labels);
308 addMetaUi(node.id().toString(), payload);
309
310 String type = messageType != null ? messageType :
311 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
312 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
313 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700314 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700315 }
316
317 // Produces a device event message to the client.
318 protected ObjectNode deviceMessage(DeviceEvent event) {
319 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700320 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700321 .put("id", device.id().toString())
322 .put("type", device.type().toString().toLowerCase())
323 .put("online", deviceService.isAvailable(device.id()))
324 .put("master", master(device.id()));
325
326 // Generate labels: id, chassis id, no-label, optional-name
327 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700328 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700329 labels.add("");
330 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
331 labels.add(device.id().toString());
332
333 // Add labels, props and stuff the payload into envelope.
334 payload.set("labels", labels);
335 payload.set("props", props(device.annotations()));
336 addGeoLocation(device, payload);
337 addMetaUi(device.id().toString(), payload);
338
339 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
340 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700341 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700342 }
343
344 // Produces a link event message to the client.
345 protected ObjectNode linkMessage(LinkEvent event) {
346 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700347 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700348 .put("id", compactLinkString(link))
349 .put("type", link.type().toString().toLowerCase())
350 .put("online", link.state() == Link.State.ACTIVE)
351 .put("linkWidth", 1.2)
352 .put("src", link.src().deviceId().toString())
353 .put("srcPort", link.src().port().toString())
354 .put("dst", link.dst().deviceId().toString())
355 .put("dstPort", link.dst().port().toString());
356 String type = (event.type() == LINK_ADDED) ? "addLink" :
357 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700358 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700359 }
360
361 // Produces a host event message to the client.
362 protected ObjectNode hostMessage(HostEvent event) {
363 Host host = event.subject();
364 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700365 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700366 .put("id", host.id().toString())
367 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
368 .put("ingress", compactLinkString(edgeLink(host, true)))
369 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700370 payload.set("cp", hostConnect(host.location()));
371 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700372 host.mac().toString()));
373 payload.set("props", props(host.annotations()));
374 addGeoLocation(host, payload);
375 addMetaUi(host.id().toString(), payload);
376
377 String type = (event.type() == HOST_ADDED) ? "addHost" :
378 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700379 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700380 }
381
382 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700383 private ObjectNode hostConnect(HostLocation location) {
384 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700385 .put("device", location.deviceId().toString())
386 .put("port", location.port().toLong());
387 }
388
389 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700390 private ArrayNode labels(String... labels) {
391 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700392 for (String label : labels) {
393 json.add(label);
394 }
395 return json;
396 }
397
398 // Returns the name of the master node for the specified device id.
399 private String master(DeviceId deviceId) {
400 NodeId master = mastershipService.getMasterFor(deviceId);
401 return master != null ? master.toString() : "";
402 }
403
404 // Generates an edge link from the specified host location.
405 private EdgeLink edgeLink(Host host, boolean ingress) {
406 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
407 host.location(), ingress);
408 }
409
410 // Adds meta UI information for the specified object.
411 private void addMetaUi(String id, ObjectNode payload) {
412 ObjectNode meta = metaUi.get(id);
413 if (meta != null) {
414 payload.set("metaUi", meta);
415 }
416 }
417
418 // Adds a geo location JSON to the specified payload object.
419 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
420 Annotations annotations = annotated.annotations();
421 if (annotations == null) {
422 return;
423 }
424
425 String slat = annotations.value(AnnotationKeys.LATITUDE);
426 String slng = annotations.value(AnnotationKeys.LONGITUDE);
427 try {
428 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
429 double lat = Double.parseDouble(slat);
430 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700431 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700432 .put("type", "latlng").put("lat", lat).put("lng", lng);
433 payload.set("location", loc);
434 }
435 } catch (NumberFormatException e) {
436 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
437 }
438 }
439
440 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700441 protected void updateMetaUi(ObjectNode payload) {
442 metaUi.put(JsonUtils.string(payload, "id"),
443 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700444 }
445
446 // Returns 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 PropertyPanel pp = new PropertyPanel("ONOS Summary", "node")
450 .add(new PropertyPanel.Prop("Devices", format(topology.deviceCount())))
451 .add(new PropertyPanel.Prop("Links", format(topology.linkCount())))
452 .add(new PropertyPanel.Prop("Hosts", format(hostService.getHostCount())))
453 .add(new PropertyPanel.Prop("Topology SCCs", format(topology.clusterCount())))
454 .add(new PropertyPanel.Separator())
455 .add(new PropertyPanel.Prop("Intents", format(intentService.getIntentCount())))
456 .add(new PropertyPanel.Prop("Tunnels", format(tunnelService.tunnelCount())))
457 .add(new PropertyPanel.Prop("Flows", format(flowService.getFlowRuleCount())))
458 .add(new PropertyPanel.Prop("Version", version));
459
460 return pp;
Thomas Vachuska329af532015-03-10 02:08:33 -0700461 }
462
463 // Returns device details response.
464 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
465 Device device = deviceService.getDevice(deviceId);
466 Annotations annot = device.annotations();
467 String name = annot.value(AnnotationKeys.NAME);
468 int portCount = deviceService.getPorts(deviceId).size();
469 int flowCount = getFlowCount(deviceId);
cheng fan35dc0f22015-06-10 06:02:47 +0800470 int tunnelCount = getTunnelCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700471 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700472 json(isNullOrEmpty(name) ? deviceId.toString() : name,
473 device.type().toString().toLowerCase(),
474 new Prop("URI", deviceId.toString()),
475 new Prop("Vendor", device.manufacturer()),
476 new Prop("H/W Version", device.hwVersion()),
477 new Prop("S/W Version", device.swVersion()),
478 new Prop("Serial Number", device.serialNumber()),
479 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
480 new Separator(),
481 new Prop("Master", master(deviceId)),
482 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
483 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
484 new Separator(),
485 new Prop("Ports", Integer.toString(portCount)),
cheng fan35dc0f22015-06-10 06:02:47 +0800486 new Prop("Flows", Integer.toString(flowCount)),
487 new Prop("Tunnels", Integer.toString(tunnelCount))
488 ));
Thomas Vachuska329af532015-03-10 02:08:33 -0700489 }
490
491 protected int getFlowCount(DeviceId deviceId) {
492 int count = 0;
493 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
494 while (it.hasNext()) {
495 count++;
496 it.next();
497 }
498 return count;
499 }
500
cheng fan35dc0f22015-06-10 06:02:47 +0800501 protected int getTunnelCount(DeviceId deviceId) {
502 int count = 0;
503 Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
504 for (Tunnel tunnel : tunnels) {
505 OpticalTunnelEndPoint src = (OpticalTunnelEndPoint) tunnel.src();
506 OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst();
507 DeviceId srcDevice = (DeviceId) src.elementId().get();
508 DeviceId dstDevice = (DeviceId) dst.elementId().get();
509 if (srcDevice.toString().equals(deviceId.toString())
510 || dstDevice.toString().equals(deviceId.toString())) {
511 count++;
512 }
513 }
514 return count;
515 }
516
Thomas Vachuska329af532015-03-10 02:08:33 -0700517 // Counts all entries that egress on the given device links.
518 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
519 List<FlowEntry> entries = new ArrayList<>();
520 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
521 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
522 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
523 while (it.hasNext()) {
524 entries.add(it.next());
525 }
526
527 // Add all edge links to the set
528 if (hosts != null) {
529 for (Host host : hosts) {
530 links.add(new DefaultEdgeLink(host.providerId(),
531 new ConnectPoint(host.id(), P0),
532 host.location(), false));
533 }
534 }
535
536 Map<Link, Integer> counts = new HashMap<>();
537 for (Link link : links) {
538 counts.put(link, getEgressFlows(link, entries));
539 }
540 return counts;
541 }
542
543 // Counts all entries that egress on the link source port.
544 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
545 int count = 0;
546 PortNumber out = link.src().port();
547 for (FlowEntry entry : entries) {
548 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700549 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700550 if (instruction.type() == Instruction.Type.OUTPUT &&
551 ((OutputInstruction) instruction).port().equals(out)) {
552 count++;
553 }
554 }
555 }
556 return count;
557 }
558
559
560 // Returns host details response.
561 protected ObjectNode hostDetails(HostId hostId, long sid) {
562 Host host = hostService.getHost(hostId);
563 Annotations annot = host.annotations();
564 String type = annot.value(AnnotationKeys.TYPE);
565 String name = annot.value(AnnotationKeys.NAME);
566 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700567 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700568 json(isNullOrEmpty(name) ? hostId.toString() : name,
569 isNullOrEmpty(type) ? "endstation" : type,
570 new Prop("MAC", host.mac().toString()),
571 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
572 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
573 new Separator(),
574 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
575 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700576 }
577
578
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700579 // Produces JSON message to trigger flow traffic overview visualization
580 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700581 ObjectNode payload = objectNode();
582 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700583 payload.set("paths", paths);
584
Simon Huntda580882015-05-12 20:58:18 -0700585 ObjectNode pathNodeN = objectNode();
586 ArrayNode linksNodeN = arrayNode();
587 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700588
589 pathNodeN.put("class", "plain").put("traffic", false);
590 pathNodeN.set("links", linksNodeN);
591 pathNodeN.set("labels", labelsN);
592 paths.add(pathNodeN);
593
Simon Huntda580882015-05-12 20:58:18 -0700594 ObjectNode pathNodeT = objectNode();
595 ArrayNode linksNodeT = arrayNode();
596 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700597
598 pathNodeT.put("class", "secondary").put("traffic", true);
599 pathNodeT.set("links", linksNodeT);
600 pathNodeT.set("labels", labelsT);
601 paths.add(pathNodeT);
602
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700603 Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
604 addEdgeLinks(biLinks);
605 for (BiLink link : biLinks.values()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700606 boolean bi = link.two != null;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700607 if (type == FLOW) {
608 link.addLoad(getLinkLoad(link.one));
609 link.addLoad(bi ? getLinkLoad(link.two) : null);
610 } else if (type == PORT) {
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700611 //For a bi-directional traffic links, use
612 //the max link rate of either direction
613 link.addLoad(portStatsService.load(link.one.src()),
614 BPS_THRESHOLD,
615 portStatsService.load(link.one.dst()),
616 BPS_THRESHOLD);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700617 }
618 if (link.hasTraffic) {
619 linksNodeT.add(compactLinkString(link.one));
620 labelsT.add(type == PORT ?
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700621 formatBitRate(link.rate) + "ps" :
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700622 formatBytes(link.bytes));
623 } else {
624 linksNodeN.add(compactLinkString(link.one));
625 labelsN.add("");
Thomas Vachuska329af532015-03-10 02:08:33 -0700626 }
627 }
Simon Huntd2747a02015-04-30 22:41:16 -0700628 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700629 }
630
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700631 private Load getLinkLoad(Link link) {
632 if (link.src().elementId() instanceof DeviceId) {
633 return flowStatsService.load(link);
634 }
635 return null;
636 }
637
638 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
639 hostService.getHosts().forEach(host -> {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700640 addLink(biLinks, createEdgeLink(host, true));
641 addLink(biLinks, createEdgeLink(host, false));
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700642 });
643 }
644
645 private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700646 Map<LinkKey, BiLink> biLinks = new HashMap<>();
647 for (Link link : links) {
648 addLink(biLinks, link);
649 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700650 return biLinks;
Thomas Vachuska329af532015-03-10 02:08:33 -0700651 }
652
653 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700654 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700655 ObjectNode payload = objectNode();
656 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700657 payload.set("paths", paths);
658
659 for (Device device : devices) {
660 Map<Link, Integer> counts = getFlowCounts(device.id());
661 for (Link link : counts.keySet()) {
662 addLinkFlows(link, paths, counts.get(link));
663 }
664 }
Simon Huntd2747a02015-04-30 22:41:16 -0700665 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700666 }
667
668 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700669 ObjectNode pathNode = objectNode();
670 ArrayNode linksNode = arrayNode();
671 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700672 boolean noFlows = count == null || count == 0;
673 pathNode.put("class", noFlows ? "secondary" : "primary");
674 pathNode.put("traffic", false);
675 pathNode.set("links", linksNode.add(compactLinkString(link)));
676 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
677 (count == 1 ? " flow" : " flows"))));
678 paths.add(pathNode);
679 }
680
681
682 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700683 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700684 ObjectNode payload = objectNode();
685 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700686 payload.set("paths", paths);
687
688 // Classify links based on their traffic traffic first...
689 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
690
691 // Then separate the links into their respective classes and send them out.
692 Map<String, ObjectNode> pathNodes = new HashMap<>();
693 for (BiLink biLink : biLinks.values()) {
694 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700695 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700696 ObjectNode pathNode = pathNodes.get(tc);
697 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700698 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700699 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700700 pathNode.set("links", arrayNode());
701 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700702 pathNodes.put(tc, pathNode);
703 paths.add(pathNode);
704 }
705 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
706 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
707 }
708
Simon Huntd2747a02015-04-30 22:41:16 -0700709 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700710 }
711
712 // Classifies the link traffic according to the specified classes.
713 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
714 Map<LinkKey, BiLink> biLinks = new HashMap<>();
715 for (TrafficClass trafficClass : trafficClasses) {
716 for (Intent intent : trafficClass.intents) {
717 boolean isOptical = intent instanceof OpticalConnectivityIntent;
718 List<Intent> installables = intentService.getInstallableIntents(intent.key());
719 if (installables != null) {
720 for (Intent installable : installables) {
721 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
722 if (installable instanceof PathIntent) {
723 classifyLinks(type, biLinks, trafficClass.showTraffic,
724 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700725 } else if (installable instanceof FlowRuleIntent) {
726 classifyLinks(type, biLinks, trafficClass.showTraffic,
727 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700728 } else if (installable instanceof LinkCollectionIntent) {
729 classifyLinks(type, biLinks, trafficClass.showTraffic,
730 ((LinkCollectionIntent) installable).links());
731 } else if (installable instanceof OpticalPathIntent) {
732 classifyLinks(type, biLinks, trafficClass.showTraffic,
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700733 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska329af532015-03-10 02:08:33 -0700734 }
735 }
736 }
737 }
738 }
739 return biLinks;
740 }
741
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700742 // Extracts links from the specified flow rule intent resources
743 private Collection<Link> linkResources(Intent installable) {
744 ImmutableList.Builder<Link> builder = ImmutableList.builder();
745 for (NetworkResource r : installable.resources()) {
746 if (r instanceof Link) {
747 builder.add((Link) r);
748 }
749 }
750 return builder.build();
751 }
752
Thomas Vachuska329af532015-03-10 02:08:33 -0700753
754 // Adds the link segments (path or tree) associated with the specified
755 // connectivity intent
756 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
757 boolean showTraffic, Iterable<Link> links) {
758 if (links != null) {
759 for (Link link : links) {
760 BiLink biLink = addLink(biLinks, link);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700761 if (showTraffic) {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700762 biLink.addLoad(getLinkLoad(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700763 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700764 biLink.addClass(type);
Thomas Vachuska329af532015-03-10 02:08:33 -0700765 }
766 }
767 }
768
769
Thomas Vachuska583bc632015-04-14 10:10:57 -0700770 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700771 LinkKey key = canonicalLinkKey(link);
772 BiLink biLink = biLinks.get(key);
773 if (biLink != null) {
774 biLink.setOther(link);
775 } else {
776 biLink = new BiLink(key, link);
777 biLinks.put(key, biLink);
778 }
779 return biLink;
780 }
781
Thomas Vachuska329af532015-03-10 02:08:33 -0700782 // Poor-mans formatting to get the labels with byte counts looking nice.
783 private String formatBytes(long bytes) {
784 String unit;
785 double value;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700786 if (bytes > GIGA) {
787 value = bytes / GIGA;
788 unit = GBYTES_UNIT;
789 } else if (bytes > MEGA) {
790 value = bytes / MEGA;
791 unit = MBYTES_UNIT;
792 } else if (bytes > KILO) {
793 value = bytes / KILO;
794 unit = KBYTES_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700795 } else {
796 value = bytes;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700797 unit = BYTES_UNIT;
798 }
799 DecimalFormat format = new DecimalFormat("#,###.##");
800 return format.format(value) + " " + unit;
801 }
802
Simon Hunt80170a72015-06-10 19:49:57 -0700803 // Poor-mans formatting to get the labels with bit rate looking nice.
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700804 private String formatBitRate(long bytes) {
805 String unit;
806 double value;
807 //Convert to bits
808 long bits = bytes * 8;
809 if (bits > GIGA) {
810 value = bits / GIGA;
811 unit = GBITS_UNIT;
Simon Hunt80170a72015-06-10 19:49:57 -0700812
813 // NOTE: temporary hack to clip rate at 10.0 Gbps
814 // Added for the CORD Fabric demo at ONS 2015
815 if (value > 10.0) {
816 value = 10.0;
817 }
818
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700819 } else if (bits > MEGA) {
820 value = bits / MEGA;
821 unit = MBITS_UNIT;
822 } else if (bits > KILO) {
823 value = bits / KILO;
824 unit = KBITS_UNIT;
825 } else {
826 value = bits;
827 unit = BITS_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700828 }
829 DecimalFormat format = new DecimalFormat("#,###.##");
830 return format.format(value) + " " + unit;
831 }
832
833 // Formats the given number into a string.
834 private String format(Number number) {
835 DecimalFormat format = new DecimalFormat("#,###");
836 return format.format(number);
837 }
838
Thomas Vachuska329af532015-03-10 02:08:33 -0700839 // Produces compact string representation of a link.
840 private static String compactLinkString(Link link) {
841 return String.format(COMPACT, link.src().elementId(), link.src().port(),
842 link.dst().elementId(), link.dst().port());
843 }
844
Simon Hunt0af1ec32015-07-24 12:17:55 -0700845 protected ObjectNode json(PropertyPanel pp) {
846 ObjectNode result = objectNode()
847 .put("title", pp.title()).put("type", pp.typeId());
848 ObjectNode pnode = objectNode();
849 ArrayNode porder = arrayNode();
850 for (PropertyPanel.Prop p : pp.properties()) {
851 porder.add(p.key());
852 pnode.put(p.key(), p.value());
853 }
854 result.set("propOrder", porder);
855 result.set("props", pnode);
856 return result;
857 }
858
Thomas Vachuska329af532015-03-10 02:08:33 -0700859 // Produces JSON property details.
860 private ObjectNode json(String id, String type, Prop... props) {
Simon Huntda580882015-05-12 20:58:18 -0700861 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700862 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700863 ObjectNode pnode = objectNode();
864 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700865 for (Prop p : props) {
866 porder.add(p.key);
867 pnode.put(p.key, p.value);
868 }
869 result.set("propOrder", porder);
870 result.set("props", pnode);
871 return result;
872 }
873
874 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700875 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700876 String sn = link.src().elementId().toString();
877 String dn = link.dst().elementId().toString();
878 return sn.compareTo(dn) < 0 ?
879 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
880 }
881
882 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700883 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700884 public final LinkKey key;
885 public final Link one;
886 public Link two;
887 public boolean hasTraffic = false;
888 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700889
890 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700891 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700892
893 BiLink(LinkKey key, Link link) {
894 this.key = key;
895 this.one = link;
896 }
897
898 void setOther(Link link) {
899 this.two = link;
900 }
901
902 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700903 addLoad(load, 0);
904 }
905
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700906 void addLoad(Load load, double threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700907 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700908 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700909 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700910 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700911 }
912 }
913
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700914 void addLoad(Load srcLinkLoad,
915 double srcLinkThreshold,
916 Load dstLinkLoad,
917 double dstLinkThreshold) {
918 //use the max of link load at source or destination
919 if (srcLinkLoad != null) {
920 this.hasTraffic = hasTraffic || srcLinkLoad.rate() > srcLinkThreshold;
921 this.bytes = srcLinkLoad.latest();
922 this.rate = srcLinkLoad.rate();
923 }
924
925 if (dstLinkLoad != null) {
926 if (dstLinkLoad.rate() > this.rate) {
927 this.bytes = dstLinkLoad.latest();
928 this.rate = dstLinkLoad.rate();
929 this.hasTraffic = hasTraffic || dstLinkLoad.rate() > dstLinkThreshold;
930 }
931 }
932 }
933
Thomas Vachuska329af532015-03-10 02:08:33 -0700934 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700935 classes.add(trafficClass);
936 }
937
938 String classes() {
939 StringBuilder sb = new StringBuilder();
940 classes.forEach(c -> sb.append(c).append(" "));
941 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700942 }
943 }
944
945 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700946 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700947 public final String key;
948 public final String value;
949
950 protected Prop(String key, String value) {
951 this.key = key;
952 this.value = value;
953 }
954 }
955
956 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700957 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700958 protected Separator() {
959 super("-", "");
960 }
961 }
962
963 // 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}