blob: 8ff3bb6f2cc0a60140c655b0b22be51e9f240e2d [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;
24import org.onosproject.cluster.ClusterService;
25import org.onosproject.cluster.NodeId;
26import org.onosproject.incubator.net.PortStatisticsService;
27import org.onosproject.incubator.net.tunnel.TunnelService;
28import org.onosproject.mastership.MastershipService;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070029import org.onosproject.net.Annotated;
30import org.onosproject.net.Annotations;
31import org.onosproject.net.Device;
Simon Huntd5b96732016-07-08 13:22:27 -070032import org.onosproject.net.device.DeviceService;
33import org.onosproject.net.flow.FlowRuleService;
34import org.onosproject.net.host.HostService;
35import org.onosproject.net.intent.IntentService;
36import org.onosproject.net.link.LinkService;
Simon Huntd5b96732016-07-08 13:22:27 -070037import org.onosproject.net.statistic.StatisticService;
38import org.onosproject.net.topology.TopologyService;
39import org.onosproject.ui.model.topo.UiClusterMember;
40import org.onosproject.ui.model.topo.UiDevice;
41import org.onosproject.ui.model.topo.UiHost;
42import org.onosproject.ui.model.topo.UiLink;
Simon Hunt977aa052016-07-20 17:08:29 -070043import org.onosproject.ui.model.topo.UiNode;
Simon Huntd5b96732016-07-08 13:22:27 -070044import org.onosproject.ui.model.topo.UiRegion;
Simon Huntc13082f2016-08-03 21:20:23 -070045import org.onosproject.ui.model.topo.UiSynthLink;
Simon Huntd5b96732016-07-08 13:22:27 -070046import org.onosproject.ui.model.topo.UiTopoLayout;
Simon Hunt98189192016-07-29 19:02:27 -070047import org.slf4j.Logger;
48import org.slf4j.LoggerFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070049
Simon Hunt977aa052016-07-20 17:08:29 -070050import java.util.ArrayList;
51import java.util.HashMap;
52import java.util.HashSet;
Simon Huntd5b96732016-07-08 13:22:27 -070053import java.util.List;
Simon Hunt977aa052016-07-20 17:08:29 -070054import java.util.Map;
55import java.util.Set;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070056import java.util.concurrent.ConcurrentHashMap;
Simon Huntd5b96732016-07-08 13:22:27 -070057
58import static com.google.common.base.Preconditions.checkNotNull;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070059import static org.onosproject.net.AnnotationKeys.LATITUDE;
60import static org.onosproject.net.AnnotationKeys.LONGITUDE;
Simon Hunt977aa052016-07-20 17:08:29 -070061import static org.onosproject.ui.model.topo.UiNode.LAYER_DEFAULT;
Simon Huntd5b96732016-07-08 13:22:27 -070062
63/**
64 * Facility for creating JSON messages to send to the topology view in the
65 * Web client.
66 */
67class Topo2Jsonifier {
68
Simon Hunt977aa052016-07-20 17:08:29 -070069 private static final String E_DEF_NOT_LAST =
70 "UiNode.LAYER_DEFAULT not last in layer list";
71 private static final String E_UNKNOWN_UI_NODE =
72 "Unknown subclass of UiNode: ";
73
Simon Hunt98189192016-07-29 19:02:27 -070074 private static final String REGION = "region";
75 private static final String DEVICE = "device";
76 private static final String HOST = "host";
77
78 private final Logger log = LoggerFactory.getLogger(getClass());
79
Simon Huntd5b96732016-07-08 13:22:27 -070080 private final ObjectMapper mapper = new ObjectMapper();
81
82 private ServiceDirectory directory;
83 private ClusterService clusterService;
84 private DeviceService deviceService;
85 private LinkService linkService;
86 private HostService hostService;
87 private MastershipService mastershipService;
88 private IntentService intentService;
89 private FlowRuleService flowService;
90 private StatisticService flowStatsService;
91 private PortStatisticsService portStatsService;
92 private TopologyService topologyService;
93 private TunnelService tunnelService;
94
95
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070096 // NOTE: we'll stick this here for now, but maybe there is a better home?
97 // (this is not distributed across the cluster)
98 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
99
100
Simon Huntd5b96732016-07-08 13:22:27 -0700101 /**
102 * Creates an instance with a reference to the services directory, so that
103 * additional information about network elements may be looked up on
104 * on the fly.
105 *
106 * @param directory service directory
107 */
108 Topo2Jsonifier(ServiceDirectory directory) {
109 this.directory = checkNotNull(directory, "Directory cannot be null");
110
111 clusterService = directory.get(ClusterService.class);
112 deviceService = directory.get(DeviceService.class);
113 linkService = directory.get(LinkService.class);
114 hostService = directory.get(HostService.class);
115 mastershipService = directory.get(MastershipService.class);
116 intentService = directory.get(IntentService.class);
117 flowService = directory.get(FlowRuleService.class);
118 flowStatsService = directory.get(StatisticService.class);
119 portStatsService = directory.get(PortStatisticsService.class);
120 topologyService = directory.get(TopologyService.class);
121 tunnelService = directory.get(TunnelService.class);
Simon Hunt977aa052016-07-20 17:08:29 -0700122 }
Simon Huntd5b96732016-07-08 13:22:27 -0700123
Simon Hunt977aa052016-07-20 17:08:29 -0700124 // for unit testing
125 Topo2Jsonifier() {
Simon Huntd5b96732016-07-08 13:22:27 -0700126 }
127
128 private ObjectNode objectNode() {
129 return mapper.createObjectNode();
130 }
131
132 private ArrayNode arrayNode() {
133 return mapper.createArrayNode();
134 }
135
136 private String nullIsEmpty(Object o) {
137 return o == null ? "" : o.toString();
138 }
139
140
141 /**
142 * Returns a JSON representation of the cluster members (ONOS instances).
143 *
144 * @param instances the instance model objects
145 * @return a JSON representation of the data
146 */
147 ObjectNode instances(List<UiClusterMember> instances) {
148 NodeId local = clusterService.getLocalNode().id();
149 ObjectNode payload = objectNode();
150
151 ArrayNode members = arrayNode();
152 payload.set("members", members);
153 for (UiClusterMember member : instances) {
154 members.add(json(member, member.id().equals(local)));
155 }
156
157 return payload;
158 }
159
160 private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
161 return objectNode()
162 .put("id", member.id().toString())
163 .put("ip", member.ip().toString())
164 .put("online", member.isOnline())
165 .put("ready", member.isReady())
166 .put("uiAttached", isUiAttached)
167 .put("switches", member.deviceCount());
168 }
169
170 /**
171 * Returns a JSON representation of the layout to use for displaying in
Simon Huntf836a872016-08-10 17:37:36 -0700172 * the topology view. The identifiers and names of regions from the
173 * current to the root is included, so that the bread-crumb widget can
174 * be rendered.
Simon Huntd5b96732016-07-08 13:22:27 -0700175 *
176 * @param layout the layout to transform
Simon Huntf836a872016-08-10 17:37:36 -0700177 * @param crumbs list of layouts in bread-crumb order
Simon Huntd5b96732016-07-08 13:22:27 -0700178 * @return a JSON representation of the data
179 */
Simon Huntf836a872016-08-10 17:37:36 -0700180 ObjectNode layout(UiTopoLayout layout, List<UiTopoLayout> crumbs) {
181 ObjectNode result = objectNode()
Simon Huntd5b96732016-07-08 13:22:27 -0700182 .put("id", layout.id().toString())
183 .put("parent", nullIsEmpty(layout.parent()))
184 .put("region", nullIsEmpty(layout.regionId()))
Simon Huntf836a872016-08-10 17:37:36 -0700185 .put("regionName", UiRegion.safeName(layout.region()));
186 addCrumbs(result, crumbs);
187 return result;
Simon Huntd5b96732016-07-08 13:22:27 -0700188 }
189
Simon Huntf836a872016-08-10 17:37:36 -0700190 private void addCrumbs(ObjectNode result, List<UiTopoLayout> crumbs) {
191 ArrayNode trail = arrayNode();
192 crumbs.forEach(c -> {
193 ObjectNode n = objectNode()
194 .put("id", c.regionId().toString())
195 .put("name", UiRegion.safeName(c.region()));
196 trail.add(n);
197 });
198 result.set("crumbs", trail);
Simon Huntd5b96732016-07-08 13:22:27 -0700199 }
200
201 /**
202 * Returns a JSON representation of the region to display in the topology
203 * view.
204 *
Simon Hunt977aa052016-07-20 17:08:29 -0700205 * @param region the region to transform to JSON
206 * @param subRegions the subregions within this region
Simon Huntc13082f2016-08-03 21:20:23 -0700207 * @param links the links within this region
Simon Huntd5b96732016-07-08 13:22:27 -0700208 * @return a JSON representation of the data
209 */
Simon Huntc13082f2016-08-03 21:20:23 -0700210 ObjectNode region(UiRegion region, Set<UiRegion> subRegions,
211 List<UiSynthLink> links) {
Simon Huntd5b96732016-07-08 13:22:27 -0700212 ObjectNode payload = objectNode();
Simon Huntd5b96732016-07-08 13:22:27 -0700213 if (region == null) {
214 payload.put("note", "no-region");
215 return payload;
216 }
Simon Hunt977aa052016-07-20 17:08:29 -0700217 payload.put("id", region.idAsString());
Simon Huntb1ce2602016-07-23 14:04:31 -0700218 if (subRegions != null) {
219 payload.set("subregions", jsonSubRegions(subRegions));
220 }
Simon Huntd5b96732016-07-08 13:22:27 -0700221
Simon Huntc13082f2016-08-03 21:20:23 -0700222 if (links != null) {
223 payload.set("links", jsonLinks(links));
224 }
225
Simon Hunt977aa052016-07-20 17:08:29 -0700226 List<String> layerTags = region.layerOrder();
227 List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
228 List<Set<UiNode>> splitHosts = splitByLayer(layerTags, region.hosts());
Simon Huntd5b96732016-07-08 13:22:27 -0700229
Simon Hunt977aa052016-07-20 17:08:29 -0700230 payload.set("devices", jsonGrouped(splitDevices));
231 payload.set("hosts", jsonGrouped(splitHosts));
Simon Hunt977aa052016-07-20 17:08:29 -0700232 payload.set("layerOrder", jsonStrings(layerTags));
Simon Huntd5b96732016-07-08 13:22:27 -0700233
234 return payload;
235 }
236
Simon Hunt977aa052016-07-20 17:08:29 -0700237 private ArrayNode jsonSubRegions(Set<UiRegion> subregions) {
238 ArrayNode kids = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700239 subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
Simon Hunt977aa052016-07-20 17:08:29 -0700240 return kids;
241 }
242
Simon Huntc13082f2016-08-03 21:20:23 -0700243 private JsonNode jsonLinks(List<UiSynthLink> links) {
244 ArrayNode synthLinks = arrayNode();
245 links.forEach(l -> synthLinks.add(json(l)));
246 return synthLinks;
247 }
248
Simon Hunt977aa052016-07-20 17:08:29 -0700249 private ArrayNode jsonStrings(List<String> strings) {
250 ArrayNode array = arrayNode();
251 strings.forEach(array::add);
252 return array;
253 }
254
Simon Hunt977aa052016-07-20 17:08:29 -0700255 private ArrayNode jsonGrouped(List<Set<UiNode>> groupedNodes) {
256 ArrayNode result = arrayNode();
257 groupedNodes.forEach(g -> {
258 ArrayNode subset = arrayNode();
259 g.forEach(n -> subset.add(json(n)));
260 result.add(subset);
261 });
262 return result;
263 }
264
Simon Hunt977aa052016-07-20 17:08:29 -0700265
266 private ObjectNode json(UiNode node) {
267 if (node instanceof UiRegion) {
268 return jsonClosedRegion((UiRegion) node);
269 }
270 if (node instanceof UiDevice) {
271 return json((UiDevice) node);
272 }
273 if (node instanceof UiHost) {
274 return json((UiHost) node);
275 }
276 throw new IllegalStateException(E_UNKNOWN_UI_NODE + node.getClass());
277 }
278
Simon Huntd5b96732016-07-08 13:22:27 -0700279 private ObjectNode json(UiDevice device) {
280 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700281 .put("id", device.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700282 .put("nodeType", DEVICE)
Simon Huntd5b96732016-07-08 13:22:27 -0700283 .put("type", device.type())
Simon Hunt3d712522016-08-11 11:20:44 -0700284 .put("online", deviceService.isAvailable(device.id()))
Simon Huntb1ce2602016-07-23 14:04:31 -0700285 .put("master", nullIsEmpty(device.master()))
Simon Huntd5b96732016-07-08 13:22:27 -0700286 .put("layer", device.layer());
287
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700288 Device d = device.backingDevice();
289
290 addProps(node, d);
291 addGeoLocation(node, d);
292 addMetaUi(node, device.idAsString());
Simon Huntd5b96732016-07-08 13:22:27 -0700293
294 return node;
295 }
296
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700297 private void addProps(ObjectNode node, Device dev) {
298 Annotations annot = dev.annotations();
299 ObjectNode props = objectNode();
300 if (annot != null) {
301 annot.keys().forEach(k -> props.put(k, annot.value(k)));
302 }
303 node.set("props", props);
304 }
Simon Huntd5b96732016-07-08 13:22:27 -0700305
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700306 private void addMetaUi(ObjectNode node, String metaInstanceId) {
307 ObjectNode meta = metaUi.get(metaInstanceId);
308 if (meta != null) {
309 node.set("metaUi", meta);
310 }
311 }
312
313 private void addGeoLocation(ObjectNode node, Annotated a) {
314 List<String> lngLat = getAnnotValues(a, LONGITUDE, LATITUDE);
315 if (lngLat != null) {
316 try {
317 double lng = Double.parseDouble(lngLat.get(0));
318 double lat = Double.parseDouble(lngLat.get(1));
319 ObjectNode loc = objectNode()
320 .put("type", "lnglat")
321 .put("lng", lng)
322 .put("lat", lat);
323 node.set("location", loc);
324
325 } catch (NumberFormatException e) {
326 log.warn("Invalid geo data: longitude={}, latitude={}",
327 lngLat.get(0), lngLat.get(1));
328 }
329 } else {
330 log.debug("No geo lng/lat for {}", a);
331 }
332 }
333
334 // return list of string values from annotated instance, for given keys
335 // return null if any keys are not present
336 List<String> getAnnotValues(Annotated a, String... annotKeys) {
337 List<String> result = new ArrayList<>(annotKeys.length);
338 for (String k : annotKeys) {
339 String v = a.annotations().value(k);
340 if (v == null) {
341 return null;
342 }
343 result.add(v);
344 }
345 return result;
346 }
347
348 // derive JSON object from annotations
349 private ObjectNode props(Annotations annotations) {
350 ObjectNode p = objectNode();
351 if (annotations != null) {
352 annotations.keys().forEach(k -> p.put(k, annotations.value(k)));
353 }
354 return p;
Simon Huntd5b96732016-07-08 13:22:27 -0700355 }
356
357 private ObjectNode json(UiHost host) {
358 return objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700359 .put("id", host.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700360 .put("nodeType", HOST)
Simon Huntd5b96732016-07-08 13:22:27 -0700361 .put("layer", host.layer());
362 // TODO: complete host details
363 }
364
Simon Huntc13082f2016-08-03 21:20:23 -0700365 private ObjectNode json(UiSynthLink sLink) {
366 UiLink uLink = sLink.link();
Simon Hunt3d712522016-08-11 11:20:44 -0700367 ObjectNode data = objectNode()
Simon Huntc13082f2016-08-03 21:20:23 -0700368 .put("id", uLink.idAsString())
369 .put("epA", uLink.endPointA())
370 .put("epB", uLink.endPointB())
371 .put("type", uLink.type());
Simon Hunt3d712522016-08-11 11:20:44 -0700372 String pA = uLink.endPortA();
373 String pB = uLink.endPortB();
374 if (pA != null) {
375 data.put("portA", pA);
376 }
377 if (pB != null) {
378 data.put("portB", pB);
379 }
380 return data;
Simon Huntd5b96732016-07-08 13:22:27 -0700381 }
382
383
Simon Hunt977aa052016-07-20 17:08:29 -0700384 private ObjectNode jsonClosedRegion(UiRegion region) {
385 return objectNode()
Simon Huntb1ce2602016-07-23 14:04:31 -0700386 .put("id", region.idAsString())
Simon Huntf836a872016-08-10 17:37:36 -0700387 .put("name", region.name())
Simon Hunt98189192016-07-29 19:02:27 -0700388 .put("nodeType", REGION)
Simon Huntb1ce2602016-07-23 14:04:31 -0700389 .put("nDevs", region.deviceCount());
Simon Hunt977aa052016-07-20 17:08:29 -0700390 // TODO: complete closed-region details
391 }
392
Simon Hunt98189192016-07-29 19:02:27 -0700393 /**
394 * Returns a JSON array representation of a set of regions/devices. Note
395 * that the information is sufficient for showing regions as nodes.
396 *
397 * @param nodes the nodes
398 * @return a JSON representation of the nodes
399 */
400 public ArrayNode closedNodes(Set<UiNode> nodes) {
401 ArrayNode array = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700402 for (UiNode node : nodes) {
Simon Hunt98189192016-07-29 19:02:27 -0700403 if (node instanceof UiRegion) {
404 array.add(jsonClosedRegion((UiRegion) node));
405 } else if (node instanceof UiDevice) {
406 array.add(json((UiDevice) node));
407 } else {
408 log.warn("Unexpected node instance: {}", node.getClass());
409 }
410 }
411 return array;
412 }
Simon Hunt977aa052016-07-20 17:08:29 -0700413
414 /**
415 * Returns a JSON array representation of a list of regions. Note that the
416 * information about each region is limited to what needs to be used to
417 * show the regions as nodes on the view.
418 *
419 * @param regions the regions
420 * @return a JSON representation of the minimal region information
421 */
422 public ArrayNode closedRegions(Set<UiRegion> regions) {
423 ArrayNode array = arrayNode();
424 for (UiRegion r : regions) {
425 array.add(jsonClosedRegion(r));
426 }
427 return array;
428 }
429
430 /**
431 * Returns a JSON array representation of a list of devices.
432 *
433 * @param devices the devices
434 * @return a JSON representation of the devices
435 */
436 public ArrayNode devices(Set<UiDevice> devices) {
437 ArrayNode array = arrayNode();
438 for (UiDevice device : devices) {
439 array.add(json(device));
440 }
441 return array;
442 }
443
444 /**
445 * Returns a JSON array representation of a list of hosts.
446 *
447 * @param hosts the hosts
448 * @return a JSON representation of the hosts
449 */
450 public ArrayNode hosts(Set<UiHost> hosts) {
451 ArrayNode array = arrayNode();
452 for (UiHost host : hosts) {
453 array.add(json(host));
454 }
455 return array;
456 }
457
Simon Hunt977aa052016-07-20 17:08:29 -0700458 // package-private for unit testing
459 List<Set<UiNode>> splitByLayer(List<String> layerTags,
460 Set<? extends UiNode> nodes) {
461 final int nLayers = layerTags.size();
462 if (!layerTags.get(nLayers - 1).equals(LAYER_DEFAULT)) {
463 throw new IllegalArgumentException(E_DEF_NOT_LAST);
464 }
465
466 List<Set<UiNode>> splitList = new ArrayList<>(layerTags.size());
467 Map<String, Set<UiNode>> byLayer = new HashMap<>(layerTags.size());
468
469 for (String tag : layerTags) {
470 Set<UiNode> set = new HashSet<>();
471 byLayer.put(tag, set);
472 splitList.add(set);
473 }
474
475 for (UiNode n : nodes) {
476 String which = n.layer();
477 if (!layerTags.contains(which)) {
478 which = LAYER_DEFAULT;
479 }
480 byLayer.get(which).add(n);
481 }
482
483 return splitList;
484 }
Simon Huntd5b96732016-07-08 13:22:27 -0700485}