Carmelo Cascone
Committed by Gerrit Code Review

Implemented convenient builders of BMv2 extension selectors and

treatments. 

Match and action parameters can now be built from primitive data types
(short, int, long or byte[]) which are then casted automatically
according to a given BMv2 configuration. Also, simplified demo
applications code / structure.

Change-Id: Ia5bebf62301c73c0b20cf6a4ddfb74165889106f
......@@ -19,11 +19,14 @@ package org.onosproject.bmv2.demo.app.ecmp;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.felix.scr.annotations.Component;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
......@@ -46,14 +49,13 @@ import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toSet;
import static org.onlab.packet.EthType.EtherType.IPV4;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpGroupTreatmentBuilder.groupIdOf;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.ECMP_GROUP_TABLE;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.TABLE0;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
/**
* Implementation of an upgradable fabric app for the ECMP configuration.
......@@ -69,6 +71,8 @@ public class EcmpFabricApp extends AbstractUpgradableFabricApp {
private static final EcmpInterpreter ECMP_INTERPRETER = new EcmpInterpreter();
protected static final Bmv2DeviceContext ECMP_CONTEXT = new Bmv2DeviceContext(ECMP_CONFIGURATION, ECMP_INTERPRETER);
private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
public EcmpFabricApp() {
super(APP_NAME, MODEL_NAME, ECMP_CONTEXT);
}
......@@ -211,10 +215,7 @@ public class EcmpFabricApp extends AbstractUpgradableFabricApp {
Iterator<PortNumber> portIterator = fabricPorts.iterator();
List<FlowRule> rules = Lists.newArrayList();
for (short i = 0; i < groupSize; i++) {
ExtensionSelector extSelector = new EcmpGroupTableSelectorBuilder()
.withGroupId(groupId)
.withSelector(i)
.build();
ExtensionSelector extSelector = buildEcmpSelector(groupId, i);
FlowRule rule = flowRuleBuilder(deviceId, ECMP_GROUP_TABLE)
.withSelector(
DefaultTrafficSelector.builder()
......@@ -228,14 +229,37 @@ public class EcmpFabricApp extends AbstractUpgradableFabricApp {
rules.add(rule);
}
ExtensionTreatment extTreatment = new EcmpGroupTreatmentBuilder()
.withGroupId(groupId)
.withGroupSize(groupSize)
.build();
ExtensionTreatment extTreatment = buildEcmpTreatment(groupId, groupSize);
return Pair.of(extTreatment, rules);
}
private Bmv2ExtensionTreatment buildEcmpTreatment(int groupId, int groupSize) {
return Bmv2ExtensionTreatment.builder()
.forConfiguration(ECMP_CONTEXT.configuration())
.setActionName(ECMP_GROUP)
.addParameter(GROUP_ID, groupId)
.addParameter(GROUP_SIZE, groupSize)
.build();
}
private Bmv2ExtensionSelector buildEcmpSelector(int groupId, int selector) {
return Bmv2ExtensionSelector.builder()
.forConfiguration(ECMP_CONTEXT.configuration())
.matchExact(ECMP_METADATA, GROUP_ID, groupId)
.matchExact(ECMP_METADATA, SELECTOR, selector)
.build();
}
public int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
// Counts the number of unique portNumber sets for each deviceId.
// Each distinct set of portNumbers will have a unique ID.
return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
(short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
}
private static Bmv2Configuration loadConfiguration() {
try {
JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
......
/*
* 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.bmv2.demo.app.ecmp;
import com.google.common.collect.ImmutableMap;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.context.Bmv2HeaderTypeModel;
import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpFabricApp.ECMP_CONTEXT;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
/**
* Builder of ECMP group table extension selector.
*/
public class EcmpGroupTableSelectorBuilder {
private int groupId;
private int selector;
/**
* Sets the ECMP group ID.
*
* @param groupId an integer value
* @return this
*/
public EcmpGroupTableSelectorBuilder withGroupId(int groupId) {
this.groupId = groupId;
return this;
}
/**
* Sets the ECMP selector.
*
* @param selector an integer value
* @return this
*/
public EcmpGroupTableSelectorBuilder withSelector(int selector) {
this.selector = selector;
return this;
}
/**
* Returns a new extension selector.
*
* @return an extension selector
*/
public ExtensionSelector build() {
Bmv2HeaderTypeModel headerTypeModel = ECMP_CONTEXT.configuration().headerType(ECMP_METADATA_T);
int groupIdBitWidth = headerTypeModel.field(GROUP_ID).bitWidth();
int selectorBitWidth = headerTypeModel.field(SELECTOR).bitWidth();
try {
ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId),
groupIdBitWidth);
ImmutableByteSequence selectorBs = fitByteSequence(ImmutableByteSequence.copyFrom(selector),
selectorBitWidth);
Bmv2ExactMatchParam groupIdMatch = new Bmv2ExactMatchParam(groupIdBs);
Bmv2ExactMatchParam hashMatch = new Bmv2ExactMatchParam(selectorBs);
return new Bmv2ExtensionSelector(ImmutableMap.of(
ECMP_METADATA + "." + GROUP_ID, groupIdMatch,
ECMP_METADATA + "." + SELECTOR, hashMatch));
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new RuntimeException(e);
}
}
}
/*
* 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.bmv2.demo.app.ecmp;
import com.google.common.collect.Maps;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.context.Bmv2ActionModel;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import java.util.Map;
import java.util.Set;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpFabricApp.ECMP_CONTEXT;
import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
/**
* Builder of ECMP extension treatments.
*/
public class EcmpGroupTreatmentBuilder {
private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
private int groupId;
private int groupSize;
/**
* Sets the group ID.
*
* @param groupId an integer value
* @return this
*/
public EcmpGroupTreatmentBuilder withGroupId(int groupId) {
this.groupId = groupId;
return this;
}
/**
* Sets the group size.
*
* @param groupSize an integer value
* @return this
*/
public EcmpGroupTreatmentBuilder withGroupSize(int groupSize) {
this.groupSize = groupSize;
return this;
}
/**
* Returns a new extension treatment.
*
* @return an extension treatment
*/
public ExtensionTreatment build() {
Bmv2ActionModel actionModel = ECMP_CONTEXT.configuration().action(ECMP_GROUP);
int groupIdBitWidth = actionModel.runtimeData(GROUP_ID).bitWidth();
int groupSizeBitWidth = actionModel.runtimeData(GROUP_SIZE).bitWidth();
try {
ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId), groupIdBitWidth);
ImmutableByteSequence groupSizeBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupSize),
groupSizeBitWidth);
return new Bmv2ExtensionTreatment(Bmv2Action.builder()
.withName(ECMP_GROUP)
.addParameter(groupIdBs)
.addParameter(groupSizeBs)
.build());
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new RuntimeException(e);
}
}
/**
* Returns a group ID for the given device and set of ports.
*
* @param deviceId a device ID
* @param ports a set of ports
* @return an integer value
*/
public static int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
// Counts the number of unique portNumber sets for each deviceId.
// Each distinct set of portNumbers will have a unique ID.
return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
(short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
}
}
......@@ -37,7 +37,6 @@ import static org.onosproject.net.flow.instructions.Instructions.OutputInstructi
*/
public class EcmpInterpreter implements Bmv2Interpreter {
protected static final String ECMP_METADATA_T = "ecmp_metadata_t";
protected static final String ECMP_METADATA = "ecmp_metadata";
protected static final String SELECTOR = "selector";
protected static final String GROUP_ID = "groupId";
......
......@@ -18,6 +18,7 @@ package org.onosproject.bmv2.demo.app.wcmp;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
......@@ -30,6 +31,8 @@ import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
import org.onosproject.bmv2.api.service.Bmv2Controller;
import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
......@@ -50,18 +53,19 @@ import org.onosproject.net.topology.TopologyGraph;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.onlab.packet.EthType.EtherType.IPV4;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpGroupTreatmentBuilder.groupIdOf;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpGroupTreatmentBuilder.toPrefixLengths;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.TABLE0;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.WCMP_GROUP_TABLE;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
/**
* Implementation of an upgradable fabric app for the WCMP configuration.
......@@ -79,6 +83,8 @@ public class WcmpFabricApp extends AbstractUpgradableFabricApp {
private static final WcmpInterpreter WCMP_INTERPRETER = new WcmpInterpreter();
protected static final Bmv2DeviceContext WCMP_CONTEXT = new Bmv2DeviceContext(WCMP_CONFIGURATION, WCMP_INTERPRETER);
private static final Map<DeviceId, Map<Map<PortNumber, Double>, Integer>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private Bmv2Controller bmv2Controller;
......@@ -252,19 +258,11 @@ public class WcmpFabricApp extends AbstractUpgradableFabricApp {
portNumbers.add(p);
weights.add(w);
});
List<Integer> prefixLengths;
try {
prefixLengths = toPrefixLengths(weights);
} catch (WcmpGroupTreatmentBuilder.WcmpGroupException e) {
throw new FlowRuleGeneratorException(e);
}
List<Integer> prefixLengths = toPrefixLengths(weights);
List<FlowRule> rules = Lists.newArrayList();
for (int i = 0; i < portNumbers.size(); i++) {
ExtensionSelector extSelector = new WcmpGroupTableSelectorBuilder()
.withGroupId(groupId)
.withPrefixLength(prefixLengths.get(i))
.build();
ExtensionSelector extSelector = buildWcmpSelector(groupId, prefixLengths.get(i));
FlowRule rule = flowRuleBuilder(deviceId, WCMP_GROUP_TABLE)
.withSelector(DefaultTrafficSelector.builder()
.extension(extSelector, deviceId)
......@@ -277,11 +275,78 @@ public class WcmpFabricApp extends AbstractUpgradableFabricApp {
rules.add(rule);
}
ExtensionTreatment extTreatment = new WcmpGroupTreatmentBuilder().withGroupId(groupId).build();
ExtensionTreatment extTreatment = buildWcmpTreatment(groupId);
return Pair.of(extTreatment, rules);
}
private Bmv2ExtensionSelector buildWcmpSelector(int groupId, int prefixLength) {
byte[] ones = new byte[roundToBytes(prefixLength)];
Arrays.fill(ones, (byte) 0xFF);
return Bmv2ExtensionSelector.builder()
.forConfiguration(WCMP_CONTEXT.configuration())
.matchExact(WCMP_META, GROUP_ID, groupId)
.matchLpm(WCMP_META, SELECTOR, ones, prefixLength)
.build();
}
private Bmv2ExtensionTreatment buildWcmpTreatment(int groupId) {
return Bmv2ExtensionTreatment.builder()
.forConfiguration(WCMP_CONTEXT.configuration())
.setActionName(WCMP_GROUP)
.addParameter(GROUP_ID, groupId)
.build();
}
public int groupIdOf(DeviceId did, Map<PortNumber, Double> weightedPorts) {
DEVICE_GROUP_ID_MAP.putIfAbsent(did, Maps.newHashMap());
// Counts the number of unique portNumber sets for each device ID.
// Each distinct set of portNumbers will have a unique ID.
return DEVICE_GROUP_ID_MAP.get(did).computeIfAbsent(weightedPorts,
(pp) -> DEVICE_GROUP_ID_MAP.get(did).size() + 1);
}
public List<Integer> toPrefixLengths(List<Double> weigths) {
final double weightSum = weigths.stream()
.mapToDouble(Double::doubleValue)
.map(this::roundDouble)
.sum();
if (Math.abs(weightSum - 1) > 0.0001) {
throw new RuntimeException("WCMP weights sum is expected to be 1, found was " + weightSum);
}
final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
final int availableBits = selectorBitWidth - 1;
List<Long> prefixDiffs = weigths.stream().map(w -> Math.round(w * availableBits)).collect(toList());
final long bitSum = prefixDiffs.stream().mapToLong(Long::longValue).sum();
final long error = availableBits - bitSum;
if (error != 0) {
// Lazy intuition here is that the error can be absorbed by the longest prefixDiff with the minor impact.
Long maxDiff = Collections.max(prefixDiffs);
int idx = prefixDiffs.indexOf(maxDiff);
prefixDiffs.remove(idx);
prefixDiffs.add(idx, maxDiff + error);
}
List<Integer> prefixLengths = Lists.newArrayList();
int prefix = 1;
for (Long p : prefixDiffs) {
prefixLengths.add(prefix);
prefix += p;
}
return ImmutableList.copyOf(prefixLengths);
}
private double roundDouble(double n) {
// 5 digits precision.
return (double) Math.round(n * 100000d) / 100000d;
}
private static Bmv2Configuration loadConfiguration() {
try {
JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
......
/*
* 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.bmv2.demo.app.wcmp;
import com.google.common.collect.ImmutableMap;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import static com.google.common.base.Preconditions.checkArgument;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpFabricApp.WCMP_CONTEXT;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
/**
* Builder of WCMP group table extension selector.
*/
public final class WcmpGroupTableSelectorBuilder {
private int groupId;
private int prefixLength;
/**
* Sets the WCMP group ID.
*
* @param groupId an integer value
* @return this
*/
public WcmpGroupTableSelectorBuilder withGroupId(int groupId) {
this.groupId = groupId;
return this;
}
/**
* Sets the WCMP selector's prefix length.
*
* @param prefixLength an integer value
* @return this
*/
public WcmpGroupTableSelectorBuilder withPrefixLength(int prefixLength) {
this.prefixLength = prefixLength;
return this;
}
/**
* Returns a new extension selector.
*
* @return an extension selector
*/
public ExtensionSelector build() {
final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
final int groupIdBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(GROUP_ID).bitWidth();
final ImmutableByteSequence ones = ImmutableByteSequence.ofOnes(roundToBytes(selectorBitWidth));
checkArgument(prefixLength >= 1 && prefixLength <= selectorBitWidth,
"prefix length must be between 1 and " + selectorBitWidth);
try {
ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId), groupIdBitWidth);
Bmv2ExactMatchParam groupIdMatch = new Bmv2ExactMatchParam(groupIdBs);
Bmv2LpmMatchParam selectorMatch = new Bmv2LpmMatchParam(ones, prefixLength);
return new Bmv2ExtensionSelector(ImmutableMap.of(
WCMP_META + "." + GROUP_ID, groupIdMatch,
WCMP_META + "." + SELECTOR, selectorMatch));
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new RuntimeException(e);
}
}
}
/*
* 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.bmv2.demo.app.wcmp;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.stream.Collectors.toList;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpFabricApp.WCMP_CONTEXT;
import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
/**
* Builder of WCMP extension treatment.
*/
public final class WcmpGroupTreatmentBuilder {
private static final double MAX_ERROR = 0.0001;
private static final Map<DeviceId, Map<Map<PortNumber, Double>, Integer>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
private int groupId;
/**
* Sets the WCMP group ID.
*
* @param groupId an integer value
* @return this
*/
public WcmpGroupTreatmentBuilder withGroupId(int groupId) {
this.groupId = groupId;
return this;
}
/**
* Returns a new extension treatment.
*
* @return an extension treatment
*/
public ExtensionTreatment build() {
checkArgument(groupId >= 0, "group id must be a non-zero positive integer");
ImmutableByteSequence groupIdBs = ImmutableByteSequence.copyFrom(groupId);
final int groupIdBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(GROUP_ID).bitWidth();
try {
groupIdBs = fitByteSequence(groupIdBs, groupIdBitWidth);
return new Bmv2ExtensionTreatment(
Bmv2Action.builder()
.withName(WCMP_GROUP)
.addParameter(groupIdBs)
.build());
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new RuntimeException(e);
}
}
public static int groupIdOf(DeviceId did, Map<PortNumber, Double> weightedPorts) {
DEVICE_GROUP_ID_MAP.putIfAbsent(did, Maps.newHashMap());
// Counts the number of unique portNumber sets for each device ID.
// Each distinct set of portNumbers will have a unique ID.
return DEVICE_GROUP_ID_MAP.get(did).computeIfAbsent(weightedPorts,
(pp) -> DEVICE_GROUP_ID_MAP.get(did).size() + 1);
}
public static List<Integer> toPrefixLengths(List<Double> weigths) throws WcmpGroupException {
double weightSum = weigths.stream()
.mapToDouble(Double::doubleValue)
.map(WcmpGroupTreatmentBuilder::roundDouble)
.sum();
if (Math.abs(weightSum - 1) > MAX_ERROR) {
throw new WcmpGroupException("weights sum is expected to be 1, found was " + weightSum);
}
final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
final int availableBits = selectorBitWidth - 1;
List<Long> prefixDiffs = weigths.stream().map(w -> Math.round(w * availableBits)).collect(toList());
final long bitSum = prefixDiffs.stream().mapToLong(Long::longValue).sum();
final long error = availableBits - bitSum;
if (error != 0) {
// Lazy intuition here is that the error can be absorbed by the longest prefixDiff with the minor impact.
Long maxDiff = Collections.max(prefixDiffs);
int idx = prefixDiffs.indexOf(maxDiff);
prefixDiffs.remove(idx);
prefixDiffs.add(idx, maxDiff + error);
}
List<Integer> prefixLengths = Lists.newArrayList();
int prefix = 1;
for (Long p : prefixDiffs) {
prefixLengths.add(prefix);
prefix += p;
}
return ImmutableList.copyOf(prefixLengths);
}
private static double roundDouble(double n) {
// 5 digits precision.
return (double) Math.round(n * 100000d) / 100000d;
}
public static class WcmpGroupException extends Exception {
public WcmpGroupException(String s) {
}
}
}
......@@ -22,8 +22,6 @@ import org.onosproject.net.driver.AbstractHandlerBehaviour;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import org.onosproject.net.flow.criteria.ExtensionSelectorType;
import java.util.Collections;
import static org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes.BMV2_MATCH_PARAMS;
/**
......@@ -34,7 +32,7 @@ public class Bmv2ExtensionSelectorResolver extends AbstractHandlerBehaviour impl
@Override
public ExtensionSelector getExtensionSelector(ExtensionSelectorType type) {
if (type.equals(BMV2_MATCH_PARAMS.type())) {
return new Bmv2ExtensionSelector(Collections.emptyMap());
return Bmv2ExtensionSelector.empty();
}
return null;
......
......@@ -32,7 +32,7 @@ public class Bmv2ExtensionTreatmentResolver extends AbstractHandlerBehaviour imp
@Override
public ExtensionTreatment getExtensionInstruction(ExtensionTreatmentType type) {
if (type.equals(BMV2_ACTION.type())) {
return new Bmv2ExtensionTreatment(null);
return Bmv2ExtensionTreatment.empty();
}
return null;
}
......
......@@ -37,7 +37,7 @@ public final class Bmv2Action {
private final String name;
private final List<ImmutableByteSequence> parameters;
private Bmv2Action(String name, List<ImmutableByteSequence> parameters) {
protected Bmv2Action(String name, List<ImmutableByteSequence> parameters) {
// hide constructor
this.name = name;
this.parameters = parameters;
......
......@@ -19,11 +19,26 @@ package org.onosproject.bmv2.api.runtime;
import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.Maps;
import org.onlab.util.ImmutableByteSequence;
import org.onlab.util.KryoNamespace;
import org.onosproject.bmv2.api.context.Bmv2ActionModel;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2RuntimeDataModel;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.flow.AbstractExtension;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.util.ImmutableByteSequence.copyFrom;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.BMV2_ACTION;
/**
......@@ -40,7 +55,7 @@ public final class Bmv2ExtensionTreatment extends AbstractExtension implements E
*
* @param action an action
*/
public Bmv2ExtensionTreatment(Bmv2Action action) {
private Bmv2ExtensionTreatment(Bmv2Action action) {
this.action = action;
}
......@@ -91,4 +106,167 @@ public final class Bmv2ExtensionTreatment extends AbstractExtension implements E
.add("action", action)
.toString();
}
/**
* Returns a new, empty BMv2 extension treatment.
*
* @return a BMv2 extension treatment
*/
public static Bmv2ExtensionTreatment empty() {
return new Bmv2ExtensionTreatment(null);
}
/**
* Returns a new BMv2 extension treatment builder.
*
* @return a builder
*/
public static Builder builder() {
return new Builder();
}
/**
* A builder of BMv2 extension treatments.
*
* BMv2 action parameters are built from primitive data types ({@code short}, {@code int}, {@code long} or
* {@code byte[]}) and automatically casted to fixed-length byte sequences according to the given BMv2
* configuration.
*/
public static final class Builder {
private Bmv2Configuration configuration;
private String actionName;
private final Map<String, ImmutableByteSequence> parameters = Maps.newHashMap();
private Builder() {
// Ban constructor.
}
/**
* Sets the BMv2 configuration to format the action parameters.
*
* @param config a BMv2 configuration
* @return this
*/
public Builder forConfiguration(Bmv2Configuration config) {
this.configuration = config;
return this;
}
/**
* Sets the action name.
*
* @param actionName a string value
* @return this
*/
public Builder setActionName(String actionName) {
this.actionName = actionName;
return this;
}
/**
* Adds an action parameter.
*
* @param parameterName a string value
* @param value a short value
* @return this
*/
public Builder addParameter(String parameterName, short value) {
this.parameters.put(parameterName, copyFrom(bb(value)));
return this;
}
/**
* Adds an action parameter.
*
* @param parameterName a string value
* @param value an integer value
* @return this
*/
public Builder addParameter(String parameterName, int value) {
this.parameters.put(parameterName, copyFrom(bb(value)));
return this;
}
/**
* Adds an action parameter.
*
* @param parameterName a string value
* @param value a long value
* @return this
*/
public Builder addParameter(String parameterName, long value) {
this.parameters.put(parameterName, copyFrom(bb(value)));
return this;
}
/**
* Adds an action parameter.
*
* @param parameterName a string value
* @param value a byte array
* @return this
*/
public Builder addParameter(String parameterName, byte[] value) {
this.parameters.put(parameterName, copyFrom(bb(value)));
return this;
}
/**
* Returns a new BMv2 extension treatment.
*
* @return a BMv2 extension treatment
* @throws NullPointerException if the given action or parameter names are not defined in the given
* configuration
* @throws IllegalArgumentException if a given parameter cannot be casted for the given configuration, e.g.
* when trying to fit an integer value into a smaller, fixed-length parameter
* produces overflow.
*/
public Bmv2ExtensionTreatment build() {
checkNotNull(configuration, "configuration cannot be null");
checkNotNull(actionName, "action name cannot be null");
Bmv2ActionModel actionModel = configuration.action(actionName);
checkNotNull(actionModel, "no such an action in configuration", actionName);
checkArgument(actionModel.runtimeDatas().size() == parameters.size(),
"invalid number of parameters", actionName);
List<ImmutableByteSequence> newParameters = new ArrayList<>(parameters.size());
for (String parameterName : parameters.keySet()) {
Bmv2RuntimeDataModel runtimeData = actionModel.runtimeData(parameterName);
checkNotNull(runtimeData, "no such an action parameter in configuration",
actionName + "->" + runtimeData.name());
int bitWidth = runtimeData.bitWidth();
try {
ImmutableByteSequence newSequence = fitByteSequence(parameters.get(parameterName), bitWidth);
int idx = actionModel.runtimeDatas().indexOf(runtimeData);
newParameters.add(idx, newSequence);
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new IllegalArgumentException(e.getMessage() +
" [" + actionName + "->" + runtimeData.name() + "]");
}
}
return new Bmv2ExtensionTreatment(new Bmv2Action(actionName, newParameters));
}
private static ByteBuffer bb(Object value) {
if (value instanceof Short) {
return ByteBuffer.allocate(Short.BYTES).putShort((short) value);
} else if (value instanceof Integer) {
return ByteBuffer.allocate(Integer.BYTES).putInt((int) value);
} else if (value instanceof Long) {
return ByteBuffer.allocate(Long.BYTES).putLong((long) value);
} else if (value instanceof byte[]) {
byte[] bytes = (byte[]) value;
return ByteBuffer.allocate(bytes.length).put(bytes);
} else {
// Never here.
return null;
}
}
}
}
......
/*
* 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.bmv2.api.runtime;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.MacAddress;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class Bmv2ExtensionBuilderTest {
private Bmv2Configuration config;
@Before
public void setUp() throws Exception {
JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
this.getClass().getResourceAsStream("/simple.json")))).asObject();
config = Bmv2DefaultConfiguration.parse(json);
}
@Test
public void testExtensionSelector() throws Exception {
Bmv2ExtensionSelector extSelectorExact = Bmv2ExtensionSelector.builder()
.forConfiguration(config)
.matchExact("standard_metadata", "ingress_port", (short) 255)
.matchExact("ethernet", "etherType", 512)
.matchExact("ethernet", "dstAddr", 1024L)
.matchExact("ethernet", "srcAddr", MacAddress.BROADCAST.toBytes())
.build();
Bmv2ExtensionSelector extSelectorTernary = Bmv2ExtensionSelector.builder()
.forConfiguration(config)
.matchTernary("standard_metadata", "ingress_port", (short) 255, (short) 255)
.matchTernary("ethernet", "etherType", 512, 512)
.matchTernary("ethernet", "dstAddr", 1024L, 1024L)
.matchTernary("ethernet", "srcAddr", MacAddress.BROADCAST.toBytes(), MacAddress.NONE.toBytes())
.build();
Bmv2ExtensionSelector extSelectorLpm = Bmv2ExtensionSelector.builder()
.forConfiguration(config)
.matchLpm("standard_metadata", "ingress_port", (short) 255, 1)
.matchLpm("ethernet", "etherType", 512, 2)
.matchLpm("ethernet", "dstAddr", 1024L, 3)
.matchLpm("ethernet", "srcAddr", MacAddress.BROADCAST.toBytes(), 4)
.build();
Bmv2ExtensionSelector extSelectorValid = Bmv2ExtensionSelector.builder()
.forConfiguration(config)
.matchValid("standard_metadata", "ingress_port", true)
.matchValid("ethernet", "etherType", true)
.matchValid("ethernet", "dstAddr", false)
.matchValid("ethernet", "srcAddr", false)
.build();
assertThat(extSelectorExact.parameterMap().size(), is(4));
assertThat(extSelectorTernary.parameterMap().size(), is(4));
assertThat(extSelectorLpm.parameterMap().size(), is(4));
assertThat(extSelectorValid.parameterMap().size(), is(4));
// TODO add more tests, e.g. check for byte sequences content and size.
}
@Test
public void testExtensionTreatment() throws Exception {
Bmv2ExtensionTreatment treatment = Bmv2ExtensionTreatment.builder()
.forConfiguration(config)
.setActionName("set_egress_port")
.addParameter("port", 1)
.build();
assertThat(treatment.action().parameters().size(), is(1));
// TODO add more tests, e.g. check for byte sequences content and size.
}
}
\ No newline at end of file