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