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