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