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