blob: 28b761b2b15fca54f6ff87d2b19d3c2d0275ad48 [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);
83 private static final Set<Mod> PROTECTED_MOD_PRIMARY_SET = ImmutableSet.of(MOD_PROT_PRIMARY);
84
85 private static final Mod MOD_PROT_BACKUP = new Mod(PROT_BACKUP);
86 private static final Set<Mod> PROTECTED_MOD_BACKUP_SET = ImmutableSet.of(MOD_PROT_BACKUP);
87
Andrea Campanella732ea832017-02-06 09:25:59 -080088
89 /**
90 * Designates the different modes of operation.
91 */
92 public enum ProtectedMode {
93 IDLE,
94 SELECTED_INTENT
95 }
96
97 private final long trafficPeriod;
98 private final ServicesBundle servicesBundle;
99 private final TopologyViewMessageHandler msgHandler;
100
101 private final Timer timer = new Timer("topo-protected-intents");
102
103 private TimerTask trafficTask = null;
104 private ProtectedMode mode = ProtectedMode.IDLE;
105 private Intent selectedIntent = null;
106
107
108 /**
109 * Constructs a protected intent monitor.
110 *
111 * @param trafficPeriod traffic task period in ms
112 * @param servicesBundle bundle of services
113 * @param msgHandler our message handler
114 */
115 public ProtectedIntentMonitor(long trafficPeriod, ServicesBundle servicesBundle,
116 TopologyViewMessageHandler msgHandler) {
117 this.trafficPeriod = trafficPeriod;
118 this.servicesBundle = servicesBundle;
119 this.msgHandler = msgHandler;
120
121 }
122
123 // =======================================================================
124 // === API ===
125
126 // TODO: move this out to the "h2h/multi-intent app"
127
128 /**
129 * Monitor for protected intent data to be sent back to the web client,
130 * for the given intent.
131 *
132 * @param intent the intent to monitor
133 */
134 public synchronized void monitor(Intent intent) {
135 log.debug("monitor intent: {}", intent.id());
136 selectedIntent = intent;
137 mode = SELECTED_INTENT;
138 scheduleTask();
139 sendSelectedIntents();
140 }
141
142 /**
143 * Stop all traffic monitoring.
144 */
145 public synchronized void stopMonitoring() {
146 log.debug("STOP monitoring");
147 if (mode != IDLE) {
148 sendClearAll();
149 }
150 }
151
152
153 // =======================================================================
154 // === Helper methods ===
155 private void sendClearAll() {
156 clearAll();
157 sendClearHighlights();
158 }
159
160 private void clearAll() {
161 this.mode = IDLE;
162 clearSelection();
163 cancelTask();
164 }
165
166 private void clearSelection() {
167 selectedIntent = null;
168 }
169
170 //TODO duplicate and can be brought in abstract upper class.
171 private synchronized void scheduleTask() {
172 if (trafficTask == null) {
173 log.debug("Starting up background protected intent task...");
174 trafficTask = new TrafficUpdateTask();
175 timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
176 } else {
177 log.debug("(protected intent task already running)");
178 }
179 }
180
181 private synchronized void cancelTask() {
182 if (trafficTask != null) {
183 trafficTask.cancel();
184 trafficTask = null;
185 }
186 }
187
188 private void sendSelectedIntents() {
189 log.debug("sendSelectedIntents: {}", selectedIntent);
190 msgHandler.sendHighlights(protectedIntentHighlights());
191 }
192
193 private void sendClearHighlights() {
194 log.debug("sendClearHighlights");
195 msgHandler.sendHighlights(new Highlights());
196 }
197
198 // =======================================================================
199 // === Generate messages in JSON object node format
200 private Highlights protectedIntentHighlights() {
201 Highlights highlights = new Highlights();
202 TrafficLinkMap linkMap = new TrafficLinkMap();
Marc De Leenheer20913c62017-04-12 14:45:15 -0700203 IntentService intentService = servicesBundle.intentService();
Andrea Campanella732ea832017-02-06 09:25:59 -0800204 if (selectedIntent != null) {
Marc De Leenheer20913c62017-04-12 14:45:15 -0700205 List<Intent> installables = intentService.getInstallableIntents(selectedIntent.key());
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700206
Andrea Campanella732ea832017-02-06 09:25:59 -0800207 if (installables != null) {
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700208 ProtectionEndpointIntent ep1 = installables.stream()
209 .filter(ProtectionEndpointIntent.class::isInstance)
210 .map(ProtectionEndpointIntent.class::cast)
211 .findFirst().orElse(null);
212 ProtectionEndpointIntent ep2 = installables.stream()
213 .filter(ii -> !ii.equals(ep1))
214 .filter(ProtectionEndpointIntent.class::isInstance)
215 .map(ProtectionEndpointIntent.class::cast)
216 .findFirst().orElse(null);
217 if (ep1 == null || ep2 == null) {
218 log.warn("Selected Intent {} didn't have 2 protection endpoints",
219 selectedIntent.key());
220 stopMonitoring();
221 return highlights;
Andrea Campanella732ea832017-02-06 09:25:59 -0800222 }
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700223 Set<Link> primary = new LinkedHashSet<>();
224 Set<Link> backup = new LinkedHashSet<>();
225
226 Map<Boolean, List<FlowRuleIntent>> transits = installables.stream()
227 .filter(FlowRuleIntent.class::isInstance)
228 .map(FlowRuleIntent.class::cast)
229 // only consider fwd links so that ants march in one direction
Marc De Leenheer20913c62017-04-12 14:45:15 -0700230 // TODO: didn't help need further investigation.
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700231 //.filter(i -> !i.resources().contains(marker("rev")))
232 .collect(Collectors.groupingBy(this::isPrimary));
233
234 // walk primary
235 ConnectPoint primHead = ep1.description().paths().get(0).output().connectPoint();
236 ConnectPoint primTail = ep2.description().paths().get(0).output().connectPoint();
237 List<FlowRuleIntent> primTransit = transits.getOrDefault(true, ImmutableList.of());
238 populateLinks(primary, primHead, primTail, primTransit);
239
240 // walk backup
241 ConnectPoint backHead = ep1.description().paths().get(1).output().connectPoint();
242 ConnectPoint backTail = ep2.description().paths().get(1).output().connectPoint();
243 List<FlowRuleIntent> backTransit = transits.getOrDefault(false, ImmutableList.of());
244 populateLinks(backup, backHead, backTail, backTransit);
245
Marc De Leenheer20913c62017-04-12 14:45:15 -0700246 // Add packet to optical links
247 if (!usingBackup(primary)) {
248 primary.addAll(protectedIntentMultiLayer(primHead, primTail));
249 }
250 backup.addAll(protectedIntentMultiLayer(backHead, backTail));
251
Andrea Campanella732ea832017-02-06 09:25:59 -0800252 boolean isOptical = selectedIntent instanceof OpticalConnectivityIntent;
Marc De Leenheer20913c62017-04-12 14:45:15 -0700253 //last parameter (traffic) signals if the link is highlighted with ants or solid line
Andrea Campanella732ea832017-02-06 09:25:59 -0800254 //Flavor is swapped so green is primary path.
255 if (usingBackup(primary)) {
256 //the backup becomes in use so we have a dotted line
Andrea Campanellaaf934682017-03-05 11:06:40 +0100257 processLinks(linkMap, backup, Flavor.PRIMARY_HIGHLIGHT,
258 isOptical, true, PROTECTED_MOD_BACKUP_SET);
Andrea Campanella732ea832017-02-06 09:25:59 -0800259 } else {
Andrea Campanellaaf934682017-03-05 11:06:40 +0100260 processLinks(linkMap, primary, Flavor.PRIMARY_HIGHLIGHT,
261 isOptical, true, PROTECTED_MOD_PRIMARY_SET);
262 processLinks(linkMap, backup, Flavor.SECONDARY_HIGHLIGHT,
263 isOptical, false, PROTECTED_MOD_BACKUP_SET);
Andrea Campanella732ea832017-02-06 09:25:59 -0800264 }
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700265
Andrea Campanella732ea832017-02-06 09:25:59 -0800266 updateHighlights(highlights, primary);
267 updateHighlights(highlights, backup);
268 colorLinks(highlights, linkMap);
269 highlights.subdueAllElse(Highlights.Amount.MINIMALLY);
270 } else {
Marc De Leenheer20913c62017-04-12 14:45:15 -0700271 log.debug("Selected Intent has no installable intents");
Andrea Campanella732ea832017-02-06 09:25:59 -0800272 }
273 } else {
274 log.debug("Selected Intent is null");
275 }
276 return highlights;
277 }
278
Marc De Leenheer20913c62017-04-12 14:45:15 -0700279 /**
280 * Returns the packet to optical mapping given a head and tail of a protection path.
281 *
282 * @param head head of path
283 * @param tail tail of path
284 */
285 private Set<Link> protectedIntentMultiLayer(ConnectPoint head, ConnectPoint tail) {
286 List<Link> links = new LinkedList<>();
287 LinkService linkService = servicesBundle.linkService();
288 IntentService intentService = servicesBundle.intentService();
289
290 // Ingress cross connect link
291 links.addAll(
292 linkService.getEgressLinks(head).stream()
293 .filter(l -> l.type() == Link.Type.OPTICAL)
294 .collect(Collectors.toList())
295 );
296
297 // Egress cross connect link
298 links.addAll(
299 linkService.getIngressLinks(tail).stream()
300 .filter(l -> l.type() == Link.Type.OPTICAL)
301 .collect(Collectors.toList())
302 );
303
304 // The protected intent does not rely on a multi-layer mapping
305 if (links.size() != 2) {
306 return Collections.emptySet();
307 }
308
309 // Expected head and tail of optical circuit (not connectivity!) intent
310 ConnectPoint ocHead = links.get(0).dst();
311 ConnectPoint ocTail = links.get(1).src();
312
313 // Optical connectivity
314 // FIXME: assumes that transponder (OTN device) is a one-to-one mapping
315 // We need to track the multi-layer aspects better
316 intentService.getIntents().forEach(intent -> {
317 if (intent instanceof OpticalConnectivityIntent) {
318 OpticalConnectivityIntent ocIntent = (OpticalConnectivityIntent) intent;
319 if (ocHead.deviceId().equals(ocIntent.getSrc().deviceId()) &&
320 ocTail.deviceId().equals(ocIntent.getDst().deviceId())) {
321 intentService.getInstallableIntents(ocIntent.key()).forEach(i -> {
322 if (i instanceof FlowRuleIntent) {
323 FlowRuleIntent fr = (FlowRuleIntent) i;
324 links.addAll(linkResources(fr));
325 }
326 });
327 }
328 }
329 });
330
331 return new LinkedHashSet<>(links);
332 }
333
Andrea Campanella732ea832017-02-06 09:25:59 -0800334 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
335
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700336 /**
337 * Populate Links along the primary/backup path.
338 *
339 * @param links link collection to populate [output]
340 * @param head head-end of primary/backup path
341 * @param tail tail-end of primary/backup path
342 * @param transit Intents if any
343 */
344 private void populateLinks(Set<Link> links,
345 ConnectPoint head,
346 ConnectPoint tail,
347 List<FlowRuleIntent> transit) {
348 // find first hop link
349 Link first = transit.stream()
350 // search for Link head -> transit Intent head
351 // as first candidate of 1st hop Link
352 .flatMap(fri -> fri.flowRules().stream())
353 .map(fr ->
354 // find first input port from FlowRule
355 Optional.ofNullable(fr.selector().getCriterion(Criterion.Type.IN_PORT))
356 .filter(PortCriterion.class::isInstance)
357 .map(PortCriterion.class::cast)
358 .map(PortCriterion::port)
359 .map(pn -> new ConnectPoint(fr.deviceId(), pn))
360 .orElse(null)
361 ).filter(Objects::nonNull)
362 .map(dst -> servicesBundle.linkService().getLink(head, dst))
363 .filter(Objects::nonNull)
364 .findFirst()
365 // if there isn't one probably 1 hop to the tail
366 .orElse(servicesBundle.linkService().getLink(head, tail));
367
368 // add first link
369 if (first != null) {
370 links.add(first);
371 }
372
373 // add links in the middle if any
374 transit.forEach(fri -> links.addAll(linkResources(fri)));
375
376 // add last hop if any
377 Lists.reverse(transit).stream()
378 // search for Link transit Intent tail -> tail
379 // as candidate of last hop Link
380 .flatMap(fri -> ImmutableList.copyOf(fri.flowRules()).reverse().stream())
381 .map(fr ->
382 // find first output port from FlowRule
383 fr.treatment().allInstructions().stream()
384 .filter(OutputInstruction.class::isInstance).findFirst()
385 .map(OutputInstruction.class::cast)
386 .map(OutputInstruction::port)
387 .map(pn -> new ConnectPoint(fr.deviceId(), pn))
388 .orElse(null)
389 ).filter(Objects::nonNull)
390 .map(src -> servicesBundle.linkService().getLink(src, tail))
391 .filter(Objects::nonNull)
392 .findFirst()
393 .ifPresent(links::add);
Andrea Campanella732ea832017-02-06 09:25:59 -0800394 }
395
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700396 /**
397 * Returns true if specified intent is marked with primary marker resource.
398 *
399 * @param intent to test
400 * @return true if it is an Intent taking part of primary transit path
401 */
402 private boolean isPrimary(Intent intent) {
403 return intent.resources()
404 .contains(marker(PRIMARY_PATH_TAG));
Andrea Campanella732ea832017-02-06 09:25:59 -0800405 }
406
407 // returns true if the backup path is the one where the traffic is currently flowing
408 private boolean usingBackup(Set<Link> primary) {
409 Set<Link> activeLinks = Sets.newHashSet(servicesBundle.linkService().getActiveLinks());
410 return primary.isEmpty() || !activeLinks.containsAll(primary);
411 }
412
413 private void updateHighlights(Highlights highlights, Iterable<Link> links) {
414 for (Link link : links) {
415 ensureNodePresent(highlights, link.src().elementId());
416 ensureNodePresent(highlights, link.dst().elementId());
417 }
418 }
419
420 //TODO duplicate and can be brought in abstract upper class.
421 private void ensureNodePresent(Highlights highlights, ElementId eid) {
422 String id = eid.toString();
423 NodeHighlight nh = highlights.getNode(id);
424 if (nh == null) {
425 if (eid instanceof DeviceId) {
426 nh = new DeviceHighlight(id);
427 highlights.add((DeviceHighlight) nh);
428 } else if (eid instanceof HostId) {
429 nh = new HostHighlight(id);
430 highlights.add((HostHighlight) nh);
431 }
432 }
433 }
434
435 private void processLinks(TrafficLinkMap linkMap, Iterable<Link> links,
436 Flavor flavor, boolean isOptical,
Andrea Campanellaaf934682017-03-05 11:06:40 +0100437 boolean showTraffic, Set<Mod> mods) {
Andrea Campanella732ea832017-02-06 09:25:59 -0800438 if (links != null) {
439 for (Link link : links) {
440 TrafficLink tlink = linkMap.add(link);
441 tlink.tagFlavor(flavor);
442 tlink.optical(isOptical);
443 if (showTraffic) {
444 tlink.antMarch(true);
445 }
Andrea Campanellaaf934682017-03-05 11:06:40 +0100446 tlink.tagMods(mods);
Andrea Campanella732ea832017-02-06 09:25:59 -0800447 }
448 }
449 }
450
451 //TODO duplicate and can be brought in abstract upper class.
452 private void colorLinks(Highlights highlights, TrafficLinkMap linkMap) {
453 for (TrafficLink tlink : linkMap.biLinks()) {
454 highlights.add(tlink.highlight(StatsType.TAGGED));
455 }
456 }
457
458 //TODO duplicate and can be brought in abstract upper class.
459 // Extracts links from the specified flow rule intent resources
460 private Collection<Link> linkResources(Intent installable) {
461 ImmutableList.Builder<Link> builder = ImmutableList.builder();
462 installable.resources().stream().filter(r -> r instanceof Link)
463 .forEach(r -> builder.add((Link) r));
464 return builder.build();
465 }
466
467 // =======================================================================
468 // === Background Task
469
470 // Provides periodic update of traffic information to the client
471 private class TrafficUpdateTask extends TimerTask {
472 @Override
473 public void run() {
474 try {
475 switch (mode) {
476 case SELECTED_INTENT:
477 sendSelectedIntents();
478 break;
479
480 default:
481 // RELATED_INTENTS and IDLE modes should never invoke
482 // the background task, but if they do, they have
483 // nothing to do
484 break;
485 }
486
487 } catch (Exception e) {
488 log.warn("Unable to process protected intent task due to {}", e.getMessage());
489 log.warn("Boom!", e);
490 }
491 }
492 }
493}