diff --git a/src/main/java/org/stratumproject/fabric/tna/behaviour/Constants.java b/src/main/java/org/stratumproject/fabric/tna/behaviour/Constants.java index a56db49a5..d0f2a556a 100644 --- a/src/main/java/org/stratumproject/fabric/tna/behaviour/Constants.java +++ b/src/main/java/org/stratumproject/fabric/tna/behaviour/Constants.java @@ -53,7 +53,6 @@ public final class Constants { // Static Queue IDs (should match those in gen-stratum-qos-config.py) // FIXME: remove hardcoded queue ID, should be derived from netcfg - public static final int QUEUE_ID_BEST_EFFORT = 0; public static final int QUEUE_ID_SYSTEM = 1; public static final int DEFAULT_SLICE_ID = 0; diff --git a/src/main/java/org/stratumproject/fabric/tna/slicing/SlicingManager.java b/src/main/java/org/stratumproject/fabric/tna/slicing/SlicingManager.java index 00d41c0ea..bbb4dfa13 100644 --- a/src/main/java/org/stratumproject/fabric/tna/slicing/SlicingManager.java +++ b/src/main/java/org/stratumproject/fabric/tna/slicing/SlicingManager.java @@ -273,27 +273,27 @@ public Set getSlices() { } @Override - public boolean addTrafficClass(SliceId sliceId, TrafficClassDescription tcConfig) { - return addTrafficClassInternal(false, sliceId, tcConfig); + public boolean addTrafficClass(SliceId sliceId, TrafficClassDescription tcDescription) { + return addTrafficClassInternal(false, sliceId, tcDescription); } - private boolean addTrafficClassInternal(boolean addSlice, SliceId sliceId, TrafficClassDescription tcConfig) { + private boolean addTrafficClassInternal(boolean addSlice, SliceId sliceId, TrafficClassDescription tcDescription) { if (!addSlice && !sliceExists(sliceId)) { throw new SlicingException(INVALID, format( "Cannot add traffic class to non-existent slice %s", sliceId)); } StringBuilder errorMessage = new StringBuilder(); - SliceStoreKey key = new SliceStoreKey(sliceId, tcConfig.trafficClass()); + SliceStoreKey key = new SliceStoreKey(sliceId, tcDescription.trafficClass()); sliceStore.compute(key, (k, v) -> { if (v != null) { errorMessage.append(format("TC %s is already allocated for slice %s", - tcConfig.trafficClass(), sliceId)); + tcDescription.trafficClass(), sliceId)); return v; } - log.info("Added traffic class {} to slice {}: {}", tcConfig.trafficClass(), sliceId, tcConfig); - return tcConfig; + log.info("Added traffic class {} to slice {}: {}", tcDescription.trafficClass(), sliceId, tcDescription); + return tcDescription; }); if (errorMessage.length() != 0) { diff --git a/src/main/java/org/stratumproject/fabric/tna/slicing/api/QueueId.java b/src/main/java/org/stratumproject/fabric/tna/slicing/api/QueueId.java index e360432a6..167a32bfc 100644 --- a/src/main/java/org/stratumproject/fabric/tna/slicing/api/QueueId.java +++ b/src/main/java/org/stratumproject/fabric/tna/slicing/api/QueueId.java @@ -5,15 +5,16 @@ import org.onlab.util.Identifier; import static com.google.common.base.Preconditions.checkArgument; -import static org.stratumproject.fabric.tna.behaviour.Constants.QUEUE_ID_BEST_EFFORT; import static org.stratumproject.fabric.tna.behaviour.P4InfoConstants.HDR_EGRESS_QID_BITWIDTH; /** * Queue identifier. */ public final class QueueId extends Identifier implements Comparable { - public static final Integer MAX = 1 << HDR_EGRESS_QID_BITWIDTH - 1; - public static final QueueId BEST_EFFORT = QueueId.of(QUEUE_ID_BEST_EFFORT); + public static final QueueId ZERO = new QueueId(0); + public static final QueueId BEST_EFFORT = ZERO; + public static final int MIN = 1; + public static final int MAX = (1 << HDR_EGRESS_QID_BITWIDTH) - 1; private QueueId(int id) { super(id); @@ -27,7 +28,7 @@ private QueueId(int id) { * @throws IllegalArgumentException if given id is invalid */ public static QueueId of(int id) { - checkArgument(id >= 0 && id <= MAX, "Invalid id %s. Valid range is from %s to %s", id, 0, MAX); + checkArgument(id >= MIN && id <= MAX, "Invalid id %s. Valid range is from %s to %s", id, MIN, MAX); return new QueueId(id); } diff --git a/src/main/java/org/stratumproject/fabric/tna/slicing/api/SliceDescription.java b/src/main/java/org/stratumproject/fabric/tna/slicing/api/SliceDescription.java new file mode 100644 index 000000000..fa4eae734 --- /dev/null +++ b/src/main/java/org/stratumproject/fabric/tna/slicing/api/SliceDescription.java @@ -0,0 +1,100 @@ +// Copyright 2022-present Open Networking Foundation +// SPDX-License-Identifier: LicenseRef-ONF-Member-Only-1.0 + +package org.stratumproject.fabric.tna.slicing.api; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; + +import java.util.Collection; +import java.util.Map; + +/** + * Describes a slice. + */ +public class SliceDescription { + + private final SliceId id; + private final String name; + private final ImmutableMap tcDescrs; + + /** + * Creates a new slice description. + * + * @param id slice ID + * @param name name + * @param tcDescrs traffic class descriptions + */ + public SliceDescription(SliceId id, String name, + Map tcDescrs) { + this.id = id; + this.tcDescrs = ImmutableMap.copyOf(tcDescrs); + this.name = name; + } + + /** + * Returns the slice ID. + * + * @return slice ID + */ + public SliceId id() { + return id; + } + + /** + * Returns the descriptions of the traffic classes within this slice. + * + * @return traffic class descriptions + */ + public Collection tcDescriptions() { + return tcDescrs.values(); + } + + /** + * Returns the description for the given traffic class. + * + * @param tc traffic class + * @return traffic class description + */ + public TrafficClassDescription tcDescription(TrafficClass tc) { + return tcDescrs.get(tc); + } + + /** + * Returns the name of this slice. + * + * @return slice name + */ + public String name() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SliceDescription that = (SliceDescription) o; + return Objects.equal(id, that.id) && + Objects.equal(name, that.name) && + Objects.equal(tcDescrs, that.tcDescrs); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, name, tcDescrs); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", id) + .add("name", name) + .add("tcs", tcDescrs) + .toString(); + } +} diff --git a/src/main/java/org/stratumproject/fabric/tna/slicing/api/SlicingConfig.java b/src/main/java/org/stratumproject/fabric/tna/slicing/api/SlicingConfig.java new file mode 100644 index 000000000..5a3e1123e --- /dev/null +++ b/src/main/java/org/stratumproject/fabric/tna/slicing/api/SlicingConfig.java @@ -0,0 +1,243 @@ +// Copyright 2021-present Open Networking Foundation +// SPDX-License-Identifier: LicenseRef-ONF-Member-Only-1.0 + +package org.stratumproject.fabric.tna.slicing.api; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigException; +import org.onosproject.net.config.InvalidFieldException; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; + +/** + * Configuration for slicing. + *

+ * Example: + *

+ * {
+ *   "apps": {
+ *     "org.stratumproject.fabric-tna": {
+ *       "slicing": {
+ *         "slices": {
+ *           "0": {
+ *             "name": "Default",
+ *             "tcs": {
+ *               "REAL_TIME": {
+ *                 "queueId": 1,
+ *                 "isSystemTc": true
+ *               }
+ *             }
+ *           },
+ *           "1": {
+ *             "name": "P4-UPF",
+ *             "tcs": {
+ *               "CONTROL": {
+ *                 "queueId": 2,
+ *                 "maxRateBps": "2000000"
+ *               },
+ *               "REAL_TIME": {
+ *                 "queueId": 3,
+ *                 "maxRateBps": "50000000"
+ *               },
+ *               "ELASTIC": {
+ *                 "queueId": 4,
+ *                 "gminRateBps": "10000000"
+ *               }
+ *             }
+ *           },
+ *           "2": {
+ *             "name": "BESS-UPF",
+ *             "tcs": {
+ *               "ELASTIC": {
+ *                 "queueId": 5
+ *               }
+ *             }
+ *           }
+ *         }
+ *       }
+ *     }
+ *   }
+ * }
+ * 
+ */ +public class SlicingConfig extends Config { + + private static final String SLICES = "slices"; + private static final String TCS = "tcs"; + private static final String NAME = "name"; + private static final String QUEUE_ID = "queueId"; + private static final String IS_SYSTEM_TC = "isSystemTc"; + private static final String MAX_RATE_BPS = "maxRateBps"; + private static final String GMIN_RATE_BPS = "gminRateBps"; + + private static final long DEFAULT_MAX_RATE_BPS = TrafficClassDescription.UNLIMITED_BPS; + private static final long DEFAULT_GMIN_RATE_BPS = 0; + private static final boolean DEFAULT_IS_SYSTEM_TC = false; + + @Override + public boolean isValid() { + if (!(hasOnlyFields(object, SLICES))) { + return false; + } + + for (JsonNode sliceNode : object.path(SLICES)) { + if (!(hasOnlyFields((ObjectNode) sliceNode, NAME, TCS) && + hasFields((ObjectNode) sliceNode, NAME) && + isString((ObjectNode) sliceNode, NAME, FieldPresence.MANDATORY))) { + return false; + } + + for (JsonNode tcNode : sliceNode.path(TCS)) { + if (!(hasOnlyFields((ObjectNode) tcNode, QUEUE_ID, MAX_RATE_BPS, GMIN_RATE_BPS, IS_SYSTEM_TC) && + hasFields((ObjectNode) tcNode, QUEUE_ID) && + isIntegralNumber((ObjectNode) tcNode, QUEUE_ID, FieldPresence.MANDATORY, + QueueId.MIN, QueueId.MAX) && + isIntegralNumber((ObjectNode) tcNode, MAX_RATE_BPS, FieldPresence.OPTIONAL, 0, + TrafficClassDescription.UNLIMITED_BPS)) && + isIntegralNumber((ObjectNode) tcNode, GMIN_RATE_BPS, FieldPresence.OPTIONAL, + 0, TrafficClassDescription.UNLIMITED_BPS) && + isBoolean((ObjectNode) tcNode, IS_SYSTEM_TC, FieldPresence.OPTIONAL)) { + return false; + } + } + } + + try { + var slices = slices(); + if (slices.isEmpty()) { + throw new InvalidFieldException(SLICES, "At least one slice should be specified"); + } + + var systemTcsCount = 0; + for (SliceDescription sliceConfig : slices) { + for (TrafficClassDescription tcDescription : sliceConfig.tcDescriptions()) { + if (tcDescription.isSystemTc()) { + systemTcsCount++; + } + } + } + if (systemTcsCount == 0) { + throw new InvalidFieldException(SLICES, format( + "At least one traffic class should be set as the system one (%s=true)", + IS_SYSTEM_TC)); + } + if (systemTcsCount > 1) { + throw new InvalidFieldException(SLICES, + "Too many traffic classes are set as the system one, only one is allowed"); + } + } catch (ConfigException e) { + throw new InvalidFieldException(SLICES, e); + } + + return true; + } + + /** + * Returns the collection of slice descriptions defined in this config. + * + * @return collection of slice descriptions + * @throws ConfigException if the config is invalid + */ + public Collection slices() throws ConfigException { + List sliceConfigs = Lists.newArrayList(); + var jsonSlices = object.path(SLICES).fields(); + while (jsonSlices.hasNext()) { + var jsonSlice = jsonSlices.next(); + SliceId sliceId; + try { + sliceId = SliceId.of(Integer.parseInt(jsonSlice.getKey())); + } catch (IllegalArgumentException e) { + // This is catching also NumberFormatException (subclass of + // IllegalArgumentException) thrown by parseInt. + throw new ConfigException(format( + "\"%s\" is not a valid slice ID", jsonSlice.getKey()), e); + } + sliceConfigs.add(slice(sliceId)); + } + return sliceConfigs; + } + + /** + * Returns the description of the specific slice with the given ID, or null + * if such slice is not defined in this config. + * + * @param sliceId slice ID + * @return slice description + * @throws ConfigException if the config is invalid + */ + public SliceDescription slice(SliceId sliceId) throws ConfigException { + var sliceNode = object.path(SLICES).path(sliceId.toString()); + if (sliceNode.isMissingNode()) { + return null; + } + + var name = sliceNode.path(NAME).asText(); + if (name.isEmpty()) { + throw new ConfigException(format( + "Slice %s must have a valid name", sliceId)); + } + + Map tcDescriptions = Maps.newHashMap(); + var tcDescriptionFields = sliceNode.path(TCS).fields(); + while (tcDescriptionFields.hasNext()) { + var tcDescriptionField = tcDescriptionFields.next(); + var tcName = tcDescriptionField.getKey(); + TrafficClass tc; + try { + tc = TrafficClass.valueOf(tcName); + } catch (IllegalArgumentException e) { + throw new ConfigException(format( + "\"%s\" is not a valid traffic class for slice %s", tcName, sliceId), e); + } + if (tc.equals(TrafficClass.BEST_EFFORT)) { + throw new ConfigException("BEST_EFFORT is implicit for all slices and cannot be configured"); + } + tcDescriptions.put(tc, tcDescription(sliceId, tc)); + } + + return new SliceDescription(sliceId, name, tcDescriptions); + } + + /** + * Returns the description of the given traffic class whitin the given + * slice, or null if missing from this config. + * + * @param sliceId slice ID + * @param tc traffic class + * @return traffic class description + * @throws ConfigException if the config is invalid + */ + public TrafficClassDescription tcDescription(SliceId sliceId, TrafficClass tc) throws ConfigException { + var tcNode = object.path(SLICES) + .path(sliceId.toString()) + .path(TCS) + .path(tc.name()); + if (tcNode.isMissingNode()) { + return null; + } + + QueueId queueId; + try { + queueId = QueueId.of(tcNode.path(QUEUE_ID).asInt()); + } catch (IllegalArgumentException e) { + throw new ConfigException(format( + "\"%s\" is not a valid queue ID for traffic class %s of slice %s", + tcNode.path(QUEUE_ID).asText(), tc, sliceId), e); + } + + var maxRateBps = tcNode.path(MAX_RATE_BPS).asLong(DEFAULT_MAX_RATE_BPS); + var gminRateBps = tcNode.path(GMIN_RATE_BPS).asLong(DEFAULT_GMIN_RATE_BPS); + var isSystemTc = tcNode.path(IS_SYSTEM_TC).asBoolean(DEFAULT_IS_SYSTEM_TC); + + return new TrafficClassDescription(tc, queueId, maxRateBps, gminRateBps, isSystemTc); + } +} diff --git a/src/main/java/org/stratumproject/fabric/tna/slicing/api/SlicingProviderService.java b/src/main/java/org/stratumproject/fabric/tna/slicing/api/SlicingProviderService.java index e9b77b0f6..6052f0b36 100644 --- a/src/main/java/org/stratumproject/fabric/tna/slicing/api/SlicingProviderService.java +++ b/src/main/java/org/stratumproject/fabric/tna/slicing/api/SlicingProviderService.java @@ -28,14 +28,14 @@ public interface SlicingProviderService { boolean removeSlice(SliceId sliceId); /** - * Adds a traffic class to given slice. + * Adds a traffic class to the given slice. * * @param sliceId slice identifier - * @param tcConfig traffic class config + * @param tcDescription traffic class config * @return true if the traffic class is added to given slice successfully. * @throws SlicingException if an error occurred. */ - boolean addTrafficClass(SliceId sliceId, TrafficClassDescription tcConfig); + boolean addTrafficClass(SliceId sliceId, TrafficClassDescription tcDescription); /** * Removes a traffic class from given slice. diff --git a/src/main/java/org/stratumproject/fabric/tna/slicing/api/TrafficClassDescription.java b/src/main/java/org/stratumproject/fabric/tna/slicing/api/TrafficClassDescription.java index 0b9df5e7d..4dbabb042 100644 --- a/src/main/java/org/stratumproject/fabric/tna/slicing/api/TrafficClassDescription.java +++ b/src/main/java/org/stratumproject/fabric/tna/slicing/api/TrafficClassDescription.java @@ -66,7 +66,7 @@ public QueueId queueId() { * * @return maximum bitrate in bps */ - public long getMaxRateBps() { + public long maxRateBps() { return maxRateBps; } @@ -88,7 +88,7 @@ public boolean isMaxRateUnlimited() { * * @return guaranteed minimum bitrate in bps */ - public long getGminRateBps() { + public long gminRateBps() { return gminRateBps; } @@ -132,6 +132,4 @@ public String toString() { .add("isSystemTc", isSystemTc) .toString(); } - - // TODO: builder } diff --git a/src/test/java/org/stratumproject/fabric/tna/slicing/api/SlicingConfigTest.java b/src/test/java/org/stratumproject/fabric/tna/slicing/api/SlicingConfigTest.java new file mode 100644 index 000000000..ada247bfe --- /dev/null +++ b/src/test/java/org/stratumproject/fabric/tna/slicing/api/SlicingConfigTest.java @@ -0,0 +1,170 @@ +// Copyright $today.year-present Open Networking Foundation +// SPDX-License-Identifier: LicenseRef-ONF-Member-Only-1.0 + +package org.stratumproject.fabric.tna.slicing.api; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.config.ConfigException; +import org.onosproject.net.config.InvalidFieldException; +import org.stratumproject.fabric.tna.utils.TestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests for SlicingConfig. + */ +public class SlicingConfigTest { + + private static final String APP_NAME = "foobar"; + private static final ApplicationId APP_ID = new TestApplicationId(APP_NAME); + + @Rule + public ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void testConstruction() throws Exception { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing.json"); + + assertTrue(config.isValid()); + + assertEquals(3, config.slices().size()); + assertNotNull(config.slice(SliceId.of(0))); + assertNotNull(config.slice(SliceId.of(1))); + assertNotNull(config.slice(SliceId.of(2))); + assertNull(config.slice(SliceId.of(3))); + + SliceDescription sliceDescr; + TrafficClassDescription tcDescr; + + sliceDescr = config.slice(SliceId.of(0)); + assertEquals(SliceId.of(0), sliceDescr.id()); + assertEquals("Default", sliceDescr.name()); + assertEquals(1, sliceDescr.tcDescriptions().size()); + + tcDescr = sliceDescr.tcDescription(TrafficClass.REAL_TIME); + assertNotNull(tcDescr); + assertEquals(QueueId.of(1), tcDescr.queueId()); + assertEquals(TrafficClassDescription.UNLIMITED_BPS, tcDescr.maxRateBps()); + assertEquals(0, tcDescr.gminRateBps()); + assertTrue(tcDescr.isSystemTc()); + + sliceDescr = config.slice(SliceId.of(1)); + assertEquals(SliceId.of(1), sliceDescr.id()); + assertEquals("P4-UPF", sliceDescr.name()); + assertEquals(3, sliceDescr.tcDescriptions().size()); + + tcDescr = sliceDescr.tcDescription(TrafficClass.CONTROL); + assertNotNull(tcDescr); + assertEquals(QueueId.of(2), tcDescr.queueId()); + assertEquals(2000000, tcDescr.maxRateBps()); + assertEquals(0, tcDescr.gminRateBps()); + assertFalse(tcDescr.isSystemTc()); + + tcDescr = sliceDescr.tcDescription(TrafficClass.REAL_TIME); + assertNotNull(tcDescr); + assertEquals(QueueId.of(3), tcDescr.queueId()); + assertEquals(50000000, tcDescr.maxRateBps()); + assertEquals(0, tcDescr.gminRateBps()); + assertFalse(tcDescr.isSystemTc()); + + tcDescr = sliceDescr.tcDescription(TrafficClass.ELASTIC); + assertNotNull(tcDescr); + assertEquals(QueueId.of(4), tcDescr.queueId()); + assertEquals(TrafficClassDescription.UNLIMITED_BPS, tcDescr.maxRateBps()); + assertEquals(10000000, tcDescr.gminRateBps()); + assertFalse(tcDescr.isSystemTc()); + + sliceDescr = config.slice(SliceId.of(2)); + assertEquals(SliceId.of(2), sliceDescr.id()); + assertEquals("BESS-UPF", sliceDescr.name()); + assertEquals(1, sliceDescr.tcDescriptions().size()); + + tcDescr = sliceDescr.tcDescription(TrafficClass.ELASTIC); + assertNotNull(tcDescr); + assertEquals(QueueId.of(5), tcDescr.queueId()); + assertEquals(TrafficClassDescription.UNLIMITED_BPS, tcDescr.maxRateBps()); + assertEquals(0, tcDescr.gminRateBps()); + assertFalse(tcDescr.isSystemTc()); + } + + @Test + public void testInvalidEmpty() { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-empty.json"); + exceptionRule.expect(InvalidFieldException.class); + exceptionRule.expectMessage("At least one slice should be specified"); + config.isValid(); + } + + + @Test + public void testInvalidMissingSystemTc() { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-no-system-tc.json"); + exceptionRule.expect(InvalidFieldException.class); + exceptionRule.expectMessage("At least one traffic class should be set as the system one"); + config.isValid(); + } + + @Test + public void testInvalidTooManySystemTcs() { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-too-many-system-tcs.json"); + exceptionRule.expect(InvalidFieldException.class); + exceptionRule.expectMessage("Too many traffic classes are set as the system one"); + config.isValid(); + } + + @Test + public void testInvalidTrafficClass() { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-traffic-class.json"); + exceptionRule.expect(InvalidFieldException.class); + exceptionRule.expectMessage("not a valid traffic class"); + config.isValid(); + } + + @Test + public void testInvalidSliceId() { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-slice-id.json"); + exceptionRule.expect(InvalidFieldException.class); + exceptionRule.expectMessage("is not a valid slice ID"); + config.isValid(); + } + + @Test + public void testInvalidBestEffortQueueId() { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-best-effort.json"); + exceptionRule.expect(InvalidFieldException.class); + exceptionRule.expectMessage("Field must be greater than 1"); + config.isValid(); + } + + @Test + public void testInvalidBestEffortTcName() throws ConfigException { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-best-effort.json"); + exceptionRule.expect(ConfigException.class); + exceptionRule.expectMessage("BEST_EFFORT is implicit for all slices and cannot be configured"); + config.slice(SliceId.of(0)); + } + + @Test + public void testInvalidQueueId() throws ConfigException { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-queue-id.json"); + exceptionRule.expect(ConfigException.class); + exceptionRule.expectMessage("is not a valid queue ID"); + config.slice(SliceId.of(0)); + } + + @Test + public void testInvalidQueueIdMissing() throws ConfigException { + SlicingConfig config = TestUtils.getSlicingConfig(APP_ID, "/slicing-invalid-queue-id-missing.json"); + exceptionRule.expect(InvalidFieldException.class); + exceptionRule.expectMessage("Field \"queueId\" is invalid: Mandatory field is not present"); + config.isValid(); + } +} diff --git a/src/test/java/org/stratumproject/fabric/tna/utils/TestUtils.java b/src/test/java/org/stratumproject/fabric/tna/utils/TestUtils.java index 352689cab..aeec34269 100644 --- a/src/test/java/org/stratumproject/fabric/tna/utils/TestUtils.java +++ b/src/test/java/org/stratumproject/fabric/tna/utils/TestUtils.java @@ -14,6 +14,7 @@ import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig; import org.stratumproject.fabric.tna.inbandtelemetry.IntReportConfig; +import org.stratumproject.fabric.tna.slicing.api.SlicingConfig; import static org.junit.Assert.fail; @@ -23,6 +24,7 @@ public final class TestUtils { private static final String INT_REPORT_CONFIG_KEY = "report"; private static final String SR_CONFIG_KEY = "segmentrouting"; + private static final String SLICING_CONFIG_KEY = "slicing"; private TestUtils() { } @@ -54,4 +56,18 @@ public static IntReportConfig getIntReportConfig(ApplicationId appId, String fil } return config; } + + public static SlicingConfig getSlicingConfig(ApplicationId appId, String filename) { + SlicingConfig config = new SlicingConfig(); + InputStream jsonStream = TestUtils.class.getResourceAsStream(filename); + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode; + try { + jsonNode = mapper.readTree(jsonStream); + config.init(appId, SLICING_CONFIG_KEY, jsonNode, mapper, c -> { }); + } catch (Exception e) { + fail("Got error when reading file " + filename + " : " + e.getMessage()); + } + return config; + } } diff --git a/src/test/resources/slicing-invalid-best-effort.json b/src/test/resources/slicing-invalid-best-effort.json new file mode 100644 index 000000000..1e082bb60 --- /dev/null +++ b/src/test/resources/slicing-invalid-best-effort.json @@ -0,0 +1,12 @@ +{ + "slices": { + "0": { + "name": "Default", + "tcs": { + "BEST_EFFORT": { + "queueId": 0 + } + } + } + } +} diff --git a/src/test/resources/slicing-invalid-empty.json b/src/test/resources/slicing-invalid-empty.json new file mode 100644 index 000000000..ee1bd2c29 --- /dev/null +++ b/src/test/resources/slicing-invalid-empty.json @@ -0,0 +1,4 @@ +{ + "slices": { + } +} diff --git a/src/test/resources/slicing-invalid-no-system-tc.json b/src/test/resources/slicing-invalid-no-system-tc.json new file mode 100644 index 000000000..9d254b663 --- /dev/null +++ b/src/test/resources/slicing-invalid-no-system-tc.json @@ -0,0 +1,22 @@ +{ + "slices": { + "0": { + "name": "Default", + "tcs": { + "REAL_TIME": { + "queueId": 1, + "isSystemTc": false + } + } + }, + "1": { + "name": "P4-UPF", + "tcs": { + "CONTROL": { + "queueId": 2, + "maxRateBps": "2000000" + } + } + } + } +} diff --git a/src/test/resources/slicing-invalid-queue-id-missing.json b/src/test/resources/slicing-invalid-queue-id-missing.json new file mode 100644 index 000000000..ec8ed6faa --- /dev/null +++ b/src/test/resources/slicing-invalid-queue-id-missing.json @@ -0,0 +1,11 @@ +{ + "slices": { + "0": { + "name": "Default", + "tcs": { + "REAL_TIME": { + } + } + } + } +} diff --git a/src/test/resources/slicing-invalid-queue-id.json b/src/test/resources/slicing-invalid-queue-id.json new file mode 100644 index 000000000..32a97d1b9 --- /dev/null +++ b/src/test/resources/slicing-invalid-queue-id.json @@ -0,0 +1,12 @@ +{ + "slices": { + "0": { + "name": "Default", + "tcs": { + "REAL_TIME": { + "queueId": "foo" + } + } + } + } +} diff --git a/src/test/resources/slicing-invalid-slice-id.json b/src/test/resources/slicing-invalid-slice-id.json new file mode 100644 index 000000000..31998915d --- /dev/null +++ b/src/test/resources/slicing-invalid-slice-id.json @@ -0,0 +1,12 @@ +{ + "slices": { + "foo": { + "name": "Default", + "tcs": { + "REAL_TIME": { + "queueId": 1 + } + } + } + } +} diff --git a/src/test/resources/slicing-invalid-too-many-system-tcs.json b/src/test/resources/slicing-invalid-too-many-system-tcs.json new file mode 100644 index 000000000..1dde8ec21 --- /dev/null +++ b/src/test/resources/slicing-invalid-too-many-system-tcs.json @@ -0,0 +1,23 @@ +{ + "slices": { + "0": { + "name": "Default", + "tcs": { + "REAL_TIME": { + "queueId": 1, + "isSystemTc": true + } + } + }, + "1": { + "name": "P4-UPF", + "tcs": { + "CONTROL": { + "queueId": 2, + "maxRateBps": "2000000", + "isSystemTc": true + } + } + } + } +} diff --git a/src/test/resources/slicing-invalid-traffic-class.json b/src/test/resources/slicing-invalid-traffic-class.json new file mode 100644 index 000000000..9e5d5210e --- /dev/null +++ b/src/test/resources/slicing-invalid-traffic-class.json @@ -0,0 +1,12 @@ +{ + "slices": { + "0": { + "name": "Default", + "tcs": { + "FOO": { + "queueId": "1" + } + } + } + } +} diff --git a/src/test/resources/slicing.json b/src/test/resources/slicing.json new file mode 100644 index 000000000..9a38e464e --- /dev/null +++ b/src/test/resources/slicing.json @@ -0,0 +1,38 @@ +{ + "slices": { + "0": { + "name": "Default", + "tcs": { + "REAL_TIME": { + "queueId": 1, + "isSystemTc": true + } + } + }, + "1": { + "name": "P4-UPF", + "tcs": { + "CONTROL": { + "queueId": 2, + "maxRateBps": "2000000" + }, + "REAL_TIME": { + "queueId": 3, + "maxRateBps": "50000000" + }, + "ELASTIC": { + "queueId": 4, + "gminRateBps": "10000000" + } + } + }, + "2": { + "name": "BESS-UPF", + "tcs": { + "ELASTIC": { + "queueId": 5 + } + } + } + } +}