blob: 81561c6d087096b3255487cb1ec71d0ada4851ce [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 -070023
24
25import org.onosproject.net.ConnectPoint;
Andrea Campanella732ea832017-02-06 09:25:59 -080026import org.onosproject.net.DeviceId;
27import org.onosproject.net.ElementId;
28import org.onosproject.net.HostId;
29import org.onosproject.net.Link;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070030import org.onosproject.net.flow.criteria.Criterion;
31import org.onosproject.net.flow.criteria.PortCriterion;
32import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
Andrea Campanella732ea832017-02-06 09:25:59 -080033import org.onosproject.net.intent.FlowRuleIntent;
34import org.onosproject.net.intent.Intent;
35import org.onosproject.net.intent.OpticalConnectivityIntent;
36import org.onosproject.net.intent.ProtectionEndpointIntent;
37import 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;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070052import java.util.LinkedHashSet;
Andrea Campanella732ea832017-02-06 09:25:59 -080053import java.util.List;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070054import java.util.Map;
55import java.util.Objects;
56import java.util.Optional;
Andrea Campanella732ea832017-02-06 09:25:59 -080057import java.util.Set;
58import java.util.Timer;
59import java.util.TimerTask;
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070060import java.util.stream.Collectors;
Andrea Campanella732ea832017-02-06 09:25:59 -080061
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -070062import static org.onosproject.net.MarkerResource.marker;
Andrea Campanella732ea832017-02-06 09:25:59 -080063import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.IDLE;
64import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.SELECTED_INTENT;
65
66/**
67 * Encapsulates the behavior of monitoring protected intents.
68 */
69//TODO refactor duplicated methods from here and the TrafficMonitor to AbstractTopoMonitor
70public class ProtectedIntentMonitor extends AbstractTopoMonitor {
71
72 private static final Logger log =
73 LoggerFactory.getLogger(ProtectedIntentMonitor.class);
Andrea Campanellaaf934682017-03-05 11:06:40 +010074 private static final String PRIMARY_PATH_TAG = "protection1";
75
76 private static final String PROT_PRIMARY = "protPrimary";
77 private static final String PROT_BACKUP = "protBackup";
78
79
80 private static final Mod MOD_PROT_PRIMARY = new Mod(PROT_PRIMARY);
81 private static final Set<Mod> PROTECTED_MOD_PRIMARY_SET = ImmutableSet.of(MOD_PROT_PRIMARY);
82
83 private static final Mod MOD_PROT_BACKUP = new Mod(PROT_BACKUP);
84 private static final Set<Mod> PROTECTED_MOD_BACKUP_SET = ImmutableSet.of(MOD_PROT_BACKUP);
85
Andrea Campanella732ea832017-02-06 09:25:59 -080086
87 /**
88 * Designates the different modes of operation.
89 */
90 public enum ProtectedMode {
91 IDLE,
92 SELECTED_INTENT
93 }
94
95 private final long trafficPeriod;
96 private final ServicesBundle servicesBundle;
97 private final TopologyViewMessageHandler msgHandler;
98
99 private final Timer timer = new Timer("topo-protected-intents");
100
101 private TimerTask trafficTask = null;
102 private ProtectedMode mode = ProtectedMode.IDLE;
103 private Intent selectedIntent = null;
104
105
106 /**
107 * Constructs a protected intent monitor.
108 *
109 * @param trafficPeriod traffic task period in ms
110 * @param servicesBundle bundle of services
111 * @param msgHandler our message handler
112 */
113 public ProtectedIntentMonitor(long trafficPeriod, ServicesBundle servicesBundle,
114 TopologyViewMessageHandler msgHandler) {
115 this.trafficPeriod = trafficPeriod;
116 this.servicesBundle = servicesBundle;
117 this.msgHandler = msgHandler;
118
119 }
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();
201 if (selectedIntent != null) {
202 List<Intent> installables = servicesBundle.intentService()
203 .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()
225 .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));
231
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
Andrea Campanella732ea832017-02-06 09:25:59 -0800244 boolean isOptical = selectedIntent instanceof OpticalConnectivityIntent;
245 //last parameter (traffic) signals if the link is highlited with ants or solid line
246 //Flavor is swapped so green is primary path.
247 if (usingBackup(primary)) {
248 //the backup becomes in use so we have a dotted line
Andrea Campanellaaf934682017-03-05 11:06:40 +0100249 processLinks(linkMap, backup, Flavor.PRIMARY_HIGHLIGHT,
250 isOptical, true, PROTECTED_MOD_BACKUP_SET);
Andrea Campanella732ea832017-02-06 09:25:59 -0800251 } else {
Andrea Campanellaaf934682017-03-05 11:06:40 +0100252 processLinks(linkMap, primary, Flavor.PRIMARY_HIGHLIGHT,
253 isOptical, true, PROTECTED_MOD_PRIMARY_SET);
254 processLinks(linkMap, backup, Flavor.SECONDARY_HIGHLIGHT,
255 isOptical, false, PROTECTED_MOD_BACKUP_SET);
Andrea Campanella732ea832017-02-06 09:25:59 -0800256 }
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700257
Andrea Campanella732ea832017-02-06 09:25:59 -0800258 updateHighlights(highlights, primary);
259 updateHighlights(highlights, backup);
260 colorLinks(highlights, linkMap);
261 highlights.subdueAllElse(Highlights.Amount.MINIMALLY);
262 } else {
263 log.debug("Selected Intent has no installables intents");
264 }
265 } else {
266 log.debug("Selected Intent is null");
267 }
268 return highlights;
269 }
270
271 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
272
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700273 /**
274 * Populate Links along the primary/backup path.
275 *
276 * @param links link collection to populate [output]
277 * @param head head-end of primary/backup path
278 * @param tail tail-end of primary/backup path
279 * @param transit Intents if any
280 */
281 private void populateLinks(Set<Link> links,
282 ConnectPoint head,
283 ConnectPoint tail,
284 List<FlowRuleIntent> transit) {
285 // find first hop link
286 Link first = transit.stream()
287 // search for Link head -> transit Intent head
288 // as first candidate of 1st hop Link
289 .flatMap(fri -> fri.flowRules().stream())
290 .map(fr ->
291 // find first input port from FlowRule
292 Optional.ofNullable(fr.selector().getCriterion(Criterion.Type.IN_PORT))
293 .filter(PortCriterion.class::isInstance)
294 .map(PortCriterion.class::cast)
295 .map(PortCriterion::port)
296 .map(pn -> new ConnectPoint(fr.deviceId(), pn))
297 .orElse(null)
298 ).filter(Objects::nonNull)
299 .map(dst -> servicesBundle.linkService().getLink(head, dst))
300 .filter(Objects::nonNull)
301 .findFirst()
302 // if there isn't one probably 1 hop to the tail
303 .orElse(servicesBundle.linkService().getLink(head, tail));
304
305 // add first link
306 if (first != null) {
307 links.add(first);
308 }
309
310 // add links in the middle if any
311 transit.forEach(fri -> links.addAll(linkResources(fri)));
312
313 // add last hop if any
314 Lists.reverse(transit).stream()
315 // search for Link transit Intent tail -> tail
316 // as candidate of last hop Link
317 .flatMap(fri -> ImmutableList.copyOf(fri.flowRules()).reverse().stream())
318 .map(fr ->
319 // find first output port from FlowRule
320 fr.treatment().allInstructions().stream()
321 .filter(OutputInstruction.class::isInstance).findFirst()
322 .map(OutputInstruction.class::cast)
323 .map(OutputInstruction::port)
324 .map(pn -> new ConnectPoint(fr.deviceId(), pn))
325 .orElse(null)
326 ).filter(Objects::nonNull)
327 .map(src -> servicesBundle.linkService().getLink(src, tail))
328 .filter(Objects::nonNull)
329 .findFirst()
330 .ifPresent(links::add);
Andrea Campanella732ea832017-02-06 09:25:59 -0800331 }
332
Yuta HIGUCHIe4fdebb2017-03-17 19:41:14 -0700333 /**
334 * Returns true if specified intent is marked with primary marker resource.
335 *
336 * @param intent to test
337 * @return true if it is an Intent taking part of primary transit path
338 */
339 private boolean isPrimary(Intent intent) {
340 return intent.resources()
341 .contains(marker(PRIMARY_PATH_TAG));
Andrea Campanella732ea832017-02-06 09:25:59 -0800342 }
343
344 // returns true if the backup path is the one where the traffic is currently flowing
345 private boolean usingBackup(Set<Link> primary) {
346 Set<Link> activeLinks = Sets.newHashSet(servicesBundle.linkService().getActiveLinks());
347 return primary.isEmpty() || !activeLinks.containsAll(primary);
348 }
349
350 private void updateHighlights(Highlights highlights, Iterable<Link> links) {
351 for (Link link : links) {
352 ensureNodePresent(highlights, link.src().elementId());
353 ensureNodePresent(highlights, link.dst().elementId());
354 }
355 }
356
357 //TODO duplicate and can be brought in abstract upper class.
358 private void ensureNodePresent(Highlights highlights, ElementId eid) {
359 String id = eid.toString();
360 NodeHighlight nh = highlights.getNode(id);
361 if (nh == null) {
362 if (eid instanceof DeviceId) {
363 nh = new DeviceHighlight(id);
364 highlights.add((DeviceHighlight) nh);
365 } else if (eid instanceof HostId) {
366 nh = new HostHighlight(id);
367 highlights.add((HostHighlight) nh);
368 }
369 }
370 }
371
372 private void processLinks(TrafficLinkMap linkMap, Iterable<Link> links,
373 Flavor flavor, boolean isOptical,
Andrea Campanellaaf934682017-03-05 11:06:40 +0100374 boolean showTraffic, Set<Mod> mods) {
Andrea Campanella732ea832017-02-06 09:25:59 -0800375 if (links != null) {
376 for (Link link : links) {
377 TrafficLink tlink = linkMap.add(link);
378 tlink.tagFlavor(flavor);
379 tlink.optical(isOptical);
380 if (showTraffic) {
381 tlink.antMarch(true);
382 }
Andrea Campanellaaf934682017-03-05 11:06:40 +0100383 tlink.tagMods(mods);
Andrea Campanella732ea832017-02-06 09:25:59 -0800384 }
385 }
386 }
387
388 //TODO duplicate and can be brought in abstract upper class.
389 private void colorLinks(Highlights highlights, TrafficLinkMap linkMap) {
390 for (TrafficLink tlink : linkMap.biLinks()) {
391 highlights.add(tlink.highlight(StatsType.TAGGED));
392 }
393 }
394
395 //TODO duplicate and can be brought in abstract upper class.
396 // Extracts links from the specified flow rule intent resources
397 private Collection<Link> linkResources(Intent installable) {
398 ImmutableList.Builder<Link> builder = ImmutableList.builder();
399 installable.resources().stream().filter(r -> r instanceof Link)
400 .forEach(r -> builder.add((Link) r));
401 return builder.build();
402 }
403
404 // =======================================================================
405 // === Background Task
406
407 // Provides periodic update of traffic information to the client
408 private class TrafficUpdateTask extends TimerTask {
409 @Override
410 public void run() {
411 try {
412 switch (mode) {
413 case SELECTED_INTENT:
414 sendSelectedIntents();
415 break;
416
417 default:
418 // RELATED_INTENTS and IDLE modes should never invoke
419 // the background task, but if they do, they have
420 // nothing to do
421 break;
422 }
423
424 } catch (Exception e) {
425 log.warn("Unable to process protected intent task due to {}", e.getMessage());
426 log.warn("Boom!", e);
427 }
428 }
429 }
430}