blob: 8ef929ba61941e26faf3b78698442139b04ea6ba [file] [log] [blame]
Andrea Campanella732ea832017-02-06 09:25:59 -08001/*
2 * Copyright 2017-present 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
17package org.onosproject.ui.impl;
18
19import com.google.common.collect.ImmutableList;
Andrea Campanellaaf934682017-03-05 11:06:40 +010020import com.google.common.collect.ImmutableSet;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070021import com.google.common.collect.Lists;
Andrea Campanella732ea832017-02-06 09:25:59 -080022import com.google.common.collect.Sets;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070023import org.onosproject.net.ConnectPoint;
Andrea Campanella732ea832017-02-06 09:25:59 -080024import org.onosproject.net.DeviceId;
25import org.onosproject.net.ElementId;
26import org.onosproject.net.HostId;
27import org.onosproject.net.Link;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070028import org.onosproject.net.flow.criteria.Criterion;
29import org.onosproject.net.flow.criteria.PortCriterion;
30import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
Andrea Campanella732ea832017-02-06 09:25:59 -080031import org.onosproject.net.intent.FlowRuleIntent;
32import org.onosproject.net.intent.Intent;
Marc De Leenheer20913c62017-04-12 14:45:15 -070033import org.onosproject.net.intent.IntentService;
Andrea Campanella732ea832017-02-06 09:25:59 -080034import org.onosproject.net.intent.OpticalConnectivityIntent;
35import org.onosproject.net.intent.ProtectionEndpointIntent;
Marc De Leenheer20913c62017-04-12 14:45:15 -070036import org.onosproject.net.link.LinkService;
Andrea Campanella732ea832017-02-06 09:25:59 -080037import org.onosproject.ui.impl.topo.util.ServicesBundle;
38import org.onosproject.ui.impl.topo.util.TrafficLink;
39import org.onosproject.ui.impl.topo.util.TrafficLink.StatsType;
40import org.onosproject.ui.impl.topo.util.TrafficLinkMap;
41import org.onosproject.ui.topo.AbstractTopoMonitor;
42import org.onosproject.ui.topo.DeviceHighlight;
43import org.onosproject.ui.topo.Highlights;
44import org.onosproject.ui.topo.HostHighlight;
45import org.onosproject.ui.topo.LinkHighlight.Flavor;
Andrea Campanellaaf934682017-03-05 11:06:40 +010046import org.onosproject.ui.topo.Mod;
Andrea Campanella732ea832017-02-06 09:25:59 -080047import org.onosproject.ui.topo.NodeHighlight;
48import org.slf4j.Logger;
49import org.slf4j.LoggerFactory;
50
51import java.util.Collection;
Marc De Leenheer20913c62017-04-12 14:45:15 -070052import java.util.Collections;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070053import java.util.LinkedHashSet;
Marc De Leenheer20913c62017-04-12 14:45:15 -070054import java.util.LinkedList;
Andrea Campanella732ea832017-02-06 09:25:59 -080055import java.util.List;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070056import java.util.Map;
57import java.util.Objects;
58import java.util.Optional;
Andrea Campanella732ea832017-02-06 09:25:59 -080059import java.util.Set;
60import java.util.Timer;
61import java.util.TimerTask;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070062import java.util.stream.Collectors;
Andrea Campanella732ea832017-02-06 09:25:59 -080063
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070064import static org.onosproject.net.MarkerResource.marker;
Andrea Campanella732ea832017-02-06 09:25:59 -080065import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.IDLE;
66import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.SELECTED_INTENT;
67
68/**
69 * Encapsulates the behavior of monitoring protected intents.
70 */
71//TODO refactor duplicated methods from here and the TrafficMonitor to AbstractTopoMonitor
72public class ProtectedIntentMonitor extends AbstractTopoMonitor {
73
74 private static final Logger log =
75 LoggerFactory.getLogger(ProtectedIntentMonitor.class);
Andrea Campanellaaf934682017-03-05 11:06:40 +010076 private static final String PRIMARY_PATH_TAG = "protection1";
77
78 private static final String PROT_PRIMARY = "protPrimary";
79 private static final String PROT_BACKUP = "protBackup";
80
81
82 private static final Mod MOD_PROT_PRIMARY = new Mod(PROT_PRIMARY);
Simon Hunt1911fe42017-05-02 18:25:58 -070083 private static final Set<Mod> PROTECTED_MOD_PRIMARY_SET =
84 ImmutableSet.of(MOD_PROT_PRIMARY);
Andrea Campanellaaf934682017-03-05 11:06:40 +010085
86 private static final Mod MOD_PROT_BACKUP = new Mod(PROT_BACKUP);
Simon Hunt1911fe42017-05-02 18:25:58 -070087 private static final Set<Mod> PROTECTED_MOD_BACKUP_SET =
88 ImmutableSet.of(MOD_PROT_BACKUP);
Andrea Campanellaaf934682017-03-05 11:06:40 +010089
Andrea Campanella732ea832017-02-06 09:25:59 -080090
91 /**
92 * Designates the different modes of operation.
93 */
94 public enum ProtectedMode {
95 IDLE,
96 SELECTED_INTENT
97 }
98
99 private final long trafficPeriod;
Simon Hunt1911fe42017-05-02 18:25:58 -0700100 private final ServicesBundle services;
Andrea Campanella732ea832017-02-06 09:25:59 -0800101 private final TopologyViewMessageHandler msgHandler;
102
103 private final Timer timer = new Timer("topo-protected-intents");
104
105 private TimerTask trafficTask = null;
106 private ProtectedMode mode = ProtectedMode.IDLE;
107 private Intent selectedIntent = null;
108
109
110 /**
111 * Constructs a protected intent monitor.
112 *
Simon Hunt1911fe42017-05-02 18:25:58 -0700113 * @param trafficPeriod traffic task period in ms
114 * @param services bundle of services
115 * @param msgHandler our message handler
Andrea Campanella732ea832017-02-06 09:25:59 -0800116 */
Simon Hunt1911fe42017-05-02 18:25:58 -0700117 public ProtectedIntentMonitor(long trafficPeriod, ServicesBundle services,
Andrea Campanella732ea832017-02-06 09:25:59 -0800118 TopologyViewMessageHandler msgHandler) {
119 this.trafficPeriod = trafficPeriod;
Simon Hunt1911fe42017-05-02 18:25:58 -0700120 this.services = services;
Andrea Campanella732ea832017-02-06 09:25:59 -0800121 this.msgHandler = msgHandler;
Andrea Campanella732ea832017-02-06 09:25:59 -0800122 }
123
124 // =======================================================================
125 // === API ===
126
127 // TODO: move this out to the "h2h/multi-intent app"
128
129 /**
130 * Monitor for protected intent data to be sent back to the web client,
131 * for the given intent.
132 *
133 * @param intent the intent to monitor
134 */
135 public synchronized void monitor(Intent intent) {
136 log.debug("monitor intent: {}", intent.id());
137 selectedIntent = intent;
138 mode = SELECTED_INTENT;
139 scheduleTask();
140 sendSelectedIntents();
141 }
142
143 /**
144 * Stop all traffic monitoring.
145 */
146 public synchronized void stopMonitoring() {
147 log.debug("STOP monitoring");
148 if (mode != IDLE) {
149 sendClearAll();
150 }
151 }
152
153
154 // =======================================================================
155 // === Helper methods ===
156 private void sendClearAll() {
157 clearAll();
158 sendClearHighlights();
159 }
160
161 private void clearAll() {
162 this.mode = IDLE;
163 clearSelection();
164 cancelTask();
165 }
166
167 private void clearSelection() {
168 selectedIntent = null;
169 }
170
171 //TODO duplicate and can be brought in abstract upper class.
172 private synchronized void scheduleTask() {
173 if (trafficTask == null) {
174 log.debug("Starting up background protected intent task...");
175 trafficTask = new TrafficUpdateTask();
176 timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
177 } else {
178 log.debug("(protected intent task already running)");
179 }
180 }
181
182 private synchronized void cancelTask() {
183 if (trafficTask != null) {
184 trafficTask.cancel();
185 trafficTask = null;
186 }
187 }
188
189 private void sendSelectedIntents() {
190 log.debug("sendSelectedIntents: {}", selectedIntent);
191 msgHandler.sendHighlights(protectedIntentHighlights());
192 }
193
194 private void sendClearHighlights() {
195 log.debug("sendClearHighlights");
196 msgHandler.sendHighlights(new Highlights());
197 }
198
199 // =======================================================================
200 // === Generate messages in JSON object node format
201 private Highlights protectedIntentHighlights() {
202 Highlights highlights = new Highlights();
203 TrafficLinkMap linkMap = new TrafficLinkMap();
Simon Hunt1911fe42017-05-02 18:25:58 -0700204 IntentService intentService = services.intent();
Andrea Campanella732ea832017-02-06 09:25:59 -0800205 if (selectedIntent != null) {
Marc De Leenheer20913c62017-04-12 14:45:15 -0700206 List<Intent> installables = intentService.getInstallableIntents(selectedIntent.key());
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700207
Andrea Campanella732ea832017-02-06 09:25:59 -0800208 if (installables != null) {
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700209 ProtectionEndpointIntent ep1 = installables.stream()
210 .filter(ProtectionEndpointIntent.class::isInstance)
211 .map(ProtectionEndpointIntent.class::cast)
212 .findFirst().orElse(null);
213 ProtectionEndpointIntent ep2 = installables.stream()
214 .filter(ii -> !ii.equals(ep1))
215 .filter(ProtectionEndpointIntent.class::isInstance)
216 .map(ProtectionEndpointIntent.class::cast)
217 .findFirst().orElse(null);
218 if (ep1 == null || ep2 == null) {
219 log.warn("Selected Intent {} didn't have 2 protection endpoints",
220 selectedIntent.key());
221 stopMonitoring();
222 return highlights;
Andrea Campanella732ea832017-02-06 09:25:59 -0800223 }
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700224 Set<Link> primary = new LinkedHashSet<>();
225 Set<Link> backup = new LinkedHashSet<>();
226
227 Map<Boolean, List<FlowRuleIntent>> transits = installables.stream()
Simon Hunt1911fe42017-05-02 18:25:58 -0700228 .filter(FlowRuleIntent.class::isInstance)
229 .map(FlowRuleIntent.class::cast)
230 // only consider fwd links so that ants march in one direction
231 // TODO: didn't help need further investigation.
232 //.filter(i -> !i.resources().contains(marker("rev")))
233 .collect(Collectors.groupingBy(this::isPrimary));
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700234
235 // walk primary
236 ConnectPoint primHead = ep1.description().paths().get(0).output().connectPoint();
237 ConnectPoint primTail = ep2.description().paths().get(0).output().connectPoint();
238 List<FlowRuleIntent> primTransit = transits.getOrDefault(true, ImmutableList.of());
239 populateLinks(primary, primHead, primTail, primTransit);
240
241 // walk backup
242 ConnectPoint backHead = ep1.description().paths().get(1).output().connectPoint();
243 ConnectPoint backTail = ep2.description().paths().get(1).output().connectPoint();
244 List<FlowRuleIntent> backTransit = transits.getOrDefault(false, ImmutableList.of());
245 populateLinks(backup, backHead, backTail, backTransit);
246
Marc De Leenheer20913c62017-04-12 14:45:15 -0700247 // Add packet to optical links
248 if (!usingBackup(primary)) {
249 primary.addAll(protectedIntentMultiLayer(primHead, primTail));
250 }
251 backup.addAll(protectedIntentMultiLayer(backHead, backTail));
252
Andrea Campanella732ea832017-02-06 09:25:59 -0800253 boolean isOptical = selectedIntent instanceof OpticalConnectivityIntent;
Marc De Leenheer20913c62017-04-12 14:45:15 -0700254 //last parameter (traffic) signals if the link is highlighted with ants or solid line
Andrea Campanella732ea832017-02-06 09:25:59 -0800255 //Flavor is swapped so green is primary path.
256 if (usingBackup(primary)) {
257 //the backup becomes in use so we have a dotted line
Andrea Campanellaaf934682017-03-05 11:06:40 +0100258 processLinks(linkMap, backup, Flavor.PRIMARY_HIGHLIGHT,
259 isOptical, true, PROTECTED_MOD_BACKUP_SET);
Andrea Campanella732ea832017-02-06 09:25:59 -0800260 } else {
Andrea Campanellaaf934682017-03-05 11:06:40 +0100261 processLinks(linkMap, primary, Flavor.PRIMARY_HIGHLIGHT,
262 isOptical, true, PROTECTED_MOD_PRIMARY_SET);
263 processLinks(linkMap, backup, Flavor.SECONDARY_HIGHLIGHT,
264 isOptical, false, PROTECTED_MOD_BACKUP_SET);
Andrea Campanella732ea832017-02-06 09:25:59 -0800265 }
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700266
Andrea Campanella732ea832017-02-06 09:25:59 -0800267 updateHighlights(highlights, primary);
268 updateHighlights(highlights, backup);
269 colorLinks(highlights, linkMap);
270 highlights.subdueAllElse(Highlights.Amount.MINIMALLY);
271 } else {
Marc De Leenheer20913c62017-04-12 14:45:15 -0700272 log.debug("Selected Intent has no installable intents");
Andrea Campanella732ea832017-02-06 09:25:59 -0800273 }
274 } else {
275 log.debug("Selected Intent is null");
276 }
277 return highlights;
278 }
279
Marc De Leenheer20913c62017-04-12 14:45:15 -0700280 /**
281 * Returns the packet to optical mapping given a head and tail of a protection path.
282 *
283 * @param head head of path
284 * @param tail tail of path
285 */
286 private Set<Link> protectedIntentMultiLayer(ConnectPoint head, ConnectPoint tail) {
287 List<Link> links = new LinkedList<>();
Simon Hunt1911fe42017-05-02 18:25:58 -0700288 LinkService linkService = services.link();
289 IntentService intentService = services.intent();
Marc De Leenheer20913c62017-04-12 14:45:15 -0700290
291 // Ingress cross connect link
292 links.addAll(
293 linkService.getEgressLinks(head).stream()
294 .filter(l -> l.type() == Link.Type.OPTICAL)
295 .collect(Collectors.toList())
296 );
297
298 // Egress cross connect link
299 links.addAll(
300 linkService.getIngressLinks(tail).stream()
301 .filter(l -> l.type() == Link.Type.OPTICAL)
302 .collect(Collectors.toList())
303 );
304
305 // The protected intent does not rely on a multi-layer mapping
306 if (links.size() != 2) {
307 return Collections.emptySet();
308 }
309
310 // Expected head and tail of optical circuit (not connectivity!) intent
311 ConnectPoint ocHead = links.get(0).dst();
312 ConnectPoint ocTail = links.get(1).src();
313
314 // Optical connectivity
315 // FIXME: assumes that transponder (OTN device) is a one-to-one mapping
316 // We need to track the multi-layer aspects better
317 intentService.getIntents().forEach(intent -> {
318 if (intent instanceof OpticalConnectivityIntent) {
319 OpticalConnectivityIntent ocIntent = (OpticalConnectivityIntent) intent;
320 if (ocHead.deviceId().equals(ocIntent.getSrc().deviceId()) &&
321 ocTail.deviceId().equals(ocIntent.getDst().deviceId())) {
322 intentService.getInstallableIntents(ocIntent.key()).forEach(i -> {
323 if (i instanceof FlowRuleIntent) {
324 FlowRuleIntent fr = (FlowRuleIntent) i;
325 links.addAll(linkResources(fr));
326 }
327 });
328 }
329 }
330 });
331
332 return new LinkedHashSet<>(links);
333 }
334
Andrea Campanella732ea832017-02-06 09:25:59 -0800335 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
336
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700337 /**
338 * Populate Links along the primary/backup path.
339 *
Simon Hunt1911fe42017-05-02 18:25:58 -0700340 * @param links link collection to populate [output]
341 * @param head head-end of primary/backup path
342 * @param tail tail-end of primary/backup path
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700343 * @param transit Intents if any
344 */
345 private void populateLinks(Set<Link> links,
346 ConnectPoint head,
347 ConnectPoint tail,
348 List<FlowRuleIntent> transit) {
349 // find first hop link
350 Link first = transit.stream()
351 // search for Link head -> transit Intent head
352 // as first candidate of 1st hop Link
353 .flatMap(fri -> fri.flowRules().stream())
354 .map(fr ->
355 // find first input port from FlowRule
356 Optional.ofNullable(fr.selector().getCriterion(Criterion.Type.IN_PORT))
357 .filter(PortCriterion.class::isInstance)
358 .map(PortCriterion.class::cast)
359 .map(PortCriterion::port)
360 .map(pn -> new ConnectPoint(fr.deviceId(), pn))
361 .orElse(null)
362 ).filter(Objects::nonNull)
Simon Hunt1911fe42017-05-02 18:25:58 -0700363 .map(dst -> services.link().getLink(head, dst))
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700364 .filter(Objects::nonNull)
365 .findFirst()
366 // if there isn't one probably 1 hop to the tail
Simon Hunt1911fe42017-05-02 18:25:58 -0700367 .orElse(services.link().getLink(head, tail));
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700368
369 // add first link
370 if (first != null) {
371 links.add(first);
372 }
373
374 // add links in the middle if any
375 transit.forEach(fri -> links.addAll(linkResources(fri)));
376
377 // add last hop if any
378 Lists.reverse(transit).stream()
379 // search for Link transit Intent tail -> tail
380 // as candidate of last hop Link
381 .flatMap(fri -> ImmutableList.copyOf(fri.flowRules()).reverse().stream())
382 .map(fr ->
383 // find first output port from FlowRule
384 fr.treatment().allInstructions().stream()
385 .filter(OutputInstruction.class::isInstance).findFirst()
386 .map(OutputInstruction.class::cast)
387 .map(OutputInstruction::port)
388 .map(pn -> new ConnectPoint(fr.deviceId(), pn))
389 .orElse(null)
390 ).filter(Objects::nonNull)
Simon Hunt1911fe42017-05-02 18:25:58 -0700391 .map(src -> services.link().getLink(src, tail))
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700392 .filter(Objects::nonNull)
393 .findFirst()
394 .ifPresent(links::add);
Andrea Campanella732ea832017-02-06 09:25:59 -0800395 }
396
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700397 /**
398 * Returns true if specified intent is marked with primary marker resource.
399 *
400 * @param intent to test
401 * @return true if it is an Intent taking part of primary transit path
402 */
403 private boolean isPrimary(Intent intent) {
404 return intent.resources()
405 .contains(marker(PRIMARY_PATH_TAG));
Andrea Campanella732ea832017-02-06 09:25:59 -0800406 }
407
408 // returns true if the backup path is the one where the traffic is currently flowing
409 private boolean usingBackup(Set<Link> primary) {
Simon Hunt1911fe42017-05-02 18:25:58 -0700410 Set<Link> activeLinks = Sets.newHashSet(services.link().getActiveLinks());
Andrea Campanella732ea832017-02-06 09:25:59 -0800411 return primary.isEmpty() || !activeLinks.containsAll(primary);
412 }
413
414 private void updateHighlights(Highlights highlights, Iterable<Link> links) {
415 for (Link link : links) {
416 ensureNodePresent(highlights, link.src().elementId());
417 ensureNodePresent(highlights, link.dst().elementId());
418 }
419 }
420
421 //TODO duplicate and can be brought in abstract upper class.
422 private void ensureNodePresent(Highlights highlights, ElementId eid) {
423 String id = eid.toString();
424 NodeHighlight nh = highlights.getNode(id);
425 if (nh == null) {
426 if (eid instanceof DeviceId) {
427 nh = new DeviceHighlight(id);
428 highlights.add((DeviceHighlight) nh);
429 } else if (eid instanceof HostId) {
430 nh = new HostHighlight(id);
431 highlights.add((HostHighlight) nh);
432 }
433 }
434 }
435
436 private void processLinks(TrafficLinkMap linkMap, Iterable<Link> links,
437 Flavor flavor, boolean isOptical,
Andrea Campanellaaf934682017-03-05 11:06:40 +0100438 boolean showTraffic, Set<Mod> mods) {
Andrea Campanella732ea832017-02-06 09:25:59 -0800439 if (links != null) {
440 for (Link link : links) {
441 TrafficLink tlink = linkMap.add(link);
442 tlink.tagFlavor(flavor);
443 tlink.optical(isOptical);
444 if (showTraffic) {
445 tlink.antMarch(true);
446 }
Andrea Campanellaaf934682017-03-05 11:06:40 +0100447 tlink.tagMods(mods);
Andrea Campanella732ea832017-02-06 09:25:59 -0800448 }
449 }
450 }
451
452 //TODO duplicate and can be brought in abstract upper class.
453 private void colorLinks(Highlights highlights, TrafficLinkMap linkMap) {
454 for (TrafficLink tlink : linkMap.biLinks()) {
455 highlights.add(tlink.highlight(StatsType.TAGGED));
456 }
457 }
458
459 //TODO duplicate and can be brought in abstract upper class.
460 // Extracts links from the specified flow rule intent resources
461 private Collection<Link> linkResources(Intent installable) {
462 ImmutableList.Builder<Link> builder = ImmutableList.builder();
463 installable.resources().stream().filter(r -> r instanceof Link)
464 .forEach(r -> builder.add((Link) r));
465 return builder.build();
466 }
467
468 // =======================================================================
469 // === Background Task
470
471 // Provides periodic update of traffic information to the client
472 private class TrafficUpdateTask extends TimerTask {
473 @Override
474 public void run() {
475 try {
476 switch (mode) {
477 case SELECTED_INTENT:
478 sendSelectedIntents();
479 break;
480
481 default:
482 // RELATED_INTENTS and IDLE modes should never invoke
483 // the background task, but if they do, they have
484 // nothing to do
485 break;
486 }
487
488 } catch (Exception e) {
489 log.warn("Unable to process protected intent task due to {}", e.getMessage());
490 log.warn("Boom!", e);
491 }
492 }
493 }
494}