blob: 2bbac10d1948daf90f751db5163ab752b13cf397 [file] [log] [blame]
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -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 */
16package org.onosproject.net.intent.impl.compiler;
17
Ray Milkey86ad7bb2018-09-27 12:32:28 -070018import com.google.common.annotations.Beta;
19import com.google.common.collect.ImmutableList;
20import com.google.common.collect.ImmutableSet;
21import com.google.common.collect.Iterables;
22import com.google.common.collect.Lists;
23import com.google.common.collect.Sets;
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -080024import org.apache.commons.lang3.RandomUtils;
25import org.apache.commons.lang3.tuple.Pair;
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -080026import org.onlab.packet.VlanId;
27import org.onosproject.net.ConnectPoint;
28import org.onosproject.net.DefaultLink;
29import org.onosproject.net.DefaultPath;
30import org.onosproject.net.DeviceId;
31import org.onosproject.net.DisjointPath;
32import org.onosproject.net.FilteredConnectPoint;
33import org.onosproject.net.Link;
34import org.onosproject.net.Link.State;
35import org.onosproject.net.NetworkResource;
36import org.onosproject.net.Path;
37import org.onosproject.net.behaviour.protection.TransportEndpointDescription;
38import org.onosproject.net.flow.DefaultTrafficSelector;
39import org.onosproject.net.intent.Intent;
40import org.onosproject.net.intent.IntentCompilationException;
41import org.onosproject.net.intent.LinkCollectionIntent;
42import org.onosproject.net.intent.ProtectedTransportIntent;
43import org.onosproject.net.intent.ProtectionEndpointIntent;
44import org.onosproject.net.resource.DiscreteResourceId;
45import org.onosproject.net.resource.Resource;
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -080046import org.onosproject.net.resource.Resources;
Ray Milkey86ad7bb2018-09-27 12:32:28 -070047import org.osgi.service.component.annotations.Activate;
48import org.osgi.service.component.annotations.Component;
49import org.osgi.service.component.annotations.Deactivate;
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -080050import org.slf4j.Logger;
51
Ray Milkey86ad7bb2018-09-27 12:32:28 -070052import java.util.ArrayList;
53import java.util.Collection;
54import java.util.List;
55import java.util.Objects;
56import java.util.Optional;
57import java.util.Set;
58import java.util.stream.Collectors;
59import java.util.stream.Stream;
60
61import static com.google.common.base.Preconditions.checkArgument;
62import static com.google.common.collect.Lists.transform;
63import static java.util.stream.Stream.concat;
64import static org.onosproject.net.MarkerResource.marker;
65import static org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription.buildDescription;
66import static org.slf4j.LoggerFactory.getLogger;
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -080067
68/**
69 * IntentCompiler for {@link ProtectedTransportIntent}.
70 */
71@Beta
72@Component(immediate = true)
73public class ProtectedTransportIntentCompiler
74 extends ConnectivityIntentCompiler<ProtectedTransportIntent> {
75
Yuta HIGUCHI32739212017-01-13 18:59:51 -080076 /**
77 * Marker value for forward path.
78 */
79 private static final String FWD = "fwd";
80
81 /**
82 * Marker value for reverse path.
83 */
84 private static final String REV = "rev";
85
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -080086 private final Logger log = getLogger(getClass());
87
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -080088 @Activate
89 public void activate() {
90 intentManager.registerCompiler(ProtectedTransportIntent.class, this);
91 log.info("started");
92 }
93
94 @Deactivate
95 public void deactivate() {
96 intentManager.unregisterCompiler(ProtectedTransportIntent.class);
97 log.info("stopped");
98 }
99
100 @Override
101 public List<Intent> compile(ProtectedTransportIntent intent,
102 List<Intent> installable) {
103 log.trace("compiling {} {}", intent, installable);
104
105 // case 0 hop, same device
106 final DeviceId did1 = intent.one();
107 final DeviceId did2 = intent.two();
108 if (Objects.equals(did1, did2)) {
109 // Doesn't really make sense to create 0 hop protected path, but
110 // can generate Flow for the device, just to provide connectivity.
111 // future work.
112 log.error("0 hop not supported yet.");
113 throw new IntentCompilationException("0 hop not supported yet.");
114 }
115
116 List<Intent> reusable = Optional.ofNullable(installable).orElse(ImmutableList.of())
117 .stream()
118 .filter(this::isIntact)
119 .collect(Collectors.toList());
120 if (reusable.isEmpty() ||
121 reusable.stream().allMatch(ProtectionEndpointIntent.class::isInstance)) {
122 // case provisioning new protected path
123 // or
124 // case re-compilation (total failure -> restoration)
125 return createFreshProtectedPaths(intent, did1, did2);
126 } else {
127 // case re-compilation (partial failure)
128 log.warn("Re-computing adding new backup path not supported yet. No-Op.");
129 // TODO This part needs to be flexible to support various use case
130 // - non-revertive behavior (Similar to PartialFailureConstraint)
131 // - revertive behavior
132 // - compute third path
133 // ...
134 // Require further input what they actually need.
135
136 // TODO handle PartialFailureConstraint
137
138 /// case only need to update transit portion
139 /// case head and/or tail needs to be updated
140
141 // TODO do we need to prune broken
142 return installable;
143 }
144 }
145
146 /**
147 * Test if resources used by specified Intent is intact.
148 *
149 * @param installed Intent to test
150 * @return true if Intent is intact
151 */
152 private boolean isIntact(Intent installed) {
153 return installed.resources().stream()
154 .filter(Link.class::isInstance)
155 .map(Link.class::cast)
156 .allMatch(this::isLive);
157 }
158
159 /**
160 * Test if specified Link is intact.
161 *
162 * @param link to test
163 * @return true if link is intact
164 */
165 private boolean isLive(Link link) {
166 // Only testing link state for now
167 // in the long run, consider verifying OAM state on ports
168 return link.state() != State.INACTIVE;
169 }
170
171 /**
172 * Creates new protected paths.
173 *
174 * @param intent original intention
175 * @param did1 identifier of first device
176 * @param did2 identifier of second device
177 * @return compilation result
178 * @throws IntentCompilationException when there's no satisfying path.
179 */
180 private List<Intent> createFreshProtectedPaths(ProtectedTransportIntent intent,
181 DeviceId did1,
182 DeviceId did2) {
183 DisjointPath disjointPath = getDisjointPath(intent, did1, did2);
184 if (disjointPath == null || disjointPath.backup() == null) {
185 log.error("Unable to find disjoint path between {}, {}", did1, did2);
186 throw new IntentCompilationException("Unable to find disjoint paths.");
187 }
188 Path primary = disjointPath.primary();
189 Path secondary = disjointPath.backup();
190
191 String fingerprint = intent.key().toString();
192
193 // pick and allocate Vlan to use as S-tag
194 Pair<VlanId, VlanId> vlans = allocateEach(intent, primary, secondary, VlanId.class);
195
196 VlanId primaryVlan = vlans.getLeft();
197 VlanId secondaryVlan = vlans.getRight();
198
199 // Build edge Intents for head/tail
200
201 // resource for head/tail
202 Collection<NetworkResource> oneResources = new ArrayList<>();
203 Collection<NetworkResource> twoResources = new ArrayList<>();
204
205 List<TransportEndpointDescription> onePaths = new ArrayList<>();
206 onePaths.add(TransportEndpointDescription.builder()
207 .withOutput(vlanPort(primary.src(), primaryVlan))
208 .build());
209 onePaths.add(TransportEndpointDescription.builder()
210 .withOutput(vlanPort(secondary.src(), secondaryVlan))
211 .build());
212
213 List<TransportEndpointDescription> twoPaths = new ArrayList<>();
214 twoPaths.add(TransportEndpointDescription.builder()
215 .withOutput(vlanPort(primary.dst(), primaryVlan))
216 .build());
217 twoPaths.add(TransportEndpointDescription.builder()
218 .withOutput(vlanPort(secondary.dst(), secondaryVlan))
219 .build());
220
221 ProtectionEndpointIntent oneIntent = ProtectionEndpointIntent.builder()
222 .key(intent.key())
223 .appId(intent.appId())
224 .priority(intent.priority())
225 .resources(oneResources)
226 .deviceId(did1)
227 .description(buildDescription(onePaths, did2, fingerprint))
228 .build();
229 ProtectionEndpointIntent twoIntent = ProtectionEndpointIntent.builder()
230 .key(intent.key())
231 .appId(intent.appId())
232 .resources(twoResources)
233 .deviceId(did2)
234 .description(buildDescription(twoPaths, did1, fingerprint))
235 .build();
236
237 // Build transit intent for primary/secondary path
238
Yuta HIGUCHI32739212017-01-13 18:59:51 -0800239 Collection<NetworkResource> resources1 = ImmutableList.of(marker("protection1"));
240 Collection<NetworkResource> resources2 = ImmutableList.of(marker("protection2"));
241
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800242 ImmutableList<Intent> result = ImmutableList.<Intent>builder()
243 // LinkCollection for primary and backup paths
Yuta HIGUCHI32739212017-01-13 18:59:51 -0800244 .addAll(createTransitIntent(intent, primary, primaryVlan, resources1))
245 .addAll(createTransitIntent(intent, secondary, secondaryVlan, resources2))
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800246 .add(oneIntent)
247 .add(twoIntent)
248 .build();
249 log.trace("createFreshProtectedPaths result: {}", result);
250 return result;
251 }
252
253 /**
254 * Creates required Intents required to transit bi-directionally the network.
255 *
256 * @param intent parent IntentId
257 * @param path whole path
258 * @param vid VlanId to use as tunnel labels
Yuta HIGUCHI32739212017-01-13 18:59:51 -0800259 * @param resources to be passed down to generated Intents
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800260 * @return List on transit Intents, if any is required.
261 */
Yuta HIGUCHI32739212017-01-13 18:59:51 -0800262 List<LinkCollectionIntent> createTransitIntent(Intent intent,
263 Path path,
264 VlanId vid,
265 Collection<NetworkResource> resources) {
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800266 if (path.links().size() <= 1) {
267 // There's no need for transit Intents
268 return ImmutableList.of();
269 }
270
Yuta HIGUCHI32739212017-01-13 18:59:51 -0800271 Collection<NetworkResource> fwd = ImmutableList.<NetworkResource>builder()
272 .addAll(resources)
273 .add(marker(FWD))
274 .build();
275 Collection<NetworkResource> rev = ImmutableList.<NetworkResource>builder()
276 .addAll(resources)
277 .add(marker(REV))
278 .build();
279
280 return ImmutableList.of(createSubTransitIntent(intent, path, vid, fwd),
281 createSubTransitIntent(intent, reverse(path), vid, rev));
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800282 }
283
284 /**
285 * Returns a path in reverse direction.
286 *
287 * @param path to reverse
288 * @return reversed path
289 */
290 Path reverse(Path path) {
291 List<Link> revLinks = Lists.reverse(transform(path.links(), this::reverse));
292 return new DefaultPath(path.providerId(),
293 revLinks,
Ray Milkeya7cf8c82018-02-08 15:07:06 -0800294 path.weight(),
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800295 path.annotations());
296 }
297
298 // TODO consider adding equivalent to Link/DefaultLink.
299 /**
300 * Returns a link in reverse direction.
301 *
302 * @param link to revese
303 * @return reversed link
304 */
305 Link reverse(Link link) {
306 return DefaultLink.builder()
307 .providerId(link.providerId())
308 .src(link.dst())
309 .dst(link.src())
310 .type(link.type())
311 .state(link.state())
312 .isExpected(link.isExpected())
313 .annotations(link.annotations())
314 .build();
315 }
316
317 /**
318 * Creates required Intents required to transit uni-directionally along the Path.
319 *
320 * @param intent parent IntentId
321 * @param path whole path
322 * @param vid VlanId to use as tunnel labels
Yuta HIGUCHI32739212017-01-13 18:59:51 -0800323 * @param resources to be passed down to generated Intents
324 * @return List of transit Intents, if any is required.
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800325 */
Yuta HIGUCHI32739212017-01-13 18:59:51 -0800326 LinkCollectionIntent createSubTransitIntent(Intent intent,
327 Path path,
328 VlanId vid,
329 Collection<NetworkResource> resources) {
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800330 checkArgument(path.links().size() > 1);
331
332 // transit ingress/egress
333 ConnectPoint one = path.links().get(0).dst();
334 ConnectPoint two = path.links().get(path.links().size() - 1).src();
335
336 return LinkCollectionIntent.builder()
337 // TODO there should probably be .parent(intent)
338 // which copies key, appId, priority, ...
339 .key(intent.key())
340 .appId(intent.appId())
341 .priority(intent.priority())
342 //.constraints(intent.constraints())
343 // VLAN tunnel
344 //.selector(DefaultTrafficSelector.builder().matchVlanId(vid).build())
345 //.treatment(intent.treatment())
Yuta HIGUCHI32739212017-01-13 18:59:51 -0800346 .resources(resources)
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800347 .links(ImmutableSet.copyOf(path.links()))
348 .filteredIngressPoints(ImmutableSet.of(vlanPort(one, vid)))
349 .filteredEgressPoints(ImmutableSet.of(vlanPort(two, vid)))
350 // magic flag required for p2p type
351 .applyTreatmentOnEgress(true)
352 .cost(path.cost())
353 .build();
354 }
355
356 /**
357 * Creates VLAN filtered-ConnectPoint.
358 *
359 * @param cp ConnectPoint
360 * @param vid VLAN ID
361 * @return filtered-ConnectPoint
362 */
363 static FilteredConnectPoint vlanPort(ConnectPoint cp, VlanId vid) {
364 return new FilteredConnectPoint(cp, DefaultTrafficSelector.builder()
365 .matchVlanId(vid)
366 .build());
367 }
368
369 /**
370 * Creates ResourceId for a port.
371 *
372 * @param cp ConnectPoint
373 * @return ResourceId
374 */
375 static DiscreteResourceId resourceId(ConnectPoint cp) {
376 return Resources.discrete(cp.deviceId(), cp.port()).id();
377 }
378
379 /**
380 * Allocate resource for each {@link Path}s.
381 *
382 * @param intent to allocate resource to
383 * @param primary path
384 * @param secondary path
385 * @param klass label resource class
386 * @return Pair of chosen resource (primary, secondary)
387 * @param <T> label resource type
388 * @throws IntentCompilationException when there is no resource available
389 */
390 <T> Pair<T, T> allocateEach(Intent intent, Path primary, Path secondary, Class<T> klass) {
391 log.trace("allocateEach({}, {}, {}, {})", intent, primary, secondary, klass);
392 Pair<T, T> vlans = null;
393 do {
394 Set<T> primaryVlans = commonLabelResource(primary, klass);
395 Set<T> secondaryVlans = commonLabelResource(secondary, klass);
396 Pair<T, T> candidates = pickEach(primaryVlans, secondaryVlans);
397 T primaryT = candidates.getLeft();
398 T secondaryT = candidates.getRight();
399
400 // try to allocate candidates along each path
401 Stream<Resource> primaryResources = primary.links().stream()
402 .flatMap(link -> Stream.of(link.src(), link.dst()))
403 .distinct()
404 .map(cp -> Resources.discrete(resourceId(cp), primaryT).resource());
405 Stream<Resource> secondaryResources = secondary.links().stream()
406 .flatMap(link -> Stream.of(link.src(), link.dst()))
407 .distinct()
408 .map(cp -> Resources.discrete(resourceId(cp), secondaryT).resource());
409
410 List<Resource> resources = concat(primaryResources, secondaryResources)
411 .collect(Collectors.toList());
Yuta HIGUCHI65d9d0e2017-05-04 12:44:32 -0700412 log.trace("Calling allocate({},{})", intent.key(), resources);
413 if (resourceService.allocate(intent.key(), resources).isEmpty()) {
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800414 log.warn("Allocation failed, retrying");
415 continue;
416 }
417 vlans = candidates;
418 } while (false);
419 log.trace("allocation done.");
420 return vlans;
421 }
422
423 /**
424 * Randomly pick one resource from candidates.
425 *
426 * @param set of candidates
427 * @return chosen one
428 * @param <T> label resource type
429 */
430 <T> T pickOne(Set<T> set) {
431 // Note: Set returned by commonLabelResource(..) assures,
432 // there is at least one element.
433
434 // FIXME more reasonable selection logic
435 return Iterables.get(set, RandomUtils.nextInt(0, set.size()));
436 }
437
438 /**
439 * Select resource from available Resources.
440 *
441 * @param primary Set of resource to pick from
442 * @param secondary Set of resource to pick from
443 * @return Pair of chosen resource (primary, secondary)
444 * @param <T> label resource type
445 */
446 <T> Pair<T, T> pickEach(Set<T> primary, Set<T> secondary) {
447 Set<T> intersection = Sets.intersection(primary, secondary);
448
449 if (!intersection.isEmpty()) {
450 // favor common
451 T picked = pickOne(intersection);
452 return Pair.of(picked, picked);
453 }
454
455 T pickedP = pickOne(primary);
456 T pickedS = pickOne(secondary);
457 return Pair.of(pickedP, pickedS);
458 }
459
460 /**
461 * Finds label resource, which can be used in common along the path.
462 *
463 * @param path path
464 * @param klass Label class
465 * @return Set of common resources
466 * @throws IntentCompilationException when there is no resource available
467 * @param <T> label resource type
468 */
469 <T> Set<T> commonLabelResource(Path path, Class<T> klass) {
470 Optional<Set<T>> common = path.links().stream()
471 .flatMap(link -> Stream.of(link.src(), link.dst()))
472 .distinct()
473 .map(cp -> getAvailableResourceValues(cp, klass))
474 .reduce(Sets::intersection);
475
476 if (!common.isPresent() || common.get().isEmpty()) {
Yuta HIGUCHI5691faf2017-02-13 16:16:55 -0800477 log.error("No common label available for: {}", path);
Yuta HIGUCHIdcfa31a2016-12-09 19:37:41 -0800478 throw new IntentCompilationException("No common label available for: " + path);
479 }
480 return common.get();
481 }
482
483 <T> Set<T> getAvailableResourceValues(ConnectPoint cp, Class<T> klass) {
484 return resourceService.getAvailableResourceValues(
485 resourceId(cp),
486 klass);
487 }
488
489}