blob: ef9427e2076800cfcf5c5078586c8cd14e521891 [file] [log] [blame]
Carmelo Cascone17fc9e42016-05-31 11:29:21 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
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.bmv2.ctl;
18
19import com.google.common.annotations.Beta;
20import com.google.common.collect.Sets;
21import org.onlab.util.ImmutableByteSequence;
22import org.onosproject.bmv2.api.context.Bmv2ActionModel;
23import org.onosproject.bmv2.api.context.Bmv2Configuration;
24import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
25import org.onosproject.bmv2.api.context.Bmv2FieldModel;
26import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator;
27import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslatorException;
28import org.onosproject.bmv2.api.context.Bmv2Interpreter;
29import org.onosproject.bmv2.api.context.Bmv2InterpreterException;
30import org.onosproject.bmv2.api.context.Bmv2RuntimeDataModel;
31import org.onosproject.bmv2.api.context.Bmv2TableKeyModel;
32import org.onosproject.bmv2.api.context.Bmv2TableModel;
33import org.onosproject.bmv2.api.runtime.Bmv2Action;
34import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
35import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
36import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
37import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
38import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
39import org.onosproject.bmv2.api.runtime.Bmv2MatchParam;
40import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
41import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
42import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
43import org.onosproject.net.flow.FlowRule;
44import org.onosproject.net.flow.TrafficSelector;
45import org.onosproject.net.flow.TrafficTreatment;
46import org.onosproject.net.flow.criteria.Criterion;
47import org.onosproject.net.flow.criteria.EthCriterion;
48import org.onosproject.net.flow.criteria.EthTypeCriterion;
49import org.onosproject.net.flow.criteria.ExtensionCriterion;
50import org.onosproject.net.flow.criteria.PortCriterion;
51import org.onosproject.net.flow.instructions.ExtensionTreatment;
52import org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes;
53import org.onosproject.net.flow.instructions.Instruction;
54import org.onosproject.net.flow.instructions.Instructions;
55import org.onosproject.net.flow.instructions.Instructions.ExtensionInstructionWrapper;
56import org.slf4j.Logger;
57import org.slf4j.LoggerFactory;
58
59import java.util.Collections;
60import java.util.Map;
61import java.util.Optional;
62import java.util.Set;
63import java.util.stream.Collectors;
64
65import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
66import static org.onosproject.net.flow.criteria.Criterion.Type.EXTENSION;
67import static org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes.BMV2_MATCH_PARAMS;
68
69/**
70 * Default implementation of a BMv2 flow rule translator.
71 */
72@Beta
73public class Bmv2FlowRuleTranslatorImpl implements Bmv2FlowRuleTranslator {
74
75 private final Logger log = LoggerFactory.getLogger(this.getClass());
76
77 @Override
78 public Bmv2TableEntry translate(FlowRule rule, Bmv2DeviceContext context)
79 throws Bmv2FlowRuleTranslatorException {
80
81 Bmv2Configuration configuration = context.configuration();
82 Bmv2Interpreter interpreter = context.interpreter();
83
84 int tableId = rule.tableId();
85 String tableName = interpreter.tableIdMap().get(tableId);
86
87 Bmv2TableModel table = (tableName == null) ? configuration.table(tableId) : configuration.table(tableName);
88
89 if (table == null) {
90 throw new Bmv2FlowRuleTranslatorException("Unknown table ID: " + tableId);
91 }
92
93 /* Translate selector */
94 Bmv2MatchKey bmv2MatchKey = buildMatchKey(interpreter, rule.selector(), table);
95
96 /* Translate treatment */
97 TrafficTreatment treatment = rule.treatment();
98 Bmv2Action bmv2Action = null;
99 // If treatment has only 1 instruction of type extension, use that
100 for (Instruction inst : treatment.allInstructions()) {
101 if (inst.type() == Instruction.Type.EXTENSION) {
102 if (treatment.allInstructions().size() == 1) {
103 bmv2Action = getActionFromExtension((ExtensionInstructionWrapper) inst);
104 } else {
105 throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic treatment, found multiple " +
106 "instructions of which one is an extension: " +
107 treatment.toString());
108 }
109 }
110 }
111
112 if (bmv2Action == null) {
113 // No extension, use interpreter to build action.
114 try {
115 bmv2Action = interpreter.mapTreatment(treatment, configuration);
116 } catch (Bmv2InterpreterException e) {
117 throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment. " + e.toString());
118 }
119 }
120
121 if (bmv2Action == null) {
122 // Interpreter returned null.
123 throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment");
124 }
125
126 // Check action
127 Bmv2ActionModel actionModel = configuration.action(bmv2Action.name());
128 if (actionModel == null) {
129 throw new Bmv2FlowRuleTranslatorException("Unknown action " + bmv2Action.name());
130 }
131 if (!table.actions().contains(actionModel)) {
132 throw new Bmv2FlowRuleTranslatorException("Action " + bmv2Action.name()
133 + " is not defined for table " + tableName);
134 }
135 if (actionModel.runtimeDatas().size() != bmv2Action.parameters().size()) {
136 throw new Bmv2FlowRuleTranslatorException("Wrong number of parameters for action "
137 + actionModel.name() + ", expected "
138 + actionModel.runtimeDatas().size() + ", but found "
139 + bmv2Action.parameters().size());
140 }
141 for (int i = 0; i < actionModel.runtimeDatas().size(); i++) {
142 Bmv2RuntimeDataModel data = actionModel.runtimeDatas().get(i);
143 ImmutableByteSequence param = bmv2Action.parameters().get(i);
144 if (param.size() != roundToBytes(data.bitWidth())) {
145 throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for parameter " + data.name()
146 + " of action " + actionModel.name()
147 + ", expected " + roundToBytes(data.bitWidth())
148 + " bytes, but found " + param.size());
149 }
150 }
151
152 Bmv2TableEntry.Builder tableEntryBuilder = Bmv2TableEntry.builder();
153
154 // In BMv2 0 is the highest priority.
155 int newPriority = Integer.MAX_VALUE - rule.priority();
156
157 tableEntryBuilder
158 .withTableName(table.name())
159 .withPriority(newPriority)
160 .withMatchKey(bmv2MatchKey)
161 .withAction(bmv2Action);
162
163 if (!rule.isPermanent()) {
164 if (table.hasTimeouts()) {
165 tableEntryBuilder.withTimeout((double) rule.timeout());
166 } else {
167 log.warn("Flow rule is temporary but table {} doesn't support timeouts, translating to permanent",
168 table.name());
169 }
170
171 }
172
173 return tableEntryBuilder.build();
174 }
175
176 private Bmv2TernaryMatchParam buildTernaryParam(Bmv2FieldModel field, Criterion criterion, int bitWidth)
177 throws Bmv2FlowRuleTranslatorException {
178
179 // Value and mask will be filled according to criterion type
180 ImmutableByteSequence value;
181 ImmutableByteSequence mask = null;
182
183 int byteWidth = roundToBytes(bitWidth);
184
185 switch (criterion.type()) {
186 case IN_PORT:
187 long port = ((PortCriterion) criterion).port().toLong();
188 value = ImmutableByteSequence.copyFrom(port);
189 break;
190 case ETH_DST:
191 EthCriterion c = (EthCriterion) criterion;
192 value = ImmutableByteSequence.copyFrom(c.mac().toBytes());
193 if (c.mask() != null) {
194 mask = ImmutableByteSequence.copyFrom(c.mask().toBytes());
195 }
196 break;
197 case ETH_SRC:
198 EthCriterion c2 = (EthCriterion) criterion;
199 value = ImmutableByteSequence.copyFrom(c2.mac().toBytes());
200 if (c2.mask() != null) {
201 mask = ImmutableByteSequence.copyFrom(c2.mask().toBytes());
202 }
203 break;
204 case ETH_TYPE:
205 short ethType = ((EthTypeCriterion) criterion).ethType().toShort();
206 value = ImmutableByteSequence.copyFrom(ethType);
207 break;
208 // TODO: implement building for other criterion types (easy with DefaultCriterion of ONOS-4034)
209 default:
210 throw new Bmv2FlowRuleTranslatorException("Feature not implemented, ternary builder for criterion" +
211 "type: " + criterion.type().name());
212 }
213
214 // Fit byte sequence in field model bit-width.
215 try {
216 value = Bmv2TranslatorUtils.fitByteSequence(value, bitWidth);
217 } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
218 throw new Bmv2FlowRuleTranslatorException(
219 "Fit exception for criterion " + criterion.type().name() + " value, " + e.getMessage());
220 }
221
222 if (mask == null) {
223 // no mask, all ones
224 mask = ImmutableByteSequence.ofOnes(byteWidth);
225 } else {
226 try {
227 mask = Bmv2TranslatorUtils.fitByteSequence(mask, bitWidth);
228 } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
229 throw new Bmv2FlowRuleTranslatorException(
230 "Fit exception for criterion " + criterion.type().name() + " mask, " + e.getMessage());
231 }
232 }
233
234 return new Bmv2TernaryMatchParam(value, mask);
235 }
236
237 private Bmv2Action getActionFromExtension(Instructions.ExtensionInstructionWrapper inst)
238 throws Bmv2FlowRuleTranslatorException {
239
240 ExtensionTreatment extTreatment = inst.extensionInstruction();
241
242 if (extTreatment.type() == ExtensionTreatmentTypes.BMV2_ACTION.type()) {
243 if (extTreatment instanceof Bmv2ExtensionTreatment) {
Carmelo Cascone9e39e312016-06-16 14:47:09 -0700244 return ((Bmv2ExtensionTreatment) extTreatment).action();
Carmelo Cascone17fc9e42016-05-31 11:29:21 -0700245 } else {
246 throw new Bmv2FlowRuleTranslatorException("Unable to decode treatment extension: " + extTreatment);
247 }
248 } else {
249 throw new Bmv2FlowRuleTranslatorException("Unsupported treatment extension type: " + extTreatment.type());
250 }
251 }
252
253 private Bmv2MatchKey buildMatchKey(Bmv2Interpreter interpreter, TrafficSelector selector, Bmv2TableModel tableModel)
254 throws Bmv2FlowRuleTranslatorException {
255
256 // Find a bmv2 extension selector (if any) and get the parameter map.
257 Optional<Bmv2ExtensionSelector> extSelector = selector.criteria().stream()
258 .filter(c -> c.type().equals(EXTENSION))
259 .map(c -> (ExtensionCriterion) c)
260 .map(ExtensionCriterion::extensionSelector)
261 .filter(c -> c.type().equals(BMV2_MATCH_PARAMS.type()))
262 .map(c -> (Bmv2ExtensionSelector) c)
263 .findFirst();
264 Map<String, Bmv2MatchParam> extParamMap =
265 (extSelector.isPresent()) ? extSelector.get().parameterMap() : Collections.emptyMap();
266
267 Set<Criterion> translatedCriteria = Sets.newHashSet();
268 Set<String> usedExtParams = Sets.newHashSet();
269
270 Bmv2MatchKey.Builder matchKeyBuilder = Bmv2MatchKey.builder();
271
272 keysLoop:
273 for (Bmv2TableKeyModel keyModel : tableModel.keys()) {
274
275 // use fieldName dot notation (e.g. ethernet.dstAddr)
276 String fieldName = keyModel.field().header().name() + "." + keyModel.field().type().name();
277
278 int bitWidth = keyModel.field().type().bitWidth();
279 int byteWidth = roundToBytes(bitWidth);
280
281 Criterion.Type criterionType = interpreter.criterionTypeMap().inverse().get(fieldName);
282
283 if (!extParamMap.containsKey(fieldName) &&
284 (criterionType == null || selector.getCriterion(criterionType) == null)) {
285 // Neither an extension nor a mapping / criterion is available for this field.
286 switch (keyModel.matchType()) {
287 case TERNARY:
288 // Wildcard field
289 matchKeyBuilder.withWildcard(byteWidth);
290 break;
291 case LPM:
292 // LPM with prefix 0
293 matchKeyBuilder.add(new Bmv2LpmMatchParam(ImmutableByteSequence.ofZeros(byteWidth), 0));
294 break;
295 default:
296 throw new Bmv2FlowRuleTranslatorException("No value found for required match field "
297 + fieldName);
298 }
299 // Next key
300 continue keysLoop;
301 }
302
303 Bmv2MatchParam matchParam;
304
305 if (extParamMap.containsKey(fieldName)) {
306 // Parameter found in extension
307 if (criterionType != null && selector.getCriterion(criterionType) != null) {
308 // Found also a criterion that can be mapped. This is bad.
309 throw new Bmv2FlowRuleTranslatorException("Both an extension and a criterion mapping are defined " +
310 "for match field " + fieldName);
311 }
312
313 matchParam = extParamMap.get(fieldName);
314 usedExtParams.add(fieldName);
315
316 // Check parameter type and size
317 if (!keyModel.matchType().equals(matchParam.type())) {
318 throw new Bmv2FlowRuleTranslatorException("Wrong match type for parameter " + fieldName
319 + ", expected " + keyModel.matchType().name()
320 + ", but found " + matchParam.type().name());
321 }
322 int foundByteWidth;
323 switch (keyModel.matchType()) {
324 case EXACT:
325 Bmv2ExactMatchParam m1 = (Bmv2ExactMatchParam) matchParam;
326 foundByteWidth = m1.value().size();
327 break;
328 case TERNARY:
329 Bmv2TernaryMatchParam m2 = (Bmv2TernaryMatchParam) matchParam;
330 foundByteWidth = m2.value().size();
331 break;
332 case LPM:
333 Bmv2LpmMatchParam m3 = (Bmv2LpmMatchParam) matchParam;
334 foundByteWidth = m3.value().size();
335 break;
336 case VALID:
337 foundByteWidth = -1;
338 break;
339 default:
340 // should never be her
341 throw new RuntimeException("Unrecognized match type " + keyModel.matchType().name());
342 }
343 if (foundByteWidth != -1 && foundByteWidth != byteWidth) {
344 throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for match parameter " + fieldName
345 + ", expected " + byteWidth + ", but found "
346 + foundByteWidth);
347 }
348
349 } else {
350 // A criterion mapping is available for this key
351 Criterion criterion = selector.getCriterion(criterionType);
352 translatedCriteria.add(criterion);
353 switch (keyModel.matchType()) {
354 case TERNARY:
355 matchParam = buildTernaryParam(keyModel.field(), criterion, bitWidth);
356 break;
357 default:
358 // TODO: implement other match param builders (exact, LPM, etc.)
359 throw new Bmv2FlowRuleTranslatorException("Feature not yet implemented, match param builder: "
360 + keyModel.matchType().name());
361 }
362 }
363
364 matchKeyBuilder.add(matchParam);
365 }
366
367 // Check if all criteria have been translated
368 Set<Criterion> ignoredCriteria = selector.criteria()
369 .stream()
370 .filter(c -> !c.type().equals(EXTENSION))
371 .filter(c -> !translatedCriteria.contains(c))
372 .collect(Collectors.toSet());
373
374 if (ignoredCriteria.size() > 0) {
375 throw new Bmv2FlowRuleTranslatorException("The following criteria cannot be translated for table "
376 + tableModel.name() + ": " + ignoredCriteria.toString());
377 }
378
379 // Check is all extension parameters have been used
380 Set<String> ignoredExtParams = extParamMap.keySet()
381 .stream()
382 .filter(k -> !usedExtParams.contains(k))
383 .collect(Collectors.toSet());
384
385 if (ignoredExtParams.size() > 0) {
386 throw new Bmv2FlowRuleTranslatorException("The following extension match parameters cannot be used for " +
387 "table " + tableModel.name() + ": "
388 + ignoredExtParams.toString());
389 }
390
391 return matchKeyBuilder.build();
392 }
393
394}