blob: 3f8bba4128daeccb595c022ffd5f3cd255357ab8 [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;
19import com.google.common.collect.Sets;
Michele Santuari6096acd2016-02-09 17:00:37 +010020import org.onlab.packet.EthType;
21import org.onlab.packet.Ethernet;
22import org.onlab.packet.MplsLabel;
Ray Milkey661c38c2016-02-26 17:12:17 -080023import org.onlab.packet.VlanId;
24import org.onosproject.net.ConnectPoint;
25import org.onosproject.net.DeviceId;
26import org.onosproject.net.Link;
27import org.onosproject.net.LinkKey;
28import org.onosproject.net.flow.DefaultTrafficSelector;
29import org.onosproject.net.flow.DefaultTrafficTreatment;
30import org.onosproject.net.flow.TrafficSelector;
31import org.onosproject.net.flow.TrafficTreatment;
32import org.onosproject.net.flow.criteria.Criterion;
Michele Santuari6096acd2016-02-09 17:00:37 +010033import org.onosproject.net.flow.criteria.EthTypeCriterion;
34import org.onosproject.net.flow.criteria.MplsCriterion;
Ray Milkey661c38c2016-02-26 17:12:17 -080035import org.onosproject.net.flow.criteria.VlanIdCriterion;
Michele Santuari6096acd2016-02-09 17:00:37 +010036import org.onosproject.net.flow.instructions.Instruction;
Ray Milkey661c38c2016-02-26 17:12:17 -080037import org.onosproject.net.flow.instructions.L2ModificationInstruction;
38import org.onosproject.net.intent.PathIntent;
39import org.onosproject.net.intent.constraint.EncapsulationConstraint;
40import org.onosproject.net.intent.impl.IntentCompilationException;
Sho SHIMIZUe18cb122016-02-22 21:04:56 -080041import org.onosproject.net.resource.Resource;
42import org.onosproject.net.resource.ResourceAllocation;
43import org.onosproject.net.resource.ResourceService;
44import org.onosproject.net.resource.Resources;
Ray Milkey661c38c2016-02-26 17:12:17 -080045import org.slf4j.Logger;
46
Jian Li11260a02016-05-19 13:07:22 -070047import java.util.Collections;
48import java.util.HashMap;
49import java.util.Iterator;
50import java.util.List;
51import java.util.Map;
52import java.util.Optional;
53import java.util.Set;
54import java.util.stream.Collectors;
55import java.util.stream.Stream;
Ray Milkey661c38c2016-02-26 17:12:17 -080056
57import static org.onosproject.net.LinkKey.linkKey;
58
59/**
60 * Shared APIs and implementations for path compilers.
61 */
62
63public class PathCompiler<T> {
64
65 /**
66 * Defines methods used to create objects representing flows.
67 */
68 public interface PathCompilerCreateFlow<T> {
69
70 void createFlow(TrafficSelector originalSelector,
71 TrafficTreatment originalTreatment,
72 ConnectPoint ingress, ConnectPoint egress,
73 int priority,
74 boolean applyTreatment,
75 List<T> flows,
76 List<DeviceId> devices);
77
78 Logger log();
79
80 ResourceService resourceService();
81 }
82
83 private boolean isLast(List<Link> links, int i) {
84 return i == links.size() - 2;
85 }
86
87 private Map<LinkKey, VlanId> assignVlanId(PathCompilerCreateFlow creator, PathIntent intent) {
88 Set<LinkKey> linkRequest =
89 Sets.newHashSetWithExpectedSize(intent.path()
90 .links().size() - 2);
91 for (int i = 1; i <= intent.path().links().size() - 2; i++) {
92 LinkKey link = linkKey(intent.path().links().get(i));
93 linkRequest.add(link);
94 // add the inverse link. I want that the VLANID is reserved both for
95 // the direct and inverse link
96 linkRequest.add(linkKey(link.dst(), link.src()));
97 }
98
99 Map<LinkKey, VlanId> vlanIds = findVlanIds(creator, linkRequest);
100 if (vlanIds.isEmpty()) {
101 creator.log().warn("No VLAN IDs available");
102 return Collections.emptyMap();
103 }
104
105 //same VLANID is used for both directions
106 Set<Resource> resources = vlanIds.entrySet().stream()
107 .flatMap(x -> Stream.of(
108 Resources.discrete(x.getKey().src().deviceId(), x.getKey().src().port(), x.getValue())
109 .resource(),
110 Resources.discrete(x.getKey().dst().deviceId(), x.getKey().dst().port(), x.getValue())
111 .resource()
112 ))
113 .collect(Collectors.toSet());
Sho SHIMIZUe18cb122016-02-22 21:04:56 -0800114 List<ResourceAllocation> allocations =
Ray Milkey661c38c2016-02-26 17:12:17 -0800115 creator.resourceService().allocate(intent.id(), ImmutableList.copyOf(resources));
116 if (allocations.isEmpty()) {
Sho SHIMIZUf36a8362016-03-30 11:09:57 -0700117 return Collections.emptyMap();
Ray Milkey661c38c2016-02-26 17:12:17 -0800118 }
119
120 return vlanIds;
121 }
122
123 private Map<LinkKey, VlanId> findVlanIds(PathCompilerCreateFlow creator, Set<LinkKey> links) {
124 Map<LinkKey, VlanId> vlanIds = new HashMap<>();
125 for (LinkKey link : links) {
126 Set<VlanId> forward = findVlanId(creator, link.src());
127 Set<VlanId> backward = findVlanId(creator, link.dst());
128 Set<VlanId> common = Sets.intersection(forward, backward);
129 if (common.isEmpty()) {
130 continue;
131 }
132 vlanIds.put(link, common.iterator().next());
133 }
134 return vlanIds;
135 }
136
137 private Set<VlanId> findVlanId(PathCompilerCreateFlow creator, ConnectPoint cp) {
138 return creator.resourceService().getAvailableResourceValues(
139 Resources.discrete(cp.deviceId(), cp.port()).id(),
140 VlanId.class);
141 }
142
143 private void manageVlanEncap(PathCompilerCreateFlow<T> creator, List<T> flows,
144 List<DeviceId> devices,
145 PathIntent intent) {
146 Map<LinkKey, VlanId> vlanIds = assignVlanId(creator, intent);
147
148 Iterator<Link> links = intent.path().links().iterator();
149 Link srcLink = links.next();
150
151 Link link = links.next();
152
153 // Ingress traffic
154 VlanId vlanId = vlanIds.get(linkKey(link));
155 if (vlanId == null) {
156 throw new IntentCompilationException("No available VLAN ID for " + link);
157 }
158 VlanId prevVlanId = vlanId;
159
160 Optional<VlanIdCriterion> vlanCriterion = intent.selector().criteria()
161 .stream().filter(criterion -> criterion.type() == Criterion.Type.VLAN_VID)
162 .map(criterion -> (VlanIdCriterion) criterion)
163 .findAny();
164
165 //Push VLAN if selector does not include VLAN
166 TrafficTreatment.Builder treatBuilder = DefaultTrafficTreatment.builder();
167 if (!vlanCriterion.isPresent()) {
168 treatBuilder.pushVlan();
169 }
170 //Tag the traffic with the new encapsulation VLAN
171 treatBuilder.setVlanId(vlanId);
172 creator.createFlow(intent.selector(), treatBuilder.build(),
173 srcLink.dst(), link.src(), intent.priority(), true,
174 flows, devices);
175
176 ConnectPoint prev = link.dst();
177
178 while (links.hasNext()) {
179
180 link = links.next();
181
182 if (links.hasNext()) {
183 // Transit traffic
184 VlanId egressVlanId = vlanIds.get(linkKey(link));
185 if (egressVlanId == null) {
186 throw new IntentCompilationException("No available VLAN ID for " + link);
187 }
188 prevVlanId = egressVlanId;
189
190 TrafficSelector transitSelector = DefaultTrafficSelector.builder()
191 .matchInPort(prev.port())
192 .matchVlanId(prevVlanId).build();
193
194 TrafficTreatment.Builder transitTreat = DefaultTrafficTreatment.builder();
195
196 // Set the new vlanId only if the previous one is different
197 if (!prevVlanId.equals(egressVlanId)) {
198 transitTreat.setVlanId(egressVlanId);
199 }
200 creator.createFlow(transitSelector,
201 transitTreat.build(), prev, link.src(),
202 intent.priority(), true, flows, devices);
203 prev = link.dst();
204 } else {
205 // Egress traffic
206 TrafficSelector egressSelector = DefaultTrafficSelector.builder()
207 .matchInPort(prev.port())
208 .matchVlanId(prevVlanId).build();
209 TrafficTreatment.Builder egressTreat = DefaultTrafficTreatment.builder(intent.treatment());
210
211 Optional<L2ModificationInstruction.ModVlanIdInstruction> modVlanIdInstruction = intent.treatment()
212 .allInstructions().stream().filter(
213 instruction -> instruction instanceof L2ModificationInstruction.ModVlanIdInstruction)
214 .map(x -> (L2ModificationInstruction.ModVlanIdInstruction) x).findAny();
215
Jian Li11260a02016-05-19 13:07:22 -0700216 Optional<L2ModificationInstruction.ModVlanHeaderInstruction> popVlanInstruction = intent.treatment()
Ray Milkey661c38c2016-02-26 17:12:17 -0800217 .allInstructions().stream().filter(
Jian Li11260a02016-05-19 13:07:22 -0700218 instruction -> instruction instanceof
219 L2ModificationInstruction.ModVlanHeaderInstruction)
220 .map(x -> (L2ModificationInstruction.ModVlanHeaderInstruction) x).findAny();
Ray Milkey661c38c2016-02-26 17:12:17 -0800221
222 if (!modVlanIdInstruction.isPresent() && !popVlanInstruction.isPresent()) {
223 if (vlanCriterion.isPresent()) {
224 egressTreat.setVlanId(vlanCriterion.get().vlanId());
225 } else {
226 egressTreat.popVlan();
227 }
228 }
229
230 creator.createFlow(egressSelector,
231 egressTreat.build(), prev, link.src(),
232 intent.priority(), true, flows, devices);
233 }
234 }
235 }
236
Michele Santuari6096acd2016-02-09 17:00:37 +0100237 private Map<LinkKey, MplsLabel> assignMplsLabel(PathCompilerCreateFlow creator, PathIntent intent) {
238 Set<LinkKey> linkRequest =
239 Sets.newHashSetWithExpectedSize(intent.path()
240 .links().size() - 2);
241 for (int i = 1; i <= intent.path().links().size() - 2; i++) {
242 LinkKey link = linkKey(intent.path().links().get(i));
243 linkRequest.add(link);
244 // add the inverse link. I want that the VLANID is reserved both for
245 // the direct and inverse link
246 linkRequest.add(linkKey(link.dst(), link.src()));
247 }
248
249 Map<LinkKey, MplsLabel> labels = findMplsLabels(creator, linkRequest);
250 if (labels.isEmpty()) {
251 throw new IntentCompilationException("No available MPLS Label");
252 }
253
254 // for short term solution: same label is used for both directions
255 // TODO: introduce the concept of Tx and Rx resources of a port
256 Set<Resource> resources = labels.entrySet().stream()
257 .flatMap(x -> Stream.of(
258 Resources.discrete(x.getKey().src().deviceId(), x.getKey().src().port(), x.getValue())
259 .resource(),
260 Resources.discrete(x.getKey().dst().deviceId(), x.getKey().dst().port(), x.getValue())
261 .resource()
262 ))
263 .collect(Collectors.toSet());
264 List<ResourceAllocation> allocations =
265 creator.resourceService().allocate(intent.id(), ImmutableList.copyOf(resources));
266 if (allocations.isEmpty()) {
Sho SHIMIZUec932bd2016-03-31 11:09:36 -0700267 return Collections.emptyMap();
Michele Santuari6096acd2016-02-09 17:00:37 +0100268 }
269
270 return labels;
271 }
272
273 private Map<LinkKey, MplsLabel> findMplsLabels(PathCompilerCreateFlow creator, Set<LinkKey> links) {
274 Map<LinkKey, MplsLabel> labels = new HashMap<>();
275 for (LinkKey link : links) {
276 Set<MplsLabel> forward = findMplsLabel(creator, link.src());
277 Set<MplsLabel> backward = findMplsLabel(creator, link.dst());
278 Set<MplsLabel> common = Sets.intersection(forward, backward);
279 if (common.isEmpty()) {
280 continue;
281 }
282 labels.put(link, common.iterator().next());
283 }
284
285 return labels;
286 }
287
288 private Set<MplsLabel> findMplsLabel(PathCompilerCreateFlow creator, ConnectPoint cp) {
289 return creator.resourceService().getAvailableResourceValues(
290 Resources.discrete(cp.deviceId(), cp.port()).id(),
291 MplsLabel.class);
292 }
293
294 private void manageMplsEncap(PathCompilerCreateFlow<T> creator, List<T> flows,
295 List<DeviceId> devices,
296 PathIntent intent) {
297 Map<LinkKey, MplsLabel> mplsLabels = assignMplsLabel(creator, intent);
298
299 Iterator<Link> links = intent.path().links().iterator();
300 Link srcLink = links.next();
301
302 Link link = links.next();
303 // List of flow rules to be installed
304
305 // Ingress traffic
306 MplsLabel mplsLabel = mplsLabels.get(linkKey(link));
307 if (mplsLabel == null) {
308 throw new IntentCompilationException("No available MPLS Label for " + link);
309 }
310 MplsLabel prevMplsLabel = mplsLabel;
311
312 Optional<MplsCriterion> mplsCriterion = intent.selector().criteria()
313 .stream().filter(criterion -> criterion.type() == Criterion.Type.MPLS_LABEL)
314 .map(criterion -> (MplsCriterion) criterion)
315 .findAny();
316
317 //Push MPLS if selector does not include MPLS
318 TrafficTreatment.Builder treatBuilder = DefaultTrafficTreatment.builder();
319 if (!mplsCriterion.isPresent()) {
320 treatBuilder.pushMpls();
321 }
322 //Tag the traffic with the new encapsulation MPLS label
323 treatBuilder.setMpls(mplsLabel);
324 creator.createFlow(intent.selector(), treatBuilder.build(),
325 srcLink.dst(), link.src(), intent.priority(), true, flows, devices);
326
327 ConnectPoint prev = link.dst();
328
329 while (links.hasNext()) {
330
331 link = links.next();
332
333 if (links.hasNext()) {
334 // Transit traffic
335 MplsLabel transitMplsLabel = mplsLabels.get(linkKey(link));
336 if (transitMplsLabel == null) {
337 throw new IntentCompilationException("No available MPLS label for " + link);
338 }
339 prevMplsLabel = transitMplsLabel;
340
341 TrafficSelector transitSelector = DefaultTrafficSelector.builder()
342 .matchInPort(prev.port())
343 .matchEthType(Ethernet.MPLS_UNICAST)
344 .matchMplsLabel(prevMplsLabel).build();
345
346 TrafficTreatment.Builder transitTreat = DefaultTrafficTreatment.builder();
347
348 // Set the new MPLS Label only if the previous one is different
349 if (!prevMplsLabel.equals(transitMplsLabel)) {
350 transitTreat.setMpls(transitMplsLabel);
351 }
352 creator.createFlow(transitSelector,
353 transitTreat.build(), prev, link.src(), intent.priority(), true, flows, devices);
354 prev = link.dst();
355 } else {
356 TrafficSelector.Builder egressSelector = DefaultTrafficSelector.builder()
357 .matchInPort(prev.port())
358 .matchEthType(Ethernet.MPLS_UNICAST)
359 .matchMplsLabel(prevMplsLabel);
360 TrafficTreatment.Builder egressTreat = DefaultTrafficTreatment.builder(intent.treatment());
361
362 // Egress traffic
363 // check if the treatement is popVlan or setVlan (rewrite),
364 // than selector needs to match any VlanId
365 for (Instruction instruct : intent.treatment().allInstructions()) {
366 if (instruct instanceof L2ModificationInstruction) {
367 L2ModificationInstruction l2Mod = (L2ModificationInstruction) instruct;
368 if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_PUSH) {
369 break;
370 }
371 if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP ||
372 l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_ID) {
373 egressSelector.matchVlanId(VlanId.ANY);
374 }
375 }
376 }
377
378 if (mplsCriterion.isPresent()) {
379 egressTreat.setMpls(mplsCriterion.get().label());
380 } else {
381 egressTreat.popMpls(outputEthType(intent.selector()));
382 }
383
384
385 if (mplsCriterion.isPresent()) {
386 egressTreat.setMpls(mplsCriterion.get().label());
387 } else {
388 egressTreat.popVlan();
389 }
390
391 creator.createFlow(egressSelector.build(),
392 egressTreat.build(), prev, link.src(), intent.priority(), true, flows, devices);
393 }
394
395 }
396
397 }
398
399 private MplsLabel getMplsLabel(Map<LinkKey, MplsLabel> labels, LinkKey link) {
400 return labels.get(link);
401 }
402
403 // if the ingress ethertype is defined, the egress traffic
404 // will be use that value, otherwise the IPv4 ethertype is used.
405 private EthType outputEthType(TrafficSelector selector) {
406 Criterion c = selector.getCriterion(Criterion.Type.ETH_TYPE);
407 if (c != null && c instanceof EthTypeCriterion) {
408 EthTypeCriterion ethertype = (EthTypeCriterion) c;
409 return ethertype.ethType();
410 } else {
411 return EthType.EtherType.IPV4.ethType();
412 }
413 }
414
415
Ray Milkey661c38c2016-02-26 17:12:17 -0800416 /**
417 * Compiles an intent down to flows.
418 *
419 * @param creator how to create the flows
420 * @param intent intent to process
421 * @param flows list of generated flows
422 * @param devices list of devices that correspond to the flows
423 */
424 public void compile(PathCompilerCreateFlow<T> creator,
425 PathIntent intent,
426 List<T> flows,
427 List<DeviceId> devices) {
428 // Note: right now recompile is not considered
429 // TODO: implement recompile behavior
430
431 List<Link> links = intent.path().links();
432
433 Optional<EncapsulationConstraint> encapConstraint = intent.constraints().stream()
434 .filter(constraint -> constraint instanceof EncapsulationConstraint)
435 .map(x -> (EncapsulationConstraint) x).findAny();
436 //if no encapsulation or is involved only a single switch use the default behaviour
437 if (!encapConstraint.isPresent() || links.size() == 1) {
438 for (int i = 0; i < links.size() - 1; i++) {
439 ConnectPoint ingress = links.get(i).dst();
440 ConnectPoint egress = links.get(i + 1).src();
441 creator.createFlow(intent.selector(), intent.treatment(),
442 ingress, egress, intent.priority(),
443 isLast(links, i), flows, devices);
444 }
445 }
446
447 encapConstraint.map(EncapsulationConstraint::encapType)
448 .map(type -> {
449 switch (type) {
450 case VLAN:
451 manageVlanEncap(creator, flows, devices, intent);
Michele Santuari6096acd2016-02-09 17:00:37 +0100452 break;
453 case MPLS:
454 manageMplsEncap(creator, flows, devices, intent);
455 break;
Ray Milkey661c38c2016-02-26 17:12:17 -0800456 default:
457 // Nothing to do
458 }
459 return 0;
460 });
461 }
462
463}