Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 1 | /* |
Brian O'Connor | a09fe5b | 2017-08-03 21:12:30 -0700 | [diff] [blame] | 2 | * Copyright 2017-present Open Networking Foundation |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 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 | */ |
| 17 | |
| 18 | package org.onosproject.ui.impl; |
| 19 | |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 20 | import com.google.common.collect.ImmutableList; |
| 21 | import com.google.common.collect.Lists; |
| 22 | import org.onosproject.net.Device; |
| 23 | import org.onosproject.net.ElementId; |
| 24 | import org.onosproject.net.Host; |
| 25 | import org.onosproject.net.HostId; |
| 26 | import org.onosproject.net.device.DeviceService; |
| 27 | import org.onosproject.net.intent.FlowObjectiveIntent; |
| 28 | import org.onosproject.net.intent.FlowRuleIntent; |
| 29 | import org.onosproject.net.intent.HostToHostIntent; |
| 30 | import org.onosproject.net.intent.Intent; |
| 31 | import org.onosproject.net.intent.LinkCollectionIntent; |
| 32 | import org.onosproject.net.intent.OpticalConnectivityIntent; |
| 33 | import org.onosproject.net.intent.OpticalPathIntent; |
| 34 | import org.onosproject.net.intent.PathIntent; |
| 35 | import org.onosproject.net.link.LinkService; |
Thomas Vachuska | 52f2cd1 | 2018-11-08 21:20:04 -0800 | [diff] [blame] | 36 | import org.onosproject.net.statistic.PortStatisticsService.MetricType; |
Pier | 59266bc | 2018-03-15 12:10:24 -0700 | [diff] [blame] | 37 | import org.onosproject.net.DefaultEdgeLink; |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 38 | import org.onosproject.net.DeviceId; |
| 39 | import org.onosproject.net.Link; |
| 40 | import org.onosproject.net.statistic.Load; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 41 | import org.onosproject.ui.impl.topo.TopoologyTrafficMessageHandlerAbstract; |
| 42 | import org.onosproject.ui.impl.topo.util.IntentSelection; |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 43 | import org.onosproject.ui.impl.topo.util.ServicesBundle; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 44 | import org.onosproject.ui.impl.topo.util.TopoIntentFilter; |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 45 | import org.onosproject.ui.impl.topo.util.TrafficLink; |
| 46 | import org.onosproject.ui.impl.topo.util.TrafficLinkMap; |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 47 | import org.onosproject.ui.topo.AbstractTopoMonitor; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 48 | import org.onosproject.ui.topo.DeviceHighlight; |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 49 | import org.onosproject.ui.topo.Highlights; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 50 | import org.onosproject.ui.topo.HostHighlight; |
| 51 | import org.onosproject.ui.topo.LinkHighlight; |
| 52 | import org.onosproject.ui.topo.NodeHighlight; |
| 53 | import org.onosproject.ui.topo.NodeSelection; |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 54 | import org.onosproject.ui.topo.TopoUtils; |
| 55 | import org.slf4j.Logger; |
| 56 | import org.slf4j.LoggerFactory; |
| 57 | |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 58 | import java.util.ArrayList; |
| 59 | import java.util.Collection; |
| 60 | import java.util.Collections; |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 61 | import java.util.HashSet; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 62 | import java.util.List; |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 63 | import java.util.Set; |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 64 | import java.util.Timer; |
| 65 | import java.util.TimerTask; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 66 | import java.util.stream.Collectors; |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 67 | |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 68 | import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; |
Thomas Vachuska | 52f2cd1 | 2018-11-08 21:20:04 -0800 | [diff] [blame] | 69 | import static org.onosproject.net.statistic.PortStatisticsService.MetricType.BYTES; |
| 70 | import static org.onosproject.net.statistic.PortStatisticsService.MetricType.PACKETS; |
Pier | 59266bc | 2018-03-15 12:10:24 -0700 | [diff] [blame] | 71 | import static org.onosproject.net.DefaultEdgeLink.createEdgeLinks; |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 72 | import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.IDLE; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 73 | import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.SELECTED_INTENT; |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 74 | |
| 75 | /** |
| 76 | * Base superclass for traffic monitor (both 'classic' and 'topo2' versions). |
| 77 | */ |
| 78 | public abstract class TrafficMonitorBase extends AbstractTopoMonitor { |
| 79 | |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 80 | protected final Logger log = LoggerFactory.getLogger(getClass()); |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 81 | |
| 82 | // 4 Kilo Bytes as threshold |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 83 | protected static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 84 | protected final TopoIntentFilter intentFilter; |
| 85 | protected IntentSelection selectedIntents = null; |
| 86 | protected final TopoologyTrafficMessageHandlerAbstract msgHandler; |
| 87 | protected NodeSelection selectedNodes = null; |
| 88 | |
| 89 | protected void sendSelectedIntents() { |
| 90 | log.debug("sendSelectedIntents: {}", selectedIntents); |
| 91 | msgHandler.sendHighlights(intentGroup()); |
| 92 | } |
| 93 | |
| 94 | protected void ensureNodePresent(Highlights highlights, ElementId eid) { |
| 95 | String id = eid.toString(); |
| 96 | NodeHighlight nh = highlights.getNode(id); |
| 97 | if (nh == null) { |
| 98 | if (eid instanceof DeviceId) { |
| 99 | nh = new DeviceHighlight(id); |
| 100 | highlights.add((DeviceHighlight) nh); |
| 101 | } else if (eid instanceof HostId) { |
| 102 | nh = new HostHighlight(id); |
| 103 | highlights.add((HostHighlight) nh); |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | protected void colorLinks(Highlights highlights, TrafficLinkMap linkMap) { |
| 109 | for (TrafficLink tlink : linkMap.biLinks()) { |
| 110 | highlights.add(tlink.highlight(TrafficLink.StatsType.TAGGED)); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | protected void processLinks(TrafficLinkMap linkMap, Iterable<Link> links, |
| 115 | LinkHighlight.Flavor flavor, boolean isOptical, |
| 116 | boolean showTraffic) { |
| 117 | if (links != null) { |
| 118 | for (Link link : links) { |
| 119 | TrafficLink tlink = linkMap.add(link); |
| 120 | tlink.tagFlavor(flavor); |
| 121 | tlink.optical(isOptical); |
| 122 | if (showTraffic) { |
| 123 | tlink.addLoad(getLinkFlowLoad(link)); |
| 124 | tlink.antMarch(true); |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | protected void updateHighlights(Highlights highlights, Iterable<Link> links) { |
| 131 | for (Link link : links) { |
| 132 | ensureNodePresent(highlights, link.src().elementId()); |
| 133 | ensureNodePresent(highlights, link.dst().elementId()); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | protected Iterable<Link> addEdgeLinksIfNeeded(Intent parentIntent, |
| 138 | Collection<Link> links) { |
| 139 | if (parentIntent instanceof HostToHostIntent) { |
| 140 | links = new HashSet<>(links); |
| 141 | HostToHostIntent h2h = (HostToHostIntent) parentIntent; |
| 142 | Host h1 = services.host().getHost(h2h.one()); |
| 143 | Host h2 = services.host().getHost(h2h.two()); |
| 144 | links.add(createEdgeLink(h1, true)); |
| 145 | links.add(createEdgeLink(h2, true)); |
| 146 | } |
| 147 | return links; |
| 148 | } |
| 149 | |
| 150 | // Extracts links from the specified flow rule intent resources |
| 151 | protected Collection<Link> linkResources(Intent installable) { |
| 152 | ImmutableList.Builder<Link> builder = ImmutableList.builder(); |
| 153 | installable.resources().stream().filter(r -> r instanceof Link) |
| 154 | .forEach(r -> builder.add((Link) r)); |
| 155 | return builder.build(); |
| 156 | } |
| 157 | |
| 158 | protected void createTrafficLinks(Highlights highlights, |
| 159 | TrafficLinkMap linkMap, Set<Intent> intents, |
| 160 | LinkHighlight.Flavor flavor, boolean showTraffic) { |
| 161 | for (Intent intent : intents) { |
| 162 | List<Intent> installables = services.intent() |
| 163 | .getInstallableIntents(intent.key()); |
| 164 | Iterable<Link> links = null; |
| 165 | if (installables != null) { |
| 166 | for (Intent installable : installables) { |
| 167 | |
| 168 | if (installable instanceof PathIntent) { |
| 169 | links = ((PathIntent) installable).path().links(); |
| 170 | } else if (installable instanceof FlowRuleIntent) { |
| 171 | Collection<Link> l = new ArrayList<>(); |
| 172 | l.addAll(linkResources(installable)); |
| 173 | // Add cross connect links |
| 174 | if (intent instanceof OpticalConnectivityIntent) { |
| 175 | OpticalConnectivityIntent ocIntent = (OpticalConnectivityIntent) intent; |
| 176 | LinkService linkService = services.link(); |
| 177 | DeviceService deviceService = services.device(); |
| 178 | l.addAll(linkService.getDeviceIngressLinks(ocIntent.getSrc().deviceId()).stream() |
| 179 | .filter(i -> |
| 180 | deviceService.getDevice(i.src().deviceId()).type() == Device.Type.SWITCH) |
| 181 | .collect(Collectors.toList())); |
| 182 | l.addAll(linkService.getDeviceEgressLinks(ocIntent.getDst().deviceId()).stream() |
| 183 | .filter(e -> |
| 184 | deviceService.getDevice(e.dst().deviceId()).type() == Device.Type.SWITCH) |
| 185 | .collect(Collectors.toList())); |
| 186 | } |
| 187 | links = l; |
| 188 | } else if (installable instanceof FlowObjectiveIntent) { |
| 189 | links = linkResources(installable); |
| 190 | } else if (installable instanceof LinkCollectionIntent) { |
| 191 | links = ((LinkCollectionIntent) installable).links(); |
| 192 | } else if (installable instanceof OpticalPathIntent) { |
| 193 | links = ((OpticalPathIntent) installable).path().links(); |
| 194 | } |
| 195 | |
| 196 | if (links == null) { |
| 197 | links = Lists.newArrayList(); |
| 198 | } |
| 199 | |
| 200 | links = addEdgeLinksIfNeeded(intent, Lists.newArrayList(links)); |
| 201 | |
| 202 | boolean isOptical = intent instanceof OpticalConnectivityIntent; |
| 203 | processLinks(linkMap, links, flavor, isOptical, showTraffic); |
| 204 | updateHighlights(highlights, links); |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | protected void highlightIntentLinks(Highlights highlights, |
| 211 | Set<Intent> primary, Set<Intent> secondary) { |
| 212 | TrafficLinkMap linkMap = new TrafficLinkMap(); |
| 213 | // NOTE: highlight secondary first, then primary, so that links shared |
| 214 | // by intents are colored correctly ("last man wins") |
| 215 | createTrafficLinks(highlights, linkMap, secondary, LinkHighlight.Flavor.SECONDARY_HIGHLIGHT, false); |
| 216 | createTrafficLinks(highlights, linkMap, primary, LinkHighlight.Flavor.PRIMARY_HIGHLIGHT, false); |
| 217 | colorLinks(highlights, linkMap); |
| 218 | } |
| 219 | |
| 220 | protected Highlights intentGroup() { |
| 221 | Highlights highlights = new Highlights(); |
| 222 | |
| 223 | if (selectedIntents != null && !selectedIntents.none()) { |
| 224 | // If 'all' intents are selected, they will all have primary |
| 225 | // highlighting; otherwise, the specifically selected intent will |
| 226 | // have primary highlighting, and the remainder will have secondary |
| 227 | // highlighting. |
| 228 | Set<Intent> primary; |
| 229 | Set<Intent> secondary; |
| 230 | int count = selectedIntents.size(); |
| 231 | |
| 232 | Set<Intent> allBut = new HashSet<>(selectedIntents.intents()); |
| 233 | Intent current; |
| 234 | |
| 235 | if (selectedIntents.all()) { |
| 236 | primary = allBut; |
| 237 | secondary = Collections.emptySet(); |
| 238 | log.debug("Highlight all intents ({})", count); |
| 239 | } else { |
| 240 | current = selectedIntents.current(); |
| 241 | primary = new HashSet<>(); |
| 242 | primary.add(current); |
| 243 | allBut.remove(current); |
| 244 | secondary = allBut; |
| 245 | log.debug("Highlight intent: {} ([{}] of {})", |
| 246 | current.id(), selectedIntents.index(), count); |
| 247 | } |
| 248 | |
| 249 | highlightIntentLinks(highlights, primary, secondary); |
| 250 | } |
| 251 | return highlights; |
| 252 | } |
| 253 | |
| 254 | protected Highlights intentTraffic() { |
| 255 | Highlights highlights = new Highlights(); |
| 256 | |
| 257 | if (selectedIntents != null && selectedIntents.single()) { |
| 258 | Intent current = selectedIntents.current(); |
| 259 | Set<Intent> primary = new HashSet<>(); |
| 260 | primary.add(current); |
| 261 | log.debug("Highlight traffic for intent: {} ([{}] of {})", |
| 262 | current.id(), selectedIntents.index(), selectedIntents.size()); |
| 263 | |
| 264 | highlightIntentLinksWithTraffic(highlights, primary); |
| 265 | highlights.subdueAllElse(Highlights.Amount.MINIMALLY); |
| 266 | } |
| 267 | return highlights; |
| 268 | } |
| 269 | |
| 270 | private void highlightIntentLinksWithTraffic(Highlights highlights, |
| 271 | Set<Intent> primary) { |
| 272 | TrafficLinkMap linkMap = new TrafficLinkMap(); |
| 273 | createTrafficLinks(highlights, linkMap, primary, LinkHighlight.Flavor.PRIMARY_HIGHLIGHT, true); |
| 274 | colorLinks(highlights, linkMap); |
| 275 | } |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 276 | |
| 277 | |
| 278 | /** |
| 279 | * Designates the different modes of operation. |
| 280 | */ |
| 281 | public enum Mode { |
| 282 | IDLE, |
| 283 | ALL_FLOW_TRAFFIC_BYTES, |
| 284 | ALL_PORT_TRAFFIC_BIT_PS, |
| 285 | ALL_PORT_TRAFFIC_PKT_PS, |
| 286 | DEV_LINK_FLOWS, |
| 287 | RELATED_INTENTS, |
| 288 | SELECTED_INTENT |
| 289 | } |
| 290 | |
| 291 | /** |
| 292 | * Number of milliseconds between invocations of sending traffic data. |
| 293 | */ |
| 294 | protected final long trafficPeriod; |
| 295 | |
| 296 | /** |
| 297 | * Holds references to services. |
| 298 | */ |
| 299 | protected final ServicesBundle services; |
| 300 | |
| 301 | /** |
| 302 | * Current operating mode. |
| 303 | */ |
| 304 | protected Mode mode = Mode.IDLE; |
| 305 | |
| 306 | private final Timer timer; |
| 307 | private TimerTask trafficTask = null; |
| 308 | |
| 309 | /** |
| 310 | * Constructs the monitor, initializing the task period and |
| 311 | * services bundle reference. |
| 312 | * |
| 313 | * @param trafficPeriod traffic task period in ms |
| 314 | * @param servicesBundle bundle of services |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 315 | * @param msgHandler Traffic Message handler |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 316 | */ |
| 317 | protected TrafficMonitorBase(long trafficPeriod, |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 318 | ServicesBundle servicesBundle, |
| 319 | TopoologyTrafficMessageHandlerAbstract msgHandler) { |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 320 | this.trafficPeriod = trafficPeriod; |
| 321 | this.services = servicesBundle; |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 322 | this.msgHandler = msgHandler; |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 323 | timer = new Timer("uiTopo-" + getClass().getSimpleName()); |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 324 | intentFilter = new TopoIntentFilter(servicesBundle); |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 325 | } |
| 326 | |
| 327 | /** |
| 328 | * Initiates monitoring of traffic for a given mode. |
| 329 | * This causes a background traffic task to be |
| 330 | * scheduled to repeatedly compute and transmit the appropriate traffic |
| 331 | * data to the client. |
| 332 | * <p> |
| 333 | * The monitoring mode is expected to be one of: |
| 334 | * <ul> |
| 335 | * <li>ALL_FLOW_TRAFFIC_BYTES</li> |
| 336 | * <li>ALL_PORT_TRAFFIC_BIT_PS</li> |
| 337 | * <li>ALL_PORT_TRAFFIC_PKT_PS</li> |
| 338 | * <li>SELECTED_INTENT</li> |
| 339 | * </ul> |
| 340 | * |
| 341 | * @param mode the monitoring mode |
| 342 | */ |
| 343 | public synchronized void monitor(Mode mode) { |
| 344 | this.mode = mode; |
| 345 | |
| 346 | switch (mode) { |
| 347 | |
| 348 | case ALL_FLOW_TRAFFIC_BYTES: |
| 349 | clearSelection(); |
| 350 | scheduleTask(); |
| 351 | sendAllFlowTraffic(); |
| 352 | break; |
| 353 | |
| 354 | case ALL_PORT_TRAFFIC_BIT_PS: |
| 355 | clearSelection(); |
| 356 | scheduleTask(); |
| 357 | sendAllPortTrafficBits(); |
| 358 | break; |
| 359 | |
| 360 | case ALL_PORT_TRAFFIC_PKT_PS: |
| 361 | clearSelection(); |
| 362 | scheduleTask(); |
| 363 | sendAllPortTrafficPackets(); |
| 364 | break; |
| 365 | |
| 366 | case SELECTED_INTENT: |
| 367 | sendSelectedIntentTraffic(); |
| 368 | scheduleTask(); |
| 369 | break; |
| 370 | |
| 371 | default: |
| 372 | log.warn("Unexpected call to monitor({})", mode); |
| 373 | clearAll(); |
| 374 | break; |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | /** |
Sean Condon | adeb716 | 2019-04-13 20:56:14 +0100 | [diff] [blame] | 379 | * Monitor for traffic data to be sent back to the web client, under |
| 380 | * the given mode, using the given selection of devices and hosts. |
| 381 | * In the case of "device link flows", this causes a background traffic |
| 382 | * task to be scheduled to repeatedly compute and transmit the appropriate |
| 383 | * traffic data to the client. In the case of "related intents", no |
| 384 | * repeating task is scheduled. |
| 385 | * <p> |
| 386 | * The monitoring mode is expected to be one of: |
| 387 | * <ul> |
| 388 | * <li>DEV_LINK_FLOWS</li> |
| 389 | * <li>RELATED_INTENTS</li> |
| 390 | * </ul> |
| 391 | * |
| 392 | * @param mode monitoring mode |
| 393 | * @param nodeSelection how to select a node |
| 394 | */ |
| 395 | public synchronized void monitor(Mode mode, NodeSelection nodeSelection) { |
| 396 | log.debug("monitor: {} -- {}", mode, nodeSelection); |
| 397 | this.mode = mode; |
| 398 | this.selectedNodes = nodeSelection; |
| 399 | |
| 400 | switch (mode) { |
| 401 | case DEV_LINK_FLOWS: |
| 402 | // only care about devices (not hosts) |
| 403 | if (selectedNodes.devicesWithHover().isEmpty()) { |
| 404 | clearAll(); |
| 405 | } else { |
| 406 | scheduleTask(); |
| 407 | sendDeviceLinkFlows(); |
| 408 | } |
| 409 | break; |
| 410 | |
| 411 | case RELATED_INTENTS: |
| 412 | if (selectedNodes.none()) { |
| 413 | clearAll(); |
| 414 | } else { |
| 415 | selectedIntents = new IntentSelection(selectedNodes, intentFilter); |
| 416 | if (selectedIntents.none()) { |
| 417 | clearAll(); |
| 418 | } else { |
| 419 | sendSelectedIntents(); |
| 420 | } |
| 421 | } |
| 422 | break; |
| 423 | |
| 424 | default: |
| 425 | log.debug("Unexpected call to monitor({}, {})", mode, nodeSelection); |
| 426 | clearAll(); |
| 427 | break; |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | /** |
| 432 | * Monitor for traffic data to be sent back to the web client, for the |
| 433 | * given intent. |
| 434 | * |
| 435 | * @param intent the intent to monitor |
| 436 | */ |
| 437 | public synchronized void monitor(Intent intent) { |
| 438 | log.debug("monitor intent: {}", intent.id()); |
| 439 | selectedNodes = null; |
| 440 | selectedIntents = new IntentSelection(intent); |
| 441 | mode = SELECTED_INTENT; |
| 442 | scheduleTask(); |
| 443 | sendSelectedIntentTraffic(); |
| 444 | } |
| 445 | |
| 446 | /** |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 447 | * Subclass should compile and send appropriate highlights data showing |
| 448 | * flow traffic (bytes on links). |
| 449 | */ |
| 450 | protected abstract void sendAllFlowTraffic(); |
| 451 | |
| 452 | /** |
| 453 | * Subclass should compile and send appropriate highlights data showing |
| 454 | * bits per second, as computed using port stats. |
| 455 | */ |
| 456 | protected abstract void sendAllPortTrafficBits(); |
| 457 | |
| 458 | /** |
| 459 | * Subclass should compile and send appropriate highlights data showing |
| 460 | * packets per second, as computed using port stats. |
| 461 | */ |
| 462 | protected abstract void sendAllPortTrafficPackets(); |
| 463 | |
| 464 | /** |
| 465 | * Subclass should compile and send appropriate highlights data showing |
| 466 | * number of flows traversing links for the "selected" device(s). |
| 467 | */ |
| 468 | protected abstract void sendDeviceLinkFlows(); |
| 469 | |
| 470 | /** |
| 471 | * Subclass should compile and send appropriate highlights data showing |
| 472 | * traffic traversing links for the "selected" intent. |
| 473 | */ |
| 474 | protected abstract void sendSelectedIntentTraffic(); |
| 475 | |
| 476 | /** |
| 477 | * Subclass should send a "clear highlights" event. |
| 478 | */ |
| 479 | protected abstract void sendClearHighlights(); |
| 480 | |
| 481 | /** |
| 482 | * Subclasses should clear any selection state. |
| 483 | */ |
| 484 | protected abstract void clearSelection(); |
| 485 | |
| 486 | /** |
| 487 | * Sets the mode to IDLE, clears the selection, cancels the background |
| 488 | * task, and sends a clear highlights event to the client. |
| 489 | */ |
| 490 | protected void clearAll() { |
| 491 | this.mode = Mode.IDLE; |
| 492 | clearSelection(); |
| 493 | cancelTask(); |
| 494 | sendClearHighlights(); |
| 495 | } |
| 496 | |
| 497 | /** |
| 498 | * Schedules the background monitor task to run. |
| 499 | */ |
| 500 | protected synchronized void scheduleTask() { |
| 501 | if (trafficTask == null) { |
| 502 | log.debug("Starting up background traffic task..."); |
| 503 | trafficTask = new TrafficUpdateTask(); |
| 504 | timer.schedule(trafficTask, trafficPeriod, trafficPeriod); |
| 505 | } else { |
| 506 | log.debug("(traffic task already running)"); |
| 507 | } |
| 508 | } |
| 509 | |
| 510 | /** |
| 511 | * Cancels the background monitor task. |
| 512 | */ |
| 513 | protected synchronized void cancelTask() { |
| 514 | if (trafficTask != null) { |
| 515 | trafficTask.cancel(); |
| 516 | trafficTask = null; |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | /** |
| 521 | * Stops monitoring. (Invokes {@link #clearAll}, if not idle). |
| 522 | */ |
| 523 | public synchronized void stopMonitoring() { |
| 524 | log.debug("STOP monitoring"); |
| 525 | if (mode != IDLE) { |
| 526 | clearAll(); |
| 527 | } |
| 528 | } |
| 529 | |
| 530 | |
| 531 | // ======================================================================= |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 532 | // === Methods for computing traffic on links |
| 533 | |
| 534 | /** |
| 535 | * Generates a {@link Highlights} object summarizing the traffic on the |
| 536 | * network, ready to be transmitted back to the client for display on |
| 537 | * the topology view. |
| 538 | * |
| 539 | * @param type the type of statistics to be displayed |
| 540 | * @return highlights, representing links to be labeled/colored |
| 541 | */ |
| 542 | protected Highlights trafficSummary(TrafficLink.StatsType type) { |
| 543 | Highlights highlights = new Highlights(); |
| 544 | |
| 545 | // TODO: consider whether a map would be better... |
| 546 | Set<TrafficLink> linksWithTraffic = computeLinksWithTraffic(type); |
| 547 | |
| 548 | Set<TrafficLink> aggregatedLinks = doAggregation(linksWithTraffic); |
| 549 | |
| 550 | for (TrafficLink tlink : aggregatedLinks) { |
| 551 | highlights.add(tlink.highlight(type)); |
| 552 | } |
| 553 | return highlights; |
| 554 | } |
| 555 | |
| 556 | /** |
| 557 | * Generates a set of "traffic links" encapsulating information about the |
| 558 | * traffic on each link (that is deemed to have traffic). |
| 559 | * |
| 560 | * @param type the type of statistics to be displayed |
| 561 | * @return the set of links with traffic |
| 562 | */ |
| 563 | protected Set<TrafficLink> computeLinksWithTraffic(TrafficLink.StatsType type) { |
| 564 | TrafficLinkMap linkMap = new TrafficLinkMap(); |
| 565 | compileLinks(linkMap); |
| 566 | addEdgeLinks(linkMap); |
| 567 | |
| 568 | Set<TrafficLink> linksWithTraffic = new HashSet<>(); |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 569 | |
| 570 | for (TrafficLink tlink : linkMap.biLinks()) { |
| 571 | if (type == TrafficLink.StatsType.FLOW_STATS) { |
| 572 | attachFlowLoad(tlink); |
| 573 | } else if (type == TrafficLink.StatsType.PORT_STATS) { |
| 574 | attachPortLoad(tlink, BYTES); |
| 575 | } else if (type == TrafficLink.StatsType.PORT_PACKET_STATS) { |
| 576 | attachPortLoad(tlink, PACKETS); |
| 577 | } |
| 578 | |
| 579 | // we only want to report on links deemed to have traffic |
| 580 | if (tlink.hasTraffic()) { |
| 581 | linksWithTraffic.add(tlink); |
| 582 | } |
| 583 | } |
| 584 | return linksWithTraffic; |
| 585 | } |
| 586 | |
| 587 | /** |
| 588 | * Iterates across the set of links in the topology and generates the |
| 589 | * appropriate set of traffic links. |
| 590 | * |
| 591 | * @param linkMap link map to augment with traffic links |
| 592 | */ |
| 593 | protected void compileLinks(TrafficLinkMap linkMap) { |
| 594 | services.link().getLinks().forEach(linkMap::add); |
| 595 | } |
| 596 | |
| 597 | /** |
| 598 | * Iterates across the set of hosts in the topology and generates the |
| 599 | * appropriate set of traffic links for the edge links. |
| 600 | * |
| 601 | * @param linkMap link map to augment with traffic links |
| 602 | */ |
| 603 | protected void addEdgeLinks(TrafficLinkMap linkMap) { |
| 604 | services.host().getHosts().forEach(host -> { |
Pier | 59266bc | 2018-03-15 12:10:24 -0700 | [diff] [blame] | 605 | // Ingress edge links |
| 606 | Set<DefaultEdgeLink> edgeLinks = createEdgeLinks(host, true); |
| 607 | edgeLinks.forEach(linkMap::add); |
| 608 | // Egress edge links |
| 609 | edgeLinks = createEdgeLinks(host, false); |
| 610 | edgeLinks.forEach(linkMap::add); |
Simon Hunt | a7aea84 | 2017-05-03 19:42:50 -0700 | [diff] [blame] | 611 | }); |
| 612 | } |
| 613 | |
| 614 | /** |
| 615 | * Processes the given traffic link to attach the "flow load" attributed |
| 616 | * to the underlying topology links. |
| 617 | * |
| 618 | * @param link the traffic link to process |
| 619 | */ |
| 620 | protected void attachFlowLoad(TrafficLink link) { |
| 621 | link.addLoad(getLinkFlowLoad(link.one())); |
| 622 | link.addLoad(getLinkFlowLoad(link.two())); |
| 623 | } |
| 624 | |
| 625 | /** |
| 626 | * Returns the load for the given link, as determined by the statistics |
| 627 | * service. May return null. |
| 628 | * |
| 629 | * @param link the link on which to look up the stats |
| 630 | * @return the corresponding load (or null) |
| 631 | */ |
| 632 | protected Load getLinkFlowLoad(Link link) { |
| 633 | if (link != null && link.src().elementId() instanceof DeviceId) { |
| 634 | return services.flowStats().load(link); |
| 635 | } |
| 636 | return null; |
| 637 | } |
| 638 | |
| 639 | /** |
| 640 | * Processes the given traffic link to attach the "port load" attributed |
| 641 | * to the underlying topology links, for the specified metric type (either |
| 642 | * bytes/sec or packets/sec). |
| 643 | * |
| 644 | * @param link the traffic link to process |
| 645 | * @param metricType the metric type (bytes or packets) |
| 646 | */ |
| 647 | protected void attachPortLoad(TrafficLink link, MetricType metricType) { |
| 648 | // For bi-directional traffic links, use |
| 649 | // the max link rate of either direction |
| 650 | // (we choose 'one' since we know that is never null) |
| 651 | Link one = link.one(); |
| 652 | Load egressSrc = services.portStats().load(one.src(), metricType); |
| 653 | Load egressDst = services.portStats().load(one.dst(), metricType); |
| 654 | link.addLoad(maxLoad(egressSrc, egressDst), metricType == BYTES ? BPS_THRESHOLD : 0); |
| 655 | } |
| 656 | |
| 657 | /** |
| 658 | * Returns the load with the greatest rate. |
| 659 | * |
| 660 | * @param a load a |
| 661 | * @param b load b |
| 662 | * @return the larger of the two |
| 663 | */ |
| 664 | protected Load maxLoad(Load a, Load b) { |
| 665 | if (a == null) { |
| 666 | return b; |
| 667 | } |
| 668 | if (b == null) { |
| 669 | return a; |
| 670 | } |
| 671 | return a.rate() > b.rate() ? a : b; |
| 672 | } |
| 673 | |
| 674 | |
| 675 | /** |
| 676 | * Subclasses (well, Traffic2Monitor really) can override this method and |
| 677 | * process the traffic links before generating the highlights object. |
| 678 | * In particular, links that roll up into "synthetic links" between |
| 679 | * regions should show aggregated data from the constituent links. |
| 680 | * <p> |
| 681 | * This default implementation does nothing. |
| 682 | * |
| 683 | * @param linksWithTraffic link data for all links |
| 684 | * @return transformed link data appropriate to the region display |
| 685 | */ |
| 686 | protected Set<TrafficLink> doAggregation(Set<TrafficLink> linksWithTraffic) { |
| 687 | return linksWithTraffic; |
| 688 | } |
| 689 | |
| 690 | |
| 691 | // ======================================================================= |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 692 | // === Background Task |
| 693 | |
| 694 | // Provides periodic update of traffic information to the client |
| 695 | private class TrafficUpdateTask extends TimerTask { |
| 696 | @Override |
| 697 | public void run() { |
| 698 | try { |
| 699 | switch (mode) { |
| 700 | case ALL_FLOW_TRAFFIC_BYTES: |
| 701 | sendAllFlowTraffic(); |
| 702 | break; |
| 703 | case ALL_PORT_TRAFFIC_BIT_PS: |
| 704 | sendAllPortTrafficBits(); |
| 705 | break; |
| 706 | case ALL_PORT_TRAFFIC_PKT_PS: |
| 707 | sendAllPortTrafficPackets(); |
| 708 | break; |
| 709 | case DEV_LINK_FLOWS: |
| 710 | sendDeviceLinkFlows(); |
| 711 | break; |
| 712 | case SELECTED_INTENT: |
| 713 | sendSelectedIntentTraffic(); |
| 714 | break; |
| 715 | |
| 716 | default: |
| 717 | // RELATED_INTENTS and IDLE modes should never invoke |
| 718 | // the background task, but if they do, they have |
| 719 | // nothing to do |
| 720 | break; |
| 721 | } |
| 722 | |
| 723 | } catch (Exception e) { |
| 724 | log.warn("Unable to process traffic task due to {}", e.getMessage()); |
| 725 | log.warn("Boom!", e); |
| 726 | } |
| 727 | } |
| 728 | } |
Simon Hunt | 1911fe4 | 2017-05-02 18:25:58 -0700 | [diff] [blame] | 729 | } |