blob: 323ccee228dab208d76834c9a5107c53c1977e37 [file] [log] [blame]
Simon Huntd5b96732016-07-08 13:22:27 -07001/*
2 * Copyright 2016-present 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 */
16
17package org.onosproject.ui.impl.topo;
18
Simon Huntc13082f2016-08-03 21:20:23 -070019import com.fasterxml.jackson.databind.JsonNode;
Simon Huntd5b96732016-07-08 13:22:27 -070020import com.fasterxml.jackson.databind.ObjectMapper;
21import com.fasterxml.jackson.databind.node.ArrayNode;
22import com.fasterxml.jackson.databind.node.ObjectNode;
23import org.onlab.osgi.ServiceDirectory;
Steven Burrows583f4be2016-11-04 14:06:50 +010024import org.onlab.packet.IpAddress;
Simon Huntd5b96732016-07-08 13:22:27 -070025import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.NodeId;
27import org.onosproject.incubator.net.PortStatisticsService;
28import org.onosproject.incubator.net.tunnel.TunnelService;
29import org.onosproject.mastership.MastershipService;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070030import org.onosproject.net.Annotated;
31import org.onosproject.net.Annotations;
32import org.onosproject.net.Device;
Steven Burrows583f4be2016-11-04 14:06:50 +010033import org.onosproject.net.Host;
Simon Huntd5b96732016-07-08 13:22:27 -070034import org.onosproject.net.device.DeviceService;
35import org.onosproject.net.flow.FlowRuleService;
36import org.onosproject.net.host.HostService;
37import org.onosproject.net.intent.IntentService;
38import org.onosproject.net.link.LinkService;
Simon Hunt53612212016-12-04 17:19:52 -080039import org.onosproject.net.region.Region;
Simon Huntd5b96732016-07-08 13:22:27 -070040import org.onosproject.net.statistic.StatisticService;
41import org.onosproject.net.topology.TopologyService;
Steven Burrowse7cc3082016-09-27 11:24:58 -070042import org.onosproject.ui.JsonUtils;
Simon Huntd5b96732016-07-08 13:22:27 -070043import org.onosproject.ui.model.topo.UiClusterMember;
44import org.onosproject.ui.model.topo.UiDevice;
45import org.onosproject.ui.model.topo.UiHost;
46import org.onosproject.ui.model.topo.UiLink;
Simon Hunt977aa052016-07-20 17:08:29 -070047import org.onosproject.ui.model.topo.UiNode;
Simon Huntd5b96732016-07-08 13:22:27 -070048import org.onosproject.ui.model.topo.UiRegion;
Simon Huntc13082f2016-08-03 21:20:23 -070049import org.onosproject.ui.model.topo.UiSynthLink;
Simon Huntd5b96732016-07-08 13:22:27 -070050import org.onosproject.ui.model.topo.UiTopoLayout;
Simon Hunt98189192016-07-29 19:02:27 -070051import org.slf4j.Logger;
52import org.slf4j.LoggerFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070053
Simon Hunt977aa052016-07-20 17:08:29 -070054import java.util.ArrayList;
55import java.util.HashMap;
56import java.util.HashSet;
Simon Huntd5b96732016-07-08 13:22:27 -070057import java.util.List;
Simon Hunt977aa052016-07-20 17:08:29 -070058import java.util.Map;
59import java.util.Set;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070060import java.util.concurrent.ConcurrentHashMap;
Simon Huntd5b96732016-07-08 13:22:27 -070061
62import static com.google.common.base.Preconditions.checkNotNull;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070063import static org.onosproject.net.AnnotationKeys.LATITUDE;
64import static org.onosproject.net.AnnotationKeys.LONGITUDE;
Simon Hunt977aa052016-07-20 17:08:29 -070065import static org.onosproject.ui.model.topo.UiNode.LAYER_DEFAULT;
Simon Huntd5b96732016-07-08 13:22:27 -070066
67/**
68 * Facility for creating JSON messages to send to the topology view in the
69 * Web client.
70 */
71class Topo2Jsonifier {
72
Simon Hunt977aa052016-07-20 17:08:29 -070073 private static final String E_DEF_NOT_LAST =
74 "UiNode.LAYER_DEFAULT not last in layer list";
75 private static final String E_UNKNOWN_UI_NODE =
76 "Unknown subclass of UiNode: ";
77
Simon Hunt98189192016-07-29 19:02:27 -070078 private static final String REGION = "region";
79 private static final String DEVICE = "device";
80 private static final String HOST = "host";
81
82 private final Logger log = LoggerFactory.getLogger(getClass());
83
Simon Huntd5b96732016-07-08 13:22:27 -070084 private final ObjectMapper mapper = new ObjectMapper();
85
86 private ServiceDirectory directory;
87 private ClusterService clusterService;
88 private DeviceService deviceService;
89 private LinkService linkService;
90 private HostService hostService;
91 private MastershipService mastershipService;
92 private IntentService intentService;
93 private FlowRuleService flowService;
94 private StatisticService flowStatsService;
95 private PortStatisticsService portStatsService;
96 private TopologyService topologyService;
97 private TunnelService tunnelService;
98
99
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700100 // NOTE: we'll stick this here for now, but maybe there is a better home?
101 // (this is not distributed across the cluster)
102 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
103
104
Simon Huntd5b96732016-07-08 13:22:27 -0700105 /**
106 * Creates an instance with a reference to the services directory, so that
107 * additional information about network elements may be looked up on
108 * on the fly.
109 *
110 * @param directory service directory
111 */
112 Topo2Jsonifier(ServiceDirectory directory) {
113 this.directory = checkNotNull(directory, "Directory cannot be null");
114
115 clusterService = directory.get(ClusterService.class);
116 deviceService = directory.get(DeviceService.class);
117 linkService = directory.get(LinkService.class);
118 hostService = directory.get(HostService.class);
119 mastershipService = directory.get(MastershipService.class);
120 intentService = directory.get(IntentService.class);
121 flowService = directory.get(FlowRuleService.class);
122 flowStatsService = directory.get(StatisticService.class);
123 portStatsService = directory.get(PortStatisticsService.class);
124 topologyService = directory.get(TopologyService.class);
125 tunnelService = directory.get(TunnelService.class);
Simon Hunt977aa052016-07-20 17:08:29 -0700126 }
Simon Huntd5b96732016-07-08 13:22:27 -0700127
Simon Hunt977aa052016-07-20 17:08:29 -0700128 // for unit testing
129 Topo2Jsonifier() {
Simon Huntd5b96732016-07-08 13:22:27 -0700130 }
131
132 private ObjectNode objectNode() {
133 return mapper.createObjectNode();
134 }
135
136 private ArrayNode arrayNode() {
137 return mapper.createArrayNode();
138 }
139
140 private String nullIsEmpty(Object o) {
141 return o == null ? "" : o.toString();
142 }
143
144
145 /**
146 * Returns a JSON representation of the cluster members (ONOS instances).
147 *
148 * @param instances the instance model objects
149 * @return a JSON representation of the data
150 */
151 ObjectNode instances(List<UiClusterMember> instances) {
152 NodeId local = clusterService.getLocalNode().id();
153 ObjectNode payload = objectNode();
154
155 ArrayNode members = arrayNode();
156 payload.set("members", members);
157 for (UiClusterMember member : instances) {
158 members.add(json(member, member.id().equals(local)));
159 }
160
161 return payload;
162 }
163
164 private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
165 return objectNode()
166 .put("id", member.id().toString())
167 .put("ip", member.ip().toString())
168 .put("online", member.isOnline())
169 .put("ready", member.isReady())
170 .put("uiAttached", isUiAttached)
171 .put("switches", member.deviceCount());
172 }
173
174 /**
175 * Returns a JSON representation of the layout to use for displaying in
Simon Huntf836a872016-08-10 17:37:36 -0700176 * the topology view. The identifiers and names of regions from the
177 * current to the root is included, so that the bread-crumb widget can
178 * be rendered.
Simon Huntd5b96732016-07-08 13:22:27 -0700179 *
180 * @param layout the layout to transform
Simon Huntf836a872016-08-10 17:37:36 -0700181 * @param crumbs list of layouts in bread-crumb order
Simon Huntd5b96732016-07-08 13:22:27 -0700182 * @return a JSON representation of the data
183 */
Simon Huntf836a872016-08-10 17:37:36 -0700184 ObjectNode layout(UiTopoLayout layout, List<UiTopoLayout> crumbs) {
185 ObjectNode result = objectNode()
Simon Huntd5b96732016-07-08 13:22:27 -0700186 .put("id", layout.id().toString())
187 .put("parent", nullIsEmpty(layout.parent()))
188 .put("region", nullIsEmpty(layout.regionId()))
Simon Huntf836a872016-08-10 17:37:36 -0700189 .put("regionName", UiRegion.safeName(layout.region()));
190 addCrumbs(result, crumbs);
191 return result;
Simon Huntd5b96732016-07-08 13:22:27 -0700192 }
193
Simon Huntf836a872016-08-10 17:37:36 -0700194 private void addCrumbs(ObjectNode result, List<UiTopoLayout> crumbs) {
195 ArrayNode trail = arrayNode();
196 crumbs.forEach(c -> {
197 ObjectNode n = objectNode()
198 .put("id", c.regionId().toString())
199 .put("name", UiRegion.safeName(c.region()));
200 trail.add(n);
201 });
202 result.set("crumbs", trail);
Simon Huntd5b96732016-07-08 13:22:27 -0700203 }
204
205 /**
206 * Returns a JSON representation of the region to display in the topology
207 * view.
208 *
Simon Hunt977aa052016-07-20 17:08:29 -0700209 * @param region the region to transform to JSON
210 * @param subRegions the subregions within this region
Simon Huntc13082f2016-08-03 21:20:23 -0700211 * @param links the links within this region
Simon Huntd5b96732016-07-08 13:22:27 -0700212 * @return a JSON representation of the data
213 */
Simon Huntc13082f2016-08-03 21:20:23 -0700214 ObjectNode region(UiRegion region, Set<UiRegion> subRegions,
215 List<UiSynthLink> links) {
Simon Huntd5b96732016-07-08 13:22:27 -0700216 ObjectNode payload = objectNode();
Simon Huntd5b96732016-07-08 13:22:27 -0700217 if (region == null) {
218 payload.put("note", "no-region");
219 return payload;
220 }
Simon Hunt977aa052016-07-20 17:08:29 -0700221 payload.put("id", region.idAsString());
Simon Huntcd508a62016-10-27 12:47:24 -0700222 payload.set("subregions", jsonSubRegions(subRegions));
223 payload.set("links", jsonLinks(links));
Simon Huntc13082f2016-08-03 21:20:23 -0700224
Simon Hunt977aa052016-07-20 17:08:29 -0700225 List<String> layerTags = region.layerOrder();
226 List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
227 List<Set<UiNode>> splitHosts = splitByLayer(layerTags, region.hosts());
Simon Huntd5b96732016-07-08 13:22:27 -0700228
Simon Hunt977aa052016-07-20 17:08:29 -0700229 payload.set("devices", jsonGrouped(splitDevices));
230 payload.set("hosts", jsonGrouped(splitHosts));
Simon Hunt977aa052016-07-20 17:08:29 -0700231 payload.set("layerOrder", jsonStrings(layerTags));
Simon Huntd5b96732016-07-08 13:22:27 -0700232
233 return payload;
234 }
235
Simon Hunt977aa052016-07-20 17:08:29 -0700236 private ArrayNode jsonSubRegions(Set<UiRegion> subregions) {
237 ArrayNode kids = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700238 subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
Simon Hunt977aa052016-07-20 17:08:29 -0700239 return kids;
240 }
241
Simon Huntc13082f2016-08-03 21:20:23 -0700242 private JsonNode jsonLinks(List<UiSynthLink> links) {
243 ArrayNode synthLinks = arrayNode();
244 links.forEach(l -> synthLinks.add(json(l)));
245 return synthLinks;
246 }
247
Simon Hunt977aa052016-07-20 17:08:29 -0700248 private ArrayNode jsonStrings(List<String> strings) {
249 ArrayNode array = arrayNode();
250 strings.forEach(array::add);
251 return array;
252 }
253
Simon Hunt977aa052016-07-20 17:08:29 -0700254 private ArrayNode jsonGrouped(List<Set<UiNode>> groupedNodes) {
255 ArrayNode result = arrayNode();
256 groupedNodes.forEach(g -> {
257 ArrayNode subset = arrayNode();
258 g.forEach(n -> subset.add(json(n)));
259 result.add(subset);
260 });
261 return result;
262 }
263
Simon Hunt977aa052016-07-20 17:08:29 -0700264
265 private ObjectNode json(UiNode node) {
266 if (node instanceof UiRegion) {
267 return jsonClosedRegion((UiRegion) node);
268 }
269 if (node instanceof UiDevice) {
270 return json((UiDevice) node);
271 }
272 if (node instanceof UiHost) {
273 return json((UiHost) node);
274 }
275 throw new IllegalStateException(E_UNKNOWN_UI_NODE + node.getClass());
276 }
277
Simon Huntd5b96732016-07-08 13:22:27 -0700278 private ObjectNode json(UiDevice device) {
279 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700280 .put("id", device.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700281 .put("nodeType", DEVICE)
Simon Huntd5b96732016-07-08 13:22:27 -0700282 .put("type", device.type())
Simon Hunt3d712522016-08-11 11:20:44 -0700283 .put("online", deviceService.isAvailable(device.id()))
Simon Huntb1ce2602016-07-23 14:04:31 -0700284 .put("master", nullIsEmpty(device.master()))
Simon Huntd5b96732016-07-08 13:22:27 -0700285 .put("layer", device.layer());
286
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700287 Device d = device.backingDevice();
288
289 addProps(node, d);
290 addGeoLocation(node, d);
291 addMetaUi(node, device.idAsString());
Simon Huntd5b96732016-07-08 13:22:27 -0700292
293 return node;
294 }
295
Simon Hunt53612212016-12-04 17:19:52 -0800296 private void addProps(ObjectNode node, Annotated a) {
297 Annotations annot = a.annotations();
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700298 ObjectNode props = objectNode();
299 if (annot != null) {
300 annot.keys().forEach(k -> props.put(k, annot.value(k)));
301 }
302 node.set("props", props);
303 }
Simon Huntd5b96732016-07-08 13:22:27 -0700304
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700305 private void addMetaUi(ObjectNode node, String metaInstanceId) {
306 ObjectNode meta = metaUi.get(metaInstanceId);
307 if (meta != null) {
308 node.set("metaUi", meta);
309 }
310 }
311
312 private void addGeoLocation(ObjectNode node, Annotated a) {
313 List<String> lngLat = getAnnotValues(a, LONGITUDE, LATITUDE);
314 if (lngLat != null) {
315 try {
316 double lng = Double.parseDouble(lngLat.get(0));
317 double lat = Double.parseDouble(lngLat.get(1));
318 ObjectNode loc = objectNode()
319 .put("type", "lnglat")
320 .put("lng", lng)
321 .put("lat", lat);
322 node.set("location", loc);
323
324 } catch (NumberFormatException e) {
325 log.warn("Invalid geo data: longitude={}, latitude={}",
326 lngLat.get(0), lngLat.get(1));
327 }
328 } else {
329 log.debug("No geo lng/lat for {}", a);
330 }
331 }
332
Steven Burrows583f4be2016-11-04 14:06:50 +0100333 private void addIps(ObjectNode node, Host h) {
334 Set<IpAddress> ips = h.ipAddresses();
335
336 ArrayNode a = arrayNode();
337 for (IpAddress ip : ips) {
338 a.add(ip.toString());
339 }
340
341 node.set("ips", a);
342 }
343
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700344 // return list of string values from annotated instance, for given keys
345 // return null if any keys are not present
346 List<String> getAnnotValues(Annotated a, String... annotKeys) {
347 List<String> result = new ArrayList<>(annotKeys.length);
348 for (String k : annotKeys) {
349 String v = a.annotations().value(k);
350 if (v == null) {
351 return null;
352 }
353 result.add(v);
354 }
355 return result;
356 }
357
358 // derive JSON object from annotations
359 private ObjectNode props(Annotations annotations) {
360 ObjectNode p = objectNode();
361 if (annotations != null) {
362 annotations.keys().forEach(k -> p.put(k, annotations.value(k)));
363 }
364 return p;
Simon Huntd5b96732016-07-08 13:22:27 -0700365 }
366
367 private ObjectNode json(UiHost host) {
Steven Burrows583f4be2016-11-04 14:06:50 +0100368 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700369 .put("id", host.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700370 .put("nodeType", HOST)
Simon Huntd5b96732016-07-08 13:22:27 -0700371 .put("layer", host.layer());
372 // TODO: complete host details
Steven Burrows583f4be2016-11-04 14:06:50 +0100373 Host h = host.backingHost();
374
375 addIps(node, h);
376 addGeoLocation(node, h);
377 addMetaUi(node, host.idAsString());
378
379 return node;
Simon Huntd5b96732016-07-08 13:22:27 -0700380 }
381
Simon Huntc13082f2016-08-03 21:20:23 -0700382 private ObjectNode json(UiSynthLink sLink) {
383 UiLink uLink = sLink.link();
Simon Hunt3d712522016-08-11 11:20:44 -0700384 ObjectNode data = objectNode()
Simon Huntc13082f2016-08-03 21:20:23 -0700385 .put("id", uLink.idAsString())
386 .put("epA", uLink.endPointA())
387 .put("epB", uLink.endPointB())
388 .put("type", uLink.type());
Simon Hunt3d712522016-08-11 11:20:44 -0700389 String pA = uLink.endPortA();
390 String pB = uLink.endPortB();
391 if (pA != null) {
392 data.put("portA", pA);
393 }
394 if (pB != null) {
395 data.put("portB", pB);
396 }
397 return data;
Simon Huntd5b96732016-07-08 13:22:27 -0700398 }
399
400
Simon Hunt977aa052016-07-20 17:08:29 -0700401 private ObjectNode jsonClosedRegion(UiRegion region) {
Steven Burrows482d9502016-09-27 11:24:58 -0700402 ObjectNode node = objectNode()
Simon Huntb1ce2602016-07-23 14:04:31 -0700403 .put("id", region.idAsString())
Simon Huntf836a872016-08-10 17:37:36 -0700404 .put("name", region.name())
Simon Hunt98189192016-07-29 19:02:27 -0700405 .put("nodeType", REGION)
Steven Burrows19e6e4f2016-10-05 13:27:07 -0500406 .put("nDevs", region.deviceCount())
407 .put("nHosts", region.hostCount());
Simon Hunt53612212016-12-04 17:19:52 -0800408
409 Region r = region.backingRegion();
410 addGeoLocation(node, r);
411 addProps(node, r);
Steven Burrows482d9502016-09-27 11:24:58 -0700412
413 addMetaUi(node, region.idAsString());
414 return node;
Simon Hunt977aa052016-07-20 17:08:29 -0700415 }
416
Simon Hunt98189192016-07-29 19:02:27 -0700417 /**
418 * Returns a JSON array representation of a set of regions/devices. Note
419 * that the information is sufficient for showing regions as nodes.
420 *
421 * @param nodes the nodes
422 * @return a JSON representation of the nodes
423 */
424 public ArrayNode closedNodes(Set<UiNode> nodes) {
425 ArrayNode array = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700426 for (UiNode node : nodes) {
Simon Hunt98189192016-07-29 19:02:27 -0700427 if (node instanceof UiRegion) {
428 array.add(jsonClosedRegion((UiRegion) node));
429 } else if (node instanceof UiDevice) {
430 array.add(json((UiDevice) node));
431 } else {
432 log.warn("Unexpected node instance: {}", node.getClass());
433 }
434 }
435 return array;
436 }
Simon Hunt977aa052016-07-20 17:08:29 -0700437
438 /**
439 * Returns a JSON array representation of a list of regions. Note that the
440 * information about each region is limited to what needs to be used to
441 * show the regions as nodes on the view.
442 *
443 * @param regions the regions
444 * @return a JSON representation of the minimal region information
445 */
446 public ArrayNode closedRegions(Set<UiRegion> regions) {
447 ArrayNode array = arrayNode();
448 for (UiRegion r : regions) {
449 array.add(jsonClosedRegion(r));
450 }
451 return array;
452 }
453
454 /**
455 * Returns a JSON array representation of a list of devices.
456 *
457 * @param devices the devices
458 * @return a JSON representation of the devices
459 */
460 public ArrayNode devices(Set<UiDevice> devices) {
461 ArrayNode array = arrayNode();
462 for (UiDevice device : devices) {
463 array.add(json(device));
464 }
465 return array;
466 }
467
468 /**
469 * Returns a JSON array representation of a list of hosts.
470 *
471 * @param hosts the hosts
472 * @return a JSON representation of the hosts
473 */
474 public ArrayNode hosts(Set<UiHost> hosts) {
475 ArrayNode array = arrayNode();
476 for (UiHost host : hosts) {
477 array.add(json(host));
478 }
479 return array;
480 }
481
Simon Hunt977aa052016-07-20 17:08:29 -0700482 // package-private for unit testing
483 List<Set<UiNode>> splitByLayer(List<String> layerTags,
484 Set<? extends UiNode> nodes) {
485 final int nLayers = layerTags.size();
486 if (!layerTags.get(nLayers - 1).equals(LAYER_DEFAULT)) {
487 throw new IllegalArgumentException(E_DEF_NOT_LAST);
488 }
489
490 List<Set<UiNode>> splitList = new ArrayList<>(layerTags.size());
491 Map<String, Set<UiNode>> byLayer = new HashMap<>(layerTags.size());
492
493 for (String tag : layerTags) {
494 Set<UiNode> set = new HashSet<>();
495 byLayer.put(tag, set);
496 splitList.add(set);
497 }
498
499 for (UiNode n : nodes) {
500 String which = n.layer();
501 if (!layerTags.contains(which)) {
502 which = LAYER_DEFAULT;
503 }
504 byLayer.get(which).add(n);
505 }
506
507 return splitList;
508 }
Steven Burrowse7cc3082016-09-27 11:24:58 -0700509
510 /**
511 * Stores the memento for an element.
512 * This method assumes the payload has an id String, memento ObjectNode
513 *
514 * @param payload event payload
515 */
516 void updateMeta(ObjectNode payload) {
517
518 String id = JsonUtils.string(payload, "id");
519 metaUi.put(id, JsonUtils.node(payload, "memento"));
520
521 log.debug("Storing metadata for {}", id);
522 }
Simon Huntd5b96732016-07-08 13:22:27 -0700523}