blob: 79a335ff49d7bb3437f8a2fd47a79a7a1599d140 [file] [log] [blame]
Thomas Vachuska329af532015-03-10 02:08:33 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Thomas Vachuska329af532015-03-10 02:08:33 -07003 *
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 */
16package org.onosproject.ui.impl;
17
18import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuska329af532015-03-10 02:08:33 -070019import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.ObjectNode;
Simon Hunt879ce452017-08-10 23:32:00 -070021import com.google.common.collect.ImmutableSet;
Thomas Vachuska329af532015-03-10 02:08:33 -070022import org.onlab.osgi.ServiceDirectory;
23import org.onlab.packet.IpAddress;
Simon Hunta58d8942017-08-11 12:51:14 -070024import org.onlab.packet.VlanId;
Simon Hunt95d56fd2015-11-12 11:06:44 -080025import org.onlab.util.DefaultHashMap;
Thomas Vachuska329af532015-03-10 02:08:33 -070026import org.onosproject.cluster.ClusterEvent;
Thomas Vachuska329af532015-03-10 02:08:33 -070027import org.onosproject.cluster.ControllerNode;
28import org.onosproject.cluster.NodeId;
29import org.onosproject.core.CoreService;
Thomas Vachuska329af532015-03-10 02:08:33 -070030import org.onosproject.net.AnnotationKeys;
31import org.onosproject.net.Annotations;
32import org.onosproject.net.ConnectPoint;
33import org.onosproject.net.DefaultEdgeLink;
34import org.onosproject.net.Device;
35import org.onosproject.net.DeviceId;
36import org.onosproject.net.EdgeLink;
37import org.onosproject.net.Host;
38import org.onosproject.net.HostId;
39import org.onosproject.net.HostLocation;
40import org.onosproject.net.Link;
Thomas Vachuska4c47a382018-05-07 13:48:38 -070041import org.onosproject.net.config.NetworkConfigService;
42import org.onosproject.net.config.basics.BasicDeviceConfig;
43import org.onosproject.net.config.basics.BasicElementConfig;
44import org.onosproject.net.config.basics.BasicHostConfig;
Thomas Vachuska329af532015-03-10 02:08:33 -070045import org.onosproject.net.device.DeviceEvent;
Thomas Vachuska164ecf62018-05-08 17:29:55 -070046import org.onosproject.net.driver.Driver;
Thomas Vachuska329af532015-03-10 02:08:33 -070047import org.onosproject.net.host.HostEvent;
Thomas Vachuska329af532015-03-10 02:08:33 -070048import org.onosproject.net.link.LinkEvent;
Thomas Vachuska329af532015-03-10 02:08:33 -070049import org.onosproject.net.provider.ProviderId;
Thomas Vachuska329af532015-03-10 02:08:33 -070050import org.onosproject.net.topology.Topology;
Simon Huntd2747a02015-04-30 22:41:16 -070051import org.onosproject.ui.JsonUtils;
Thomas Vachuska329af532015-03-10 02:08:33 -070052import org.onosproject.ui.UiConnection;
Sean Condonadeb7162019-04-13 20:56:14 +010053import org.onosproject.ui.impl.topo.TopoologyTrafficMessageHandlerAbstract;
Simon Hunted804d52016-03-30 09:51:40 -070054import org.onosproject.ui.impl.topo.util.ServicesBundle;
Simon Hunt879ce452017-08-10 23:32:00 -070055import org.onosproject.ui.lion.LionBundle;
Simon Hunt0af1ec32015-07-24 12:17:55 -070056import org.onosproject.ui.topo.PropertyPanel;
Thomas Vachuska329af532015-03-10 02:08:33 -070057import org.slf4j.Logger;
58import org.slf4j.LoggerFactory;
59
Thomas Vachuska329af532015-03-10 02:08:33 -070060import java.util.Collections;
Thomas Vachuska329af532015-03-10 02:08:33 -070061import java.util.Iterator;
Thomas Vachuska329af532015-03-10 02:08:33 -070062import java.util.Map;
Thomas Vachuskac616e172018-04-17 16:57:12 -070063import java.util.Objects;
Simon Huntf4fd2a22016-08-10 15:41:09 -070064import java.util.Set;
Thomas Vachuska329af532015-03-10 02:08:33 -070065import java.util.concurrent.ConcurrentHashMap;
66
Thomas Vachuska329af532015-03-10 02:08:33 -070067import static com.google.common.base.Strings.isNullOrEmpty;
Thomas Vachuska164ecf62018-05-08 17:29:55 -070068import static org.onosproject.net.AnnotationKeys.DRIVER;
Sean Condon71910542019-02-16 18:16:42 +000069import static org.onosproject.net.AnnotationKeys.UI_TYPE;
Thomas Vachuska329af532015-03-10 02:08:33 -070070import static org.onosproject.net.PortNumber.portNumber;
Thomas Vachuskac616e172018-04-17 16:57:12 -070071import static org.onosproject.net.config.basics.BasicElementConfig.LOC_TYPE_GEO;
72import static org.onosproject.net.config.basics.BasicElementConfig.LOC_TYPE_GRID;
Simon Hunt3a0598f2015-08-04 19:59:04 -070073import static org.onosproject.ui.topo.TopoConstants.CoreButtons;
Thomas Vachuska4c47a382018-05-07 13:48:38 -070074import static org.onosproject.ui.topo.TopoConstants.Properties.*;
Simon Huntd3ceffa2015-08-25 12:44:35 -070075import static org.onosproject.ui.topo.TopoUtils.compactLinkString;
Thomas Vachuska329af532015-03-10 02:08:33 -070076
77/**
78 * Facility for creating messages bound for the topology viewer.
79 */
Sean Condonadeb7162019-04-13 20:56:14 +010080public abstract class TopologyViewMessageHandlerBase extends TopoologyTrafficMessageHandlerAbstract {
Thomas Vachuska329af532015-03-10 02:08:33 -070081
Simon Huntf4fd2a22016-08-10 15:41:09 -070082 private static final String NO_GEO_VALUE = "0.0";
Simon Hunt10618f62017-06-15 19:30:52 -070083 private static final String DASH = "-";
Simon Hunta58d8942017-08-11 12:51:14 -070084 private static final String SLASH = " / ";
Simon Hunt10618f62017-06-15 19:30:52 -070085
86 // nav paths are the view names for hot-link navigation from topo view...
87 private static final String DEVICE_NAV_PATH = "device";
88 private static final String HOST_NAV_PATH = "host";
Simon Huntf4fd2a22016-08-10 15:41:09 -070089
Simon Hunta58d8942017-08-11 12:51:14 -070090 // link panel label keys
91 private static final String LPL_FRIENDLY = "lp_label_friendly";
92 private static final String LPL_A_TYPE = "lp_label_a_type";
93 private static final String LPL_A_ID = "lp_label_a_id";
94 private static final String LPL_A_FRIENDLY = "lp_label_a_friendly";
95 private static final String LPL_A_PORT = "lp_label_a_port";
96 private static final String LPL_B_TYPE = "lp_label_b_type";
97 private static final String LPL_B_ID = "lp_label_b_id";
98 private static final String LPL_B_FRIENDLY = "lp_label_b_friendly";
99 private static final String LPL_B_PORT = "lp_label_b_port";
100 private static final String LPL_A2B = "lp_label_a2b";
101 private static final String LPL_B2A = "lp_label_b2a";
102 private static final String LPV_NO_LINK = "lp_value_no_link";
103
104 // other Lion keys
105 private static final String HOST = "host";
106 private static final String DEVICE = "device";
107 private static final String EXPECTED = "expected";
108 private static final String NOT_EXPECTED = "not_expected";
109
Simon Hunt95d56fd2015-11-12 11:06:44 -0800110 // default to an "add" event...
111 private static final DefaultHashMap<ClusterEvent.Type, String> CLUSTER_EVENT =
112 new DefaultHashMap<>("addInstance");
113
114 // default to an "update" event...
115 private static final DefaultHashMap<DeviceEvent.Type, String> DEVICE_EVENT =
116 new DefaultHashMap<>("updateDevice");
117 private static final DefaultHashMap<LinkEvent.Type, String> LINK_EVENT =
118 new DefaultHashMap<>("updateLink");
119 private static final DefaultHashMap<HostEvent.Type, String> HOST_EVENT =
120 new DefaultHashMap<>("updateHost");
121
122 // but call out specific events that we care to differentiate...
123 static {
124 CLUSTER_EVENT.put(ClusterEvent.Type.INSTANCE_REMOVED, "removeInstance");
125
126 DEVICE_EVENT.put(DeviceEvent.Type.DEVICE_ADDED, "addDevice");
127 DEVICE_EVENT.put(DeviceEvent.Type.DEVICE_REMOVED, "removeDevice");
128
129 LINK_EVENT.put(LinkEvent.Type.LINK_ADDED, "addLink");
130 LINK_EVENT.put(LinkEvent.Type.LINK_REMOVED, "removeLink");
131
132 HOST_EVENT.put(HostEvent.Type.HOST_ADDED, "addHost");
133 HOST_EVENT.put(HostEvent.Type.HOST_REMOVED, "removeHost");
134 HOST_EVENT.put(HostEvent.Type.HOST_MOVED, "moveHost");
135 }
136
Simon Hunta58d8942017-08-11 12:51:14 -0700137 private static final DefaultHashMap<Device.Type, String> DEVICE_GLYPHS =
138 new DefaultHashMap<>("m_unknown");
139
140 static {
141 DEVICE_GLYPHS.put(Device.Type.SWITCH, "m_switch");
142 DEVICE_GLYPHS.put(Device.Type.ROUTER, "m_router");
143 DEVICE_GLYPHS.put(Device.Type.ROADM, "m_roadm");
Andrea Campanellafa0f6cc2018-12-18 15:17:53 +0100144 DEVICE_GLYPHS.put(Device.Type.OLS, "m_roadm");
Simon Hunta58d8942017-08-11 12:51:14 -0700145 DEVICE_GLYPHS.put(Device.Type.OTN, "m_otn");
Andrea Campanella1c24fb92018-12-20 16:43:59 +0100146 DEVICE_GLYPHS.put(Device.Type.TERMINAL_DEVICE, "m_otn");
Simon Hunta58d8942017-08-11 12:51:14 -0700147 DEVICE_GLYPHS.put(Device.Type.ROADM_OTN, "m_roadm_otn");
148 DEVICE_GLYPHS.put(Device.Type.BALANCER, "m_balancer");
149 DEVICE_GLYPHS.put(Device.Type.IPS, "m_ips");
150 DEVICE_GLYPHS.put(Device.Type.IDS, "m_ids");
151 DEVICE_GLYPHS.put(Device.Type.CONTROLLER, "m_controller");
152 DEVICE_GLYPHS.put(Device.Type.VIRTUAL, "m_virtual");
153 DEVICE_GLYPHS.put(Device.Type.FIBER_SWITCH, "m_fiberSwitch");
154 DEVICE_GLYPHS.put(Device.Type.MICROWAVE, "m_microwave");
155 DEVICE_GLYPHS.put(Device.Type.OLT, "m_olt");
156 DEVICE_GLYPHS.put(Device.Type.ONU, "m_onu");
157 DEVICE_GLYPHS.put(Device.Type.OPTICAL_AMPLIFIER, "unknown"); // TODO glyph needed
158 DEVICE_GLYPHS.put(Device.Type.OTHER, "m_other");
Georgios Katsikas40ecef32018-07-03 11:17:44 +0200159 DEVICE_GLYPHS.put(Device.Type.SERVER, "m_endpoint");
Simon Hunta58d8942017-08-11 12:51:14 -0700160 }
161
162 private static final String DEFAULT_HOST_GLYPH = "m_endstation";
163 private static final String LINK_GLYPH = "m_ports";
164
165
Ray Milkey9c9cde42018-01-12 14:22:06 -0800166 static final Logger log =
Simon Huntd2747a02015-04-30 22:41:16 -0700167 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700168
Simon Huntd2747a02015-04-30 22:41:16 -0700169 private static final ProviderId PID =
170 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700171
Thomas Vachuska329af532015-03-10 02:08:33 -0700172 // TODO: extract into an external & durable state; good enough for now and demo
173 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
174
175 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700176 * Returns read-only view of the meta-ui information.
177 *
178 * @return map of id to meta-ui mementos
179 */
180 static Map<String, ObjectNode> getMetaUi() {
181 return Collections.unmodifiableMap(metaUi);
182 }
183
Thomas Vachuskac616e172018-04-17 16:57:12 -0700184 /**
185 * Clears any meta-ui information.
186 */
187 public static void clearMetaUi() {
188 metaUi.clear();
189 }
190
Simon Hunt879ce452017-08-10 23:32:00 -0700191 private static final String LION_TOPO = "core.view.Topo";
192
193 private static final Set<String> REQ_LION_BUNDLES = ImmutableSet.of(
194 LION_TOPO
195 );
Simon Hunt1911fe42017-05-02 18:25:58 -0700196
197 protected ServicesBundle services;
198
199 private String version;
200
201
Thomas Vachuska329af532015-03-10 02:08:33 -0700202 @Override
203 public void init(UiConnection connection, ServiceDirectory directory) {
204 super.init(connection, directory);
Simon Hunt1911fe42017-05-02 18:25:58 -0700205 services = new ServicesBundle(directory);
206 setVersionString(directory);
207 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700208
Simon Hunt1911fe42017-05-02 18:25:58 -0700209 // Creates a palatable version string to display on the summary panel
210 private void setVersionString(ServiceDirectory directory) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700211 String ver = directory.get(CoreService.class).version().toString();
212 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
213 }
214
Simon Hunt879ce452017-08-10 23:32:00 -0700215 @Override
216 public Set<String> requiredLionBundles() {
217 return REQ_LION_BUNDLES;
218 }
219
Simon Hunt10618f62017-06-15 19:30:52 -0700220 // Returns the first of the given set of IP addresses as a string.
Thomas Vachuska329af532015-03-10 02:08:33 -0700221 private String ip(Set<IpAddress> ipAddresses) {
222 Iterator<IpAddress> it = ipAddresses.iterator();
223 return it.hasNext() ? it.next().toString() : "unknown";
224 }
225
226 // Produces JSON structure from annotations.
227 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700228 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700229 if (annotations != null) {
230 for (String key : annotations.keys()) {
231 props.put(key, annotations.value(key));
232 }
233 }
234 return props;
235 }
236
237 // Produces an informational log message event bound to the client.
Simon Hunt36740d02017-06-07 11:25:51 -0700238 protected ObjectNode info(String message) {
239 return message("info", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700240 }
241
242 // Produces a warning log message event bound to the client.
Simon Hunt36740d02017-06-07 11:25:51 -0700243 protected ObjectNode warning(String message) {
244 return message("warning", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700245 }
246
247 // Produces an error log message event bound to the client.
Simon Hunt36740d02017-06-07 11:25:51 -0700248 protected ObjectNode error(String message) {
249 return message("error", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700250 }
251
252 // Produces a log message event bound to the client.
Simon Hunt36740d02017-06-07 11:25:51 -0700253 private ObjectNode message(String severity, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700254 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700255 .put("severity", severity)
256 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700257
Simon Hunt36740d02017-06-07 11:25:51 -0700258 return JsonUtils.envelope("message", payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700259 }
260
Thomas Vachuska329af532015-03-10 02:08:33 -0700261 // Produces a cluster instance message to the client.
Simon Hunt95d56fd2015-11-12 11:06:44 -0800262 protected ObjectNode instanceMessage(ClusterEvent event, String msgType) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700263 ControllerNode node = event.subject();
pierventrefa5dc3c2021-11-05 15:37:32 +0100264 IpAddress nodeIp = node.ip();
Simon Hunt1911fe42017-05-02 18:25:58 -0700265 int switchCount = services.mastership().getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700266 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700267 .put("id", node.id().toString())
pierventrefa5dc3c2021-11-05 15:37:32 +0100268 .put("ip", nodeIp != null ? nodeIp.toString() : node.host())
Simon Hunt1911fe42017-05-02 18:25:58 -0700269 .put("online", services.cluster().getState(node.id()).isActive())
270 .put("ready", services.cluster().getState(node.id()).isReady())
271 .put("uiAttached", node.equals(services.cluster().getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700272 .put("switches", switchCount);
273
Simon Huntda580882015-05-12 20:58:18 -0700274 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700275 labels.add(node.id().toString());
pierventrefa5dc3c2021-11-05 15:37:32 +0100276 labels.add(nodeIp != null ? nodeIp.toString() : node.host());
Thomas Vachuska329af532015-03-10 02:08:33 -0700277
278 // Add labels, props and stuff the payload into envelope.
279 payload.set("labels", labels);
280 addMetaUi(node.id().toString(), payload);
281
Simon Hunt95d56fd2015-11-12 11:06:44 -0800282 String type = msgType != null ? msgType : CLUSTER_EVENT.get(event.type());
Simon Hunt36740d02017-06-07 11:25:51 -0700283 return JsonUtils.envelope(type, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700284 }
285
286 // Produces a device event message to the client.
287 protected ObjectNode deviceMessage(DeviceEvent event) {
288 Device device = event.subject();
Simon Hunt1e20dae2016-10-28 11:26:26 -0700289 String uiType = device.annotations().value(AnnotationKeys.UI_TYPE);
Thomas Vachuska164ecf62018-05-08 17:29:55 -0700290 String driverName = device.annotations().value(DRIVER);
291 Driver driver = driverName == null ? null : services.driver().getDriver(driverName);
Simon Hunt1e20dae2016-10-28 11:26:26 -0700292 String devType = uiType != null ? uiType :
Thomas Vachuska164ecf62018-05-08 17:29:55 -0700293 (driver != null ? driver.getProperty(AnnotationKeys.UI_TYPE) : null);
Laszlo Pappe1579fa2018-03-08 09:18:48 +0000294 if (devType == null) {
295 devType = device.type().toString().toLowerCase();
296 }
Simon Hunt10618f62017-06-15 19:30:52 -0700297 String name = device.annotations().value(AnnotationKeys.NAME);
298 name = isNullOrEmpty(name) ? device.id().toString() : name;
Simon Hunt1e20dae2016-10-28 11:26:26 -0700299
Simon Huntda580882015-05-12 20:58:18 -0700300 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700301 .put("id", device.id().toString())
Simon Hunt1e20dae2016-10-28 11:26:26 -0700302 .put("type", devType)
Simon Hunt1911fe42017-05-02 18:25:58 -0700303 .put("online", services.device().isAvailable(device.id()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700304 .put("master", master(device.id()));
305
Simon Hunt10618f62017-06-15 19:30:52 -0700306 payload.set("labels", labels("", name, device.id().toString()));
Thomas Vachuska329af532015-03-10 02:08:33 -0700307 payload.set("props", props(device.annotations()));
Thomas Vachuskac616e172018-04-17 16:57:12 -0700308
Thomas Vachuska4c47a382018-05-07 13:48:38 -0700309 BasicDeviceConfig cfg = get(NetworkConfigService.class)
310 .getConfig(device.id(), BasicDeviceConfig.class);
311 if (!addLocation(cfg, payload)) {
Thomas Vachuskac616e172018-04-17 16:57:12 -0700312 addMetaUi(device.id().toString(), payload);
313 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700314
Simon Hunt95d56fd2015-11-12 11:06:44 -0800315 String type = DEVICE_EVENT.get(event.type());
Simon Hunt36740d02017-06-07 11:25:51 -0700316 return JsonUtils.envelope(type, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700317 }
318
319 // Produces a link event message to the client.
320 protected ObjectNode linkMessage(LinkEvent event) {
321 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700322 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700323 .put("id", compactLinkString(link))
324 .put("type", link.type().toString().toLowerCase())
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800325 .put("expected", link.isExpected())
Thomas Vachuska329af532015-03-10 02:08:33 -0700326 .put("online", link.state() == Link.State.ACTIVE)
327 .put("linkWidth", 1.2)
328 .put("src", link.src().deviceId().toString())
329 .put("srcPort", link.src().port().toString())
330 .put("dst", link.dst().deviceId().toString())
331 .put("dstPort", link.dst().port().toString());
Simon Hunt95d56fd2015-11-12 11:06:44 -0800332 String type = LINK_EVENT.get(event.type());
Simon Hunt36740d02017-06-07 11:25:51 -0700333 return JsonUtils.envelope(type, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700334 }
335
336 // Produces a host event message to the client.
337 protected ObjectNode hostMessage(HostEvent event) {
338 Host host = event.subject();
Charles Chan33f28a92015-11-13 13:12:38 -0800339 Host prevHost = event.prevSubject();
Simon Hunt1e20dae2016-10-28 11:26:26 -0700340 String hostType = host.annotations().value(AnnotationKeys.UI_TYPE);
Thomas Vachuska541edfc2021-03-10 14:24:47 -0800341 String connectionType = host.annotations().value(AnnotationKeys.CONNECTION_TYPE);
Simon Hunt10618f62017-06-15 19:30:52 -0700342 String ip = ip(host.ipAddresses());
Simon Hunt95d56fd2015-11-12 11:06:44 -0800343
Simon Huntda580882015-05-12 20:58:18 -0700344 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700345 .put("id", host.id().toString())
Thomas Vachuska541edfc2021-03-10 14:24:47 -0800346 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
347 .put("connectionType", isNullOrEmpty(connectionType) ? "wired" : connectionType);
Simon Hunt10618f62017-06-15 19:30:52 -0700348
Simon Hunt12c79ed2017-09-12 11:58:44 -0700349 // set most recent connect point (and previous if we know it)
Simon Huntda580882015-05-12 20:58:18 -0700350 payload.set("cp", hostConnect(host.location()));
Charles Chan33f28a92015-11-13 13:12:38 -0800351 if (prevHost != null && prevHost.location() != null) {
352 payload.set("prevCp", hostConnect(prevHost.location()));
Simon Hunt95d56fd2015-11-12 11:06:44 -0800353 }
Simon Hunt12c79ed2017-09-12 11:58:44 -0700354
355 // set ALL connect points
356 addAllCps(host.locations(), payload);
357
Thomas Vachuska528ea952017-10-09 18:25:35 +0200358 payload.set("labels", labels(nameForHost(host), ip, host.mac().toString(), ""));
Thomas Vachuska329af532015-03-10 02:08:33 -0700359 payload.set("props", props(host.annotations()));
Thomas Vachuskac616e172018-04-17 16:57:12 -0700360
Thomas Vachuska4c47a382018-05-07 13:48:38 -0700361 BasicHostConfig cfg = get(NetworkConfigService.class)
362 .getConfig(host.id(), BasicHostConfig.class);
363 if (!addLocation(cfg, payload)) {
Thomas Vachuskac616e172018-04-17 16:57:12 -0700364 addMetaUi(host.id().toString(), payload);
365 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700366
Simon Hunt95d56fd2015-11-12 11:06:44 -0800367 String type = HOST_EVENT.get(event.type());
Simon Hunt36740d02017-06-07 11:25:51 -0700368 return JsonUtils.envelope(type, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700369 }
370
Simon Hunt12c79ed2017-09-12 11:58:44 -0700371 private void addAllCps(Set<HostLocation> locations, ObjectNode payload) {
372 ArrayNode cps = arrayNode();
373 locations.forEach(loc -> cps.add(hostConnect(loc)));
374 payload.set("allCps", cps);
375 }
376
Thomas Vachuska329af532015-03-10 02:08:33 -0700377 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700378 private ObjectNode hostConnect(HostLocation location) {
379 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700380 .put("device", location.deviceId().toString())
pierventre178046b2021-10-20 17:37:52 +0200381 .put("port", location.port().toString());
Thomas Vachuska329af532015-03-10 02:08:33 -0700382 }
383
384 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700385 private ArrayNode labels(String... labels) {
386 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700387 for (String label : labels) {
388 json.add(label);
389 }
390 return json;
391 }
392
393 // Returns the name of the master node for the specified device id.
394 private String master(DeviceId deviceId) {
Simon Hunt1911fe42017-05-02 18:25:58 -0700395 NodeId master = services.mastership().getMasterFor(deviceId);
Thomas Vachuska329af532015-03-10 02:08:33 -0700396 return master != null ? master.toString() : "";
397 }
398
399 // Generates an edge link from the specified host location.
400 private EdgeLink edgeLink(Host host, boolean ingress) {
401 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
402 host.location(), ingress);
403 }
404
405 // Adds meta UI information for the specified object.
406 private void addMetaUi(String id, ObjectNode payload) {
407 ObjectNode meta = metaUi.get(id);
408 if (meta != null) {
409 payload.set("metaUi", meta);
410 }
411 }
412
Thomas Vachuska4c47a382018-05-07 13:48:38 -0700413 private boolean addLocation(BasicElementConfig cfg, ObjectNode payload) {
414 if (cfg != null) {
415 String locType = cfg.locType();
416 boolean isGeo = Objects.equals(locType, LOC_TYPE_GEO);
417 boolean isGrid = Objects.equals(locType, LOC_TYPE_GRID);
418 if (isGeo || isGrid) {
Thomas Vachuskac616e172018-04-17 16:57:12 -0700419 try {
Thomas Vachuskac616e172018-04-17 16:57:12 -0700420 ObjectNode loc = objectNode()
Thomas Vachuska4c47a382018-05-07 13:48:38 -0700421 .put("locType", locType)
422 .put("latOrY", isGeo ? cfg.latitude() : cfg.gridY())
423 .put("longOrX", isGeo ? cfg.longitude() : cfg.gridX());
Thomas Vachuskac616e172018-04-17 16:57:12 -0700424 payload.set("location", loc);
425 return true;
426 } catch (NumberFormatException e) {
Thomas Vachuska4c47a382018-05-07 13:48:38 -0700427 log.warn("Invalid location data: {}", cfg);
Thomas Vachuskac616e172018-04-17 16:57:12 -0700428 }
Thomas Vachuskac67a9912018-03-06 14:37:45 -0800429 }
430 }
Thomas Vachuskac616e172018-04-17 16:57:12 -0700431 return false;
Thomas Vachuskac67a9912018-03-06 14:37:45 -0800432 }
433
Thomas Vachuska329af532015-03-10 02:08:33 -0700434 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700435 protected void updateMetaUi(ObjectNode payload) {
436 metaUi.put(JsonUtils.string(payload, "id"),
437 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700438 }
439
Simon Hunta17fa672015-08-19 18:42:22 -0700440
Simon Huntb745ca62015-07-28 15:37:11 -0700441 // -----------------------------------------------------------------------
442 // Create models of the data to return, that overlays can adjust / augment
443
Simon Hunta58d8942017-08-11 12:51:14 -0700444 private String lookupGlyph(Device device) {
Sean Condon71910542019-02-16 18:16:42 +0000445 String uiType = device.annotations().value(UI_TYPE);
Sean Condon59d31372019-02-02 20:07:00 +0000446 if (uiType != null && !uiType.equalsIgnoreCase("undefined")) {
447 return uiType;
448 } else {
449 return DEVICE_GLYPHS.get(device.type());
450 }
Simon Hunta58d8942017-08-11 12:51:14 -0700451 }
452
453
Simon Huntb745ca62015-07-28 15:37:11 -0700454 // Returns property panel model for summary response.
Simon Hunt8a0429a2017-01-06 16:52:47 -0800455 protected PropertyPanel summmaryMessage() {
Simon Hunta58d8942017-08-11 12:51:14 -0700456 // chose NOT to add debug messages, since this is called every few seconds
Simon Hunt1911fe42017-05-02 18:25:58 -0700457 Topology topology = services.topology().currentTopology();
Simon Hunt879ce452017-08-10 23:32:00 -0700458 LionBundle lion = getLionBundle(LION_TOPO);
459 String panelTitle = lion.getSafe("title_panel_summary");
Simon Hunt0af1ec32015-07-24 12:17:55 -0700460
Simon Hunta58d8942017-08-11 12:51:14 -0700461 return new PropertyPanel(panelTitle, "bird")
Simon Hunt879ce452017-08-10 23:32:00 -0700462 .addProp(VERSION, lion.getSafe(VERSION), version)
Simon Hunt1911fe42017-05-02 18:25:58 -0700463 .addSeparator()
Simon Hunt879ce452017-08-10 23:32:00 -0700464 .addProp(DEVICES, lion.getSafe(DEVICES), services.device().getDeviceCount())
465 .addProp(LINKS, lion.getSafe(LINKS), topology.linkCount())
466 .addProp(HOSTS, lion.getSafe(HOSTS), services.host().getHostCount())
467 .addProp(TOPOLOGY_SSCS, lion.getSafe(TOPOLOGY_SSCS), topology.clusterCount())
Simon Hunt1911fe42017-05-02 18:25:58 -0700468 .addSeparator()
Simon Hunt879ce452017-08-10 23:32:00 -0700469 .addProp(INTENTS, lion.getSafe(INTENTS), services.intent().getIntentCount())
Simon Hunt879ce452017-08-10 23:32:00 -0700470 .addProp(FLOWS, lion.getSafe(FLOWS), services.flow().getFlowRuleCount());
Thomas Vachuska329af532015-03-10 02:08:33 -0700471 }
472
Simon Hunta58d8942017-08-11 12:51:14 -0700473
474 private String friendlyDevice(DeviceId deviceId) {
Simon Hunt1911fe42017-05-02 18:25:58 -0700475 Device device = services.device().getDevice(deviceId);
Thomas Vachuska329af532015-03-10 02:08:33 -0700476 Annotations annot = device.annotations();
477 String name = annot.value(AnnotationKeys.NAME);
Simon Hunta58d8942017-08-11 12:51:14 -0700478 return isNullOrEmpty(name) ? deviceId.toString() : name;
479 }
480
481 // Generates a property panel model for device details response
482 protected PropertyPanel deviceDetails(DeviceId deviceId) {
483 log.debug("generate prop panel data for device {}", deviceId);
484 Device device = services.device().getDevice(deviceId);
485 Annotations annot = device.annotations();
486 String proto = annot.value(AnnotationKeys.PROTOCOL);
487 String title = friendlyDevice(deviceId);
488 LionBundle lion = getLionBundle(LION_TOPO);
489
490 PropertyPanel pp = new PropertyPanel(title, lookupGlyph(device))
491 .navPath(DEVICE_NAV_PATH)
492 .id(deviceId.toString());
493 addDeviceBasicProps(pp, deviceId, device, proto, lion);
494 addLocationProps(pp, annot, lion);
495 addDeviceCountStats(pp, deviceId, lion);
496 addDeviceCoreButtons(pp);
497 return pp;
498 }
499
500 private void addDeviceBasicProps(PropertyPanel pp, DeviceId deviceId,
501 Device device, String proto, LionBundle lion) {
502 pp.addProp(URI, lion.getSafe(URI), deviceId.toString())
503 .addProp(VENDOR, lion.getSafe(VENDOR), device.manufacturer())
504 .addProp(HW_VERSION, lion.getSafe(HW_VERSION), device.hwVersion())
505 .addProp(SW_VERSION, lion.getSafe(SW_VERSION), device.swVersion())
506 .addProp(SERIAL_NUMBER, lion.getSafe(SERIAL_NUMBER), device.serialNumber())
507 .addProp(PROTOCOL, lion.getSafe(PROTOCOL), proto)
508 .addSeparator();
509 }
510
511 // only add location properties if we have them
512 private void addLocationProps(PropertyPanel pp, Annotations annot,
513 LionBundle lion) {
514 String slat = annot.value(AnnotationKeys.LATITUDE);
515 String slng = annot.value(AnnotationKeys.LONGITUDE);
516 String sgrY = annot.value(AnnotationKeys.GRID_Y);
517 String sgrX = annot.value(AnnotationKeys.GRID_X);
518
519 boolean validLat = slat != null && !slat.equals(NO_GEO_VALUE);
520 boolean validLng = slng != null && !slng.equals(NO_GEO_VALUE);
521 if (validLat && validLng) {
522 pp.addProp(LATITUDE, lion.getSafe(LATITUDE), slat)
523 .addProp(LONGITUDE, lion.getSafe(LONGITUDE), slng)
524 .addSeparator();
525
526 } else if (sgrY != null && sgrX != null) {
527 pp.addProp(GRID_Y, lion.getSafe(GRID_Y), sgrY)
528 .addProp(GRID_X, lion.getSafe(GRID_X), sgrX)
529 .addSeparator();
530 }
531 // else, no location
532 }
533
534 private void addDeviceCountStats(PropertyPanel pp, DeviceId deviceId, LionBundle lion) {
Simon Hunt1911fe42017-05-02 18:25:58 -0700535 int portCount = services.device().getPorts(deviceId).size();
Thomas Vachuska329af532015-03-10 02:08:33 -0700536 int flowCount = getFlowCount(deviceId);
cheng fan35dc0f22015-06-10 06:02:47 +0800537 int tunnelCount = getTunnelCount(deviceId);
Simon Huntb745ca62015-07-28 15:37:11 -0700538
Simon Hunta58d8942017-08-11 12:51:14 -0700539 pp.addProp(PORTS, lion.getSafe(PORTS), portCount)
540 .addProp(FLOWS, lion.getSafe(FLOWS), flowCount)
541 .addProp(TUNNELS, lion.getSafe(TUNNELS), tunnelCount);
542 }
Simon Huntb745ca62015-07-28 15:37:11 -0700543
Simon Hunta58d8942017-08-11 12:51:14 -0700544 private void addDeviceCoreButtons(PropertyPanel pp) {
545 pp.addButton(CoreButtons.SHOW_DEVICE_VIEW)
Simon Hunt1911fe42017-05-02 18:25:58 -0700546 .addButton(CoreButtons.SHOW_FLOW_VIEW)
547 .addButton(CoreButtons.SHOW_PORT_VIEW)
548 .addButton(CoreButtons.SHOW_GROUP_VIEW)
549 .addButton(CoreButtons.SHOW_METER_VIEW);
Thomas Vachuska329af532015-03-10 02:08:33 -0700550 }
551
552 protected int getFlowCount(DeviceId deviceId) {
Thomas Vachuskaa8e74772018-02-26 11:33:35 -0800553 return services.flow().getFlowRuleCount(deviceId);
Thomas Vachuska329af532015-03-10 02:08:33 -0700554 }
555
Thomas Vachuska52f2cd12018-11-08 21:20:04 -0800556 @Deprecated
cheng fan35dc0f22015-06-10 06:02:47 +0800557 protected int getTunnelCount(DeviceId deviceId) {
Thomas Vachuska52f2cd12018-11-08 21:20:04 -0800558 return 0;
cheng fan35dc0f22015-06-10 06:02:47 +0800559 }
560
Simon Hunt10618f62017-06-15 19:30:52 -0700561 private boolean useDefaultName(String annotName) {
562 return isNullOrEmpty(annotName) || DASH.equals(annotName);
563 }
564
565 private String nameForHost(Host host) {
566 String name = host.annotations().value(AnnotationKeys.NAME);
567 return useDefaultName(name) ? ip(host.ipAddresses()) : name;
568 }
569
Simon Hunta58d8942017-08-11 12:51:14 -0700570 private String glyphForHost(Annotations annot) {
571 String uiType = annot.value(AnnotationKeys.UI_TYPE);
Thomas Vachuska8d262322021-03-02 17:59:58 -0800572 return isNullOrEmpty(uiType) ? DEFAULT_HOST_GLYPH : "m_" + uiType;
Thomas Vachuska329af532015-03-10 02:08:33 -0700573 }
574
Simon Hunta58d8942017-08-11 12:51:14 -0700575 // Generates a property panel model for a host details response
576 protected PropertyPanel hostDetails(HostId hostId) {
577 log.debug("generate prop panel data for host {}", hostId);
578 Host host = services.host().getHost(hostId);
579 Annotations annot = host.annotations();
580 String glyphId = glyphForHost(annot);
581 LionBundle lion = getLionBundle(LION_TOPO);
582
583 PropertyPanel pp = new PropertyPanel(nameForHost(host), glyphId)
584 .navPath(HOST_NAV_PATH)
585 .id(hostId.toString());
586 addHostBasicProps(pp, host, lion);
587 addLocationProps(pp, annot, lion);
588 return pp;
589 }
590
591 private void addHostBasicProps(PropertyPanel pp, Host host, LionBundle lion) {
592 pp.addProp(LPL_FRIENDLY, lion.getSafe(LPL_FRIENDLY), nameForHost(host))
593 .addProp(MAC, lion.getSafe(MAC), host.mac())
594 .addProp(IP, lion.getSafe(IP), host.ipAddresses(), "[\\[\\]]")
595 .addProp(VLAN, lion.getSafe(VLAN), displayVlan(host.vlan(), lion))
596 .addSeparator();
597 }
598
599 private String displayVlan(VlanId vlan, LionBundle lion) {
600 return VlanId.NONE.equals(vlan) ? lion.getSafe(VLAN_NONE) : vlan.toString();
601 }
602
603 // Generates a property panel model for a link details response (edge-link)
604 protected PropertyPanel edgeLinkDetails(HostId hid, ConnectPoint cp) {
605 log.debug("generate prop panel data for edgelink {} {}", hid, cp);
606 LionBundle lion = getLionBundle(LION_TOPO);
607 String title = lion.getSafe("title_edge_link");
608
609 PropertyPanel pp = new PropertyPanel(title, LINK_GLYPH);
610 addLinkHostProps(pp, hid, lion);
611 addLinkCpBProps(pp, cp, lion);
612 return pp;
613 }
614
615 // Generates a property panel model for a link details response (infra-link)
616 protected PropertyPanel infraLinkDetails(ConnectPoint cpA, ConnectPoint cpB) {
617 log.debug("generate prop panel data for infralink {} {}", cpA, cpB);
618 LionBundle lion = getLionBundle(LION_TOPO);
619 String title = lion.getSafe("title_infra_link");
620
621 PropertyPanel pp = new PropertyPanel(title, LINK_GLYPH);
622 addLinkCpAProps(pp, cpA, lion);
623 addLinkCpBProps(pp, cpB, lion);
624 addLinkBackingProps(pp, cpA, cpB, lion);
625 return pp;
626 }
627
628 private void addLinkHostProps(PropertyPanel pp, HostId hostId, LionBundle lion) {
629 Host host = services.host().getHost(hostId);
630
631 pp.addProp(LPL_A_TYPE, lion.getSafe(LPL_A_TYPE), lion.getSafe(HOST))
632 .addProp(LPL_A_ID, lion.getSafe(LPL_A_ID), hostId.toString())
633 .addProp(LPL_A_FRIENDLY, lion.getSafe(LPL_A_FRIENDLY), nameForHost(host))
634 .addSeparator();
635 }
636
637 private void addLinkCpAProps(PropertyPanel pp, ConnectPoint cp, LionBundle lion) {
638 DeviceId did = cp.deviceId();
639
640 pp.addProp(LPL_A_TYPE, lion.getSafe(LPL_A_TYPE), lion.getSafe(DEVICE))
641 .addProp(LPL_A_ID, lion.getSafe(LPL_A_ID), did.toString())
642 .addProp(LPL_A_FRIENDLY, lion.getSafe(LPL_A_FRIENDLY), friendlyDevice(did))
pierventre178046b2021-10-20 17:37:52 +0200643 .addProp(LPL_A_PORT, lion.getSafe(LPL_A_PORT), cp.port().toString())
Simon Hunta58d8942017-08-11 12:51:14 -0700644 .addSeparator();
645 }
646
647 private void addLinkCpBProps(PropertyPanel pp, ConnectPoint cp, LionBundle lion) {
648 DeviceId did = cp.deviceId();
649
650 pp.addProp(LPL_B_TYPE, lion.getSafe(LPL_B_TYPE), lion.getSafe(DEVICE))
651 .addProp(LPL_B_ID, lion.getSafe(LPL_B_ID), did.toString())
652 .addProp(LPL_B_FRIENDLY, lion.getSafe(LPL_B_FRIENDLY), friendlyDevice(did))
pierventre178046b2021-10-20 17:37:52 +0200653 .addProp(LPL_B_PORT, lion.getSafe(LPL_B_PORT), cp.port().toString())
Simon Hunta58d8942017-08-11 12:51:14 -0700654 .addSeparator();
655 }
656
657 private void addLinkBackingProps(PropertyPanel pp, ConnectPoint cpA,
658 ConnectPoint cpB, LionBundle lion) {
659 Link a2b = services.link().getLink(cpA, cpB);
660 Link b2a = services.link().getLink(cpB, cpA);
661
662 pp.addProp(LPL_A2B, lion.getSafe(LPL_A2B), linkPropString(a2b, lion))
663 .addProp(LPL_B2A, lion.getSafe(LPL_B2A), linkPropString(b2a, lion));
664 }
665
666 private String linkPropString(Link link, LionBundle lion) {
667 if (link == null) {
668 return lion.getSafe(LPV_NO_LINK);
669 }
670 return lion.getSafe(link.type()) + SLASH +
671 lion.getSafe(link.state()) + SLASH +
672 lion.getSafe(link.isExpected() ? EXPECTED : NOT_EXPECTED);
673 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700674}