Carmelo Cascone

ONOS-4044 Implemented ONOS-to-Bmv2 flow rule translator

In Bmv2, tables, header fields and actions all depend on the packet
processing model configuration (Bmv2Model) currently deployed on the
device. For this reason, translation is needed from protocol-aware ONOS
FlowRule objects into properly formatted, protocol-independent
Bmv2TableEntry objects. Translation is based on a TranslatorConfig that
provides a mapping between ONOS types and Bmv2 model-dependent types.

Change-Id: I620802c2024b5250867dc6b1b988b739177f582a
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.bmv2.translators;
import com.google.common.annotations.Beta;
import org.onosproject.bmv2.api.model.Bmv2Model;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import java.util.Map;
/**
* Translator of ONOS flow rules to BMv2 table entries. Translation depends on a
* {@link TranslatorConfig translator configuration}.
*/
@Beta
public interface Bmv2FlowRuleTranslator {
/**
* Returns a new BMv2 table entry equivalent to the given flow rule.
*
* @param rule a flow rule
* @return a BMv2 table entry
* @throws Bmv2FlowRuleTranslatorException if the flow rule cannot be
* translated
*/
Bmv2TableEntry translate(FlowRule rule) throws Bmv2FlowRuleTranslatorException;
/**
* Returns the configuration of this translator.
*
* @return a translator configuration
*/
TranslatorConfig config();
/**
* BMv2 flow rules translator configuration. Such a configuration is used to
* generate table entries that are compatible with a given {@link Bmv2Model}.
*/
@Beta
interface TranslatorConfig {
/**
* Return the {@link Bmv2Model} associated with this configuration.
*
* @return a BMv2 model
*/
Bmv2Model model();
/**
* Returns a map describing a one-to-one relationship between BMv2
* header field names and ONOS criterion types. Header field names are
* formatted using the notation {@code header_name.field_name}
* representing a specific header field instance extracted by the BMv2
* parser (e.g. {@code ethernet.dstAddr}).
*
* @return a map where the keys represent BMv2 header field names and
* values are criterion types
*/
Map<String, Criterion.Type> fieldToCriterionTypeMap();
/**
* Return a BMv2 action that is equivalent to the given ONOS traffic
* treatment.
*
* @param treatment a traffic treatment
* @return a BMv2 action object
* @throws Bmv2FlowRuleTranslatorException if the treatment cannot be
* translated to a BMv2 action
*/
Bmv2Action buildAction(TrafficTreatment treatment)
throws Bmv2FlowRuleTranslatorException;
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.bmv2.translators;
/**
* BMv2 flow rule translator exception.
*/
public class Bmv2FlowRuleTranslatorException extends Exception {
Bmv2FlowRuleTranslatorException(String msg) {
super(msg);
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.bmv2.translators;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.google.common.annotations.Beta;
import com.google.common.collect.Maps;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.model.Bmv2Model;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
/**
* Implementation of a Bmv2 flow rule translator configuration for the
* simple_pipeline.p4 model.
*/
@Beta
public class Bmv2SimplePipelineTranslatorConfig implements Bmv2FlowRuleTranslator.TranslatorConfig {
private static final String JSON_CONFIG_PATH = "/simple_pipeline.json";
private final Map<String, Criterion.Type> fieldMap = Maps.newHashMap();
private final Bmv2Model model;
/**
* Creates a new simple pipeline translator configuration.
*/
public Bmv2SimplePipelineTranslatorConfig() {
this.model = getModel();
// populate fieldMap
fieldMap.put("standard_metadata.ingress_port", Criterion.Type.IN_PORT);
fieldMap.put("ethernet.dstAddr", Criterion.Type.ETH_DST);
fieldMap.put("ethernet.srcAddr", Criterion.Type.ETH_SRC);
fieldMap.put("ethernet.etherType", Criterion.Type.ETH_TYPE);
}
private static Bmv2Action buildDropAction() {
return Bmv2Action.builder()
.withName("_drop")
.build();
}
private static Bmv2Action buildFwdAction(Instructions.OutputInstruction inst)
throws Bmv2FlowRuleTranslatorException {
Bmv2Action.Builder actionBuilder = Bmv2Action.builder();
actionBuilder.withName("fwd");
if (inst.port().isLogical()) {
throw new Bmv2FlowRuleTranslatorException(
"Output logic port numbers not supported: " + inst);
}
actionBuilder.addParameter(
ImmutableByteSequence.copyFrom((short) inst.port().toLong()));
return actionBuilder.build();
}
private static Bmv2Model getModel() {
InputStream inputStream = Bmv2SimplePipelineTranslatorConfig.class
.getResourceAsStream(JSON_CONFIG_PATH);
InputStreamReader reader = new InputStreamReader(inputStream);
BufferedReader bufReader = new BufferedReader(reader);
JsonObject json = null;
try {
json = Json.parse(bufReader).asObject();
} catch (IOException e) {
throw new RuntimeException("Unable to parse JSON file: " + e.getMessage());
}
return Bmv2Model.parse(json);
}
@Override
public Bmv2Model model() {
return this.model;
}
@Override
public Map<String, Criterion.Type> fieldToCriterionTypeMap() {
return fieldMap;
}
@Override
public Bmv2Action buildAction(TrafficTreatment treatment)
throws Bmv2FlowRuleTranslatorException {
if (treatment.allInstructions().size() == 0) {
// no instructions means drop
return buildDropAction();
} else if (treatment.allInstructions().size() > 1) {
// otherwise, we understand treatments with only 1 instruction
throw new Bmv2FlowRuleTranslatorException(
"Treatment not supported, more than 1 instructions found: "
+ treatment.toString());
}
Instruction instruction = treatment.allInstructions().get(0);
switch (instruction.type()) {
case OUTPUT:
return buildFwdAction((Instructions.OutputInstruction) instruction);
case NOACTION:
return buildDropAction();
default:
throw new Bmv2FlowRuleTranslatorException(
"Instruction type not supported: "
+ instruction.type().name());
}
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Translators of ONOS abstractions to BMv2 model-dependent abstractions.
*/
package org.onosproject.drivers.bmv2.translators;
\ No newline at end of file
{
"header_types": [
{
"name": "standard_metadata_t",
"id": 0,
"fields": [
[
"ingress_port",
9
],
[
"packet_length",
32
],
[
"egress_spec",
9
],
[
"egress_port",
9
],
[
"egress_instance",
32
],
[
"instance_type",
32
],
[
"clone_spec",
32
],
[
"_padding",
5
]
],
"length_exp": null,
"max_length": null
},
{
"name": "ethernet_t",
"id": 1,
"fields": [
[
"dstAddr",
48
],
[
"srcAddr",
48
],
[
"etherType",
16
]
],
"length_exp": null,
"max_length": null
},
{
"name": "intrinsic_metadata_t",
"id": 2,
"fields": [
[
"ingress_global_timestamp",
32
],
[
"lf_field_list",
32
],
[
"mcast_grp",
16
],
[
"egress_rid",
16
]
],
"length_exp": null,
"max_length": null
},
{
"name": "cpu_header_t",
"id": 3,
"fields": [
[
"device",
8
],
[
"reason",
8
]
],
"length_exp": null,
"max_length": null
}
],
"headers": [
{
"name": "standard_metadata",
"id": 0,
"header_type": "standard_metadata_t",
"metadata": true
},
{
"name": "ethernet",
"id": 1,
"header_type": "ethernet_t",
"metadata": false
},
{
"name": "intrinsic_metadata",
"id": 2,
"header_type": "intrinsic_metadata_t",
"metadata": true
},
{
"name": "cpu_header",
"id": 3,
"header_type": "cpu_header_t",
"metadata": false
}
],
"header_stacks": [],
"parsers": [
{
"name": "parser",
"id": 0,
"init_state": "start",
"parse_states": [
{
"name": "start",
"id": 0,
"parser_ops": [],
"transition_key": [
{
"type": "lookahead",
"value": [
0,
64
]
}
],
"transitions": [
{
"value": "0x0000000000000000",
"mask": null,
"next_state": "parse_cpu_header"
},
{
"value": "default",
"mask": null,
"next_state": "parse_ethernet"
}
]
},
{
"name": "parse_cpu_header",
"id": 1,
"parser_ops": [
{
"op": "extract",
"parameters": [
{
"type": "regular",
"value": "cpu_header"
}
]
}
],
"transition_key": [],
"transitions": [
{
"value": "default",
"mask": null,
"next_state": "parse_ethernet"
}
]
},
{
"name": "parse_ethernet",
"id": 2,
"parser_ops": [
{
"op": "extract",
"parameters": [
{
"type": "regular",
"value": "ethernet"
}
]
}
],
"transition_key": [],
"transitions": [
{
"value": "default",
"mask": null,
"next_state": null
}
]
}
]
}
],
"deparsers": [
{
"name": "deparser",
"id": 0,
"order": [
"cpu_header",
"ethernet"
]
}
],
"meter_arrays": [],
"actions": [
{
"name": "flood",
"id": 0,
"runtime_data": [],
"primitives": [
{
"op": "modify_field",
"parameters": [
{
"type": "field",
"value": [
"intrinsic_metadata",
"mcast_grp"
]
},
{
"type": "field",
"value": [
"standard_metadata",
"ingress_port"
]
}
]
}
]
},
{
"name": "_drop",
"id": 1,
"runtime_data": [],
"primitives": [
{
"op": "modify_field",
"parameters": [
{
"type": "field",
"value": [
"standard_metadata",
"egress_spec"
]
},
{
"type": "hexstr",
"value": "0x1ff"
}
]
}
]
},
{
"name": "fwd",
"id": 2,
"runtime_data": [
{
"name": "port",
"bitwidth": 9
}
],
"primitives": [
{
"op": "modify_field",
"parameters": [
{
"type": "field",
"value": [
"standard_metadata",
"egress_spec"
]
},
{
"type": "runtime_data",
"value": 0
}
]
}
]
},
{
"name": "send_to_cpu",
"id": 3,
"runtime_data": [
{
"name": "device",
"bitwidth": 8
},
{
"name": "reason",
"bitwidth": 8
}
],
"primitives": [
{
"op": "add_header",
"parameters": [
{
"type": "header",
"value": "cpu_header"
}
]
},
{
"op": "modify_field",
"parameters": [
{
"type": "field",
"value": [
"cpu_header",
"device"
]
},
{
"type": "runtime_data",
"value": 0
}
]
},
{
"op": "modify_field",
"parameters": [
{
"type": "field",
"value": [
"cpu_header",
"reason"
]
},
{
"type": "runtime_data",
"value": 1
}
]
},
{
"op": "modify_field",
"parameters": [
{
"type": "field",
"value": [
"standard_metadata",
"egress_spec"
]
},
{
"type": "hexstr",
"value": "0xfa"
}
]
}
]
}
],
"pipelines": [
{
"name": "ingress",
"id": 0,
"init_table": "table0",
"tables": [
{
"name": "table0",
"id": 0,
"match_type": "ternary",
"type": "simple",
"max_size": 16384,
"with_counters": false,
"direct_meters": null,
"support_timeout": false,
"key": [
{
"match_type": "ternary",
"target": [
"standard_metadata",
"ingress_port"
],
"mask": null
},
{
"match_type": "ternary",
"target": [
"ethernet",
"dstAddr"
],
"mask": null
},
{
"match_type": "ternary",
"target": [
"ethernet",
"srcAddr"
],
"mask": null
},
{
"match_type": "ternary",
"target": [
"ethernet",
"etherType"
],
"mask": null
}
],
"actions": [
"fwd",
"flood",
"send_to_cpu",
"_drop"
],
"next_tables": {
"fwd": null,
"flood": null,
"send_to_cpu": null,
"_drop": null
},
"default_action": null
}
],
"conditionals": []
},
{
"name": "egress",
"id": 1,
"init_table": null,
"tables": [],
"conditionals": []
}
],
"calculations": [],
"checksums": [],
"learn_lists": [],
"field_lists": [],
"counter_arrays": [],
"register_arrays": [],
"force_arith": [
[
"standard_metadata",
"ingress_port"
],
[
"standard_metadata",
"packet_length"
],
[
"standard_metadata",
"egress_spec"
],
[
"standard_metadata",
"egress_port"
],
[
"standard_metadata",
"egress_instance"
],
[
"standard_metadata",
"instance_type"
],
[
"standard_metadata",
"clone_spec"
],
[
"standard_metadata",
"_padding"
],
[
"intrinsic_metadata",
"ingress_global_timestamp"
],
[
"intrinsic_metadata",
"lf_field_list"
],
[
"intrinsic_metadata",
"mcast_grp"
],
[
"intrinsic_metadata",
"egress_rid"
]
]
}
\ No newline at end of file
/*
* Copyright 2014-2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.bmv2;
import com.google.common.testing.EqualsTester;
import org.junit.Test;
import org.onlab.packet.MacAddress;
import org.onosproject.bmv2.api.model.Bmv2Model;
import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.DefaultApplicationId;
import org.onosproject.drivers.bmv2.translators.Bmv2DefaultFlowRuleTranslator;
import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslator;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import java.util.Random;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Tests for {@link Bmv2DefaultFlowRuleTranslator}.
*/
public class Bmv2DefaultFlowRuleTranslatorTest {
private Random random = new Random();
private Bmv2FlowRuleTranslator translator = new Bmv2DefaultFlowRuleTranslator();
private Bmv2Model model = translator.config().model();
@Test
public void testCompiler() throws Exception {
DeviceId deviceId = DeviceId.NONE;
ApplicationId appId = new DefaultApplicationId(1, "test");
int tableId = 0;
MacAddress ethDstMac = MacAddress.valueOf(random.nextLong());
MacAddress ethSrcMac = MacAddress.valueOf(random.nextLong());
short ethType = (short) (0x0000FFFF & random.nextInt());
short outPort = (short) random.nextInt(65);
short inPort = (short) random.nextInt(65);
int timeout = random.nextInt(100);
int priority = random.nextInt(100);
TrafficSelector matchInPort1 = DefaultTrafficSelector
.builder()
.matchInPort(PortNumber.portNumber(inPort))
.matchEthDst(ethDstMac)
.matchEthSrc(ethSrcMac)
.matchEthType(ethType)
.build();
TrafficTreatment outPort2 = DefaultTrafficTreatment
.builder()
.setOutput(PortNumber.portNumber(outPort))
.build();
FlowRule rule1 = DefaultFlowRule.builder()
.forDevice(deviceId)
.forTable(tableId)
.fromApp(appId)
.withSelector(matchInPort1)
.withTreatment(outPort2)
.makeTemporary(timeout)
.withPriority(priority)
.build();
FlowRule rule2 = DefaultFlowRule.builder()
.forDevice(deviceId)
.forTable(tableId)
.fromApp(appId)
.withSelector(matchInPort1)
.withTreatment(outPort2)
.makeTemporary(timeout)
.withPriority(priority)
.build();
Bmv2TableEntry entry1 = translator.translate(rule1);
Bmv2TableEntry entry2 = translator.translate(rule1);
// check equality, i.e. same rules must produce same entries
new EqualsTester()
.addEqualityGroup(rule1, rule2)
.addEqualityGroup(entry1, entry2)
.testEquals();
int numMatchParams = model.table(0).keys().size();
// parse values stored in entry1
Bmv2TernaryMatchParam inPortParam = (Bmv2TernaryMatchParam) entry1.matchKey().matchParams().get(0);
Bmv2TernaryMatchParam ethDstParam = (Bmv2TernaryMatchParam) entry1.matchKey().matchParams().get(1);
Bmv2TernaryMatchParam ethSrcParam = (Bmv2TernaryMatchParam) entry1.matchKey().matchParams().get(2);
Bmv2TernaryMatchParam ethTypeParam = (Bmv2TernaryMatchParam) entry1.matchKey().matchParams().get(3);
double expectedTimeout = (double) (model.table(0).hasTimeouts() ? rule1.timeout() : -1);
// check that the number of parameters in the entry is the same as the number of table keys
assertThat("Incorrect number of match parameters",
entry1.matchKey().matchParams().size(), is(equalTo(numMatchParams)));
// check that values stored in entry are the same used for the flow rule
assertThat("Incorrect inPort match param value",
inPortParam.value().asReadOnlyBuffer().getShort(), is(equalTo(inPort)));
assertThat("Incorrect ethDestMac match param value",
ethDstParam.value().asArray(), is(equalTo(ethDstMac.toBytes())));
assertThat("Incorrect ethSrcMac match param value",
ethSrcParam.value().asArray(), is(equalTo(ethSrcMac.toBytes())));
assertThat("Incorrect ethType match param value",
ethTypeParam.value().asReadOnlyBuffer().getShort(), is(equalTo(ethType)));
assertThat("Incorrect priority value",
entry1.priority(), is(equalTo(rule1.priority())));
assertThat("Incorrect timeout value",
entry1.timeout(), is(equalTo(expectedTimeout)));
}
}
\ No newline at end of file