blob: 082665552ea869ad08d56ca23cb3dfaf90dd636e [file] [log] [blame]
Simon Hunta17fa672015-08-19 18:42:22 -07001/*
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 */
17
18package org.onosproject.ui.impl;
19
20import com.google.common.collect.ImmutableList;
21import org.onosproject.net.Device;
22import org.onosproject.net.DeviceId;
23import org.onosproject.net.Host;
24import org.onosproject.net.Link;
25import org.onosproject.net.LinkKey;
26import org.onosproject.net.PortNumber;
27import org.onosproject.net.flow.FlowEntry;
28import org.onosproject.net.flow.TrafficTreatment;
29import org.onosproject.net.flow.instructions.Instruction;
30import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
31import org.onosproject.net.intent.FlowRuleIntent;
32import org.onosproject.net.intent.Intent;
33import org.onosproject.net.intent.LinkCollectionIntent;
34import org.onosproject.net.intent.OpticalConnectivityIntent;
35import org.onosproject.net.intent.OpticalPathIntent;
36import org.onosproject.net.intent.PathIntent;
37import org.onosproject.net.statistic.Load;
38import org.onosproject.ui.impl.topo.BiLink;
39import org.onosproject.ui.impl.topo.IntentSelection;
40import org.onosproject.ui.impl.topo.LinkStatsType;
41import org.onosproject.ui.impl.topo.NodeSelection;
42import org.onosproject.ui.impl.topo.ServicesBundle;
43import org.onosproject.ui.impl.topo.TopoUtils;
44import org.onosproject.ui.impl.topo.TopologyViewIntentFilter;
45import org.onosproject.ui.impl.topo.TrafficClass;
46import org.onosproject.ui.topo.Highlights;
47import org.slf4j.Logger;
48import org.slf4j.LoggerFactory;
49
50import java.util.ArrayList;
51import java.util.Collection;
52import java.util.Collections;
53import java.util.HashMap;
54import java.util.HashSet;
55import java.util.List;
56import java.util.Map;
57import java.util.Set;
58import java.util.Timer;
59import java.util.TimerTask;
60
61import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
62import static org.onosproject.ui.impl.TrafficMonitorObject.Mode.IDLE;
63import static org.onosproject.ui.impl.TrafficMonitorObject.Mode.SEL_INTENT;
64import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
65import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
66
67/**
68 * Encapsulates the behavior of monitoring specific traffic patterns.
69 */
70public class TrafficMonitorObject {
71
72 // 4 Kilo Bytes as threshold
73 private static final double BPS_THRESHOLD = 4 * TopoUtils.KILO;
74
75 private static final Logger log =
76 LoggerFactory.getLogger(TrafficMonitorObject.class);
77
78 /**
79 * Designates the different modes of operation.
80 */
81 public enum Mode {
82 IDLE,
83 ALL_FLOW_TRAFFIC,
84 ALL_PORT_TRAFFIC,
85 DEV_LINK_FLOWS,
86 RELATED_INTENTS,
87 SEL_INTENT
88 }
89
90 private final long trafficPeriod;
91 private final ServicesBundle servicesBundle;
92 private final TopologyViewMessageHandler messageHandler;
93 private final TopologyViewIntentFilter intentFilter;
94
95 private final Timer timer = new Timer("topo-traffic");
96
97 private TimerTask trafficTask = null;
98 private Mode mode = IDLE;
99 private NodeSelection selectedNodes = null;
100 private IntentSelection selectedIntents = null;
101
102
103 /**
104 * Constructs a traffic monitor.
105 *
106 * @param trafficPeriod traffic task period in ms
107 * @param servicesBundle bundle of services
108 * @param messageHandler our message handler
109 */
110 public TrafficMonitorObject(long trafficPeriod,
111 ServicesBundle servicesBundle,
112 TopologyViewMessageHandler messageHandler) {
113 this.trafficPeriod = trafficPeriod;
114 this.servicesBundle = servicesBundle;
115 this.messageHandler = messageHandler;
116
117 intentFilter = new TopologyViewIntentFilter(servicesBundle);
118 }
119
120 // =======================================================================
121 // === API === // TODO: add javadocs
122
123 public synchronized void monitor(Mode mode) {
124 log.debug("monitor: {}", mode);
125 this.mode = mode;
126
127 switch (mode) {
128 case ALL_FLOW_TRAFFIC:
129 clearSelection();
130 scheduleTask();
131 sendAllFlowTraffic();
132 break;
133
134 case ALL_PORT_TRAFFIC:
135 clearSelection();
136 scheduleTask();
137 sendAllPortTraffic();
138 break;
139
140 case SEL_INTENT:
141 scheduleTask();
142 sendSelectedIntentTraffic();
143 break;
144
145 default:
146 log.debug("Unexpected call to monitor({})", mode);
147 clearAll();
148 break;
149 }
150 }
151
152 public synchronized void monitor(Mode mode, NodeSelection nodeSelection) {
153 log.debug("monitor: {} -- {}", mode, nodeSelection);
154 this.mode = mode;
155 this.selectedNodes = nodeSelection;
156
157 switch (mode) {
158 case DEV_LINK_FLOWS:
159 // only care about devices (not hosts)
160 if (selectedNodes.devices().isEmpty()) {
161 sendClearAll();
162 } else {
163 scheduleTask();
164 sendDeviceLinkFlows();
165 }
166 break;
167
168 case RELATED_INTENTS:
169 if (selectedNodes.none()) {
170 sendClearAll();
171 } else {
172 selectedIntents = new IntentSelection(selectedNodes, intentFilter);
173 if (selectedIntents.none()) {
174 sendClearAll();
175 } else {
176 sendSelectedIntents();
177 }
178 }
179 break;
180
181 default:
182 log.debug("Unexpected call to monitor({}, {})", mode, nodeSelection);
183 clearAll();
184 break;
185 }
186 }
187
188 public synchronized void monitor(Intent intent) {
189 log.debug("monitor intent: {}", intent.id());
190 selectedNodes = null;
191 selectedIntents = new IntentSelection(intent);
192 mode = SEL_INTENT;
193 scheduleTask();
194 sendSelectedIntentTraffic();
195 }
196
197 public synchronized void selectNextIntent() {
198 if (selectedIntents != null) {
199 selectedIntents.next();
200 sendSelectedIntents();
201 }
202 }
203
204 public synchronized void selectPreviousIntent() {
205 if (selectedIntents != null) {
206 selectedIntents.prev();
207 sendSelectedIntents();
208 }
209 }
210
211 public synchronized void pokeIntent() {
212 if (mode == SEL_INTENT) {
213 sendSelectedIntentTraffic();
214 }
215 }
216
217 public synchronized void stop() {
218 log.debug("STOP");
219 if (mode != IDLE) {
220 sendClearAll();
221 }
222 }
223
224
225 // =======================================================================
226 // === Helper methods ===
227
228 private void sendClearAll() {
229 clearAll();
230 sendClearHighlights();
231 }
232
233 private void clearAll() {
234 this.mode = IDLE;
235 clearSelection();
236 cancelTask();
237 }
238
239 private void clearSelection() {
240 selectedNodes = null;
241 selectedIntents = null;
242 }
243
244 private synchronized void scheduleTask() {
245 if (trafficTask == null) {
246 log.debug("Starting up background traffic task...");
247 trafficTask = new TrafficMonitor();
248 timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
249 } else {
250 // TEMPORARY until we are sure this is working correctly
251 log.debug("(traffic task already running)");
252 }
253 }
254
255 private synchronized void cancelTask() {
256 if (trafficTask != null) {
257 trafficTask.cancel();
258 trafficTask = null;
259 }
260 }
261
262 // ---
263
264 private void sendAllFlowTraffic() {
265 log.debug("sendAllFlowTraffic");
266 sendHighlights(trafficSummary(LinkStatsType.FLOW_STATS));
267 }
268
269 private void sendAllPortTraffic() {
270 log.debug("sendAllPortTraffic");
271 sendHighlights(trafficSummary(LinkStatsType.PORT_STATS));
272 }
273
274 private void sendDeviceLinkFlows() {
275 log.debug("sendDeviceLinkFlows: {}", selectedNodes);
276 sendHighlights(deviceLinkFlows());
277 }
278
279 private void sendSelectedIntents() {
280 log.debug("sendSelectedIntents: {}", selectedIntents);
281 sendHighlights(intentGroup());
282 }
283
284 private void sendSelectedIntentTraffic() {
285 log.debug("sendSelectedIntentTraffic: {}", selectedIntents);
286 sendHighlights(intentTraffic());
287 }
288
289 private void sendClearHighlights() {
290 log.debug("sendClearHighlights");
291 sendHighlights(new Highlights());
292 }
293
294 private void sendHighlights(Highlights highlights) {
295 messageHandler.sendHighlights(highlights);
296 }
297
298
299 // =======================================================================
300 // === Generate messages in JSON object node format
301
302 private Highlights trafficSummary(LinkStatsType type) {
303 Highlights highlights = new Highlights();
304
305 // compile a set of bilinks (combining pairs of unidirectional links)
306 Map<LinkKey, BiLink> linkMap = new HashMap<>();
307 compileLinks(linkMap);
308 addEdgeLinks(linkMap);
309
310 for (BiLink blink : linkMap.values()) {
311 if (type == LinkStatsType.FLOW_STATS) {
312 attachFlowLoad(blink);
313 } else if (type == LinkStatsType.PORT_STATS) {
314 attachPortLoad(blink);
315 }
316
317 // we only want to report on links deemed to have traffic
318 if (blink.hasTraffic()) {
319 highlights.add(blink.generateHighlight(type));
320 }
321 }
322 return highlights;
323 }
324
325 // create highlights for links, showing flows for selected devices.
326 private Highlights deviceLinkFlows() {
327 Highlights highlights = new Highlights();
328
329 if (selectedNodes != null && !selectedNodes.devices().isEmpty()) {
330 // capture flow counts on bilinks
331 Map<LinkKey, BiLink> linkMap = new HashMap<>();
332
333 for (Device device : selectedNodes.devices()) {
334 Map<Link, Integer> counts = getLinkFlowCounts(device.id());
335 for (Link link : counts.keySet()) {
336 BiLink blink = TopoUtils.addLink(linkMap, link);
337 blink.addFlows(counts.get(link));
338 }
339 }
340
341 // now report on our collated links
342 for (BiLink blink : linkMap.values()) {
343 highlights.add(blink.generateHighlight(LinkStatsType.FLOW_COUNT));
344 }
345
346 }
347 return highlights;
348 }
349
350 private Highlights intentGroup() {
351 Highlights highlights = new Highlights();
352
353 if (selectedIntents != null && !selectedIntents.none()) {
354 // If 'all' intents are selected, they will all have primary
355 // highlighting; otherwise, the specifically selected intent will
356 // have primary highlighting, and the remainder will have secondary
357 // highlighting.
358 Set<Intent> primary;
359 Set<Intent> secondary;
360 int count = selectedIntents.size();
361
362 Set<Intent> allBut = new HashSet<>(selectedIntents.intents());
363 Intent current;
364
365 if (selectedIntents.all()) {
366 primary = allBut;
367 secondary = Collections.emptySet();
368 log.debug("Highlight all intents ({})", count);
369 } else {
370 current = selectedIntents.current();
371 primary = new HashSet<>();
372 primary.add(current);
373 allBut.remove(current);
374 secondary = allBut;
375 log.debug("Highlight intent: {} ([{}] of {})",
376 current.id(), selectedIntents.index(), count);
377 }
378 TrafficClass tc1 = new TrafficClass(PRIMARY_HIGHLIGHT, primary);
379 TrafficClass tc2 = new TrafficClass(SECONDARY_HIGHLIGHT, secondary);
380 // classify primary links after secondary (last man wins)
381 highlightIntents(highlights, tc2, tc1);
382 }
383 return highlights;
384 }
385
386 private Highlights intentTraffic() {
387 Highlights highlights = new Highlights();
388
389 if (selectedIntents != null && selectedIntents.single()) {
390 Intent current = selectedIntents.current();
391 Set<Intent> primary = new HashSet<>();
392 primary.add(current);
393 log.debug("Highlight traffic for intent: {} ([{}] of {})",
394 current.id(), selectedIntents.index(), selectedIntents.size());
395 TrafficClass tc1 = new TrafficClass(PRIMARY_HIGHLIGHT, primary, true);
396 highlightIntents(highlights, tc1);
397 }
398 return highlights;
399 }
400
401
402 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
403
404 private void compileLinks(Map<LinkKey, BiLink> linkMap) {
405 servicesBundle.linkService().getLinks()
406 .forEach(link -> TopoUtils.addLink(linkMap, link));
407 }
408
409 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
410 servicesBundle.hostService().getHosts().forEach(host -> {
411 TopoUtils.addLink(biLinks, createEdgeLink(host, true));
412 TopoUtils.addLink(biLinks, createEdgeLink(host, false));
413 });
414 }
415
416 private Load getLinkFlowLoad(Link link) {
417 if (link != null && link.src().elementId() instanceof DeviceId) {
418 return servicesBundle.flowStatsService().load(link);
419 }
420 return null;
421 }
422
423 private void attachFlowLoad(BiLink link) {
424 link.addLoad(getLinkFlowLoad(link.one()));
425 link.addLoad(getLinkFlowLoad(link.two()));
426 }
427
428 private void attachPortLoad(BiLink link) {
429 // For bi-directional traffic links, use
430 // the max link rate of either direction
431 // (we choose 'one' since we know that is never null)
432 Link one = link.one();
433 Load egressSrc = servicesBundle.portStatsService().load(one.src());
434 Load egressDst = servicesBundle.portStatsService().load(one.dst());
435// link.addLoad(maxLoad(egressSrc, egressDst), BPS_THRESHOLD);
436 link.addLoad(maxLoad(egressSrc, egressDst), 10); // FIXME - debug only
437 }
438
439 private Load maxLoad(Load a, Load b) {
440 if (a == null) {
441 return b;
442 }
443 if (b == null) {
444 return a;
445 }
446 return a.rate() > b.rate() ? a : b;
447 }
448
449 // ---
450
451 // Counts all flow entries that egress on the links of the given device.
452 private Map<Link, Integer> getLinkFlowCounts(DeviceId deviceId) {
453 // get the flows for the device
454 List<FlowEntry> entries = new ArrayList<>();
455 for (FlowEntry flowEntry : servicesBundle.flowService().getFlowEntries(deviceId)) {
456 entries.add(flowEntry);
457 }
458
459 // get egress links from device, and include edge links
460 Set<Link> links = new HashSet<>(servicesBundle.linkService().getDeviceEgressLinks(deviceId));
461 Set<Host> hosts = servicesBundle.hostService().getConnectedHosts(deviceId);
462 if (hosts != null) {
463 for (Host host : hosts) {
464 links.add(createEdgeLink(host, false));
465 }
466 }
467
468 // compile flow counts per link
469 Map<Link, Integer> counts = new HashMap<>();
470 for (Link link : links) {
471 counts.put(link, getEgressFlows(link, entries));
472 }
473 return counts;
474 }
475
476 // Counts all entries that egress on the link source port.
477 private int getEgressFlows(Link link, List<FlowEntry> entries) {
478 int count = 0;
479 PortNumber out = link.src().port();
480 for (FlowEntry entry : entries) {
481 TrafficTreatment treatment = entry.treatment();
482 for (Instruction instruction : treatment.allInstructions()) {
483 if (instruction.type() == Instruction.Type.OUTPUT &&
484 ((OutputInstruction) instruction).port().equals(out)) {
485 count++;
486 }
487 }
488 }
489 return count;
490 }
491
492 // ---
493 private void highlightIntents(Highlights highlights,
494 TrafficClass... trafficClasses) {
495 Map<LinkKey, BiLink> linkMap = new HashMap<>();
496
497
498 for (TrafficClass trafficClass : trafficClasses) {
499 classifyLinkTraffic(linkMap, trafficClass);
500 }
501
502 for (BiLink blink : linkMap.values()) {
503 highlights.add(blink.generateHighlight(LinkStatsType.TAGGED));
504 }
505 }
506
507 private void classifyLinkTraffic(Map<LinkKey, BiLink> linkMap,
508 TrafficClass trafficClass) {
509 for (Intent intent : trafficClass.intents()) {
510 boolean isOptical = intent instanceof OpticalConnectivityIntent;
511 List<Intent> installables = servicesBundle.intentService()
512 .getInstallableIntents(intent.key());
513 Iterable<Link> links = null;
514
515 if (installables != null) {
516 for (Intent installable : installables) {
517
518 if (installable instanceof PathIntent) {
519 links = ((PathIntent) installable).path().links();
520 } else if (installable instanceof FlowRuleIntent) {
521 links = linkResources(installable);
522 } else if (installable instanceof LinkCollectionIntent) {
523 links = ((LinkCollectionIntent) installable).links();
524 } else if (installable instanceof OpticalPathIntent) {
525 links = ((OpticalPathIntent) installable).path().links();
526 }
527
528 classifyLinks(trafficClass, isOptical, linkMap, links);
529 }
530 }
531 }
532 }
533
534 private void classifyLinks(TrafficClass trafficClass, boolean isOptical,
535 Map<LinkKey, BiLink> linkMap,
536 Iterable<Link> links) {
537 if (links != null) {
538 for (Link link : links) {
539 BiLink blink = TopoUtils.addLink(linkMap, link);
540 if (trafficClass.showTraffic()) {
541 blink.addLoad(getLinkFlowLoad(link));
542 blink.setAntMarch(true);
543 }
544 blink.setOptical(isOptical);
545 blink.tagFlavor(trafficClass.flavor());
546 }
547 }
548 }
549
550 // Extracts links from the specified flow rule intent resources
551 private Collection<Link> linkResources(Intent installable) {
552 ImmutableList.Builder<Link> builder = ImmutableList.builder();
553 installable.resources().stream().filter(r -> r instanceof Link)
554 .forEach(r -> builder.add((Link) r));
555 return builder.build();
556 }
557
558 // =======================================================================
559 // === Background Task
560
561 // Provides periodic update of traffic information to the client
562 private class TrafficMonitor extends TimerTask {
563 @Override
564 public void run() {
565 try {
566 switch (mode) {
567 case ALL_FLOW_TRAFFIC:
568 sendAllFlowTraffic();
569 break;
570 case ALL_PORT_TRAFFIC:
571 sendAllPortTraffic();
572 break;
573 case DEV_LINK_FLOWS:
574 sendDeviceLinkFlows();
575 break;
576 case SEL_INTENT:
577 sendSelectedIntentTraffic();
578 break;
579
580 default:
581 // RELATED_INTENTS and IDLE modes should never invoke
582 // the background task, but if they do, they have
583 // nothing to do
584 break;
585 }
586
587 } catch (Exception e) {
588 log.warn("Unable to process traffic task due to {}", e.getMessage());
589 log.warn("Boom!", e);
590 }
591 }
592 }
593
594}