blob: 8b28b5e3476d7c4921041d4432ae82fdbbb4155a [file] [log] [blame]
Carmelo Cascone00a59962017-06-16 17:51:49 +09001/*
2 * Copyright 2017-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.net.pi.impl;
18
19import com.google.common.collect.Maps;
20import com.google.common.collect.Sets;
21import org.onlab.util.ImmutableByteSequence;
22import org.onosproject.net.Device;
23import org.onosproject.net.flow.FlowRule;
24import org.onosproject.net.flow.IndexTableId;
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.instructions.Instruction;
30import org.onosproject.net.flow.instructions.PiInstruction;
31import org.onosproject.net.pi.model.PiActionModel;
32import org.onosproject.net.pi.model.PiActionParamModel;
33import org.onosproject.net.pi.model.PiPipeconf;
34import org.onosproject.net.pi.model.PiPipelineInterpreter;
35import org.onosproject.net.pi.model.PiPipelineModel;
36import org.onosproject.net.pi.model.PiTableMatchFieldModel;
37import org.onosproject.net.pi.model.PiTableModel;
38import org.onosproject.net.pi.runtime.PiAction;
39import org.onosproject.net.pi.runtime.PiActionParam;
40import org.onosproject.net.pi.runtime.PiExactFieldMatch;
41import org.onosproject.net.pi.runtime.PiFieldMatch;
42import org.onosproject.net.pi.runtime.PiHeaderFieldId;
43import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
44import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
45import org.onosproject.net.pi.runtime.PiTableAction;
46import org.onosproject.net.pi.runtime.PiTableEntry;
47import org.onosproject.net.pi.runtime.PiTableId;
48import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
49import org.slf4j.Logger;
50import org.slf4j.LoggerFactory;
51
52import java.util.Collection;
53import java.util.Map;
54import java.util.Optional;
55import java.util.Set;
56import java.util.StringJoiner;
57
58import static java.lang.String.format;
59import static org.onlab.util.ImmutableByteSequence.ByteSequenceTrimException;
60import static org.onlab.util.ImmutableByteSequence.fit;
61import static org.onosproject.net.flow.criteria.Criterion.Type.PROTOCOL_INDEPENDENT;
62import static org.onosproject.net.pi.impl.CriterionTranslatorHelper.translateCriterion;
63import static org.onosproject.net.pi.runtime.PiFlowRuleTranslationService.PiFlowRuleTranslationException;
64
65/**
66 * Implementation of flow rule translation logic.
67 */
68final class PiFlowRuleTranslator {
69
70 private static final Logger log = LoggerFactory.getLogger(PiFlowRuleTranslationServiceImpl.class);
71
72 private PiFlowRuleTranslator() {
73 // Hide constructor.
74 }
75
76 static PiTableEntry translateFlowRule(FlowRule rule, PiPipeconf pipeconf, Device device)
77 throws PiFlowRuleTranslationException {
78
79 PiPipelineModel pipelineModel = pipeconf.pipelineModel();
80
81 // Retrieve interpreter, if any.
82 // FIXME: get interpreter via driver once implemented.
83 // final PiPipelineInterpreter interpreter = device.is(PiPipelineInterpreter.class)
84 // ? device.as(PiPipelineInterpreter.class) : null;
85
86 final PiPipelineInterpreter interpreter;
87 try {
88 interpreter = (PiPipelineInterpreter) pipeconf.implementation(PiPipelineInterpreter.class)
89 .orElse(null)
90 .newInstance();
91 } catch (InstantiationException | IllegalAccessException e) {
92 throw new PiFlowRuleTranslationException(format(
93 "Unable to instantiate interpreter of pipeconf %s", pipeconf.id()));
94 }
95
96 PiTableId piTableId;
97 switch (rule.table().type()) {
98 case PIPELINE_INDEPENDENT:
99 piTableId = (PiTableId) rule.table();
100 break;
101 case INDEX:
102 IndexTableId indexId = (IndexTableId) rule.table();
103 if (interpreter == null) {
104 throw new PiFlowRuleTranslationException(format(
105 "Unable to map table ID '%d' from index to PI: missing interpreter", indexId.id()));
106 } else if (!interpreter.mapFlowRuleTableId(indexId.id()).isPresent()) {
107 throw new PiFlowRuleTranslationException(format(
108 "Unable to map table ID '%d' from index to PI: missing ID in interpreter", indexId.id()));
109 } else {
110 piTableId = interpreter.mapFlowRuleTableId(indexId.id()).get();
111 }
112 break;
113 default:
114 throw new PiFlowRuleTranslationException(format(
115 "Unrecognized table ID type %s", rule.table().type().name()));
116 }
117
118 PiTableModel table = pipelineModel.table(piTableId.toString())
119 .orElseThrow(() -> new PiFlowRuleTranslationException(format(
120 "Not such a table in pipeline model: %s", piTableId)));
121
122 /* Translate selector */
123 Collection<PiFieldMatch> fieldMatches = buildFieldMatches(interpreter, rule.selector(), table);
124
125 /* Translate treatment */
Carmelo Casconef3a1a382017-07-27 12:04:39 -0400126 PiAction piAction = buildAction(rule.treatment(), interpreter, piTableId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900127 piAction = typeCheckAction(piAction, table);
128
129 PiTableEntry.Builder tableEntryBuilder = PiTableEntry.builder();
130
131 // In BMv2 0 is the highest priority.
132 // FIXME: Check P4Runtime and agree on maximum priority in the TableEntry javadoc.
133 // int newPriority = Integer.MAX_VALUE - rule.priority();
134
135 tableEntryBuilder
136 .forTable(piTableId)
137 .withPriority(rule.priority())
138 .withFieldMatches(fieldMatches)
139 .withAction(piAction);
140
141 if (!rule.isPermanent()) {
142 if (table.supportsAging()) {
143 tableEntryBuilder.withTimeout((double) rule.timeout());
144 } else {
145 log.warn("Flow rule is temporary, but table '{}' doesn't support " +
146 "aging, translating to permanent.", table.name());
147 }
148
149 }
150
151 return tableEntryBuilder.build();
152 }
153
154 /**
155 * Builds a PI action out of the given treatment, optionally using the given interpreter.
156 */
157 private static PiAction buildAction(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
Carmelo Casconef3a1a382017-07-27 12:04:39 -0400158 PiTableId tableId)
Carmelo Cascone00a59962017-06-16 17:51:49 +0900159 throws PiFlowRuleTranslationException {
160
161 PiTableAction piTableAction = null;
162
163 // If treatment has only one instruction of type PiInstruction, use that.
164 for (Instruction inst : treatment.allInstructions()) {
165 if (inst.type() == Instruction.Type.PROTOCOL_INDEPENDENT) {
166 if (treatment.allInstructions().size() == 1) {
167 piTableAction = ((PiInstruction) inst).action();
168 } else {
169 throw new PiFlowRuleTranslationException(format(
170 "Unable to translate treatment, found multiple instructions " +
171 "of which one is protocol-independent: %s", treatment));
172 }
173 }
174 }
175
176 if (piTableAction == null && interpreter != null) {
177 // No PiInstruction, use interpreter to build action.
178 try {
Carmelo Casconef3a1a382017-07-27 12:04:39 -0400179 piTableAction = interpreter.mapTreatment(treatment, tableId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900180 } catch (PiPipelineInterpreter.PiInterpreterException e) {
181 throw new PiFlowRuleTranslationException(
182 "Interpreter was unable to translate treatment. " + e.getMessage());
183 }
184 }
185
186 if (piTableAction == null) {
187 // No PiInstruction, no interpreter. It's time to give up.
188 throw new PiFlowRuleTranslationException(
189 "Unable to translate treatment, neither an interpreter or a "
190 + "protocol-independent instruction were provided.");
191 }
192
193 if (piTableAction.type() != PiTableAction.Type.ACTION) {
194 // TODO: implement handling of other table action types, e.g. action profiles.
195 throw new PiFlowRuleTranslationException(format(
196 "PiTableAction type %s is not supported yet.", piTableAction.type()));
197 }
198
199 return (PiAction) piTableAction;
200 }
201
202 /**
203 * Checks that the given PI action is suitable for the given table model and returns a new action instance with
204 * parameters well-sized, according to the table model. If not suitable, throws an exception explaining why.
205 */
206 private static PiAction typeCheckAction(PiAction piAction, PiTableModel table)
207 throws PiFlowRuleTranslationException {
208
209 // Table supports this action?
210 PiActionModel actionModel = table.action(piAction.id().name()).orElseThrow(
211 () -> new PiFlowRuleTranslationException(format("Not such action '%s' for table '%s'",
212 piAction.id(), table.name())));
213
214 // Is the number of runtime parameters correct?
215 if (actionModel.params().size() != piAction.parameters().size()) {
216 throw new PiFlowRuleTranslationException(format(
217 "Wrong number of runtime parameters for action '%s', expected %d but found %d",
218 actionModel.name(), actionModel.params().size(), piAction.parameters().size()));
219 }
220
221 // Forge a new action instance with well-sized parameters.
222 // The same comment as in typeCheckFieldMatch() about duplicating field match instances applies here.
223 PiAction.Builder newActionBuilder = PiAction.builder().withId(piAction.id());
224 for (PiActionParam param : piAction.parameters()) {
225 PiActionParamModel paramModel = actionModel.param(param.id().name())
226 .orElseThrow(() -> new PiFlowRuleTranslationException(format(
227 "Not such parameter '%s' for action '%s'", param.id(), actionModel.name())));
228 try {
229 newActionBuilder.withParameter(new PiActionParam(param.id(),
230 fit(param.value(), paramModel.bitWidth())));
231 } catch (ByteSequenceTrimException e) {
232 throw new PiFlowRuleTranslationException(format(
233 "Size mismatch for parameter '%s' of action '%s': %s",
234 param.id(), piAction.id(), e.getMessage()));
235 }
236 }
237
238 return newActionBuilder.build();
239 }
240
241 /**
242 * Builds a collection of PI field matches out of the given selector, optionally using the given interpreter. The
243 * field matches returned are guaranteed to be compatible for the given table model.
244 */
245 private static Collection<PiFieldMatch> buildFieldMatches(PiPipelineInterpreter interpreter,
246 TrafficSelector selector, PiTableModel tableModel)
247 throws PiFlowRuleTranslationException {
248
249 Map<PiHeaderFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
250
251 // If present, find a PiCriterion and get its field matches as a map. Otherwise, use an empty map.
252 Map<PiHeaderFieldId, PiFieldMatch> piCriterionFields = selector.criteria().stream()
253 .filter(c -> c.type().equals(PROTOCOL_INDEPENDENT))
254 .map(c -> (PiCriterion) c)
255 .findFirst()
256 .map(PiCriterion::fieldMatches)
257 .map(c -> {
258 Map<PiHeaderFieldId, PiFieldMatch> fieldMap = Maps.newHashMap();
259 c.forEach(fieldMatch -> fieldMap.put(fieldMatch.fieldId(), fieldMatch));
260 return fieldMap;
261 })
262 .orElse(Maps.newHashMap());
263
264 Set<Criterion> translatedCriteria = Sets.newHashSet();
265 Set<Criterion> ignoredCriteria = Sets.newHashSet();
266 Set<PiHeaderFieldId> usedPiCriterionFields = Sets.newHashSet();
267 Set<PiHeaderFieldId> ignoredPiCriterionFields = Sets.newHashSet();
268
269 for (PiTableMatchFieldModel fieldModel : tableModel.matchFields()) {
270
271 PiHeaderFieldId fieldId = PiHeaderFieldId.of(fieldModel.field().header().type().name(),
272 fieldModel.field().type().name(),
273 fieldModel.field().header().index());
274
275 int bitWidth = fieldModel.field().type().bitWidth();
276 int fieldByteWidth = (int) Math.ceil((double) bitWidth / 8);
277
278 Optional<Criterion.Type> criterionType =
279 interpreter == null
280 ? Optional.empty()
281 : interpreter.mapPiHeaderFieldId(fieldId);
282
283 Criterion criterion = criterionType.map(selector::getCriterion).orElse(null);
284
285 if (!piCriterionFields.containsKey(fieldId) && criterion == null) {
286 // Neither a field in PiCriterion is available nor a Criterion mapping is possible.
287 // Can ignore if the match is ternary or LPM.
288 switch (fieldModel.matchType()) {
289 case TERNARY:
290 // Wildcard the whole field.
291 fieldMatches.put(fieldId, new PiTernaryFieldMatch(
292 fieldId,
293 ImmutableByteSequence.ofZeros(fieldByteWidth),
294 ImmutableByteSequence.ofZeros(fieldByteWidth)));
295 break;
296 case LPM:
297 // LPM with prefix 0
298 fieldMatches.put(fieldId, new PiLpmFieldMatch(fieldId,
299 ImmutableByteSequence.ofZeros(fieldByteWidth),
300 0));
301 break;
302 // FIXME: Can we handle the case of RANGE or VALID match?
303 default:
304 throw new PiFlowRuleTranslationException(format(
305 "No value found for required match field '%s'", fieldId));
306 }
307 // Next field.
308 continue;
309 }
310
311 PiFieldMatch fieldMatch = null;
312
313 if (criterion != null) {
314 // Criterion mapping is possible for this field id.
315 try {
316 fieldMatch = translateCriterion(criterion, fieldId, fieldModel.matchType(), bitWidth);
317 translatedCriteria.add(criterion);
318 } catch (PiFlowRuleTranslationException ex) {
319 // Ignore exception if the same field was found in PiCriterion.
320 if (piCriterionFields.containsKey(fieldId)) {
321 ignoredCriteria.add(criterion);
322 } else {
323 throw ex;
324 }
325 }
326 }
327
328 if (piCriterionFields.containsKey(fieldId)) {
329 // Field was found in PiCriterion.
330 if (fieldMatch != null) {
331 // Field was already translated from other criterion.
332 // Throw exception only if we are trying to match on different values of the same field...
333 if (!fieldMatch.equals(piCriterionFields.get(fieldId))) {
334 throw new PiFlowRuleTranslationException(format(
335 "Duplicate match field '%s': instance translated from criterion '%s' is different to " +
336 "what found in PiCriterion.", fieldId, criterion.type()));
337 }
338 ignoredPiCriterionFields.add(fieldId);
339 } else {
340 fieldMatch = piCriterionFields.get(fieldId);
341 fieldMatch = typeCheckFieldMatch(fieldMatch, fieldModel);
342 usedPiCriterionFields.add(fieldId);
343 }
344 }
345
346 fieldMatches.put(fieldId, fieldMatch);
347 }
348
349 // Check if all criteria have been translated.
350 StringJoiner skippedCriteriaJoiner = new StringJoiner(", ");
351 selector.criteria().stream()
352 .filter(c -> !c.type().equals(PROTOCOL_INDEPENDENT))
353 .filter(c -> !translatedCriteria.contains(c) && !ignoredCriteria.contains(c))
354 .forEach(c -> skippedCriteriaJoiner.add(c.type().name()));
355 if (skippedCriteriaJoiner.length() > 0) {
356 throw new PiFlowRuleTranslationException(format(
357 "The following criteria cannot be translated for table '%s': %s",
358 tableModel.name(), skippedCriteriaJoiner.toString()));
359 }
360
361 // Check if all fields found in PiCriterion have been used.
362 StringJoiner skippedPiFieldsJoiner = new StringJoiner(", ");
363 piCriterionFields.keySet().stream()
364 .filter(k -> !usedPiCriterionFields.contains(k) && !ignoredPiCriterionFields.contains(k))
365 .forEach(k -> skippedPiFieldsJoiner.add(k.id()));
366 if (skippedPiFieldsJoiner.length() > 0) {
367 throw new PiFlowRuleTranslationException(format(
368 "The following PiCriterion field matches are not supported in table '%s': %s",
369 tableModel.name(), skippedPiFieldsJoiner.toString()));
370 }
371
372 return fieldMatches.values();
373 }
374
375 private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiTableMatchFieldModel fieldModel)
376 throws PiFlowRuleTranslationException {
377
378 // Check parameter type and size
379 if (!fieldModel.matchType().equals(fieldMatch.type())) {
380 throw new PiFlowRuleTranslationException(format(
381 "Wrong match type for field '%s', expected %s, but found %s",
382 fieldMatch.fieldId(), fieldModel.matchType().name(), fieldMatch.type().name()));
383 }
384
385 int modelBitWidth = fieldModel.field().type().bitWidth();
386
387 /*
388 Here we try to be robust against wrong size fields with the goal of having PiCriterion independent of the
389 pipeline model. We duplicate the field match, fitting the byte sequences to the bit-width specified in the
390 model. This operation is expensive when performed for each field match of each flow rule, but should be
391 mitigated by the translation cache provided by PiFlowRuleTranslationServiceImpl.
392 */
393
394 try {
395 switch (fieldModel.matchType()) {
396 case EXACT:
397 return new PiExactFieldMatch(fieldMatch.fieldId(),
398 fit(((PiExactFieldMatch) fieldMatch).value(), modelBitWidth));
399 case TERNARY:
400 return new PiTernaryFieldMatch(fieldMatch.fieldId(),
401 fit(((PiTernaryFieldMatch) fieldMatch).value(), modelBitWidth),
402 fit(((PiTernaryFieldMatch) fieldMatch).mask(), modelBitWidth));
403 case LPM:
404 PiLpmFieldMatch lpmfield = (PiLpmFieldMatch) fieldMatch;
405 if (lpmfield.prefixLength() > modelBitWidth) {
406 throw new PiFlowRuleTranslationException(format(
407 "Invalid prefix length for LPM field '%s', found %d but field has bit-width %d",
408 fieldMatch.fieldId(), lpmfield.prefixLength(), modelBitWidth));
409 }
410 return new PiLpmFieldMatch(fieldMatch.fieldId(),
411 fit(lpmfield.value(), modelBitWidth),
412 lpmfield.prefixLength());
413 case RANGE:
414 return new PiRangeFieldMatch(fieldMatch.fieldId(),
415 fit(((PiRangeFieldMatch) fieldMatch).lowValue(), modelBitWidth),
416 fit(((PiRangeFieldMatch) fieldMatch).highValue(), modelBitWidth));
417 case VALID:
418 return fieldMatch;
419 default:
420 // Should never be here.
421 throw new RuntimeException(
422 "Unrecognized match type " + fieldModel.matchType().name());
423 }
424 } catch (ByteSequenceTrimException e) {
425 throw new PiFlowRuleTranslationException(format(
426 "Size mismatch for field %s: %s", fieldMatch.fieldId(), e.getMessage()));
427 }
428 }
429}