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