blob: b831fef641d426f40facc88f1b5cae6c26e68f3b [file] [log] [blame]
Ray Milkey661c38c2016-02-26 17:12:17 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Ray Milkey661c38c2016-02-26 17:12:17 -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
Jian Li11260a02016-05-19 13:07:22 -070018import com.google.common.collect.ImmutableList;
Pier Luigi Ventre51313bd2016-06-02 10:50:30 +020019import com.google.common.collect.Iterables;
Jian Li11260a02016-05-19 13:07:22 -070020import com.google.common.collect.Sets;
Pier Luigi Ventre51313bd2016-06-02 10:50:30 +020021import org.apache.commons.lang.math.RandomUtils;
Michele Santuari6096acd2016-02-09 17:00:37 +010022import org.onlab.packet.EthType;
23import org.onlab.packet.Ethernet;
24import org.onlab.packet.MplsLabel;
Ray Milkey661c38c2016-02-26 17:12:17 -080025import org.onlab.packet.VlanId;
26import org.onosproject.net.ConnectPoint;
27import org.onosproject.net.DeviceId;
28import org.onosproject.net.Link;
29import org.onosproject.net.LinkKey;
30import org.onosproject.net.flow.DefaultTrafficSelector;
31import org.onosproject.net.flow.DefaultTrafficTreatment;
32import org.onosproject.net.flow.TrafficSelector;
33import org.onosproject.net.flow.TrafficTreatment;
34import org.onosproject.net.flow.criteria.Criterion;
Michele Santuari6096acd2016-02-09 17:00:37 +010035import org.onosproject.net.flow.criteria.EthTypeCriterion;
36import org.onosproject.net.flow.criteria.MplsCriterion;
Ray Milkey661c38c2016-02-26 17:12:17 -080037import org.onosproject.net.flow.criteria.VlanIdCriterion;
Michele Santuari6096acd2016-02-09 17:00:37 +010038import org.onosproject.net.flow.instructions.Instruction;
Ray Milkey661c38c2016-02-26 17:12:17 -080039import org.onosproject.net.flow.instructions.L2ModificationInstruction;
Yuta HIGUCHId95d5902016-06-27 00:18:45 -070040import org.onosproject.net.intent.IntentCompilationException;
Ray Milkey661c38c2016-02-26 17:12:17 -080041import org.onosproject.net.intent.PathIntent;
42import org.onosproject.net.intent.constraint.EncapsulationConstraint;
Sho SHIMIZUe18cb122016-02-22 21:04:56 -080043import org.onosproject.net.resource.Resource;
44import org.onosproject.net.resource.ResourceAllocation;
45import org.onosproject.net.resource.ResourceService;
46import org.onosproject.net.resource.Resources;
Ray Milkey661c38c2016-02-26 17:12:17 -080047import org.slf4j.Logger;
48
Jian Li11260a02016-05-19 13:07:22 -070049import java.util.Collections;
50import java.util.HashMap;
51import java.util.Iterator;
52import java.util.List;
53import java.util.Map;
54import java.util.Optional;
55import java.util.Set;
56import java.util.stream.Collectors;
57import java.util.stream.Stream;
Ray Milkey661c38c2016-02-26 17:12:17 -080058
59import static org.onosproject.net.LinkKey.linkKey;
60
61/**
62 * Shared APIs and implementations for path compilers.
63 */
64
65public class PathCompiler<T> {
66
Pier Luigi Ventre51313bd2016-06-02 10:50:30 +020067 public static final boolean RANDOM_SELECTION = true;
68
Ray Milkey661c38c2016-02-26 17:12:17 -080069 /**
70 * Defines methods used to create objects representing flows.
71 */
72 public interface PathCompilerCreateFlow<T> {
73
74 void createFlow(TrafficSelector originalSelector,
75 TrafficTreatment originalTreatment,
76 ConnectPoint ingress, ConnectPoint egress,
77 int priority,
78 boolean applyTreatment,
79 List<T> flows,
80 List<DeviceId> devices);
81
82 Logger log();
83
84 ResourceService resourceService();
85 }
86
87 private boolean isLast(List<Link> links, int i) {
88 return i == links.size() - 2;
89 }
90
91 private Map<LinkKey, VlanId> assignVlanId(PathCompilerCreateFlow creator, PathIntent intent) {
92 Set<LinkKey> linkRequest =
93 Sets.newHashSetWithExpectedSize(intent.path()
94 .links().size() - 2);
95 for (int i = 1; i <= intent.path().links().size() - 2; i++) {
96 LinkKey link = linkKey(intent.path().links().get(i));
97 linkRequest.add(link);
98 // add the inverse link. I want that the VLANID is reserved both for
99 // the direct and inverse link
100 linkRequest.add(linkKey(link.dst(), link.src()));
101 }
102
103 Map<LinkKey, VlanId> vlanIds = findVlanIds(creator, linkRequest);
104 if (vlanIds.isEmpty()) {
105 creator.log().warn("No VLAN IDs available");
106 return Collections.emptyMap();
107 }
108
109 //same VLANID is used for both directions
110 Set<Resource> resources = vlanIds.entrySet().stream()
111 .flatMap(x -> Stream.of(
112 Resources.discrete(x.getKey().src().deviceId(), x.getKey().src().port(), x.getValue())
113 .resource(),
114 Resources.discrete(x.getKey().dst().deviceId(), x.getKey().dst().port(), x.getValue())
115 .resource()
116 ))
117 .collect(Collectors.toSet());
Sho SHIMIZUe18cb122016-02-22 21:04:56 -0800118 List<ResourceAllocation> allocations =
Ray Milkey661c38c2016-02-26 17:12:17 -0800119 creator.resourceService().allocate(intent.id(), ImmutableList.copyOf(resources));
120 if (allocations.isEmpty()) {
Sho SHIMIZUf36a8362016-03-30 11:09:57 -0700121 return Collections.emptyMap();
Ray Milkey661c38c2016-02-26 17:12:17 -0800122 }
123
124 return vlanIds;
125 }
126
Pier Luigi Ventre51313bd2016-06-02 10:50:30 +0200127 /**
128 * Implements the first fit selection behavior.
129 *
130 * @param available the set of available VLAN ids.
131 * @return the chosen VLAN id.
132 */
133 private VlanId firsFitSelection(Set<VlanId> available) {
134 if (!available.isEmpty()) {
135 return available.iterator().next();
136 }
137 return VlanId.vlanId(VlanId.NO_VID);
138 }
139
140 /**
141 * Implements the random selection behavior.
142 *
143 * @param available the set of available VLAN ids.
144 * @return the chosen VLAN id.
145 */
146 private VlanId randomSelection(Set<VlanId> available) {
147 if (!available.isEmpty()) {
148 int size = available.size();
149 int index = RandomUtils.nextInt(size);
150 return Iterables.get(available, index);
151 }
152 return VlanId.vlanId(VlanId.NO_VID);
153 }
154
155 /**
156 * Select a VLAN id from the set of available VLAN ids.
157 *
158 * @param available the set of available VLAN ids.
159 * @return the chosen VLAN id.
160 */
161 private VlanId selectVlanId(Set<VlanId> available) {
162 return RANDOM_SELECTION ? randomSelection(available) : firsFitSelection(available);
163 }
164
Ray Milkey661c38c2016-02-26 17:12:17 -0800165 private Map<LinkKey, VlanId> findVlanIds(PathCompilerCreateFlow creator, Set<LinkKey> links) {
166 Map<LinkKey, VlanId> vlanIds = new HashMap<>();
167 for (LinkKey link : links) {
168 Set<VlanId> forward = findVlanId(creator, link.src());
169 Set<VlanId> backward = findVlanId(creator, link.dst());
170 Set<VlanId> common = Sets.intersection(forward, backward);
171 if (common.isEmpty()) {
172 continue;
173 }
Pier Luigi Ventre51313bd2016-06-02 10:50:30 +0200174 VlanId selected = selectVlanId(common);
175 if (selected.toShort() == VlanId.NO_VID) {
176 continue;
177 }
178 vlanIds.put(link, selected);
Ray Milkey661c38c2016-02-26 17:12:17 -0800179 }
180 return vlanIds;
181 }
182
183 private Set<VlanId> findVlanId(PathCompilerCreateFlow creator, ConnectPoint cp) {
184 return creator.resourceService().getAvailableResourceValues(
185 Resources.discrete(cp.deviceId(), cp.port()).id(),
186 VlanId.class);
187 }
188
189 private void manageVlanEncap(PathCompilerCreateFlow<T> creator, List<T> flows,
190 List<DeviceId> devices,
191 PathIntent intent) {
192 Map<LinkKey, VlanId> vlanIds = assignVlanId(creator, intent);
193
194 Iterator<Link> links = intent.path().links().iterator();
195 Link srcLink = links.next();
196
197 Link link = links.next();
198
199 // Ingress traffic
200 VlanId vlanId = vlanIds.get(linkKey(link));
201 if (vlanId == null) {
202 throw new IntentCompilationException("No available VLAN ID for " + link);
203 }
204 VlanId prevVlanId = vlanId;
205
206 Optional<VlanIdCriterion> vlanCriterion = intent.selector().criteria()
207 .stream().filter(criterion -> criterion.type() == Criterion.Type.VLAN_VID)
208 .map(criterion -> (VlanIdCriterion) criterion)
209 .findAny();
210
211 //Push VLAN if selector does not include VLAN
212 TrafficTreatment.Builder treatBuilder = DefaultTrafficTreatment.builder();
213 if (!vlanCriterion.isPresent()) {
214 treatBuilder.pushVlan();
215 }
216 //Tag the traffic with the new encapsulation VLAN
217 treatBuilder.setVlanId(vlanId);
218 creator.createFlow(intent.selector(), treatBuilder.build(),
219 srcLink.dst(), link.src(), intent.priority(), true,
220 flows, devices);
221
222 ConnectPoint prev = link.dst();
223
224 while (links.hasNext()) {
225
226 link = links.next();
227
228 if (links.hasNext()) {
229 // Transit traffic
230 VlanId egressVlanId = vlanIds.get(linkKey(link));
231 if (egressVlanId == null) {
232 throw new IntentCompilationException("No available VLAN ID for " + link);
233 }
Ray Milkey661c38c2016-02-26 17:12:17 -0800234
235 TrafficSelector transitSelector = DefaultTrafficSelector.builder()
236 .matchInPort(prev.port())
237 .matchVlanId(prevVlanId).build();
238
239 TrafficTreatment.Builder transitTreat = DefaultTrafficTreatment.builder();
240
241 // Set the new vlanId only if the previous one is different
242 if (!prevVlanId.equals(egressVlanId)) {
243 transitTreat.setVlanId(egressVlanId);
244 }
245 creator.createFlow(transitSelector,
246 transitTreat.build(), prev, link.src(),
247 intent.priority(), true, flows, devices);
Pier Luigi Ventre51313bd2016-06-02 10:50:30 +0200248 /* For the next hop we have to remember
249 * the previous egress VLAN id and the egress
250 * node
251 */
252 prevVlanId = egressVlanId;
Ray Milkey661c38c2016-02-26 17:12:17 -0800253 prev = link.dst();
254 } else {
255 // Egress traffic
256 TrafficSelector egressSelector = DefaultTrafficSelector.builder()
257 .matchInPort(prev.port())
258 .matchVlanId(prevVlanId).build();
259 TrafficTreatment.Builder egressTreat = DefaultTrafficTreatment.builder(intent.treatment());
260
261 Optional<L2ModificationInstruction.ModVlanIdInstruction> modVlanIdInstruction = intent.treatment()
262 .allInstructions().stream().filter(
263 instruction -> instruction instanceof L2ModificationInstruction.ModVlanIdInstruction)
264 .map(x -> (L2ModificationInstruction.ModVlanIdInstruction) x).findAny();
265
Jian Li11260a02016-05-19 13:07:22 -0700266 Optional<L2ModificationInstruction.ModVlanHeaderInstruction> popVlanInstruction = intent.treatment()
Ray Milkey661c38c2016-02-26 17:12:17 -0800267 .allInstructions().stream().filter(
Jian Li11260a02016-05-19 13:07:22 -0700268 instruction -> instruction instanceof
269 L2ModificationInstruction.ModVlanHeaderInstruction)
270 .map(x -> (L2ModificationInstruction.ModVlanHeaderInstruction) x).findAny();
Ray Milkey661c38c2016-02-26 17:12:17 -0800271
272 if (!modVlanIdInstruction.isPresent() && !popVlanInstruction.isPresent()) {
273 if (vlanCriterion.isPresent()) {
274 egressTreat.setVlanId(vlanCriterion.get().vlanId());
275 } else {
276 egressTreat.popVlan();
277 }
278 }
279
280 creator.createFlow(egressSelector,
281 egressTreat.build(), prev, link.src(),
282 intent.priority(), true, flows, devices);
283 }
284 }
285 }
286
Michele Santuari6096acd2016-02-09 17:00:37 +0100287 private Map<LinkKey, MplsLabel> assignMplsLabel(PathCompilerCreateFlow creator, PathIntent intent) {
288 Set<LinkKey> linkRequest =
289 Sets.newHashSetWithExpectedSize(intent.path()
290 .links().size() - 2);
291 for (int i = 1; i <= intent.path().links().size() - 2; i++) {
292 LinkKey link = linkKey(intent.path().links().get(i));
293 linkRequest.add(link);
294 // add the inverse link. I want that the VLANID is reserved both for
295 // the direct and inverse link
296 linkRequest.add(linkKey(link.dst(), link.src()));
297 }
298
299 Map<LinkKey, MplsLabel> labels = findMplsLabels(creator, linkRequest);
300 if (labels.isEmpty()) {
301 throw new IntentCompilationException("No available MPLS Label");
302 }
303
304 // for short term solution: same label is used for both directions
305 // TODO: introduce the concept of Tx and Rx resources of a port
306 Set<Resource> resources = labels.entrySet().stream()
307 .flatMap(x -> Stream.of(
308 Resources.discrete(x.getKey().src().deviceId(), x.getKey().src().port(), x.getValue())
309 .resource(),
310 Resources.discrete(x.getKey().dst().deviceId(), x.getKey().dst().port(), x.getValue())
311 .resource()
312 ))
313 .collect(Collectors.toSet());
314 List<ResourceAllocation> allocations =
315 creator.resourceService().allocate(intent.id(), ImmutableList.copyOf(resources));
316 if (allocations.isEmpty()) {
Sho SHIMIZUec932bd2016-03-31 11:09:36 -0700317 return Collections.emptyMap();
Michele Santuari6096acd2016-02-09 17:00:37 +0100318 }
319
320 return labels;
321 }
322
323 private Map<LinkKey, MplsLabel> findMplsLabels(PathCompilerCreateFlow creator, Set<LinkKey> links) {
324 Map<LinkKey, MplsLabel> labels = new HashMap<>();
325 for (LinkKey link : links) {
326 Set<MplsLabel> forward = findMplsLabel(creator, link.src());
327 Set<MplsLabel> backward = findMplsLabel(creator, link.dst());
328 Set<MplsLabel> common = Sets.intersection(forward, backward);
329 if (common.isEmpty()) {
330 continue;
331 }
332 labels.put(link, common.iterator().next());
333 }
334
335 return labels;
336 }
337
338 private Set<MplsLabel> findMplsLabel(PathCompilerCreateFlow creator, ConnectPoint cp) {
339 return creator.resourceService().getAvailableResourceValues(
340 Resources.discrete(cp.deviceId(), cp.port()).id(),
341 MplsLabel.class);
342 }
343
344 private void manageMplsEncap(PathCompilerCreateFlow<T> creator, List<T> flows,
345 List<DeviceId> devices,
346 PathIntent intent) {
347 Map<LinkKey, MplsLabel> mplsLabels = assignMplsLabel(creator, intent);
348
349 Iterator<Link> links = intent.path().links().iterator();
350 Link srcLink = links.next();
351
352 Link link = links.next();
353 // List of flow rules to be installed
354
355 // Ingress traffic
356 MplsLabel mplsLabel = mplsLabels.get(linkKey(link));
357 if (mplsLabel == null) {
358 throw new IntentCompilationException("No available MPLS Label for " + link);
359 }
360 MplsLabel prevMplsLabel = mplsLabel;
361
362 Optional<MplsCriterion> mplsCriterion = intent.selector().criteria()
363 .stream().filter(criterion -> criterion.type() == Criterion.Type.MPLS_LABEL)
364 .map(criterion -> (MplsCriterion) criterion)
365 .findAny();
366
367 //Push MPLS if selector does not include MPLS
368 TrafficTreatment.Builder treatBuilder = DefaultTrafficTreatment.builder();
369 if (!mplsCriterion.isPresent()) {
370 treatBuilder.pushMpls();
371 }
372 //Tag the traffic with the new encapsulation MPLS label
373 treatBuilder.setMpls(mplsLabel);
374 creator.createFlow(intent.selector(), treatBuilder.build(),
375 srcLink.dst(), link.src(), intent.priority(), true, flows, devices);
376
377 ConnectPoint prev = link.dst();
378
379 while (links.hasNext()) {
380
381 link = links.next();
382
383 if (links.hasNext()) {
384 // Transit traffic
385 MplsLabel transitMplsLabel = mplsLabels.get(linkKey(link));
386 if (transitMplsLabel == null) {
387 throw new IntentCompilationException("No available MPLS label for " + link);
388 }
389 prevMplsLabel = transitMplsLabel;
390
391 TrafficSelector transitSelector = DefaultTrafficSelector.builder()
392 .matchInPort(prev.port())
393 .matchEthType(Ethernet.MPLS_UNICAST)
394 .matchMplsLabel(prevMplsLabel).build();
395
396 TrafficTreatment.Builder transitTreat = DefaultTrafficTreatment.builder();
397
398 // Set the new MPLS Label only if the previous one is different
399 if (!prevMplsLabel.equals(transitMplsLabel)) {
400 transitTreat.setMpls(transitMplsLabel);
401 }
402 creator.createFlow(transitSelector,
403 transitTreat.build(), prev, link.src(), intent.priority(), true, flows, devices);
404 prev = link.dst();
405 } else {
406 TrafficSelector.Builder egressSelector = DefaultTrafficSelector.builder()
407 .matchInPort(prev.port())
408 .matchEthType(Ethernet.MPLS_UNICAST)
409 .matchMplsLabel(prevMplsLabel);
410 TrafficTreatment.Builder egressTreat = DefaultTrafficTreatment.builder(intent.treatment());
411
412 // Egress traffic
413 // check if the treatement is popVlan or setVlan (rewrite),
414 // than selector needs to match any VlanId
415 for (Instruction instruct : intent.treatment().allInstructions()) {
416 if (instruct instanceof L2ModificationInstruction) {
417 L2ModificationInstruction l2Mod = (L2ModificationInstruction) instruct;
418 if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_PUSH) {
419 break;
420 }
421 if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP ||
422 l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_ID) {
423 egressSelector.matchVlanId(VlanId.ANY);
424 }
425 }
426 }
427
428 if (mplsCriterion.isPresent()) {
429 egressTreat.setMpls(mplsCriterion.get().label());
430 } else {
431 egressTreat.popMpls(outputEthType(intent.selector()));
432 }
433
434
435 if (mplsCriterion.isPresent()) {
436 egressTreat.setMpls(mplsCriterion.get().label());
437 } else {
438 egressTreat.popVlan();
439 }
440
441 creator.createFlow(egressSelector.build(),
442 egressTreat.build(), prev, link.src(), intent.priority(), true, flows, devices);
443 }
444
445 }
446
447 }
448
449 private MplsLabel getMplsLabel(Map<LinkKey, MplsLabel> labels, LinkKey link) {
450 return labels.get(link);
451 }
452
453 // if the ingress ethertype is defined, the egress traffic
454 // will be use that value, otherwise the IPv4 ethertype is used.
455 private EthType outputEthType(TrafficSelector selector) {
456 Criterion c = selector.getCriterion(Criterion.Type.ETH_TYPE);
457 if (c != null && c instanceof EthTypeCriterion) {
458 EthTypeCriterion ethertype = (EthTypeCriterion) c;
459 return ethertype.ethType();
460 } else {
461 return EthType.EtherType.IPV4.ethType();
462 }
463 }
464
465
Ray Milkey661c38c2016-02-26 17:12:17 -0800466 /**
467 * Compiles an intent down to flows.
468 *
469 * @param creator how to create the flows
470 * @param intent intent to process
471 * @param flows list of generated flows
472 * @param devices list of devices that correspond to the flows
473 */
474 public void compile(PathCompilerCreateFlow<T> creator,
475 PathIntent intent,
476 List<T> flows,
477 List<DeviceId> devices) {
478 // Note: right now recompile is not considered
479 // TODO: implement recompile behavior
480
481 List<Link> links = intent.path().links();
482
483 Optional<EncapsulationConstraint> encapConstraint = intent.constraints().stream()
484 .filter(constraint -> constraint instanceof EncapsulationConstraint)
485 .map(x -> (EncapsulationConstraint) x).findAny();
486 //if no encapsulation or is involved only a single switch use the default behaviour
Pier1677f9f2016-07-06 15:42:17 +0200487 if (!encapConstraint.isPresent() || links.size() == 2) {
Ray Milkey661c38c2016-02-26 17:12:17 -0800488 for (int i = 0; i < links.size() - 1; i++) {
489 ConnectPoint ingress = links.get(i).dst();
490 ConnectPoint egress = links.get(i + 1).src();
491 creator.createFlow(intent.selector(), intent.treatment(),
492 ingress, egress, intent.priority(),
493 isLast(links, i), flows, devices);
494 }
Pier1677f9f2016-07-06 15:42:17 +0200495 return;
Ray Milkey661c38c2016-02-26 17:12:17 -0800496 }
497
498 encapConstraint.map(EncapsulationConstraint::encapType)
499 .map(type -> {
500 switch (type) {
501 case VLAN:
502 manageVlanEncap(creator, flows, devices, intent);
Michele Santuari6096acd2016-02-09 17:00:37 +0100503 break;
504 case MPLS:
505 manageMplsEncap(creator, flows, devices, intent);
506 break;
Ray Milkey661c38c2016-02-26 17:12:17 -0800507 default:
508 // Nothing to do
509 }
510 return 0;
511 });
512 }
513
514}