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