blob: 3af3c3e0f4c184f42f604d15e9619f0c8a54e023 [file] [log] [blame]
Carmelo Casconeb5324e72018-11-25 02:26:32 -08001/*
2 * Copyright 2017-present Open Networking Foundation
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 */
16
17package org.onosproject.pipelines.fabric.pipeliner;
18
19import com.google.common.collect.Lists;
Carmelo Casconed06a8512018-12-02 16:34:20 -080020import org.onlab.packet.VlanId;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080021import org.onosproject.net.DeviceId;
22import org.onosproject.net.PortNumber;
23import org.onosproject.net.flow.DefaultTrafficSelector;
24import org.onosproject.net.flow.DefaultTrafficTreatment;
25import org.onosproject.net.flow.TrafficSelector;
26import org.onosproject.net.flow.TrafficTreatment;
27import org.onosproject.net.flow.criteria.Criterion;
28import org.onosproject.net.flow.criteria.PiCriterion;
29import org.onosproject.net.flow.criteria.VlanIdCriterion;
30import org.onosproject.net.flow.instructions.Instruction;
Carmelo Casconed06a8512018-12-02 16:34:20 -080031import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080032import org.onosproject.net.flowobjective.DefaultNextTreatment;
33import org.onosproject.net.flowobjective.NextObjective;
34import org.onosproject.net.flowobjective.NextTreatment;
35import org.onosproject.net.flowobjective.Objective;
36import org.onosproject.net.flowobjective.ObjectiveError;
37import org.onosproject.net.group.DefaultGroupBucket;
38import org.onosproject.net.group.DefaultGroupDescription;
39import org.onosproject.net.group.DefaultGroupKey;
40import org.onosproject.net.group.GroupBucket;
41import org.onosproject.net.group.GroupBuckets;
42import org.onosproject.net.group.GroupDescription;
43import org.onosproject.net.group.GroupKey;
44import org.onosproject.net.pi.model.PiTableId;
45import org.onosproject.net.pi.runtime.PiAction;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080046import org.onosproject.net.pi.runtime.PiActionParam;
Carmelo Casconecb4327a2018-09-11 15:17:23 -070047import org.onosproject.net.pi.runtime.PiActionProfileGroupId;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080048import org.onosproject.net.pi.runtime.PiGroupKey;
49import org.onosproject.pipelines.fabric.FabricCapabilities;
50import org.onosproject.pipelines.fabric.FabricConstants;
51import org.onosproject.pipelines.fabric.FabricUtils;
52
53import java.util.Collection;
54import java.util.List;
55import java.util.Objects;
56import java.util.Set;
57import java.util.stream.Collectors;
58
59import static java.lang.String.format;
Carmelo Casconed06a8512018-12-02 16:34:20 -080060import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_ID;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080061import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP;
62import static org.onosproject.pipelines.fabric.FabricUtils.criterion;
63import static org.onosproject.pipelines.fabric.FabricUtils.l2Instruction;
64import static org.onosproject.pipelines.fabric.FabricUtils.outputPort;
65
66/**
67 * ObjectiveTranslator implementation for NextObjective.
68 */
69class NextObjectiveTranslator
70 extends AbstractObjectiveTranslator<NextObjective> {
71
Carmelo Cascone45cc0862018-11-26 11:50:41 -080072 private static final String XCONNECT = "xconnect";
73
Carmelo Casconeb5324e72018-11-25 02:26:32 -080074 NextObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
75 super(deviceId, capabilities);
76 }
77
78 @Override
79 public ObjectiveTranslation doTranslate(NextObjective obj)
80 throws FabricPipelinerException {
81
82 final ObjectiveTranslation.Builder resultBuilder =
83 ObjectiveTranslation.builder();
84
85 switch (obj.type()) {
86 case SIMPLE:
87 simpleNext(obj, resultBuilder, false);
88 break;
89 case HASHED:
90 hashedNext(obj, resultBuilder);
91 break;
92 case BROADCAST:
Carmelo Cascone45cc0862018-11-26 11:50:41 -080093 if (isXconnect(obj)) {
94 xconnectNext(obj, resultBuilder);
95 } else {
96 multicastNext(obj, resultBuilder);
97 }
Carmelo Casconeb5324e72018-11-25 02:26:32 -080098 break;
99 default:
100 log.warn("Unsupported NextObjective type '{}'", obj);
101 return ObjectiveTranslation.ofError(ObjectiveError.UNSUPPORTED);
102 }
103
104 if (!isGroupModifyOp(obj)) {
105 // Generate next VLAN rules.
106 nextVlan(obj, resultBuilder);
107 }
108
109 return resultBuilder.build();
110 }
111
112 private void nextVlan(NextObjective obj,
113 ObjectiveTranslation.Builder resultBuilder)
114 throws FabricPipelinerException {
Carmelo Casconed06a8512018-12-02 16:34:20 -0800115
116 // Set the egress VLAN for this next ID. Expect either a VLAN_ID
117 // criterion in meta, or a VLAN_ID instruction in the treatment (if
118 // SIMPLE, e.g. for a routing rule).
119
120 final VlanIdCriterion vlanIdCriterion = obj.meta() == null ? null
121 : (VlanIdCriterion) criterion(obj.meta().criteria(), Criterion.Type.VLAN_VID);
122 final ModVlanIdInstruction vlanIdInstruction;
123 if (obj.type() == NextObjective.Type.SIMPLE) {
124 final TrafficTreatment treatment = getFirstDefaultNextTreatmentIfAny(
125 obj.nextTreatments());
126 vlanIdInstruction = treatment == null ? null
127 : (ModVlanIdInstruction) l2Instruction(treatment, VLAN_ID);
128 } else {
129 vlanIdInstruction = null;
130 }
131
132 if (vlanIdCriterion == null && vlanIdInstruction == null) {
133 // Do nothing.
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800134 return;
135 }
136
Carmelo Casconed06a8512018-12-02 16:34:20 -0800137 if (vlanIdCriterion != null && vlanIdInstruction != null) {
138 if (!vlanIdCriterion.vlanId().equals(vlanIdInstruction.vlanId())) {
139 throw new FabricPipelinerException(
140 "Found both meta VLAN_ID criterion and VLAN_ID " +
141 "treatment, but they have different ID " +
142 "values. Cannot process next_vlan rule.");
143 }
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800144 }
145
Carmelo Casconed06a8512018-12-02 16:34:20 -0800146 final VlanId vlanId = vlanIdCriterion != null
147 ? vlanIdCriterion.vlanId() : vlanIdInstruction.vlanId();
148
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800149 final TrafficSelector selector = nextIdSelector(obj.id());
150 final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Carmelo Casconed06a8512018-12-02 16:34:20 -0800151 .setVlanId(vlanId)
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800152 .build();
153
154 resultBuilder.addFlowRule(flowRule(
155 obj, FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN,
156 selector, treatment));
157 }
158
159 private void simpleNext(NextObjective obj,
160 ObjectiveTranslation.Builder resultBuilder,
161 boolean forceSimple)
162 throws FabricPipelinerException {
163
164 if (capabilities.hasHashedTable()) {
165 // Use hashed table when possible.
166 hashedNext(obj, resultBuilder);
167 return;
168 }
169
170 if (obj.nextTreatments().isEmpty()) {
171 // Do nothing.
172 return;
173 } else if (!forceSimple && obj.nextTreatments().size() != 1) {
174 throw new FabricPipelinerException(format(
175 "SIMPLE NextObjective should contain only 1 treatment, found %d",
176 obj.nextTreatments().size()), ObjectiveError.BADPARAMS);
177 }
178
179 final TrafficSelector selector = nextIdSelector(obj.id());
180
Carmelo Casconed06a8512018-12-02 16:34:20 -0800181 final List<DefaultNextTreatment> treatments = defaultNextTreatments(
182 obj.nextTreatments(), true);
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800183
184 if (forceSimple && treatments.size() > 1) {
185 log.warn("Forcing SIMPLE behavior for NextObjective with {} treatments []",
186 treatments.size(), obj);
187 }
188
189 // If not forcing, we are essentially extracting the only available treatment.
Carmelo Casconed06a8512018-12-02 16:34:20 -0800190 final TrafficTreatment treatment = defaultNextTreatments(
191 obj.nextTreatments(), true).get(0).treatment();
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800192
193 resultBuilder.addFlowRule(flowRule(
194 obj, FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE,
195 selector, treatment));
196
197 handleEgress(obj, treatment, resultBuilder, false);
198 }
199
200 private void hashedNext(NextObjective obj,
201 ObjectiveTranslation.Builder resultBuilder)
202 throws FabricPipelinerException {
203
204 if (!capabilities.hasHashedTable()) {
205 simpleNext(obj, resultBuilder, true);
206 return;
207 }
208
209 // Updated result builder with hashed group.
210 final int groupId = selectGroup(obj, resultBuilder);
211
212 if (isGroupModifyOp(obj)) {
213 // No changes to flow rules.
214 return;
215 }
216
217 final TrafficSelector selector = nextIdSelector(obj.id());
218 final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Carmelo Casconecb4327a2018-09-11 15:17:23 -0700219 .piTableAction(PiActionProfileGroupId.of(groupId))
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800220 .build();
221
222 resultBuilder.addFlowRule(flowRule(
223 obj, FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
224 selector, treatment));
225 }
226
227 private void handleEgress(NextObjective obj, TrafficTreatment treatment,
228 ObjectiveTranslation.Builder resultBuilder,
229 boolean strict)
230 throws FabricPipelinerException {
231 final PortNumber outPort = outputPort(treatment);
232 final Instruction popVlanInst = l2Instruction(treatment, VLAN_POP);
233 if (popVlanInst != null && outPort != null) {
234 if (strict && treatment.allInstructions().size() > 2) {
235 throw new FabricPipelinerException(
236 "Treatment contains instructions other " +
237 "than OUTPUT and VLAN_POP, cannot generate " +
238 "egress rules");
239 }
240 egressVlanPop(outPort, obj, resultBuilder);
241 }
242 }
243
244 private void egressVlanPop(PortNumber outPort, NextObjective obj,
245 ObjectiveTranslation.Builder resultBuilder)
246 throws FabricPipelinerException {
247
248 if (obj.meta() == null) {
249 throw new FabricPipelinerException(
250 "Cannot process egress pop VLAN rule, NextObjective has null meta",
251 ObjectiveError.BADPARAMS);
252 }
253
254 final VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) criterion(
255 obj.meta(), Criterion.Type.VLAN_VID);
256 if (vlanIdCriterion == null) {
257 throw new FabricPipelinerException(
258 "Cannot process egress pop VLAN rule, missing VLAN_VID criterion " +
259 "in NextObjective meta",
260 ObjectiveError.BADPARAMS);
261 }
262
263 final PiCriterion egressVlanTableMatch = PiCriterion.builder()
264 .matchExact(FabricConstants.HDR_EG_PORT, outPort.toLong())
265 .build();
266 final TrafficSelector selector = DefaultTrafficSelector.builder()
267 .matchPi(egressVlanTableMatch)
268 .matchVlanId(vlanIdCriterion.vlanId())
269 .build();
270 final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
271 .popVlan()
272 .build();
273
274 resultBuilder.addFlowRule(flowRule(
275 obj, FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN,
276 selector, treatment));
277 }
278
279 private TrafficSelector nextIdSelector(int nextId) {
Carmelo Cascone45cc0862018-11-26 11:50:41 -0800280 return nextIdSelectorBuilder(nextId).build();
281 }
282
283 private TrafficSelector.Builder nextIdSelectorBuilder(int nextId) {
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800284 final PiCriterion nextIdCriterion = PiCriterion.builder()
285 .matchExact(FabricConstants.HDR_NEXT_ID, nextId)
286 .build();
287 return DefaultTrafficSelector.builder()
Carmelo Cascone45cc0862018-11-26 11:50:41 -0800288 .matchPi(nextIdCriterion);
289 }
290
291 private void xconnectNext(NextObjective obj, ObjectiveTranslation.Builder resultBuilder)
292 throws FabricPipelinerException {
293
294 final Collection<DefaultNextTreatment> defaultNextTreatments =
Carmelo Casconed06a8512018-12-02 16:34:20 -0800295 defaultNextTreatments(obj.nextTreatments(), true);
Carmelo Cascone45cc0862018-11-26 11:50:41 -0800296
297 final List<PortNumber> outPorts = defaultNextTreatments.stream()
298 .map(DefaultNextTreatment::treatment)
299 .map(FabricUtils::outputPort)
300 .filter(Objects::nonNull)
301 .collect(Collectors.toList());
302
303 if (outPorts.size() != 2) {
304 throw new FabricPipelinerException(format(
305 "Handling XCONNECT with %d treatments (ports), but expected is 2",
306 defaultNextTreatments.size()), ObjectiveError.UNSUPPORTED);
307 }
308
309 final PortNumber port1 = outPorts.get(0);
310 final PortNumber port2 = outPorts.get(1);
311 final TrafficSelector selector1 = nextIdSelectorBuilder(obj.id())
312 .matchInPort(port1)
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800313 .build();
Carmelo Cascone45cc0862018-11-26 11:50:41 -0800314 final TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
315 .setOutput(port2)
316 .build();
317 final TrafficSelector selector2 = nextIdSelectorBuilder(obj.id())
318 .matchInPort(port2)
319 .build();
320 final TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
321 .setOutput(port1)
322 .build();
323
324 resultBuilder.addFlowRule(flowRule(
325 obj, FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT,
326 selector1, treatment1));
327 resultBuilder.addFlowRule(flowRule(
328 obj, FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT,
329 selector2, treatment2));
330
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800331 }
332
333 private void multicastNext(NextObjective obj,
334 ObjectiveTranslation.Builder resultBuilder)
335 throws FabricPipelinerException {
336
337 // Create ALL group that will be translated to a PRE multicast entry.
338 final int groupId = allGroup(obj, resultBuilder);
339
340 if (isGroupModifyOp(obj)) {
341 // No changes to flow rules.
342 return;
343 }
344
345 final TrafficSelector selector = nextIdSelector(obj.id());
346 final PiActionParam groupIdParam = new PiActionParam(
347 FabricConstants.GROUP_ID, groupId);
348 final PiAction setMcGroupAction = PiAction.builder()
349 .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_MCAST_GROUP_ID)
350 .withParameter(groupIdParam)
351 .build();
352 final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
353 .piTableAction(setMcGroupAction)
354 .build();
355
356 resultBuilder.addFlowRule(flowRule(
357 obj, FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST,
358 selector, treatment));
359 }
360
361 private int selectGroup(NextObjective obj,
362 ObjectiveTranslation.Builder resultBuilder)
363 throws FabricPipelinerException {
364
365 final PiTableId hashedTableId = FabricConstants.FABRIC_INGRESS_NEXT_HASHED;
366 final List<DefaultNextTreatment> defaultNextTreatments =
Carmelo Casconed06a8512018-12-02 16:34:20 -0800367 defaultNextTreatments(obj.nextTreatments(), true);
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800368 final List<TrafficTreatment> piTreatments = Lists.newArrayList();
369
370 for (DefaultNextTreatment t : defaultNextTreatments) {
371 // Map treatment to PI...
372 piTreatments.add(mapTreatmentToPiIfNeeded(t.treatment(), hashedTableId));
373 // ...and handle egress if necessary.
374 handleEgress(obj, t.treatment(), resultBuilder, false);
375 }
376
377 final List<GroupBucket> bucketList = piTreatments.stream()
378 .map(DefaultGroupBucket::createSelectGroupBucket)
379 .collect(Collectors.toList());
380
381 final int groupId = obj.id();
382 final PiGroupKey groupKey = new PiGroupKey(
383 hashedTableId,
384 FabricConstants.FABRIC_INGRESS_NEXT_HASHED_SELECTOR,
385 groupId);
386
387 resultBuilder.addGroup(new DefaultGroupDescription(
388 deviceId,
389 GroupDescription.Type.SELECT,
390 new GroupBuckets(bucketList),
391 groupKey,
392 groupId,
393 obj.appId()));
394
395 return groupId;
396 }
397
398 private int allGroup(NextObjective obj,
399 ObjectiveTranslation.Builder resultBuilder)
400 throws FabricPipelinerException {
401
402 final Collection<DefaultNextTreatment> defaultNextTreatments =
Carmelo Casconed06a8512018-12-02 16:34:20 -0800403 defaultNextTreatments(obj.nextTreatments(), true);
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800404 // No need to map treatments to PI as translation of ALL groups to PRE
405 // multicast entries is based solely on the output port.
406 for (DefaultNextTreatment t : defaultNextTreatments) {
407 handleEgress(obj, t.treatment(), resultBuilder, true);
408 }
409
410 // FIXME: this implementation supports only the case in which each
411 // switch interface is associated with only one VLAN, otherwise we would
412 // need to support replicating multiple times the same packet for the
413 // same port while setting different VLAN IDs. Hence, collect in a set.
414 final Set<PortNumber> outPorts = defaultNextTreatments.stream()
415 .map(DefaultNextTreatment::treatment)
416 .map(FabricUtils::outputPort)
417 .filter(Objects::nonNull)
418 .collect(Collectors.toSet());
419
420 if (outPorts.size() != defaultNextTreatments.size()) {
421 throw new FabricPipelinerException(format(
422 "Found BROADCAST NextObjective with %d treatments but " +
423 "found only %d distinct OUTPUT port numbers, cannot " +
424 "translate to ALL groups",
425 defaultNextTreatments.size(), outPorts.size()),
426 ObjectiveError.UNSUPPORTED);
427 }
428
429 final List<GroupBucket> bucketList = outPorts.stream()
430 .map(p -> DefaultTrafficTreatment.builder().setOutput(p).build())
431 .map(DefaultGroupBucket::createAllGroupBucket)
432 .collect(Collectors.toList());
433 // FIXME: remove once support for clone sessions is available
434 // Right now we add a CPU port to all multicast groups. The egress
435 // pipeline is expected to drop replicated packets to the CPU if a clone
436 // was not requested in the ingress pipeline.
437 bucketList.add(
438 DefaultGroupBucket.createAllGroupBucket(
439 DefaultTrafficTreatment.builder()
440 .setOutput(PortNumber.CONTROLLER)
441 .build()));
442
443 final int groupId = obj.id();
444 // Use DefaultGroupKey instead of PiGroupKey as we don't have any
445 // action profile to apply to the groups of ALL type.
446 final GroupKey groupKey = new DefaultGroupKey(
447 FabricPipeliner.KRYO.serialize(groupId));
448
449 resultBuilder.addGroup(
450 new DefaultGroupDescription(
451 deviceId,
452 GroupDescription.Type.ALL,
453 new GroupBuckets(bucketList),
454 groupKey,
455 groupId,
456 obj.appId()));
457
458 return groupId;
459 }
460
Carmelo Casconed06a8512018-12-02 16:34:20 -0800461 private List<DefaultNextTreatment> defaultNextTreatments(
462 Collection<NextTreatment> nextTreatments, boolean strict)
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800463 throws FabricPipelinerException {
464 final List<DefaultNextTreatment> defaultNextTreatments = Lists.newArrayList();
465 final List<NextTreatment> unsupportedNextTreatments = Lists.newArrayList();
466 for (NextTreatment n : nextTreatments) {
467 if (n.type() == NextTreatment.Type.TREATMENT) {
468 defaultNextTreatments.add((DefaultNextTreatment) n);
469 } else {
470 unsupportedNextTreatments.add(n);
471 }
472 }
Carmelo Casconed06a8512018-12-02 16:34:20 -0800473 if (strict && !unsupportedNextTreatments.isEmpty()) {
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800474 throw new FabricPipelinerException(format(
475 "Unsupported NextTreatments: %s",
476 unsupportedNextTreatments));
477 }
478 return defaultNextTreatments;
479 }
480
Carmelo Casconed06a8512018-12-02 16:34:20 -0800481 private TrafficTreatment getFirstDefaultNextTreatmentIfAny(
482 Collection<NextTreatment> nextTreatments)
483 throws FabricPipelinerException {
484 final Collection<DefaultNextTreatment> nexts = defaultNextTreatments(nextTreatments, false);
485 return nexts.isEmpty() ? null : nexts.iterator().next().treatment();
486 }
487
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800488 private boolean isGroupModifyOp(NextObjective obj) {
489 // If operation is ADD_TO_EXIST or REMOVE_FROM_EXIST, it means we modify
490 // group buckets only, no changes for flow rules.
491 return obj.op() == Objective.Operation.ADD_TO_EXISTING ||
492 obj.op() == Objective.Operation.REMOVE_FROM_EXISTING;
493 }
Carmelo Cascone45cc0862018-11-26 11:50:41 -0800494
495 private boolean isXconnect(NextObjective obj) {
496 return obj.appId().name().contains(XCONNECT);
497 }
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800498}