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
diff --git a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpFabricApp.java b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpFabricApp.java
index e028458..ab42d2f 100644
--- a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpFabricApp.java
+++ b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpFabricApp.java
@@ -19,11 +19,14 @@
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.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 @@
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 @@
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 @@
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(
diff --git a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTableSelectorBuilder.java b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTableSelectorBuilder.java
deleted file mode 100644
index e9ce0cf..0000000
--- a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTableSelectorBuilder.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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);
- }
- }
-}
diff --git a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTreatmentBuilder.java b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTreatmentBuilder.java
deleted file mode 100644
index eee8c59..0000000
--- a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTreatmentBuilder.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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));
- }
-}
diff --git a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpInterpreter.java b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpInterpreter.java
index 5585a64..c5ea2ae 100644
--- a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpInterpreter.java
+++ b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpInterpreter.java
@@ -37,7 +37,6 @@
*/
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";
diff --git a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpFabricApp.java b/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpFabricApp.java
index db7567e..f86e5f9 100644
--- a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpFabricApp.java
+++ b/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpFabricApp.java
@@ -18,6 +18,7 @@
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.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 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 @@
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 @@
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 @@
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(
diff --git a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTableSelectorBuilder.java b/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTableSelectorBuilder.java
deleted file mode 100644
index bac9d61..0000000
--- a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTableSelectorBuilder.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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);
- }
- }
-}
diff --git a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTreatmentBuilder.java b/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTreatmentBuilder.java
deleted file mode 100644
index 731b2ca..0000000
--- a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTreatmentBuilder.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * 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) {
- }
- }
-}
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionSelectorResolver.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionSelectorResolver.java
index 28e93a7..78986a6 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionSelectorResolver.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionSelectorResolver.java
@@ -22,8 +22,6 @@
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 @@
@Override
public ExtensionSelector getExtensionSelector(ExtensionSelectorType type) {
if (type.equals(BMV2_MATCH_PARAMS.type())) {
- return new Bmv2ExtensionSelector(Collections.emptyMap());
+ return Bmv2ExtensionSelector.empty();
}
return null;
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionTreatmentResolver.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionTreatmentResolver.java
index 00a22bb..6ef46d1 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionTreatmentResolver.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionTreatmentResolver.java
@@ -32,7 +32,7 @@
@Override
public ExtensionTreatment getExtensionInstruction(ExtensionTreatmentType type) {
if (type.equals(BMV2_ACTION.type())) {
- return new Bmv2ExtensionTreatment(null);
+ return Bmv2ExtensionTreatment.empty();
}
return null;
}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
index e05803c..a9d3ed8 100644
--- a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
@@ -37,7 +37,7 @@
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;
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
index cd8ec77..d9d9d0e 100644
--- a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
@@ -19,18 +19,29 @@
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.apache.commons.lang3.tuple.Pair;
import org.onlab.util.KryoNamespace;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2FieldTypeModel;
+import org.onosproject.bmv2.api.context.Bmv2HeaderModel;
+import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.flow.AbstractExtension;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import org.onosproject.net.flow.criteria.ExtensionSelectorType;
+import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
-import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.*;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
/**
- * Extension selector for BMv2 used as a wrapper for multiple BMv2 match parameters.
+ * Extension selector for BMv2 used as a wrapper for multiple BMv2 match parameters. Match parameters are
+ * encoded using a map where the keys are expected to be field names formatted as {@code headerName.fieldName}
+ * (e.g. {@code ethernet.dstAddr}).
*/
@Beta
public final class Bmv2ExtensionSelector extends AbstractExtension implements ExtensionSelector {
@@ -47,13 +58,12 @@
private Map<String, Bmv2MatchParam> parameterMap;
/**
- * Creates a new BMv2 extension selector for the given match parameters map, where the keys are expected to be field
- * names formatted as headerName.fieldMemberName (e.g. ethernet.dstAddr).
+ * Creates a new BMv2 extension selector for the given match parameters map.
*
* @param paramMap a map
*/
- public Bmv2ExtensionSelector(Map<String, Bmv2MatchParam> paramMap) {
- this.parameterMap = checkNotNull(paramMap, "param map cannot be null");
+ private Bmv2ExtensionSelector(Map<String, Bmv2MatchParam> paramMap) {
+ this.parameterMap = paramMap;
}
/**
@@ -104,4 +114,340 @@
.add("parameterMap", parameterMap)
.toString();
}
+
+ /**
+ * Returns a new, empty BMv2 extension selector.
+ *
+ * @return a BMv2 extension treatment
+ */
+ public static Bmv2ExtensionSelector empty() {
+ return new Bmv2ExtensionSelector(null);
+ }
+
+ /**
+ * Returns a new builder of BMv2 extension selectors.
+ *
+ * @return a builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of BMv2 extension selectors.
+ * <p>
+ * Match 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 final Map<Pair<String, String>, Bmv2MatchParam> parameterMap = Maps.newHashMap();
+ private Bmv2Configuration configuration;
+
+ private Builder() {
+ // ban constructor.
+ }
+
+ /**
+ * Sets the BMv2 configuration to format the match parameters of the selector.
+ *
+ * @param config a BMv2 configuration
+ * @return this
+ */
+ public Builder forConfiguration(Bmv2Configuration config) {
+ this.configuration = config;
+ return this;
+ }
+
+ /**
+ * Adds an exact match parameter for the given header field and value.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a short value
+ * @return this
+ */
+ public Builder matchExact(String headerName, String fieldName, short value) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ exact(value));
+ return this;
+ }
+
+ /**
+ * Adds an exact match parameter for the given header field and value.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value an integer value
+ * @return this
+ */
+ public Builder matchExact(String headerName, String fieldName, int value) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ exact(value));
+ return this;
+ }
+
+ /**
+ * Adds an exact match parameter for the given header field and value.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a long value
+ * @return this
+ */
+ public Builder matchExact(String headerName, String fieldName, long value) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ exact(value));
+ return this;
+ }
+
+ /**
+ * Adds an exact match parameter for the given header field and value.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a byte array
+ * @return this
+ */
+ public Builder matchExact(String headerName, String fieldName, byte[] value) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ exact(value));
+ return this;
+ }
+
+ /**
+ * Adds a ternary match parameter for the given header field, value and mask.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a short value
+ * @param mask a short value
+ * @return this
+ */
+ public Builder matchTernary(String headerName, String fieldName, short value, short mask) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ ternary(value, mask));
+ return this;
+ }
+ /**
+ * Adds a ternary match parameter for the given header field, value and mask.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value an integer value
+ * @param mask an integer value
+ * @return this
+ */
+ public Builder matchTernary(String headerName, String fieldName, int value, int mask) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ ternary(value, mask));
+ return this;
+ }
+ /**
+ * Adds a ternary match parameter for the given header field, value and mask.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a long value
+ * @param mask a long value
+ * @return this
+ */
+ public Builder matchTernary(String headerName, String fieldName, long value, long mask) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ ternary(value, mask));
+ return this;
+ }
+ /**
+ * Adds a ternary match parameter for the given header field, value and mask.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a byte array
+ * @param mask a byte array
+ * @return this
+ */
+ public Builder matchTernary(String headerName, String fieldName, byte[] value, byte[] mask) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ ternary(value, mask));
+ return this;
+ }
+
+ /**
+ * Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a short value
+ * @param prefixLength an integer value
+ * @return this
+ */
+ public Builder matchLpm(String headerName, String fieldName, short value, int prefixLength) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ lpm(value, prefixLength));
+ return this;
+ }
+ /**
+ * Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value an integer value
+ * @param prefixLength an integer value
+ * @return this
+ */
+ public Builder matchLpm(String headerName, String fieldName, int value, int prefixLength) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ lpm(value, prefixLength));
+ return this;
+ }
+ /**
+ * Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a long value
+ * @param prefixLength an integer value
+ * @return this
+ */
+ public Builder matchLpm(String headerName, String fieldName, long value, int prefixLength) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ lpm(value, prefixLength));
+ return this;
+ }
+ /**
+ * Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param value a byte array
+ * @param prefixLength an integer value
+ * @return this
+ */
+ public Builder matchLpm(String headerName, String fieldName, byte[] value, int prefixLength) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ lpm(value, prefixLength));
+ return this;
+ }
+
+ /**
+ * Adds a valid match parameter for the given header field.
+ *
+ * @param headerName a string value
+ * @param fieldName a string value
+ * @param flag a boolean value
+ * @return this
+ */
+ public Builder matchValid(String headerName, String fieldName, boolean flag) {
+ parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+ checkNotNull(fieldName, "field name cannot be null")),
+ new Bmv2ValidMatchParam(flag));
+ return this;
+ }
+
+ /**
+ * Returns a new BMv2 extension selector.
+ *
+ * @return a BMv2 extension selector
+ * @throws NullPointerException if a given header or field name is 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 Bmv2ExtensionSelector build() {
+ checkNotNull(configuration, "configuration cannot be null");
+ checkState(parameterMap.size() > 0, "parameter map cannot be empty");
+
+ final Map<String, Bmv2MatchParam> newParameterMap = Maps.newHashMap();
+
+ for (Pair<String, String> key : parameterMap.keySet()) {
+
+ String headerName = key.getLeft();
+ String fieldName = key.getRight();
+
+ Bmv2HeaderModel headerModel = configuration.header(headerName);
+ checkNotNull(headerModel, "no such a header in configuration", headerName);
+
+ Bmv2FieldTypeModel fieldModel = headerModel.type().field(fieldName);
+ checkNotNull(fieldModel, "no such a field in configuration", key);
+
+ int bitWidth = fieldModel.bitWidth();
+
+ Bmv2MatchParam oldParam = parameterMap.get(key);
+ Bmv2MatchParam newParam = null;
+
+ try {
+ switch (oldParam.type()) {
+ case EXACT:
+ Bmv2ExactMatchParam e = (Bmv2ExactMatchParam) oldParam;
+ newParam = new Bmv2ExactMatchParam(fitByteSequence(e.value(), bitWidth));
+ break;
+ case TERNARY:
+ Bmv2TernaryMatchParam t = (Bmv2TernaryMatchParam) oldParam;
+ newParam = new Bmv2TernaryMatchParam(fitByteSequence(t.value(), bitWidth),
+ fitByteSequence(t.mask(), bitWidth));
+ break;
+ case LPM:
+ Bmv2LpmMatchParam l = (Bmv2LpmMatchParam) oldParam;
+ checkArgument(l.prefixLength() <= bitWidth, "LPM parameter has prefix length too long",
+ key);
+ newParam = new Bmv2LpmMatchParam(fitByteSequence(l.value(), bitWidth),
+ l.prefixLength());
+ break;
+ case VALID:
+ newParam = oldParam;
+ break;
+ default:
+ throw new RuntimeException("Match parameter type not supported: " + oldParam.type());
+ }
+ } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
+ throw new IllegalArgumentException(e.getMessage() + " [" + key + "]");
+ }
+ // FIXME: should put the pair object instead of building a new string for the key.
+ newParameterMap.put(headerName + "." + fieldName, newParam);
+ }
+
+ return new Bmv2ExtensionSelector(newParameterMap);
+ }
+
+ private static Bmv2MatchParam exact(Object value) {
+ return new Bmv2ExactMatchParam(copyFrom(bb(value)));
+ }
+
+ private static Bmv2MatchParam ternary(Object value, Object mask) {
+ return new Bmv2TernaryMatchParam(copyFrom(bb(value)), copyFrom(bb(mask)));
+ }
+
+ private static Bmv2MatchParam lpm(Object value, int prefixLength) {
+ return new Bmv2LpmMatchParam(copyFrom(bb(value)), prefixLength);
+ }
+
+ 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;
+ }
+ }
+ }
}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
index b2f65f3..b2a9ba3 100644
--- a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
@@ -19,11 +19,26 @@
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 @@
*
* @param action an action
*/
- public Bmv2ExtensionTreatment(Bmv2Action action) {
+ private Bmv2ExtensionTreatment(Bmv2Action action) {
this.action = action;
}
@@ -91,4 +106,167 @@
.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;
+ }
+ }
+ }
}
diff --git a/protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionBuilderTest.java b/protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionBuilderTest.java
new file mode 100644
index 0000000..2c9cc95
--- /dev/null
+++ b/protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionBuilderTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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