blob: 0079fe584d2140e76d7fed976fb25eb2b6352cd0 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07003 *
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 */
Sho SHIMIZU6c28f832015-02-20 16:12:19 -080016package org.onosproject.net.intent.impl.compiler;
Ray Milkeya058c732014-10-08 13:52:34 -070017
Pier Ventreffe88d62016-10-13 14:34:40 -070018import com.google.common.collect.ImmutableSet;
Daniele Moro3a6e1512017-12-22 12:14:44 +010019import org.apache.commons.lang3.tuple.Pair;
Ray Milkeya7cf8c82018-02-08 15:07:06 -080020import org.onlab.graph.ScalarWeight;
Brian O'Connorabafb502014-12-02 22:26:20 -080021import org.onosproject.net.ConnectPoint;
22import org.onosproject.net.DefaultPath;
helenyrwu2a674902016-07-20 09:48:04 -070023import org.onosproject.net.DeviceId;
24import org.onosproject.net.DisjointPath;
25import org.onosproject.net.EdgeLink;
Brian O'Connorabafb502014-12-02 22:26:20 -080026import org.onosproject.net.Link;
27import org.onosproject.net.Path;
helenyrwu2a674902016-07-20 09:48:04 -070028import org.onosproject.net.Port;
29import org.onosproject.net.PortNumber;
helenyrwu2a674902016-07-20 09:48:04 -070030import org.onosproject.net.flow.DefaultFlowRule;
31import org.onosproject.net.flow.DefaultTrafficSelector;
32import org.onosproject.net.flow.DefaultTrafficTreatment;
33import org.onosproject.net.flow.FlowRule;
34import org.onosproject.net.flow.TrafficSelector;
35import org.onosproject.net.flow.TrafficTreatment;
36import org.onosproject.net.flow.instructions.Instruction;
37import org.onosproject.net.flow.instructions.Instructions;
38import org.onosproject.net.group.DefaultGroupBucket;
39import org.onosproject.net.group.DefaultGroupDescription;
40import org.onosproject.net.group.DefaultGroupKey;
41import org.onosproject.net.group.Group;
42import org.onosproject.net.group.GroupBucket;
43import org.onosproject.net.group.GroupBuckets;
44import org.onosproject.net.group.GroupDescription;
45import org.onosproject.net.group.GroupKey;
Yuta HIGUCHI625fb642016-09-01 16:10:24 -070046import org.onosproject.net.group.GroupListener;
helenyrwu2a674902016-07-20 09:48:04 -070047import org.onosproject.net.group.GroupService;
48import org.onosproject.net.intent.FlowRuleIntent;
Brian O'Connorabafb502014-12-02 22:26:20 -080049import org.onosproject.net.intent.Intent;
Yuta HIGUCHI625fb642016-09-01 16:10:24 -070050import org.onosproject.net.intent.IntentCompilationException;
helenyrwu2a674902016-07-20 09:48:04 -070051import org.onosproject.net.intent.IntentId;
Pier Ventreffe88d62016-10-13 14:34:40 -070052import org.onosproject.net.intent.LinkCollectionIntent;
Brian O'Connorabafb502014-12-02 22:26:20 -080053import org.onosproject.net.intent.PathIntent;
54import org.onosproject.net.intent.PointToPointIntent;
helenyrwu2a674902016-07-20 09:48:04 -070055import org.onosproject.net.intent.constraint.ProtectionConstraint;
56import org.onosproject.net.intent.impl.PathNotFoundException;
57import org.onosproject.net.link.LinkService;
Brian O'Connorabafb502014-12-02 22:26:20 -080058import org.onosproject.net.provider.ProviderId;
Ray Milkey86ad7bb2018-09-27 12:32:28 -070059import org.osgi.service.component.annotations.Activate;
60import org.osgi.service.component.annotations.Component;
61import org.osgi.service.component.annotations.Deactivate;
62import org.osgi.service.component.annotations.Reference;
63import org.osgi.service.component.annotations.ReferenceCardinality;
Yuta HIGUCHI625fb642016-09-01 16:10:24 -070064import org.slf4j.Logger;
Thomas Vachuskaedc944c2014-11-04 15:42:25 -080065
helenyrwu2a674902016-07-20 09:48:04 -070066import java.nio.ByteBuffer;
Thomas Vachuskaedc944c2014-11-04 15:42:25 -080067import java.util.ArrayList;
helenyrwu2a674902016-07-20 09:48:04 -070068import java.util.Collections;
69import java.util.Iterator;
Thomas Vachuskaedc944c2014-11-04 15:42:25 -080070import java.util.List;
helenyrwu2a674902016-07-20 09:48:04 -070071import java.util.ListIterator;
Pier Ventreffe88d62016-10-13 14:34:40 -070072import java.util.Set;
Yuta HIGUCHI625fb642016-09-01 16:10:24 -070073import java.util.concurrent.CompletableFuture;
74import java.util.concurrent.ExecutionException;
75import java.util.concurrent.TimeUnit;
76import java.util.concurrent.TimeoutException;
Luca Pretede10c782017-01-05 17:23:08 -080077import java.util.stream.Collectors;
Daniele Moro3a6e1512017-12-22 12:14:44 +010078import java.util.stream.IntStream;
Luca Pretede10c782017-01-05 17:23:08 -080079import java.util.stream.Stream;
Ray Milkeya058c732014-10-08 13:52:34 -070080
Thomas Vachuska425a2d72014-10-29 11:28:28 -070081import static java.util.Arrays.asList;
Brian O'Connorabafb502014-12-02 22:26:20 -080082import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
Yuta HIGUCHI625fb642016-09-01 16:10:24 -070083import static org.slf4j.LoggerFactory.getLogger;
Thomas Vachuska425a2d72014-10-29 11:28:28 -070084
Ray Milkeya058c732014-10-08 13:52:34 -070085/**
Brian O'Connorabafb502014-12-02 22:26:20 -080086 * An intent compiler for {@link org.onosproject.net.intent.PointToPointIntent}.
Ray Milkeya058c732014-10-08 13:52:34 -070087 */
88@Component(immediate = true)
89public class PointToPointIntentCompiler
Thomas Vachuskaedc944c2014-11-04 15:42:25 -080090 extends ConnectivityIntentCompiler<PointToPointIntent> {
Ray Milkeya058c732014-10-08 13:52:34 -070091
Thomas Vachuskaedc944c2014-11-04 15:42:25 -080092 // TODO: use off-the-shell core provider ID
93 private static final ProviderId PID =
Brian O'Connorabafb502014-12-02 22:26:20 -080094 new ProviderId("core", "org.onosproject.core", true);
Sho SHIMIZU3908fde2014-11-19 16:30:22 -080095 // TODO: consider whether the default cost is appropriate or not
96 public static final int DEFAULT_COST = 1;
Yuta HIGUCHI625fb642016-09-01 16:10:24 -070097
helenyrwu2a674902016-07-20 09:48:04 -070098 protected static final int PRIORITY = Intent.DEFAULT_INTENT_PRIORITY;
Yuta HIGUCHI625fb642016-09-01 16:10:24 -070099
100 private static final int GROUP_TIMEOUT = 5;
101
102 private final Logger log = getLogger(getClass());
103
helenyrwu2a674902016-07-20 09:48:04 -0700104 protected boolean erasePrimary = false;
105 protected boolean eraseBackup = false;
106
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700107 @Reference(cardinality = ReferenceCardinality.MANDATORY)
helenyrwu2a674902016-07-20 09:48:04 -0700108 protected GroupService groupService;
109
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700110 @Reference(cardinality = ReferenceCardinality.MANDATORY)
helenyrwu2a674902016-07-20 09:48:04 -0700111 protected LinkService linkService;
112
Ray Milkeya058c732014-10-08 13:52:34 -0700113 @Activate
114 public void activate() {
Ray Milkeya058c732014-10-08 13:52:34 -0700115 intentManager.registerCompiler(PointToPointIntent.class, this);
116 }
117
118 @Deactivate
119 public void deactivate() {
120 intentManager.unregisterCompiler(PointToPointIntent.class);
121 }
122
123 @Override
Sho SHIMIZUec07ffd2016-02-22 20:45:21 -0800124 public List<Intent> compile(PointToPointIntent intent, List<Intent> installable) {
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700125 log.trace("compiling {} {}", intent, installable);
Luca Pretede10c782017-01-05 17:23:08 -0800126 ConnectPoint ingressPoint = intent.filteredIngressPoint().connectPoint();
127 ConnectPoint egressPoint = intent.filteredEgressPoint().connectPoint();
Sho SHIMIZUde8e6b52014-11-13 11:23:17 -0800128
Daniele Moro3a6e1512017-12-22 12:14:44 +0100129 //TODO: handle protected path case with suggested path!!
130 //Idea: use suggested path as primary and another path from path service as protection
131 if (intent.suggestedPath() != null && intent.suggestedPath().size() > 0) {
132 Path path = new DefaultPath(PID, intent.suggestedPath(), new ScalarWeight(1));
133 //Check intent constraints against suggested path and suggested path availability
134 if (checkPath(path, intent.constraints()) && pathAvailable(intent)) {
135 allocateIntentBandwidth(intent, path);
136 return asList(createLinkCollectionIntent(ImmutableSet.copyOf(intent.suggestedPath()),
137 DEFAULT_COST, intent));
138 }
139 }
140
Sho SHIMIZUde8e6b52014-11-13 11:23:17 -0800141 if (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
Pier Ventreffe88d62016-10-13 14:34:40 -0700142 return createZeroHopLinkCollectionIntent(intent);
Sho SHIMIZUde8e6b52014-11-13 11:23:17 -0800143 }
Ray Milkeya058c732014-10-08 13:52:34 -0700144
helenyrwu2a674902016-07-20 09:48:04 -0700145 // proceed with no protected paths
146 if (!ProtectionConstraint.requireProtectedPath(intent)) {
Pier Ventreffe88d62016-10-13 14:34:40 -0700147 return createUnprotectedLinkCollectionIntent(intent);
helenyrwu2a674902016-07-20 09:48:04 -0700148 }
149
150 try {
151 // attempt to compute and implement backup path
152 return createProtectedIntent(ingressPoint, egressPoint, intent, installable);
153 } catch (PathNotFoundException e) {
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700154 log.warn("Could not find disjoint Path for {}", intent);
helenyrwu2a674902016-07-20 09:48:04 -0700155 // no disjoint path extant -- maximum one path exists between devices
156 return createSinglePathIntent(ingressPoint, egressPoint, intent, installable);
157 }
158 }
159
Daniele Moro3a6e1512017-12-22 12:14:44 +0100160 private void allocateIntentBandwidth(PointToPointIntent intent, Path path) {
161 ConnectPoint ingressCP = intent.filteredIngressPoint().connectPoint();
162 ConnectPoint egressCP = intent.filteredEgressPoint().connectPoint();
163
164 List<ConnectPoint> pathCPs =
165 path.links().stream()
166 .flatMap(l -> Stream.of(l.src(), l.dst()))
167 .collect(Collectors.toList());
168
169 pathCPs.add(ingressCP);
170 pathCPs.add(egressCP);
171
172 allocateBandwidth(intent, pathCPs);
173 }
174
helenyrwu2a674902016-07-20 09:48:04 -0700175 private List<Intent> createZeroHopIntent(ConnectPoint ingressPoint,
176 ConnectPoint egressPoint,
177 PointToPointIntent intent) {
178 List<Link> links = asList(createEdgeLink(ingressPoint, true), createEdgeLink(egressPoint, false));
Ray Milkeya7cf8c82018-02-08 15:07:06 -0800179 return asList(createPathIntent(new DefaultPath(PID, links, ScalarWeight.toWeight(DEFAULT_COST)),
helenyrwu2a674902016-07-20 09:48:04 -0700180 intent, PathIntent.ProtectionType.PRIMARY));
181 }
182
Pier Ventreffe88d62016-10-13 14:34:40 -0700183 private List<Intent> createZeroHopLinkCollectionIntent(PointToPointIntent intent) {
184 return asList(createLinkCollectionIntent(ImmutableSet.of(), DEFAULT_COST,
185 intent));
186 }
187
Pier Ventreffe88d62016-10-13 14:34:40 -0700188 private List<Intent> createUnprotectedLinkCollectionIntent(PointToPointIntent intent) {
Luca Preted26ea652017-01-03 15:59:30 -0800189 Path path = getPathOrException(intent, intent.filteredIngressPoint().connectPoint().deviceId(),
190 intent.filteredEgressPoint().connectPoint().deviceId());
Pier Ventreffe88d62016-10-13 14:34:40 -0700191
Luca Pretede10c782017-01-05 17:23:08 -0800192 // Allocate bandwidth if a bandwidth constraint is set
Daniele Moro3a6e1512017-12-22 12:14:44 +0100193 allocateIntentBandwidth(intent, path);
Luca Pretede10c782017-01-05 17:23:08 -0800194
Pier Ventreffe88d62016-10-13 14:34:40 -0700195 return asList(createLinkCollectionIntent(ImmutableSet.copyOf(path.links()),
196 path.cost(),
197 intent));
198 }
199
helenyrwu2a674902016-07-20 09:48:04 -0700200 //FIXME: Compatibility with EncapsulationConstraint
201 private List<Intent> createProtectedIntent(ConnectPoint ingressPoint,
202 ConnectPoint egressPoint,
203 PointToPointIntent intent,
204 List<Intent> installable) {
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700205 log.trace("createProtectedIntent");
helenyrwu2a674902016-07-20 09:48:04 -0700206 DisjointPath path = getDisjointPath(intent, ingressPoint.deviceId(),
207 egressPoint.deviceId());
208
209 List<Intent> reusableIntents = null;
210 if (installable != null) {
211 reusableIntents = filterInvalidSubIntents(installable, intent);
212 if (reusableIntents.size() == installable.size()) {
213 // all old paths are still viable
214 return installable;
215 }
216 }
217
218 List<Intent> intentList = new ArrayList<>();
219
220 // primary path intent
221 List<Link> links = new ArrayList<>();
222 links.addAll(path.links());
223 links.add(createEdgeLink(egressPoint, false));
224
225 // backup path intent
226 List<Link> backupLinks = new ArrayList<>();
227 backupLinks.addAll(path.backup().links());
228 backupLinks.add(createEdgeLink(egressPoint, false));
229
230 /*
231 * One of the old paths is still entirely intact. This old path has
232 * already been made primary, so we must add a backup path intent
233 * and modify the failover group treatment accordingly.
234 */
235 if (reusableIntents != null && reusableIntents.size() > 1) {
236 /*
237 * Ensures that the egress port on source device is different than
238 * that of existing path so that failover group will be useful
239 * (would not be useful if both output ports in group bucket were
240 * the same). Does not necessarily ensure that the new backup path
241 * is entirely disjoint from the old path.
242 */
243 PortNumber primaryPort = getPrimaryPort(intent);
244 if (primaryPort != null && !links.get(0).src().port().equals(primaryPort)) {
245 reusableIntents.add(createPathIntent(new DefaultPath(PID, links,
Ray Milkeya7cf8c82018-02-08 15:07:06 -0800246 path.weight(), path.annotations()),
helenyrwu2a674902016-07-20 09:48:04 -0700247 intent, PathIntent.ProtectionType.BACKUP));
248 updateFailoverGroup(intent, links);
249 return reusableIntents;
250
251 } else {
Ray Milkeya7cf8c82018-02-08 15:07:06 -0800252 reusableIntents.add(createPathIntent(new DefaultPath(PID, backupLinks,
253 path.backup().weight(),
254 path.backup().annotations()), intent, PathIntent.ProtectionType.BACKUP));
helenyrwu2a674902016-07-20 09:48:04 -0700255 updateFailoverGroup(intent, backupLinks);
256 return reusableIntents;
257 }
258 }
259
Ray Milkeya7cf8c82018-02-08 15:07:06 -0800260 intentList.add(createPathIntent(new DefaultPath(PID, links, path.weight(),
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700261 path.annotations()),
helenyrwu2a674902016-07-20 09:48:04 -0700262 intent, PathIntent.ProtectionType.PRIMARY));
Ray Milkeya7cf8c82018-02-08 15:07:06 -0800263 intentList.add(createPathIntent(new DefaultPath(PID, backupLinks, path.backup().weight(),
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700264 path.backup().annotations()),
265 intent, PathIntent.ProtectionType.BACKUP));
helenyrwu2a674902016-07-20 09:48:04 -0700266
267 // Create fast failover flow rule intent or, if it already exists,
268 // add contents appropriately.
269 if (groupService.getGroup(ingressPoint.deviceId(),
270 makeGroupKey(intent.id())) == null) {
271 // manufactured fast failover flow rule intent
272 createFailoverTreatmentGroup(path.links(), path.backup().links(), intent);
273
274 FlowRuleIntent frIntent = new FlowRuleIntent(intent.appId(),
Yuta HIGUCHI652f27f2016-10-31 16:54:30 -0700275 intent.key(),
helenyrwu2a674902016-07-20 09:48:04 -0700276 createFailoverFlowRules(intent),
277 asList(ingressPoint.deviceId()),
Luca Prete670ac5d2017-02-03 15:55:43 -0800278 PathIntent.ProtectionType.FAILOVER,
279 intent.resourceGroup());
helenyrwu2a674902016-07-20 09:48:04 -0700280 intentList.add(frIntent);
281 } else {
282 updateFailoverGroup(intent, links);
283 updateFailoverGroup(intent, backupLinks);
284 }
285
286 return intentList;
287 }
288
289 private List<Intent> createSinglePathIntent(ConnectPoint ingressPoint,
290 ConnectPoint egressPoint,
291 PointToPointIntent intent,
292 List<Intent> installable) {
293 List<Link> links = new ArrayList<>();
Luca Preted26ea652017-01-03 15:59:30 -0800294 Path onlyPath = getPathOrException(intent, ingressPoint.deviceId(),
295 egressPoint.deviceId());
helenyrwu2a674902016-07-20 09:48:04 -0700296
297 List<Intent> reusableIntents = null;
298 if (installable != null) {
299 reusableIntents = filterInvalidSubIntents(installable, intent);
300 if (reusableIntents.size() == installable.size()) {
301 // all old paths are still viable
302 return installable;
303 }
304 }
305
306 // If there exists a full path from old installable intents,
307 // return the intents that comprise it.
308 if (reusableIntents != null && reusableIntents.size() > 1) {
309 return reusableIntents;
310 } else {
Luca Pretede10c782017-01-05 17:23:08 -0800311 // Allocate bandwidth if a bandwidth constraint is set
Daniele Moro3a6e1512017-12-22 12:14:44 +0100312 allocateIntentBandwidth(intent, onlyPath);
Luca Pretede10c782017-01-05 17:23:08 -0800313
helenyrwu2a674902016-07-20 09:48:04 -0700314 links.add(createEdgeLink(ingressPoint, true));
315 links.addAll(onlyPath.links());
316 links.add(createEdgeLink(egressPoint, false));
Ray Milkeya7cf8c82018-02-08 15:07:06 -0800317 return asList(createPathIntent(new DefaultPath(PID, links, onlyPath.weight(),
helenyrwu2a674902016-07-20 09:48:04 -0700318 onlyPath.annotations()),
319 intent, PathIntent.ProtectionType.PRIMARY));
320 }
Ray Milkeya058c732014-10-08 13:52:34 -0700321 }
322
323 /**
324 * Creates a path intent from the specified path and original
325 * connectivity intent.
326 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700327 * @param path path to create an intent for
Ray Milkeya058c732014-10-08 13:52:34 -0700328 * @param intent original intent
helenyrwu2a674902016-07-20 09:48:04 -0700329 * @param type primary or backup
Ray Milkeya058c732014-10-08 13:52:34 -0700330 */
331 private Intent createPathIntent(Path path,
helenyrwu2a674902016-07-20 09:48:04 -0700332 PointToPointIntent intent,
333 PathIntent.ProtectionType type) {
Ray Milkeyebc5d222015-03-18 15:45:36 -0700334 return PathIntent.builder()
335 .appId(intent.appId())
Yuta HIGUCHI652f27f2016-10-31 16:54:30 -0700336 .key(intent.key())
Ray Milkeyebc5d222015-03-18 15:45:36 -0700337 .selector(intent.selector())
338 .treatment(intent.treatment())
339 .path(path)
340 .constraints(intent.constraints())
341 .priority(intent.priority())
helenyrwu2a674902016-07-20 09:48:04 -0700342 .setType(type)
Luca Prete670ac5d2017-02-03 15:55:43 -0800343 .resourceGroup(intent.resourceGroup())
Ray Milkeyebc5d222015-03-18 15:45:36 -0700344 .build();
Ray Milkeya058c732014-10-08 13:52:34 -0700345 }
346
Pier Ventreffe88d62016-10-13 14:34:40 -0700347
348 /**
349 * Creates a link collection intent from the specified path and original
350 * point to point intent.
351 *
352 * @param links the links of the packets
353 * @param cost the cost associated to the links
354 * @param intent the point to point intent we are compiling
355 * @return the link collection intent
356 */
357 private Intent createLinkCollectionIntent(Set<Link> links,
358 double cost,
359 PointToPointIntent intent) {
360
361 return LinkCollectionIntent.builder()
362 .key(intent.key())
363 .appId(intent.appId())
364 .selector(intent.selector())
365 .treatment(intent.treatment())
366 .links(ImmutableSet.copyOf(links))
367 .filteredIngressPoints(ImmutableSet.of(
368 intent.filteredIngressPoint()
369 ))
370 .filteredEgressPoints(ImmutableSet.of(
371 intent.filteredEgressPoint()
372 ))
373 .applyTreatmentOnEgress(true)
374 .constraints(intent.constraints())
375 .priority(intent.priority())
376 .cost(cost)
Luca Prete670ac5d2017-02-03 15:55:43 -0800377 .resourceGroup(intent.resourceGroup())
Pier Ventreffe88d62016-10-13 14:34:40 -0700378 .build();
379 }
380
helenyrwu2a674902016-07-20 09:48:04 -0700381 /**
382 * Gets primary port number through failover group associated
383 * with this intent.
384 */
385 private PortNumber getPrimaryPort(PointToPointIntent intent) {
Pier Ventreffe88d62016-10-13 14:34:40 -0700386 Group group = groupService.getGroup(intent.filteredIngressPoint().connectPoint().deviceId(),
helenyrwu2a674902016-07-20 09:48:04 -0700387 makeGroupKey(intent.id()));
388 PortNumber primaryPort = null;
389 if (group != null) {
390 List<GroupBucket> buckets = group.buckets().buckets();
391 Iterator<GroupBucket> iterator = buckets.iterator();
392 while (primaryPort == null && iterator.hasNext()) {
393 GroupBucket bucket = iterator.next();
394 Instruction individualInstruction = bucket.treatment().allInstructions().get(0);
395 if (individualInstruction instanceof Instructions.OutputInstruction) {
396 Instructions.OutputInstruction outInstruction =
397 (Instructions.OutputInstruction) individualInstruction;
398 PortNumber tempPortNum = outInstruction.port();
Pier Ventreffe88d62016-10-13 14:34:40 -0700399 Port port = deviceService.getPort(intent.filteredIngressPoint().connectPoint().deviceId(),
helenyrwu2a674902016-07-20 09:48:04 -0700400 tempPortNum);
401 if (port != null && port.isEnabled()) {
402 primaryPort = tempPortNum;
403 }
404 }
405 }
406 }
407 return primaryPort;
408 }
409
410 /**
411 * Creates group key unique to each intent.
Ray Milkeyef794342016-11-09 16:20:29 -0800412 *
413 * @param intentId identifier of intent to get a key for
414 * @return unique group key for the intent identifier
helenyrwu2a674902016-07-20 09:48:04 -0700415 */
416 public static GroupKey makeGroupKey(IntentId intentId) {
417 ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
418 buffer.putLong(intentId.fingerprint());
419 return new DefaultGroupKey(buffer.array());
420 }
421
422 /**
423 * Creates a new failover group with the initial ports of the links
424 * from the primary and backup path.
425 *
426 * @param links links from the primary path
427 * @param backupLinks links from the backup path
428 * @param intent intent from which this call originates
429 */
430 private void createFailoverTreatmentGroup(List<Link> links,
431 List<Link> backupLinks,
432 PointToPointIntent intent) {
433
434 List<GroupBucket> buckets = new ArrayList<>();
435
436 TrafficTreatment.Builder tBuilderIn = DefaultTrafficTreatment.builder();
437 ConnectPoint src = links.get(0).src();
438 tBuilderIn.setOutput(src.port());
439
440 TrafficTreatment.Builder tBuilderIn2 = DefaultTrafficTreatment.builder();
441 ConnectPoint src2 = backupLinks.get(0).src();
442 tBuilderIn2.setOutput(src2.port());
443
444 buckets.add(DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn.build(), src.port(), null));
445 buckets.add(DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn2.build(), src2.port(), null));
446
447 GroupBuckets groupBuckets = new GroupBuckets(buckets);
448
449 GroupDescription groupDesc = new DefaultGroupDescription(src.deviceId(), Group.Type.FAILOVER,
450 groupBuckets, makeGroupKey(intent.id()), null, intent.appId());
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700451 log.trace("adding failover group {}", groupDesc);
helenyrwu2a674902016-07-20 09:48:04 -0700452 groupService.addGroup(groupDesc);
453 }
454
455 /**
456 * Manufactures flow rule with treatment that is defined by failover
457 * group and traffic selector determined by ingress port of the intent.
458 *
459 * @param intent intent which is being compiled (for appId)
460 * @return a list of a singular flow rule with fast failover
461 * outport traffic treatment
462 */
463 private List<FlowRule> createFailoverFlowRules(PointToPointIntent intent) {
464 List<FlowRule> flowRules = new ArrayList<>();
465
Ray Milkeya2cf3a12018-02-15 16:13:56 -0800466 ConnectPoint ingress = intent.filteredIngressPoint().connectPoint();
helenyrwu2a674902016-07-20 09:48:04 -0700467 DeviceId deviceId = ingress.deviceId();
468
469 // flow rule with failover traffic treatment
470 TrafficSelector trafficSelector = DefaultTrafficSelector.builder(intent.selector())
471 .matchInPort(ingress.port()).build();
472
473 FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder();
474 flowRules.add(flowRuleBuilder.withSelector(trafficSelector)
475 .withTreatment(buildFailoverTreatment(deviceId, makeGroupKey(intent.id())))
476 .fromApp(intent.appId())
477 .makePermanent()
478 .forDevice(deviceId)
479 .withPriority(PRIORITY)
480 .build());
481
482 return flowRules;
483 }
484
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700485
486 /**
487 * Waits for specified group to appear maximum of {@value #GROUP_TIMEOUT} seconds.
488 *
489 * @param deviceId {@link DeviceId}
490 * @param groupKey {@link GroupKey} to wait for.
491 * @return {@link Group}
492 * @throws IntentCompilationException on any error.
493 */
494 private Group waitForGroup(DeviceId deviceId, GroupKey groupKey) {
495 return waitForGroup(deviceId, groupKey, GROUP_TIMEOUT, TimeUnit.SECONDS);
496 }
497
498 /**
499 * Waits for specified group to appear until timeout.
500 *
501 * @param deviceId {@link DeviceId}
502 * @param groupKey {@link GroupKey} to wait for.
503 * @param timeout timeout
504 * @param unit unit of timeout
505 * @return {@link Group}
506 * @throws IntentCompilationException on any error.
507 */
508 private Group waitForGroup(DeviceId deviceId, GroupKey groupKey, long timeout, TimeUnit unit) {
509 Group group = groupService.getGroup(deviceId, groupKey);
510 if (group != null) {
511 return group;
512 }
513
514 final CompletableFuture<Group> future = new CompletableFuture<>();
515 final GroupListener listener = event -> {
516 if (event.subject().deviceId() == deviceId &&
517 event.subject().appCookie().equals(groupKey)) {
518 future.complete(event.subject());
519 return;
520 }
521 };
522
523 groupService.addListener(listener);
524 try {
525 group = groupService.getGroup(deviceId, groupKey);
526 if (group != null) {
527 return group;
528 }
529 return future.get(timeout, unit);
530 } catch (InterruptedException e) {
531 log.debug("Interrupted", e);
532 Thread.currentThread().interrupt();
533 throw new IntentCompilationException("Interrupted", e);
534 } catch (ExecutionException e) {
535 log.debug("ExecutionException", e);
536 throw new IntentCompilationException("ExecutionException caught", e);
537 } catch (TimeoutException e) {
538 // one last try
539 group = groupService.getGroup(deviceId, groupKey);
540 if (group != null) {
541 return group;
542 } else {
543 log.debug("Timeout", e);
544 throw new IntentCompilationException("Timeout", e);
545 }
546 } finally {
547 groupService.removeListener(listener);
548 }
549 }
550
helenyrwu2a674902016-07-20 09:48:04 -0700551 private TrafficTreatment buildFailoverTreatment(DeviceId srcDevice,
552 GroupKey groupKey) {
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700553 Group group = waitForGroup(srcDevice, groupKey);
helenyrwu2a674902016-07-20 09:48:04 -0700554 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
555 TrafficTreatment trafficTreatment = tBuilder.group(group.id()).build();
556 return trafficTreatment;
557 }
558
559 /**
560 * Deletes intents from the given list if the ports or links the intent
561 * relies on are no longer viable. The failover flow rule intent is never
562 * deleted -- only its contents are updated.
563 *
564 * @param oldInstallables list of intents to examine
565 * @return list of reusable installable intents
566 */
567 private List<Intent> filterInvalidSubIntents(List<Intent> oldInstallables,
568 PointToPointIntent pointIntent) {
569 List<Intent> intentList = new ArrayList<>();
570 intentList.addAll(oldInstallables);
Ray Milkeyfe0e0852018-01-18 11:14:05 -0800571
572 Iterator<Intent> iterator = intentList.iterator();
573 while (iterator.hasNext()) {
574 Intent intent = iterator.next();
575 intent.resources().forEach(resource -> {
576 if (resource instanceof Link) {
577 Link link = (Link) resource;
578 if (link.state() == Link.State.INACTIVE) {
579 setPathsToRemove(intent);
580 } else if (link instanceof EdgeLink) {
581 ConnectPoint connectPoint = (link.src().elementId() instanceof DeviceId)
582 ? link.src() : link.dst();
583 Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
584 if (port == null || !port.isEnabled()) {
helenyrwu2a674902016-07-20 09:48:04 -0700585 setPathsToRemove(intent);
Ray Milkeyfe0e0852018-01-18 11:14:05 -0800586 }
587 } else {
588 Port port1 = deviceService.getPort(link.src().deviceId(), link.src().port());
589 Port port2 = deviceService.getPort(link.dst().deviceId(), link.dst().port());
590 if (port1 == null || !port1.isEnabled() || port2 == null || !port2.isEnabled()) {
591 setPathsToRemove(intent);
helenyrwu2a674902016-07-20 09:48:04 -0700592 }
593 }
Ray Milkeyfe0e0852018-01-18 11:14:05 -0800594 }
595 });
helenyrwu2a674902016-07-20 09:48:04 -0700596 }
Ray Milkeyfe0e0852018-01-18 11:14:05 -0800597 removeAndUpdateIntents(intentList, pointIntent);
598
helenyrwu2a674902016-07-20 09:48:04 -0700599 return intentList;
600 }
601
602 /**
603 * Sets instance variables erasePrimary and eraseBackup. If erasePrimary,
604 * the primary path is no longer viable and related intents will be deleted.
605 * If eraseBackup, the backup path is no longer viable and related intents
606 * will be deleted.
607 *
608 * @param intent intent whose resources are found to be disabled/inactive:
609 * if intent is part of primary path, primary path set for removal;
610 * if intent is part of backup path, backup path set for removal;
611 * if bad intent is of type failover, the ingress point is down,
612 * and both paths are rendered inactive.
613 * @return true if both primary and backup paths are to be removed
614 */
615 private boolean setPathsToRemove(Intent intent) {
616 if (intent instanceof FlowRuleIntent) {
617 FlowRuleIntent frIntent = (FlowRuleIntent) intent;
618 PathIntent.ProtectionType type = frIntent.type();
619 if (type == PathIntent.ProtectionType.PRIMARY || type == PathIntent.ProtectionType.FAILOVER) {
620 erasePrimary = true;
621 }
622 if (type == PathIntent.ProtectionType.BACKUP || type == PathIntent.ProtectionType.FAILOVER) {
623 eraseBackup = true;
624 }
625 }
626 return erasePrimary && eraseBackup;
627 }
628
629 /**
630 * Removes intents from installables list, depending on the values
631 * of instance variables erasePrimary and eraseBackup. Flow rule intents
632 * that contain the manufactured fast failover flow rules are never deleted.
633 * The contents are simply modified as necessary. If cleanUpIntents size
634 * is greater than 1 (failover intent), then one whole path from previous
635 * installables must be still viable.
636 *
637 * @param cleanUpIntents list of installable intents
638 */
639 private void removeAndUpdateIntents(List<Intent> cleanUpIntents,
640 PointToPointIntent pointIntent) {
641 ListIterator<Intent> iterator = cleanUpIntents.listIterator();
642 while (iterator.hasNext()) {
643 Intent cIntent = iterator.next();
644 if (cIntent instanceof FlowRuleIntent) {
645 FlowRuleIntent fIntent = (FlowRuleIntent) cIntent;
646 if (fIntent.type() == PathIntent.ProtectionType.PRIMARY && erasePrimary) {
647 // remove primary path's flow rule intents
648 iterator.remove();
649 } else if (fIntent.type() == PathIntent.ProtectionType.BACKUP && eraseBackup) {
650 //remove backup path's flow rule intents
651 iterator.remove();
652 } else if (fIntent.type() == PathIntent.ProtectionType.BACKUP && erasePrimary) {
653 // promote backup path's flow rule intents to primary
654 iterator.set(new FlowRuleIntent(fIntent, PathIntent.ProtectionType.PRIMARY));
655 }
656 }
657 }
658 // remove buckets whose watchports are disabled if the failover group exists
Pier Ventreffe88d62016-10-13 14:34:40 -0700659 Group group = groupService.getGroup(pointIntent.filteredIngressPoint().connectPoint().deviceId(),
helenyrwu2a674902016-07-20 09:48:04 -0700660 makeGroupKey(pointIntent.id()));
661 if (group != null) {
662 updateFailoverGroup(pointIntent);
663 }
664 }
665
666 // Removes buckets whose treatments rely on disabled ports from the
667 // failover group.
668 private void updateFailoverGroup(PointToPointIntent pointIntent) {
Pier Ventreffe88d62016-10-13 14:34:40 -0700669 DeviceId deviceId = pointIntent.filteredIngressPoint().connectPoint().deviceId();
helenyrwu2a674902016-07-20 09:48:04 -0700670 GroupKey groupKey = makeGroupKey(pointIntent.id());
Yuta HIGUCHI625fb642016-09-01 16:10:24 -0700671 Group group = waitForGroup(deviceId, groupKey);
helenyrwu2a674902016-07-20 09:48:04 -0700672 Iterator<GroupBucket> groupIterator = group.buckets().buckets().iterator();
673 while (groupIterator.hasNext()) {
674 GroupBucket bucket = groupIterator.next();
675 Instruction individualInstruction = bucket.treatment().allInstructions().get(0);
676 if (individualInstruction instanceof Instructions.OutputInstruction) {
677 Instructions.OutputInstruction outInstruction =
678 (Instructions.OutputInstruction) individualInstruction;
679 Port port = deviceService.getPort(deviceId, outInstruction.port());
680 if (port == null || !port.isEnabled()) {
681 GroupBuckets removeBuckets = new GroupBuckets(Collections.singletonList(bucket));
682 groupService.removeBucketsFromGroup(deviceId, groupKey,
683 removeBuckets, groupKey,
684 pointIntent.appId());
685 }
686 }
687 }
688 }
689
690 // Adds failover group bucket with treatment outport determined by the
691 // ingress point of the links.
692 private void updateFailoverGroup(PointToPointIntent intent, List<Link> links) {
693 GroupKey groupKey = makeGroupKey(intent.id());
694
695 TrafficTreatment.Builder tBuilderIn = DefaultTrafficTreatment.builder();
696 ConnectPoint src = links.get(0).src();
697 tBuilderIn.setOutput(src.port());
698 GroupBucket bucket = DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn.build(), src.port(), null);
699 GroupBuckets addBuckets = new GroupBuckets(Collections.singletonList(bucket));
700
701 groupService.addBucketsToGroup(src.deviceId(), groupKey, addBuckets, groupKey, intent.appId());
702 }
Daniele Moro3a6e1512017-12-22 12:14:44 +0100703
704 /**
705 * Checks suggested path availability.
706 * It checks:
707 * - single links availability;
708 * - that first and last device of the path are coherent with ingress and egress devices;
709 * - links contiguity.
710 *
711 * @param intent Intent with suggested path to check
712 * @return true if the suggested path is available
713 */
714 private boolean pathAvailable(PointToPointIntent intent) {
715 // Check links availability
716 List<Link> suggestedPath = intent.suggestedPath();
717 for (Link link : suggestedPath) {
718 if (!(link instanceof EdgeLink) && !linkService.getLinks(link.src()).contains(link)) {
719 return false;
720 }
721 }
722
723 //Check that first and last device of the path are intent ingress and egress devices
724 if (!suggestedPath.get(0).src()
725 .deviceId().equals(intent.filteredIngressPoint().connectPoint().deviceId())) {
726 return false;
727 }
728 if (!suggestedPath.get(suggestedPath.size() - 1).dst()
729 .deviceId().equals(intent.filteredEgressPoint().connectPoint().deviceId())) {
730 return false;
731 }
732
733 // Check contiguity
734 List<Pair<Link, Link>> linkPairs = IntStream.
735 range(0, suggestedPath.size() - 1)
736 .mapToObj(i -> Pair.of(suggestedPath.get(i), suggestedPath.get(i + 1)))
737 .collect(Collectors.toList());
738
739 for (Pair<Link, Link> linkPair : linkPairs) {
740 if (!linkPair.getKey().dst().deviceId().equals(linkPair.getValue().src().deviceId())) {
741 return false;
742 }
743 }
744 return true;
745 }
Ray Milkeya058c732014-10-08 13:52:34 -0700746}