blob: 6d8ae53ac49fea9b14e4d46e8a72b668a0b4a652 [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;
37import org.onosproject.net.region.Region;
38import org.onosproject.net.statistic.StatisticService;
39import org.onosproject.net.topology.TopologyService;
40import org.onosproject.ui.model.topo.UiClusterMember;
41import org.onosproject.ui.model.topo.UiDevice;
42import org.onosproject.ui.model.topo.UiHost;
43import org.onosproject.ui.model.topo.UiLink;
Simon Hunt977aa052016-07-20 17:08:29 -070044import org.onosproject.ui.model.topo.UiNode;
Simon Huntd5b96732016-07-08 13:22:27 -070045import org.onosproject.ui.model.topo.UiRegion;
Simon Huntc13082f2016-08-03 21:20:23 -070046import org.onosproject.ui.model.topo.UiSynthLink;
Simon Huntd5b96732016-07-08 13:22:27 -070047import org.onosproject.ui.model.topo.UiTopoLayout;
Simon Hunt98189192016-07-29 19:02:27 -070048import org.slf4j.Logger;
49import org.slf4j.LoggerFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070050
Simon Hunt977aa052016-07-20 17:08:29 -070051import java.util.ArrayList;
52import java.util.HashMap;
53import java.util.HashSet;
Simon Huntd5b96732016-07-08 13:22:27 -070054import java.util.List;
Simon Hunt977aa052016-07-20 17:08:29 -070055import java.util.Map;
56import java.util.Set;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070057import java.util.concurrent.ConcurrentHashMap;
Simon Huntd5b96732016-07-08 13:22:27 -070058
59import static com.google.common.base.Preconditions.checkNotNull;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070060import static org.onosproject.net.AnnotationKeys.LATITUDE;
61import static org.onosproject.net.AnnotationKeys.LONGITUDE;
Simon Hunt977aa052016-07-20 17:08:29 -070062import static org.onosproject.ui.model.topo.UiNode.LAYER_DEFAULT;
Simon Huntd5b96732016-07-08 13:22:27 -070063
64/**
65 * Facility for creating JSON messages to send to the topology view in the
66 * Web client.
67 */
68class Topo2Jsonifier {
69
Simon Hunt977aa052016-07-20 17:08:29 -070070 private static final String E_DEF_NOT_LAST =
71 "UiNode.LAYER_DEFAULT not last in layer list";
72 private static final String E_UNKNOWN_UI_NODE =
73 "Unknown subclass of UiNode: ";
74
Simon Hunt98189192016-07-29 19:02:27 -070075 private static final String REGION = "region";
76 private static final String DEVICE = "device";
77 private static final String HOST = "host";
78
79 private final Logger log = LoggerFactory.getLogger(getClass());
80
Simon Huntd5b96732016-07-08 13:22:27 -070081 private final ObjectMapper mapper = new ObjectMapper();
82
83 private ServiceDirectory directory;
84 private ClusterService clusterService;
85 private DeviceService deviceService;
86 private LinkService linkService;
87 private HostService hostService;
88 private MastershipService mastershipService;
89 private IntentService intentService;
90 private FlowRuleService flowService;
91 private StatisticService flowStatsService;
92 private PortStatisticsService portStatsService;
93 private TopologyService topologyService;
94 private TunnelService tunnelService;
95
96
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070097 // NOTE: we'll stick this here for now, but maybe there is a better home?
98 // (this is not distributed across the cluster)
99 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
100
101
Simon Huntd5b96732016-07-08 13:22:27 -0700102 /**
103 * Creates an instance with a reference to the services directory, so that
104 * additional information about network elements may be looked up on
105 * on the fly.
106 *
107 * @param directory service directory
108 */
109 Topo2Jsonifier(ServiceDirectory directory) {
110 this.directory = checkNotNull(directory, "Directory cannot be null");
111
112 clusterService = directory.get(ClusterService.class);
113 deviceService = directory.get(DeviceService.class);
114 linkService = directory.get(LinkService.class);
115 hostService = directory.get(HostService.class);
116 mastershipService = directory.get(MastershipService.class);
117 intentService = directory.get(IntentService.class);
118 flowService = directory.get(FlowRuleService.class);
119 flowStatsService = directory.get(StatisticService.class);
120 portStatsService = directory.get(PortStatisticsService.class);
121 topologyService = directory.get(TopologyService.class);
122 tunnelService = directory.get(TunnelService.class);
Simon Hunt977aa052016-07-20 17:08:29 -0700123 }
Simon Huntd5b96732016-07-08 13:22:27 -0700124
Simon Hunt977aa052016-07-20 17:08:29 -0700125 // for unit testing
126 Topo2Jsonifier() {
Simon Huntd5b96732016-07-08 13:22:27 -0700127 }
128
129 private ObjectNode objectNode() {
130 return mapper.createObjectNode();
131 }
132
133 private ArrayNode arrayNode() {
134 return mapper.createArrayNode();
135 }
136
137 private String nullIsEmpty(Object o) {
138 return o == null ? "" : o.toString();
139 }
140
141
142 /**
143 * Returns a JSON representation of the cluster members (ONOS instances).
144 *
145 * @param instances the instance model objects
146 * @return a JSON representation of the data
147 */
148 ObjectNode instances(List<UiClusterMember> instances) {
149 NodeId local = clusterService.getLocalNode().id();
150 ObjectNode payload = objectNode();
151
152 ArrayNode members = arrayNode();
153 payload.set("members", members);
154 for (UiClusterMember member : instances) {
155 members.add(json(member, member.id().equals(local)));
156 }
157
158 return payload;
159 }
160
161 private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
162 return objectNode()
163 .put("id", member.id().toString())
164 .put("ip", member.ip().toString())
165 .put("online", member.isOnline())
166 .put("ready", member.isReady())
167 .put("uiAttached", isUiAttached)
168 .put("switches", member.deviceCount());
169 }
170
171 /**
172 * Returns a JSON representation of the layout to use for displaying in
173 * the topology view.
174 *
175 * @param layout the layout to transform
176 * @return a JSON representation of the data
177 */
178 ObjectNode layout(UiTopoLayout layout) {
179 return objectNode()
180 .put("id", layout.id().toString())
181 .put("parent", nullIsEmpty(layout.parent()))
182 .put("region", nullIsEmpty(layout.regionId()))
183 .put("regionName", regionName(layout.region()));
184 }
185
186 private String regionName(Region region) {
187 return region == null ? "" : region.name();
188 }
189
190 /**
191 * Returns a JSON representation of the region to display in the topology
192 * view.
193 *
Simon Hunt977aa052016-07-20 17:08:29 -0700194 * @param region the region to transform to JSON
195 * @param subRegions the subregions within this region
Simon Huntc13082f2016-08-03 21:20:23 -0700196 * @param links the links within this region
Simon Huntd5b96732016-07-08 13:22:27 -0700197 * @return a JSON representation of the data
198 */
Simon Huntc13082f2016-08-03 21:20:23 -0700199 ObjectNode region(UiRegion region, Set<UiRegion> subRegions,
200 List<UiSynthLink> links) {
Simon Huntd5b96732016-07-08 13:22:27 -0700201 ObjectNode payload = objectNode();
Simon Huntd5b96732016-07-08 13:22:27 -0700202 if (region == null) {
203 payload.put("note", "no-region");
204 return payload;
205 }
Simon Hunt977aa052016-07-20 17:08:29 -0700206 payload.put("id", region.idAsString());
Simon Huntb1ce2602016-07-23 14:04:31 -0700207 if (subRegions != null) {
208 payload.set("subregions", jsonSubRegions(subRegions));
209 }
Simon Huntd5b96732016-07-08 13:22:27 -0700210
Simon Huntc13082f2016-08-03 21:20:23 -0700211 if (links != null) {
212 payload.set("links", jsonLinks(links));
213 }
214
Simon Hunt977aa052016-07-20 17:08:29 -0700215 List<String> layerTags = region.layerOrder();
216 List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
217 List<Set<UiNode>> splitHosts = splitByLayer(layerTags, region.hosts());
Simon Huntd5b96732016-07-08 13:22:27 -0700218
Simon Hunt977aa052016-07-20 17:08:29 -0700219 payload.set("devices", jsonGrouped(splitDevices));
220 payload.set("hosts", jsonGrouped(splitHosts));
Simon Hunt977aa052016-07-20 17:08:29 -0700221 payload.set("layerOrder", jsonStrings(layerTags));
Simon Huntd5b96732016-07-08 13:22:27 -0700222
223 return payload;
224 }
225
Simon Hunt977aa052016-07-20 17:08:29 -0700226 private ArrayNode jsonSubRegions(Set<UiRegion> subregions) {
227 ArrayNode kids = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700228 subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
Simon Hunt977aa052016-07-20 17:08:29 -0700229 return kids;
230 }
231
Simon Huntc13082f2016-08-03 21:20:23 -0700232 private JsonNode jsonLinks(List<UiSynthLink> links) {
233 ArrayNode synthLinks = arrayNode();
234 links.forEach(l -> synthLinks.add(json(l)));
235 return synthLinks;
236 }
237
Simon Hunt977aa052016-07-20 17:08:29 -0700238 private ArrayNode jsonStrings(List<String> strings) {
239 ArrayNode array = arrayNode();
240 strings.forEach(array::add);
241 return array;
242 }
243
Simon Hunt977aa052016-07-20 17:08:29 -0700244 private ArrayNode jsonGrouped(List<Set<UiNode>> groupedNodes) {
245 ArrayNode result = arrayNode();
246 groupedNodes.forEach(g -> {
247 ArrayNode subset = arrayNode();
248 g.forEach(n -> subset.add(json(n)));
249 result.add(subset);
250 });
251 return result;
252 }
253
Simon Hunt977aa052016-07-20 17:08:29 -0700254
255 private ObjectNode json(UiNode node) {
256 if (node instanceof UiRegion) {
257 return jsonClosedRegion((UiRegion) node);
258 }
259 if (node instanceof UiDevice) {
260 return json((UiDevice) node);
261 }
262 if (node instanceof UiHost) {
263 return json((UiHost) node);
264 }
265 throw new IllegalStateException(E_UNKNOWN_UI_NODE + node.getClass());
266 }
267
Simon Huntd5b96732016-07-08 13:22:27 -0700268 private ObjectNode json(UiDevice device) {
269 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700270 .put("id", device.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700271 .put("nodeType", DEVICE)
Simon Huntd5b96732016-07-08 13:22:27 -0700272 .put("type", device.type())
273 .put("online", device.isOnline())
Simon Huntb1ce2602016-07-23 14:04:31 -0700274 .put("master", nullIsEmpty(device.master()))
Simon Huntd5b96732016-07-08 13:22:27 -0700275 .put("layer", device.layer());
276
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700277 Device d = device.backingDevice();
278
279 addProps(node, d);
280 addGeoLocation(node, d);
281 addMetaUi(node, device.idAsString());
Simon Huntd5b96732016-07-08 13:22:27 -0700282
283 return node;
284 }
285
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700286 private void addProps(ObjectNode node, Device dev) {
287 Annotations annot = dev.annotations();
288 ObjectNode props = objectNode();
289 if (annot != null) {
290 annot.keys().forEach(k -> props.put(k, annot.value(k)));
291 }
292 node.set("props", props);
293 }
Simon Huntd5b96732016-07-08 13:22:27 -0700294
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700295 private void addMetaUi(ObjectNode node, String metaInstanceId) {
296 ObjectNode meta = metaUi.get(metaInstanceId);
297 if (meta != null) {
298 node.set("metaUi", meta);
299 }
300 }
301
302 private void addGeoLocation(ObjectNode node, Annotated a) {
303 List<String> lngLat = getAnnotValues(a, LONGITUDE, LATITUDE);
304 if (lngLat != null) {
305 try {
306 double lng = Double.parseDouble(lngLat.get(0));
307 double lat = Double.parseDouble(lngLat.get(1));
308 ObjectNode loc = objectNode()
309 .put("type", "lnglat")
310 .put("lng", lng)
311 .put("lat", lat);
312 node.set("location", loc);
313
314 } catch (NumberFormatException e) {
315 log.warn("Invalid geo data: longitude={}, latitude={}",
316 lngLat.get(0), lngLat.get(1));
317 }
318 } else {
319 log.debug("No geo lng/lat for {}", a);
320 }
321 }
322
323 // return list of string values from annotated instance, for given keys
324 // return null if any keys are not present
325 List<String> getAnnotValues(Annotated a, String... annotKeys) {
326 List<String> result = new ArrayList<>(annotKeys.length);
327 for (String k : annotKeys) {
328 String v = a.annotations().value(k);
329 if (v == null) {
330 return null;
331 }
332 result.add(v);
333 }
334 return result;
335 }
336
337 // derive JSON object from annotations
338 private ObjectNode props(Annotations annotations) {
339 ObjectNode p = objectNode();
340 if (annotations != null) {
341 annotations.keys().forEach(k -> p.put(k, annotations.value(k)));
342 }
343 return p;
Simon Huntd5b96732016-07-08 13:22:27 -0700344 }
345
346 private ObjectNode json(UiHost host) {
347 return objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700348 .put("id", host.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700349 .put("nodeType", HOST)
Simon Huntd5b96732016-07-08 13:22:27 -0700350 .put("layer", host.layer());
351 // TODO: complete host details
352 }
353
Simon Huntc13082f2016-08-03 21:20:23 -0700354 private ObjectNode json(UiSynthLink sLink) {
355 UiLink uLink = sLink.link();
Simon Huntd5b96732016-07-08 13:22:27 -0700356 return objectNode()
Simon Huntc13082f2016-08-03 21:20:23 -0700357 .put("id", uLink.idAsString())
358 .put("epA", uLink.endPointA())
359 .put("epB", uLink.endPointB())
360 .put("type", uLink.type());
Simon Huntd5b96732016-07-08 13:22:27 -0700361 }
362
363
Simon Hunt977aa052016-07-20 17:08:29 -0700364 private ObjectNode jsonClosedRegion(UiRegion region) {
365 return objectNode()
Simon Huntb1ce2602016-07-23 14:04:31 -0700366 .put("id", region.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700367 .put("nodeType", REGION)
Simon Huntb1ce2602016-07-23 14:04:31 -0700368 .put("nDevs", region.deviceCount());
Simon Hunt977aa052016-07-20 17:08:29 -0700369 // TODO: complete closed-region details
370 }
371
Simon Hunt98189192016-07-29 19:02:27 -0700372 /**
373 * Returns a JSON array representation of a set of regions/devices. Note
374 * that the information is sufficient for showing regions as nodes.
375 *
376 * @param nodes the nodes
377 * @return a JSON representation of the nodes
378 */
379 public ArrayNode closedNodes(Set<UiNode> nodes) {
380 ArrayNode array = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700381 for (UiNode node : nodes) {
Simon Hunt98189192016-07-29 19:02:27 -0700382 if (node instanceof UiRegion) {
383 array.add(jsonClosedRegion((UiRegion) node));
384 } else if (node instanceof UiDevice) {
385 array.add(json((UiDevice) node));
386 } else {
387 log.warn("Unexpected node instance: {}", node.getClass());
388 }
389 }
390 return array;
391 }
Simon Hunt977aa052016-07-20 17:08:29 -0700392
393 /**
394 * Returns a JSON array representation of a list of regions. Note that the
395 * information about each region is limited to what needs to be used to
396 * show the regions as nodes on the view.
397 *
398 * @param regions the regions
399 * @return a JSON representation of the minimal region information
400 */
401 public ArrayNode closedRegions(Set<UiRegion> regions) {
402 ArrayNode array = arrayNode();
403 for (UiRegion r : regions) {
404 array.add(jsonClosedRegion(r));
405 }
406 return array;
407 }
408
409 /**
410 * Returns a JSON array representation of a list of devices.
411 *
412 * @param devices the devices
413 * @return a JSON representation of the devices
414 */
415 public ArrayNode devices(Set<UiDevice> devices) {
416 ArrayNode array = arrayNode();
417 for (UiDevice device : devices) {
418 array.add(json(device));
419 }
420 return array;
421 }
422
423 /**
424 * Returns a JSON array representation of a list of hosts.
425 *
426 * @param hosts the hosts
427 * @return a JSON representation of the hosts
428 */
429 public ArrayNode hosts(Set<UiHost> hosts) {
430 ArrayNode array = arrayNode();
431 for (UiHost host : hosts) {
432 array.add(json(host));
433 }
434 return array;
435 }
436
Simon Hunt977aa052016-07-20 17:08:29 -0700437 // package-private for unit testing
438 List<Set<UiNode>> splitByLayer(List<String> layerTags,
439 Set<? extends UiNode> nodes) {
440 final int nLayers = layerTags.size();
441 if (!layerTags.get(nLayers - 1).equals(LAYER_DEFAULT)) {
442 throw new IllegalArgumentException(E_DEF_NOT_LAST);
443 }
444
445 List<Set<UiNode>> splitList = new ArrayList<>(layerTags.size());
446 Map<String, Set<UiNode>> byLayer = new HashMap<>(layerTags.size());
447
448 for (String tag : layerTags) {
449 Set<UiNode> set = new HashSet<>();
450 byLayer.put(tag, set);
451 splitList.add(set);
452 }
453
454 for (UiNode n : nodes) {
455 String which = n.layer();
456 if (!layerTags.contains(which)) {
457 which = LAYER_DEFAULT;
458 }
459 byLayer.get(which).add(n);
460 }
461
462 return splitList;
463 }
Simon Huntd5b96732016-07-08 13:22:27 -0700464}