blob: 2fe3552d600d22edd4577f3cd443133b1f4adbc8 [file] [log] [blame]
Andrea Campanella732ea832017-02-06 09:25:59 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Andrea Campanella732ea832017-02-06 09:25:59 -08003 *
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
Simon Hunt1911fe42017-05-02 18:25:58 -070099 private final ServicesBundle services;
Andrea Campanella732ea832017-02-06 09:25:59 -0800100 private final TopologyViewMessageHandler msgHandler;
101
102 private final Timer timer = new Timer("topo-protected-intents");
103
104 private TimerTask trafficTask = null;
105 private ProtectedMode mode = ProtectedMode.IDLE;
106 private Intent selectedIntent = null;
107
108
109 /**
110 * Constructs a protected intent monitor.
111 *
Simon Hunt1911fe42017-05-02 18:25:58 -0700112 * @param services bundle of services
113 * @param msgHandler our message handler
Andrea Campanella732ea832017-02-06 09:25:59 -0800114 */
Thomas Vachuskaa5e986d2021-04-06 11:14:09 -0700115 public ProtectedIntentMonitor(ServicesBundle services,
Andrea Campanella732ea832017-02-06 09:25:59 -0800116 TopologyViewMessageHandler msgHandler) {
Simon Hunt1911fe42017-05-02 18:25:58 -0700117 this.services = services;
Andrea Campanella732ea832017-02-06 09:25:59 -0800118 this.msgHandler = msgHandler;
Andrea Campanella732ea832017-02-06 09:25:59 -0800119 }
120
121 // =======================================================================
122 // === API ===
123
124 // TODO: move this out to the "h2h/multi-intent app"
125
126 /**
127 * Monitor for protected intent data to be sent back to the web client,
128 * for the given intent.
129 *
130 * @param intent the intent to monitor
131 */
132 public synchronized void monitor(Intent intent) {
133 log.debug("monitor intent: {}", intent.id());
134 selectedIntent = intent;
135 mode = SELECTED_INTENT;
136 scheduleTask();
137 sendSelectedIntents();
138 }
139
140 /**
141 * Stop all traffic monitoring.
142 */
143 public synchronized void stopMonitoring() {
144 log.debug("STOP monitoring");
145 if (mode != IDLE) {
146 sendClearAll();
147 }
148 }
149
150
151 // =======================================================================
152 // === Helper methods ===
153 private void sendClearAll() {
154 clearAll();
155 sendClearHighlights();
156 }
157
158 private void clearAll() {
159 this.mode = IDLE;
160 clearSelection();
161 cancelTask();
162 }
163
164 private void clearSelection() {
165 selectedIntent = null;
166 }
167
168 //TODO duplicate and can be brought in abstract upper class.
169 private synchronized void scheduleTask() {
170 if (trafficTask == null) {
171 log.debug("Starting up background protected intent task...");
172 trafficTask = new TrafficUpdateTask();
173 timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
174 } else {
175 log.debug("(protected intent task already running)");
176 }
177 }
178
179 private synchronized void cancelTask() {
180 if (trafficTask != null) {
181 trafficTask.cancel();
182 trafficTask = null;
183 }
184 }
185
186 private void sendSelectedIntents() {
187 log.debug("sendSelectedIntents: {}", selectedIntent);
188 msgHandler.sendHighlights(protectedIntentHighlights());
189 }
190
191 private void sendClearHighlights() {
192 log.debug("sendClearHighlights");
193 msgHandler.sendHighlights(new Highlights());
194 }
195
196 // =======================================================================
197 // === Generate messages in JSON object node format
198 private Highlights protectedIntentHighlights() {
199 Highlights highlights = new Highlights();
200 TrafficLinkMap linkMap = new TrafficLinkMap();
Simon Hunt1911fe42017-05-02 18:25:58 -0700201 IntentService intentService = services.intent();
Andrea Campanella732ea832017-02-06 09:25:59 -0800202 if (selectedIntent != null) {
Marc De Leenheer20913c62017-04-12 14:45:15 -0700203 List<Intent> installables = intentService.getInstallableIntents(selectedIntent.key());
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700204
Andrea Campanella732ea832017-02-06 09:25:59 -0800205 if (installables != null) {
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700206 ProtectionEndpointIntent ep1 = installables.stream()
207 .filter(ProtectionEndpointIntent.class::isInstance)
208 .map(ProtectionEndpointIntent.class::cast)
209 .findFirst().orElse(null);
210 ProtectionEndpointIntent ep2 = installables.stream()
211 .filter(ii -> !ii.equals(ep1))
212 .filter(ProtectionEndpointIntent.class::isInstance)
213 .map(ProtectionEndpointIntent.class::cast)
214 .findFirst().orElse(null);
215 if (ep1 == null || ep2 == null) {
216 log.warn("Selected Intent {} didn't have 2 protection endpoints",
217 selectedIntent.key());
218 stopMonitoring();
219 return highlights;
Andrea Campanella732ea832017-02-06 09:25:59 -0800220 }
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700221 Set<Link> primary = new LinkedHashSet<>();
222 Set<Link> backup = new LinkedHashSet<>();
223
224 Map<Boolean, List<FlowRuleIntent>> transits = installables.stream()
Simon Hunt1911fe42017-05-02 18:25:58 -0700225 .filter(FlowRuleIntent.class::isInstance)
226 .map(FlowRuleIntent.class::cast)
227 // only consider fwd links so that ants march in one direction
228 // TODO: didn't help need further investigation.
229 //.filter(i -> !i.resources().contains(marker("rev")))
230 .collect(Collectors.groupingBy(this::isPrimary));
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700231
232 // walk primary
233 ConnectPoint primHead = ep1.description().paths().get(0).output().connectPoint();
234 ConnectPoint primTail = ep2.description().paths().get(0).output().connectPoint();
235 List<FlowRuleIntent> primTransit = transits.getOrDefault(true, ImmutableList.of());
236 populateLinks(primary, primHead, primTail, primTransit);
237
238 // walk backup
239 ConnectPoint backHead = ep1.description().paths().get(1).output().connectPoint();
240 ConnectPoint backTail = ep2.description().paths().get(1).output().connectPoint();
241 List<FlowRuleIntent> backTransit = transits.getOrDefault(false, ImmutableList.of());
242 populateLinks(backup, backHead, backTail, backTransit);
243
Marc De Leenheer20913c62017-04-12 14:45:15 -0700244 // Add packet to optical links
245 if (!usingBackup(primary)) {
246 primary.addAll(protectedIntentMultiLayer(primHead, primTail));
247 }
248 backup.addAll(protectedIntentMultiLayer(backHead, backTail));
249
Andrea Campanella732ea832017-02-06 09:25:59 -0800250 boolean isOptical = selectedIntent instanceof OpticalConnectivityIntent;
Marc De Leenheer20913c62017-04-12 14:45:15 -0700251 //last parameter (traffic) signals if the link is highlighted with ants or solid line
Andrea Campanella732ea832017-02-06 09:25:59 -0800252 //Flavor is swapped so green is primary path.
253 if (usingBackup(primary)) {
254 //the backup becomes in use so we have a dotted line
Andrea Campanellaaf934682017-03-05 11:06:40 +0100255 processLinks(linkMap, backup, Flavor.PRIMARY_HIGHLIGHT,
256 isOptical, true, PROTECTED_MOD_BACKUP_SET);
Andrea Campanella732ea832017-02-06 09:25:59 -0800257 } else {
Andrea Campanellaaf934682017-03-05 11:06:40 +0100258 processLinks(linkMap, primary, Flavor.PRIMARY_HIGHLIGHT,
259 isOptical, true, PROTECTED_MOD_PRIMARY_SET);
260 processLinks(linkMap, backup, Flavor.SECONDARY_HIGHLIGHT,
261 isOptical, false, PROTECTED_MOD_BACKUP_SET);
Andrea Campanella732ea832017-02-06 09:25:59 -0800262 }
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700263
Andrea Campanella732ea832017-02-06 09:25:59 -0800264 updateHighlights(highlights, primary);
265 updateHighlights(highlights, backup);
266 colorLinks(highlights, linkMap);
267 highlights.subdueAllElse(Highlights.Amount.MINIMALLY);
268 } else {
Marc De Leenheer20913c62017-04-12 14:45:15 -0700269 log.debug("Selected Intent has no installable intents");
Andrea Campanella732ea832017-02-06 09:25:59 -0800270 }
271 } else {
272 log.debug("Selected Intent is null");
273 }
274 return highlights;
275 }
276
Marc De Leenheer20913c62017-04-12 14:45:15 -0700277 /**
278 * Returns the packet to optical mapping given a head and tail of a protection path.
279 *
280 * @param head head of path
281 * @param tail tail of path
282 */
283 private Set<Link> protectedIntentMultiLayer(ConnectPoint head, ConnectPoint tail) {
284 List<Link> links = new LinkedList<>();
Simon Hunt1911fe42017-05-02 18:25:58 -0700285 LinkService linkService = services.link();
286 IntentService intentService = services.intent();
Marc De Leenheer20913c62017-04-12 14:45:15 -0700287
288 // Ingress cross connect link
289 links.addAll(
290 linkService.getEgressLinks(head).stream()
291 .filter(l -> l.type() == Link.Type.OPTICAL)
292 .collect(Collectors.toList())
293 );
294
295 // Egress cross connect link
296 links.addAll(
297 linkService.getIngressLinks(tail).stream()
298 .filter(l -> l.type() == Link.Type.OPTICAL)
299 .collect(Collectors.toList())
300 );
301
302 // The protected intent does not rely on a multi-layer mapping
303 if (links.size() != 2) {
304 return Collections.emptySet();
305 }
306
307 // Expected head and tail of optical circuit (not connectivity!) intent
308 ConnectPoint ocHead = links.get(0).dst();
309 ConnectPoint ocTail = links.get(1).src();
310
311 // Optical connectivity
312 // FIXME: assumes that transponder (OTN device) is a one-to-one mapping
313 // We need to track the multi-layer aspects better
314 intentService.getIntents().forEach(intent -> {
315 if (intent instanceof OpticalConnectivityIntent) {
316 OpticalConnectivityIntent ocIntent = (OpticalConnectivityIntent) intent;
317 if (ocHead.deviceId().equals(ocIntent.getSrc().deviceId()) &&
318 ocTail.deviceId().equals(ocIntent.getDst().deviceId())) {
319 intentService.getInstallableIntents(ocIntent.key()).forEach(i -> {
320 if (i instanceof FlowRuleIntent) {
321 FlowRuleIntent fr = (FlowRuleIntent) i;
322 links.addAll(linkResources(fr));
323 }
324 });
325 }
326 }
327 });
328
329 return new LinkedHashSet<>(links);
330 }
331
Andrea Campanella732ea832017-02-06 09:25:59 -0800332 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
333
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700334 /**
335 * Populate Links along the primary/backup path.
336 *
Simon Hunt1911fe42017-05-02 18:25:58 -0700337 * @param links link collection to populate [output]
338 * @param head head-end of primary/backup path
339 * @param tail tail-end of primary/backup path
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700340 * @param transit Intents if any
341 */
342 private void populateLinks(Set<Link> links,
343 ConnectPoint head,
344 ConnectPoint tail,
345 List<FlowRuleIntent> transit) {
346 // find first hop link
347 Link first = transit.stream()
348 // search for Link head -> transit Intent head
349 // as first candidate of 1st hop Link
350 .flatMap(fri -> fri.flowRules().stream())
351 .map(fr ->
352 // find first input port from FlowRule
353 Optional.ofNullable(fr.selector().getCriterion(Criterion.Type.IN_PORT))
354 .filter(PortCriterion.class::isInstance)
355 .map(PortCriterion.class::cast)
356 .map(PortCriterion::port)
357 .map(pn -> new ConnectPoint(fr.deviceId(), pn))
358 .orElse(null)
359 ).filter(Objects::nonNull)
Simon Hunt1911fe42017-05-02 18:25:58 -0700360 .map(dst -> services.link().getLink(head, dst))
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700361 .filter(Objects::nonNull)
362 .findFirst()
363 // if there isn't one probably 1 hop to the tail
Simon Hunt1911fe42017-05-02 18:25:58 -0700364 .orElse(services.link().getLink(head, tail));
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700365
366 // add first link
367 if (first != null) {
368 links.add(first);
369 }
370
371 // add links in the middle if any
372 transit.forEach(fri -> links.addAll(linkResources(fri)));
373
374 // add last hop if any
375 Lists.reverse(transit).stream()
376 // search for Link transit Intent tail -> tail
377 // as candidate of last hop Link
378 .flatMap(fri -> ImmutableList.copyOf(fri.flowRules()).reverse().stream())
379 .map(fr ->
380 // find first output port from FlowRule
381 fr.treatment().allInstructions().stream()
382 .filter(OutputInstruction.class::isInstance).findFirst()
383 .map(OutputInstruction.class::cast)
384 .map(OutputInstruction::port)
385 .map(pn -> new ConnectPoint(fr.deviceId(), pn))
386 .orElse(null)
387 ).filter(Objects::nonNull)
Simon Hunt1911fe42017-05-02 18:25:58 -0700388 .map(src -> services.link().getLink(src, tail))
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700389 .filter(Objects::nonNull)
390 .findFirst()
391 .ifPresent(links::add);
Andrea Campanella732ea832017-02-06 09:25:59 -0800392 }
393
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700394 /**
395 * Returns true if specified intent is marked with primary marker resource.
396 *
397 * @param intent to test
398 * @return true if it is an Intent taking part of primary transit path
399 */
400 private boolean isPrimary(Intent intent) {
401 return intent.resources()
402 .contains(marker(PRIMARY_PATH_TAG));
Andrea Campanella732ea832017-02-06 09:25:59 -0800403 }
404
405 // returns true if the backup path is the one where the traffic is currently flowing
406 private boolean usingBackup(Set<Link> primary) {
Simon Hunt1911fe42017-05-02 18:25:58 -0700407 Set<Link> activeLinks = Sets.newHashSet(services.link().getActiveLinks());
Andrea Campanella732ea832017-02-06 09:25:59 -0800408 return primary.isEmpty() || !activeLinks.containsAll(primary);
409 }
410
411 private void updateHighlights(Highlights highlights, Iterable<Link> links) {
412 for (Link link : links) {
413 ensureNodePresent(highlights, link.src().elementId());
414 ensureNodePresent(highlights, link.dst().elementId());
415 }
416 }
417
418 //TODO duplicate and can be brought in abstract upper class.
419 private void ensureNodePresent(Highlights highlights, ElementId eid) {
420 String id = eid.toString();
421 NodeHighlight nh = highlights.getNode(id);
422 if (nh == null) {
423 if (eid instanceof DeviceId) {
424 nh = new DeviceHighlight(id);
425 highlights.add((DeviceHighlight) nh);
426 } else if (eid instanceof HostId) {
427 nh = new HostHighlight(id);
428 highlights.add((HostHighlight) nh);
429 }
430 }
431 }
432
433 private void processLinks(TrafficLinkMap linkMap, Iterable<Link> links,
434 Flavor flavor, boolean isOptical,
Andrea Campanellaaf934682017-03-05 11:06:40 +0100435 boolean showTraffic, Set<Mod> mods) {
Andrea Campanella732ea832017-02-06 09:25:59 -0800436 if (links != null) {
437 for (Link link : links) {
438 TrafficLink tlink = linkMap.add(link);
439 tlink.tagFlavor(flavor);
440 tlink.optical(isOptical);
441 if (showTraffic) {
442 tlink.antMarch(true);
443 }
Andrea Campanellaaf934682017-03-05 11:06:40 +0100444 tlink.tagMods(mods);
Andrea Campanella732ea832017-02-06 09:25:59 -0800445 }
446 }
447 }
448
449 //TODO duplicate and can be brought in abstract upper class.
450 private void colorLinks(Highlights highlights, TrafficLinkMap linkMap) {
451 for (TrafficLink tlink : linkMap.biLinks()) {
452 highlights.add(tlink.highlight(StatsType.TAGGED));
453 }
454 }
455
456 //TODO duplicate and can be brought in abstract upper class.
457 // Extracts links from the specified flow rule intent resources
458 private Collection<Link> linkResources(Intent installable) {
459 ImmutableList.Builder<Link> builder = ImmutableList.builder();
460 installable.resources().stream().filter(r -> r instanceof Link)
461 .forEach(r -> builder.add((Link) r));
462 return builder.build();
463 }
464
465 // =======================================================================
466 // === Background Task
467
468 // Provides periodic update of traffic information to the client
469 private class TrafficUpdateTask extends TimerTask {
470 @Override
471 public void run() {
472 try {
473 switch (mode) {
474 case SELECTED_INTENT:
475 sendSelectedIntents();
476 break;
477
478 default:
479 // RELATED_INTENTS and IDLE modes should never invoke
480 // the background task, but if they do, they have
481 // nothing to do
482 break;
483 }
484
485 } catch (Exception e) {
486 log.warn("Unable to process protected intent task due to {}", e.getMessage());
487 log.warn("Boom!", e);
488 }
489 }
490 }
491}