840e89f3406f7b9e36f869b22c59b81275b62d81
[onosfw.git] /
1 /*
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  */
16 package org.onosproject.ui.impl;
17
18 import com.fasterxml.jackson.databind.JsonNode;
19 import com.fasterxml.jackson.databind.node.ArrayNode;
20 import com.fasterxml.jackson.databind.node.ObjectNode;
21 import org.onlab.osgi.ServiceDirectory;
22 import org.onlab.packet.IpAddress;
23 import org.onosproject.cluster.ClusterEvent;
24 import org.onosproject.cluster.ClusterService;
25 import org.onosproject.cluster.ControllerNode;
26 import org.onosproject.cluster.NodeId;
27 import org.onosproject.core.CoreService;
28 import org.onosproject.incubator.net.PortStatisticsService;
29 import org.onosproject.incubator.net.tunnel.OpticalTunnelEndPoint;
30 import org.onosproject.incubator.net.tunnel.Tunnel;
31 import org.onosproject.incubator.net.tunnel.TunnelService;
32 import org.onosproject.mastership.MastershipService;
33 import org.onosproject.net.Annotated;
34 import org.onosproject.net.AnnotationKeys;
35 import org.onosproject.net.Annotations;
36 import org.onosproject.net.ConnectPoint;
37 import org.onosproject.net.DefaultEdgeLink;
38 import org.onosproject.net.Device;
39 import org.onosproject.net.DeviceId;
40 import org.onosproject.net.EdgeLink;
41 import org.onosproject.net.Host;
42 import org.onosproject.net.HostId;
43 import org.onosproject.net.HostLocation;
44 import org.onosproject.net.Link;
45 import org.onosproject.net.PortNumber;
46 import org.onosproject.net.device.DeviceEvent;
47 import org.onosproject.net.device.DeviceService;
48 import org.onosproject.net.flow.FlowEntry;
49 import org.onosproject.net.flow.FlowRuleService;
50 import org.onosproject.net.flow.TrafficTreatment;
51 import org.onosproject.net.flow.instructions.Instruction;
52 import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
53 import org.onosproject.net.host.HostEvent;
54 import org.onosproject.net.host.HostService;
55 import org.onosproject.net.intent.IntentService;
56 import org.onosproject.net.link.LinkEvent;
57 import org.onosproject.net.link.LinkService;
58 import org.onosproject.net.provider.ProviderId;
59 import org.onosproject.net.statistic.StatisticService;
60 import org.onosproject.net.topology.Topology;
61 import org.onosproject.net.topology.TopologyService;
62 import org.onosproject.ui.JsonUtils;
63 import org.onosproject.ui.UiConnection;
64 import org.onosproject.ui.UiMessageHandler;
65 import org.onosproject.ui.impl.topo.ServicesBundle;
66 import org.onosproject.ui.topo.PropertyPanel;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 import java.util.ArrayList;
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.List;
77 import java.util.Map;
78 import java.util.Set;
79 import java.util.concurrent.ConcurrentHashMap;
80
81 import static com.google.common.base.Preconditions.checkNotNull;
82 import static com.google.common.base.Strings.isNullOrEmpty;
83 import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
84 import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
85 import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
86 import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
87 import static org.onosproject.net.PortNumber.portNumber;
88 import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
89 import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
90 import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
91 import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
92 import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
93 import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
94 import static org.onosproject.ui.topo.TopoConstants.CoreButtons;
95 import static org.onosproject.ui.topo.TopoConstants.Properties;
96 import static org.onosproject.ui.topo.TopoUtils.compactLinkString;
97
98 /**
99  * Facility for creating messages bound for the topology viewer.
100  */
101 public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
102
103     protected static final Logger log =
104             LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
105
106     private static final ProviderId PID =
107             new ProviderId("core", "org.onosproject.core", true);
108
109     protected static final String SHOW_HIGHLIGHTS = "showHighlights";
110
111     protected ServiceDirectory directory;
112     protected ClusterService clusterService;
113     protected DeviceService deviceService;
114     protected LinkService linkService;
115     protected HostService hostService;
116     protected MastershipService mastershipService;
117     protected IntentService intentService;
118     protected FlowRuleService flowService;
119     protected StatisticService flowStatsService;
120     protected PortStatisticsService portStatsService;
121     protected TopologyService topologyService;
122     protected TunnelService tunnelService;
123
124     protected ServicesBundle servicesBundle;
125
126     private String version;
127
128     // TODO: extract into an external & durable state; good enough for now and demo
129     private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
130
131     /**
132      * Returns read-only view of the meta-ui information.
133      *
134      * @return map of id to meta-ui mementos
135      */
136     static Map<String, ObjectNode> getMetaUi() {
137         return Collections.unmodifiableMap(metaUi);
138     }
139
140     @Override
141     public void init(UiConnection connection, ServiceDirectory directory) {
142         super.init(connection, directory);
143         this.directory = checkNotNull(directory, "Directory cannot be null");
144         clusterService = directory.get(ClusterService.class);
145         deviceService = directory.get(DeviceService.class);
146         linkService = directory.get(LinkService.class);
147         hostService = directory.get(HostService.class);
148         mastershipService = directory.get(MastershipService.class);
149         intentService = directory.get(IntentService.class);
150         flowService = directory.get(FlowRuleService.class);
151         flowStatsService = directory.get(StatisticService.class);
152         portStatsService = directory.get(PortStatisticsService.class);
153         topologyService = directory.get(TopologyService.class);
154         tunnelService = directory.get(TunnelService.class);
155
156         servicesBundle = new ServicesBundle(intentService, deviceService,
157                                             hostService, linkService,
158                                             flowService,
159                                             flowStatsService, portStatsService);
160
161         String ver = directory.get(CoreService.class).version().toString();
162         version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
163     }
164
165     // Returns the specified set of IP addresses as a string.
166     private String ip(Set<IpAddress> ipAddresses) {
167         Iterator<IpAddress> it = ipAddresses.iterator();
168         return it.hasNext() ? it.next().toString() : "unknown";
169     }
170
171     // Produces JSON structure from annotations.
172     private JsonNode props(Annotations annotations) {
173         ObjectNode props = objectNode();
174         if (annotations != null) {
175             for (String key : annotations.keys()) {
176                 props.put(key, annotations.value(key));
177             }
178         }
179         return props;
180     }
181
182     // Produces an informational log message event bound to the client.
183     protected ObjectNode info(long id, String message) {
184         return message("info", id, message);
185     }
186
187     // Produces a warning log message event bound to the client.
188     protected ObjectNode warning(long id, String message) {
189         return message("warning", id, message);
190     }
191
192     // Produces an error log message event bound to the client.
193     protected ObjectNode error(long id, String message) {
194         return message("error", id, message);
195     }
196
197     // Produces a log message event bound to the client.
198     private ObjectNode message(String severity, long id, String message) {
199         ObjectNode payload = objectNode()
200                 .put("severity", severity)
201                 .put("message", message);
202
203         return JsonUtils.envelope("message", id, payload);
204     }
205
206     // Produces a cluster instance message to the client.
207     protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
208         ControllerNode node = event.subject();
209         int switchCount = mastershipService.getDevicesOf(node.id()).size();
210         ObjectNode payload = objectNode()
211                 .put("id", node.id().toString())
212                 .put("ip", node.ip().toString())
213                 .put("online", clusterService.getState(node.id()) == ACTIVE)
214                 .put("uiAttached", node.equals(clusterService.getLocalNode()))
215                 .put("switches", switchCount);
216
217         ArrayNode labels = arrayNode();
218         labels.add(node.id().toString());
219         labels.add(node.ip().toString());
220
221         // Add labels, props and stuff the payload into envelope.
222         payload.set("labels", labels);
223         addMetaUi(node.id().toString(), payload);
224
225         String type = messageType != null ? messageType :
226                 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
227                         ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
228                                 "addInstance")));
229         return JsonUtils.envelope(type, 0, payload);
230     }
231
232     // Produces a device event message to the client.
233     protected ObjectNode deviceMessage(DeviceEvent event) {
234         Device device = event.subject();
235         ObjectNode payload = objectNode()
236                 .put("id", device.id().toString())
237                 .put("type", device.type().toString().toLowerCase())
238                 .put("online", deviceService.isAvailable(device.id()))
239                 .put("master", master(device.id()));
240
241         // Generate labels: id, chassis id, no-label, optional-name
242         String name = device.annotations().value(AnnotationKeys.NAME);
243         ArrayNode labels = arrayNode();
244         labels.add("");
245         labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
246         labels.add(device.id().toString());
247
248         // Add labels, props and stuff the payload into envelope.
249         payload.set("labels", labels);
250         payload.set("props", props(device.annotations()));
251         addGeoLocation(device, payload);
252         addMetaUi(device.id().toString(), payload);
253
254         String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
255                 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
256         return JsonUtils.envelope(type, 0, payload);
257     }
258
259     // Produces a link event message to the client.
260     protected ObjectNode linkMessage(LinkEvent event) {
261         Link link = event.subject();
262         ObjectNode payload = objectNode()
263                 .put("id", compactLinkString(link))
264                 .put("type", link.type().toString().toLowerCase())
265                 .put("online", link.state() == Link.State.ACTIVE)
266                 .put("linkWidth", 1.2)
267                 .put("src", link.src().deviceId().toString())
268                 .put("srcPort", link.src().port().toString())
269                 .put("dst", link.dst().deviceId().toString())
270                 .put("dstPort", link.dst().port().toString());
271         String type = (event.type() == LINK_ADDED) ? "addLink" :
272                 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
273         return JsonUtils.envelope(type, 0, payload);
274     }
275
276     // Produces a host event message to the client.
277     protected ObjectNode hostMessage(HostEvent event) {
278         Host host = event.subject();
279         String hostType = host.annotations().value(AnnotationKeys.TYPE);
280         ObjectNode payload = objectNode()
281                 .put("id", host.id().toString())
282                 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
283                 .put("ingress", compactLinkString(edgeLink(host, true)))
284                 .put("egress", compactLinkString(edgeLink(host, false)));
285         payload.set("cp", hostConnect(host.location()));
286         payload.set("labels", labels(ip(host.ipAddresses()),
287                                      host.mac().toString()));
288         payload.set("props", props(host.annotations()));
289         addGeoLocation(host, payload);
290         addMetaUi(host.id().toString(), payload);
291
292         String type = (event.type() == HOST_ADDED) ? "addHost" :
293                 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
294         return JsonUtils.envelope(type, 0, payload);
295     }
296
297     // Encodes the specified host location into a JSON object.
298     private ObjectNode hostConnect(HostLocation location) {
299         return objectNode()
300                 .put("device", location.deviceId().toString())
301                 .put("port", location.port().toLong());
302     }
303
304     // Encodes the specified list of labels a JSON array.
305     private ArrayNode labels(String... labels) {
306         ArrayNode json = arrayNode();
307         for (String label : labels) {
308             json.add(label);
309         }
310         return json;
311     }
312
313     // Returns the name of the master node for the specified device id.
314     private String master(DeviceId deviceId) {
315         NodeId master = mastershipService.getMasterFor(deviceId);
316         return master != null ? master.toString() : "";
317     }
318
319     // Generates an edge link from the specified host location.
320     private EdgeLink edgeLink(Host host, boolean ingress) {
321         return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
322                                    host.location(), ingress);
323     }
324
325     // Adds meta UI information for the specified object.
326     private void addMetaUi(String id, ObjectNode payload) {
327         ObjectNode meta = metaUi.get(id);
328         if (meta != null) {
329             payload.set("metaUi", meta);
330         }
331     }
332
333     // Adds a geo location JSON to the specified payload object.
334     private void addGeoLocation(Annotated annotated, ObjectNode payload) {
335         Annotations annotations = annotated.annotations();
336         if (annotations == null) {
337             return;
338         }
339
340         String slat = annotations.value(AnnotationKeys.LATITUDE);
341         String slng = annotations.value(AnnotationKeys.LONGITUDE);
342         try {
343             if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
344                 double lat = Double.parseDouble(slat);
345                 double lng = Double.parseDouble(slng);
346                 ObjectNode loc = objectNode()
347                         .put("type", "latlng").put("lat", lat).put("lng", lng);
348                 payload.set("location", loc);
349             }
350         } catch (NumberFormatException e) {
351             log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
352         }
353     }
354
355     // Updates meta UI information for the specified object.
356     protected void updateMetaUi(ObjectNode payload) {
357         metaUi.put(JsonUtils.string(payload, "id"),
358                    JsonUtils.node(payload, "memento"));
359     }
360
361
362     // -----------------------------------------------------------------------
363     // Create models of the data to return, that overlays can adjust / augment
364
365     // Returns property panel model for summary response.
366     protected PropertyPanel summmaryMessage(long sid) {
367         Topology topology = topologyService.currentTopology();
368
369         return new PropertyPanel("ONOS Summary", "node")
370             .addProp(Properties.DEVICES, topology.deviceCount())
371             .addProp(Properties.LINKS, topology.linkCount())
372             .addProp(Properties.HOSTS, hostService.getHostCount())
373             .addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
374             .addSeparator()
375             .addProp(Properties.INTENTS, intentService.getIntentCount())
376             .addProp(Properties.TUNNELS, tunnelService.tunnelCount())
377             .addProp(Properties.FLOWS, flowService.getFlowRuleCount())
378             .addProp(Properties.VERSION, version);
379     }
380
381     // Returns property panel model for device details response.
382     protected PropertyPanel deviceDetails(DeviceId deviceId, long sid) {
383         Device device = deviceService.getDevice(deviceId);
384         Annotations annot = device.annotations();
385         String name = annot.value(AnnotationKeys.NAME);
386         int portCount = deviceService.getPorts(deviceId).size();
387         int flowCount = getFlowCount(deviceId);
388         int tunnelCount = getTunnelCount(deviceId);
389
390         String title = isNullOrEmpty(name) ? deviceId.toString() : name;
391         String typeId = device.type().toString().toLowerCase();
392
393         PropertyPanel pp = new PropertyPanel(title, typeId)
394             .id(deviceId.toString())
395
396             .addProp(Properties.URI, deviceId.toString())
397             .addProp(Properties.VENDOR, device.manufacturer())
398             .addProp(Properties.HW_VERSION, device.hwVersion())
399             .addProp(Properties.SW_VERSION, device.swVersion())
400             .addProp(Properties.SERIAL_NUMBER, device.serialNumber())
401             .addProp(Properties.PROTOCOL, annot.value(AnnotationKeys.PROTOCOL))
402             .addSeparator()
403
404             .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
405             .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE))
406             .addSeparator()
407
408             .addProp(Properties.PORTS, portCount)
409             .addProp(Properties.FLOWS, flowCount)
410             .addProp(Properties.TUNNELS, tunnelCount)
411
412             .addButton(CoreButtons.SHOW_DEVICE_VIEW)
413             .addButton(CoreButtons.SHOW_FLOW_VIEW)
414             .addButton(CoreButtons.SHOW_PORT_VIEW)
415             .addButton(CoreButtons.SHOW_GROUP_VIEW);
416
417         return pp;
418     }
419
420     protected int getFlowCount(DeviceId deviceId) {
421         int count = 0;
422         for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
423             count++;
424         }
425         return count;
426     }
427
428     protected int getTunnelCount(DeviceId deviceId) {
429         int count = 0;
430         Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
431         for (Tunnel tunnel : tunnels) {
432             OpticalTunnelEndPoint src = (OpticalTunnelEndPoint) tunnel.src();
433             OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst();
434             DeviceId srcDevice = (DeviceId) src.elementId().get();
435             DeviceId dstDevice = (DeviceId) dst.elementId().get();
436             if (srcDevice.toString().equals(deviceId.toString()) ||
437                 dstDevice.toString().equals(deviceId.toString())) {
438                 count++;
439             }
440         }
441         return count;
442     }
443
444     // Counts all flow entries that egress on the links of the given device.
445     private Map<Link, Integer> getLinkFlowCounts(DeviceId deviceId) {
446         // get the flows for the device
447         List<FlowEntry> entries = new ArrayList<>();
448         for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
449             entries.add(flowEntry);
450         }
451
452         // get egress links from device, and include edge links
453         Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
454         Set<Host> hosts = hostService.getConnectedHosts(deviceId);
455         if (hosts != null) {
456             for (Host host : hosts) {
457                 links.add(createEdgeLink(host, false));
458             }
459         }
460
461         // compile flow counts per link
462         Map<Link, Integer> counts = new HashMap<>();
463         for (Link link : links) {
464             counts.put(link, getEgressFlows(link, entries));
465         }
466         return counts;
467     }
468
469     // Counts all entries that egress on the link source port.
470     private int getEgressFlows(Link link, List<FlowEntry> entries) {
471         int count = 0;
472         PortNumber out = link.src().port();
473         for (FlowEntry entry : entries) {
474             TrafficTreatment treatment = entry.treatment();
475             for (Instruction instruction : treatment.allInstructions()) {
476                 if (instruction.type() == Instruction.Type.OUTPUT &&
477                         ((OutputInstruction) instruction).port().equals(out)) {
478                     count++;
479                 }
480             }
481         }
482         return count;
483     }
484
485     // Returns host details response.
486     protected PropertyPanel hostDetails(HostId hostId, long sid) {
487         Host host = hostService.getHost(hostId);
488         Annotations annot = host.annotations();
489         String type = annot.value(AnnotationKeys.TYPE);
490         String name = annot.value(AnnotationKeys.NAME);
491         String vlan = host.vlan().toString();
492
493         String title = isNullOrEmpty(name) ? hostId.toString() : name;
494         String typeId = isNullOrEmpty(type) ? "endstation" : type;
495
496         PropertyPanel pp = new PropertyPanel(title, typeId)
497             .id(hostId.toString())
498             .addProp(Properties.MAC, host.mac())
499             .addProp(Properties.IP, host.ipAddresses(), "[\\[\\]]")
500             .addProp(Properties.VLAN, vlan.equals("-1") ? "none" : vlan)
501             .addSeparator()
502             .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
503             .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
504
505         // Potentially add button descriptors here
506         return pp;
507     }
508
509 }