Carmelo Cascone | 17fc9e4 | 2016-05-31 11:29:21 -0700 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package org.onosproject.bmv2.ctl; |
| 18 | |
| 19 | import com.google.common.annotations.Beta; |
| 20 | import com.google.common.collect.Sets; |
| 21 | import org.onlab.util.ImmutableByteSequence; |
| 22 | import org.onosproject.bmv2.api.context.Bmv2ActionModel; |
| 23 | import org.onosproject.bmv2.api.context.Bmv2Configuration; |
| 24 | import org.onosproject.bmv2.api.context.Bmv2DeviceContext; |
| 25 | import org.onosproject.bmv2.api.context.Bmv2FieldModel; |
| 26 | import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator; |
| 27 | import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslatorException; |
| 28 | import org.onosproject.bmv2.api.context.Bmv2Interpreter; |
| 29 | import org.onosproject.bmv2.api.context.Bmv2InterpreterException; |
| 30 | import org.onosproject.bmv2.api.context.Bmv2RuntimeDataModel; |
| 31 | import org.onosproject.bmv2.api.context.Bmv2TableKeyModel; |
| 32 | import org.onosproject.bmv2.api.context.Bmv2TableModel; |
| 33 | import org.onosproject.bmv2.api.runtime.Bmv2Action; |
| 34 | import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam; |
| 35 | import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector; |
| 36 | import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment; |
| 37 | import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam; |
| 38 | import org.onosproject.bmv2.api.runtime.Bmv2MatchKey; |
| 39 | import org.onosproject.bmv2.api.runtime.Bmv2MatchParam; |
| 40 | import org.onosproject.bmv2.api.runtime.Bmv2TableEntry; |
| 41 | import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam; |
| 42 | import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils; |
| 43 | import org.onosproject.net.flow.FlowRule; |
| 44 | import org.onosproject.net.flow.TrafficSelector; |
| 45 | import org.onosproject.net.flow.TrafficTreatment; |
| 46 | import org.onosproject.net.flow.criteria.Criterion; |
| 47 | import org.onosproject.net.flow.criteria.EthCriterion; |
| 48 | import org.onosproject.net.flow.criteria.EthTypeCriterion; |
| 49 | import org.onosproject.net.flow.criteria.ExtensionCriterion; |
| 50 | import org.onosproject.net.flow.criteria.PortCriterion; |
| 51 | import org.onosproject.net.flow.instructions.ExtensionTreatment; |
| 52 | import org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes; |
| 53 | import org.onosproject.net.flow.instructions.Instruction; |
| 54 | import org.onosproject.net.flow.instructions.Instructions; |
| 55 | import org.onosproject.net.flow.instructions.Instructions.ExtensionInstructionWrapper; |
| 56 | import org.slf4j.Logger; |
| 57 | import org.slf4j.LoggerFactory; |
| 58 | |
| 59 | import java.util.Collections; |
| 60 | import java.util.Map; |
| 61 | import java.util.Optional; |
| 62 | import java.util.Set; |
| 63 | import java.util.stream.Collectors; |
| 64 | |
| 65 | import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes; |
| 66 | import static org.onosproject.net.flow.criteria.Criterion.Type.EXTENSION; |
| 67 | import 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 |
| 73 | public 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 Cascone | 9e39e31 | 2016-06-16 14:47:09 -0700 | [diff] [blame] | 244 | return ((Bmv2ExtensionTreatment) extTreatment).action(); |
Carmelo Cascone | 17fc9e4 | 2016-05-31 11:29:21 -0700 | [diff] [blame] | 245 | } 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 | } |