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