--- /dev/null
+/*
+ * Copyright © 2025 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.node.mccapabilities;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import java.math.BigDecimal;
+
+/**
+ * This interface is intended to specify methods from
+ * which the client may retrieve MC Capabilities.
+ */
+public interface McCapability {
+
+ /**
+ * Width of a slot measured in GHz.
+ */
+ @NonNull BigDecimal slotWidthGranularity();
+
+ /**
+ * Granularity of allowed center frequencies.
+ * The base frequency for this computation is 193.1 THz (G.694.1)
+ */
+ @NonNull BigDecimal centerFrequencyGranularity();
+
+ /**
+ * Minimum number of slots permitted to be joined together to form a media channel.
+ * Must be less than or equal to the max-slots.
+ */
+ int minSlots();
+
+ /**
+ * Maximum number of slots permitted to be joined together to form a media channel.
+ * Must be greater than or equal to the min-slots.
+ */
+ int maxSlots();
+
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.node.mccapabilities;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import java.math.BigDecimal;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.portmapping.rev250115.mc.capabilities.McCapabilities;
+
+/**
+ * The primary purpose of this POJO is to specify default values in regard to MC-capabilities
+ * and thus saving the client from the hassle of implementing null-checks.
+ */
+public class NodeMcCapability implements McCapability {
+
+ private BigDecimal slotWidthGranularityGHz = BigDecimal.valueOf(50);
+
+ private BigDecimal centerFrequencyGranularityGHz = BigDecimal.valueOf(50);
+
+ private int minSlots = 1;
+
+ private int maxSlots = 1;
+
+ /**
+ * Create a NodeMcCapability object with default values defined in the yang model:
+ * - CenterFrequencyGranularity = 50(GHz).
+ * - SlotWidthFrequencyGranularity = 50(GHz).
+ * - min and max slots set to 1.
+ */
+ public NodeMcCapability() {
+ }
+
+ public NodeMcCapability(BigDecimal slotWidthGranularityGHz, BigDecimal centerFrequencyGranularityGHz, int minSlots,
+ int maxSlots) {
+
+ if (slotWidthGranularityGHz != null) {
+ this.slotWidthGranularityGHz = slotWidthGranularityGHz;
+ }
+ if (centerFrequencyGranularityGHz != null) {
+ this.centerFrequencyGranularityGHz = centerFrequencyGranularityGHz;
+ }
+
+ this.minSlots = minSlots;
+ this.maxSlots = maxSlots;
+ }
+
+ /**
+ * Create an object using the data in mcCapabilities. If a piece of data is null
+ * in mcCapabilities, the default values for this class is used.
+ *
+ * @see NodeMcCapability#NodeMcCapability()
+ */
+ public NodeMcCapability(McCapabilities mcCapabilities) {
+ this();
+
+ if (mcCapabilities != null) {
+ if (mcCapabilities.getSlotWidthGranularity() != null) {
+ this.slotWidthGranularityGHz = mcCapabilities.getSlotWidthGranularity().getValue().decimalValue();
+ }
+
+ if (mcCapabilities.getCenterFreqGranularity() != null) {
+ this.centerFrequencyGranularityGHz = mcCapabilities
+ .getCenterFreqGranularity().getValue().decimalValue();
+ }
+
+ if (mcCapabilities.getMinSlots() != null) {
+ this.minSlots = mcCapabilities.getMinSlots().intValue();
+ }
+
+ if (mcCapabilities.getMaxSlots() != null) {
+ this.maxSlots = mcCapabilities.getMaxSlots().intValue();
+ }
+ }
+ }
+
+ @Override
+ public @NonNull BigDecimal slotWidthGranularity() {
+ return slotWidthGranularityGHz;
+ }
+
+ @Override
+ public @NonNull BigDecimal centerFrequencyGranularity() {
+ return centerFrequencyGranularityGHz;
+ }
+
+ @Override
+ public int minSlots() {
+ return minSlots;
+ }
+
+ @Override
+ public int maxSlots() {
+ return maxSlots;
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.spectrum.observer;
+
+public interface Observer {
+
+ /**
+ * Send an error message to the observer.
+ */
+ void error(String message);
+
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.spectrum.observer;
+
+public class VoidObserver implements Observer {
+
+ @Override
+ public void error(String message) {
+ //Don't do anything.
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.spectrum.slot;
+
+public interface CapabilityCollection {
+
+ /**
+ * Add a MCCapability to this collection.
+ */
+ boolean add(McCapability mcCapability);
+
+ /**
+ * Determine if this MC interface is compatible with the required
+ * service frequency width (i.e. slotWidthGranularityGHz x slotCount).
+ *
+ * @param slotWidthGranularityGHz Typically the frequency width of each slot in a 768 grid.
+ * @param slotCount Typically the nr of slots the service requires on a 768 slot grid.
+ */
+ boolean isCompatibleService(double slotWidthGranularityGHz, int slotCount);
+
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.spectrum.slot;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.transportpce.pce.spectrum.observer.Observer;
+import org.opendaylight.transportpce.pce.spectrum.observer.VoidObserver;
+
+public class InterfaceMcCapability implements McCapability {
+
+ private final String node;
+
+ private final BigDecimal slotWidthGranularity;
+
+ private final int minSlots;
+
+ private final int maxSlots;
+
+ public InterfaceMcCapability(BigDecimal slotWidthGranularity, int minSlots, int maxSlots) {
+ this("Unknown node", slotWidthGranularity, minSlots, maxSlots);
+ }
+
+ public InterfaceMcCapability(@NonNull String node, BigDecimal slotWidthGranularity, int minSlots, int maxSlots) {
+ this.node = node;
+ this.slotWidthGranularity = slotWidthGranularity;
+ this.minSlots = minSlots;
+ this.maxSlots = maxSlots;
+ }
+
+ public InterfaceMcCapability(double slotWidthGranularity, int minSlots, int maxSlots) {
+ this(BigDecimal.valueOf(slotWidthGranularity), minSlots, maxSlots);
+ }
+
+ public InterfaceMcCapability(@NonNull String node, double slotWidthGranularity, int minSlots, int maxSlots) {
+ this(node, BigDecimal.valueOf(slotWidthGranularity), minSlots, maxSlots);
+ }
+
+ @Override
+ public boolean isCompatibleWithServiceFrequency(BigDecimal requiredFrequencyWidthGHz) {
+ return isCompatibleWithServiceFrequency(requiredFrequencyWidthGHz, new VoidObserver());
+ }
+
+ @Override
+ public boolean isCompatibleWithServiceFrequency(BigDecimal requiredFrequencyWidthGHz, Observer observer) {
+
+ BigDecimal quotient;
+ try {
+ quotient = requiredFrequencyWidthGHz.divide(slotWidthGranularity);
+ } catch (ArithmeticException e) {
+ return false;
+ }
+
+ BigDecimal remainder = requiredFrequencyWidthGHz.remainder(slotWidthGranularity);
+
+ if (remainder.compareTo(BigDecimal.ZERO) == 0
+ && quotient.compareTo(BigDecimal.valueOf(minSlots)) >= 0
+ && quotient.compareTo(BigDecimal.valueOf(maxSlots)) <= 0) {
+
+ return true;
+ }
+
+ observer.error(String.format("Cannot fit a service with %sGHz on node %s, "
+ + "with slot width granularity %s min slots %s and max slots %s",
+ requiredFrequencyWidthGHz,
+ node,
+ slotWidthGranularity,
+ minSlots,
+ maxSlots));
+
+ return false;
+ }
+
+ @Override
+ public boolean isCompatibleWithServiceFrequency(double requiredFrequencyWidthGHz) {
+ return isCompatibleWithServiceFrequency(BigDecimal.valueOf(requiredFrequencyWidthGHz), new VoidObserver());
+ }
+
+ @Override
+ public boolean isCompatibleWithServiceFrequency(double requiredFrequencyWidthGHz, Observer observer) {
+ return isCompatibleWithServiceFrequency(BigDecimal.valueOf(requiredFrequencyWidthGHz), observer);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof InterfaceMcCapability interfaceMcCapability)) {
+ return false;
+ }
+ return node.equals(interfaceMcCapability.node)
+ && minSlots == interfaceMcCapability.minSlots
+ && maxSlots == interfaceMcCapability.maxSlots
+ && Objects.equals(slotWidthGranularity, interfaceMcCapability.slotWidthGranularity);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(slotWidthGranularity, minSlots, maxSlots);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.spectrum.slot;
+
+import java.math.BigDecimal;
+import org.opendaylight.transportpce.pce.spectrum.observer.Observer;
+
+public interface McCapability {
+
+ /**
+ * Determine if this MC interface is compatible with the required
+ * service frequency width.
+ */
+ boolean isCompatibleWithServiceFrequency(BigDecimal requiredFrequencyWidthGHz);
+
+ /**
+ * Determine if this MC interface is compatible with the required
+ * service frequency width.
+ * The observer is notified about errors.
+ */
+ boolean isCompatibleWithServiceFrequency(BigDecimal requiredFrequencyWidthGHz, Observer observer);
+
+ /**
+ * Determine if this MC interface is compatible with the required
+ * service frequency width.
+ *
+ * @see McCapability#isCompatibleWithServiceFrequency(BigDecimal)
+ */
+ boolean isCompatibleWithServiceFrequency(double requiredFrequencyWidthGHz);
+
+ /**
+ * Determine if this MC interface is compatible with the required
+ * service frequency width.
+ * The observer is notified about errors.
+ *
+ * @see McCapability#isCompatibleWithServiceFrequency(BigDecimal)
+ * @see McCapability#isCompatibleWithServiceFrequency(BigDecimal, Observer)
+ */
+ boolean isCompatibleWithServiceFrequency(double requiredFrequencyWidthGHz, Observer observer);
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.spectrum.slot;
+
+import java.math.BigDecimal;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import org.opendaylight.transportpce.pce.spectrum.observer.Observer;
+import org.opendaylight.transportpce.pce.spectrum.observer.VoidObserver;
+
+public class McCapabilityCollection implements CapabilityCollection {
+
+ private final Set<McCapability> slots = new LinkedHashSet<>();
+
+ private final Observer observer;
+
+ public McCapabilityCollection() {
+ this(new VoidObserver());
+ }
+
+ public McCapabilityCollection(Observer observer) {
+ this.observer = observer;
+ }
+
+ @Override
+ public boolean add(McCapability mcCapability) {
+ return slots.add(mcCapability);
+ }
+
+ @Override
+ public boolean isCompatibleService(double slotWidthGranularityGHz, int slotCount) {
+ BigDecimal widthGHz = BigDecimal.valueOf(slotWidthGranularityGHz).multiply(BigDecimal.valueOf(slotCount));
+
+ for (McCapability mcCapability : slots) {
+ if (!mcCapability.isCompatibleWithServiceFrequency(widthGHz, observer)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.spectrum.slot;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.math.BigDecimal;
+import org.junit.Test;
+import org.opendaylight.transportpce.pce.spectrum.observer.Observer;
+
+public class InterfaceMcCapabilityTest {
+
+ Observer observer = mock(Observer.class);
+
+ @Test
+ public void slotWidthEqualToServiceWidth() {
+ McCapability slot = new InterfaceMcCapability(50, 1, 1);
+
+ assertTrue(slot.isCompatibleWithServiceFrequency(50));
+ assertTrue(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50)));
+ assertTrue(slot.isCompatibleWithServiceFrequency(50, observer));
+ assertTrue(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50), observer));
+ }
+
+ @Test
+ public void slotWidthIsLessThanServiceWidth() {
+ McCapability slot = new InterfaceMcCapability(6.25, 1, 8);
+
+ assertTrue(slot.isCompatibleWithServiceFrequency(50));
+ assertTrue(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50)));
+ assertTrue(slot.isCompatibleWithServiceFrequency(50, observer));
+ assertTrue(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50), observer));
+ }
+
+ @Test
+ public void slotWidthIsLessThanServiceWidthTwo() {
+ McCapability slot = new InterfaceMcCapability(3.125, 1, 16);
+
+ assertTrue(slot.isCompatibleWithServiceFrequency(50));
+ assertTrue(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50)));
+ assertTrue(slot.isCompatibleWithServiceFrequency(50, observer));
+ assertTrue(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50), observer));
+ }
+
+ @Test
+ public void slotWidthIsLessThanServiceWidthThree() {
+ McCapability slot = new InterfaceMcCapability(4.6875, 1, 16);
+
+ assertTrue(slot.isCompatibleWithServiceFrequency(37.5));
+ assertTrue(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(37.5)));
+ assertTrue(slot.isCompatibleWithServiceFrequency(37.5, observer));
+ assertTrue(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(37.5), observer));
+ }
+
+ @Test
+ public void incompatibleGranularityIsFalse() {
+ McCapability slot = new InterfaceMcCapability(4.6875, 1, 16);
+
+ assertFalse(slot.isCompatibleWithServiceFrequency(50));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50)));
+ assertFalse(slot.isCompatibleWithServiceFrequency(50, observer));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50), observer));
+ }
+
+ @Test
+ public void incompatibleGranularityIsFalseTwo() {
+ McCapability slot = new InterfaceMcCapability(6.30, 1, 8);
+
+ assertFalse(slot.isCompatibleWithServiceFrequency(50));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50)));
+ assertFalse(slot.isCompatibleWithServiceFrequency(50, observer));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50), observer));
+ }
+
+ @Test
+ public void incompatibleGranularityIsFalseThree() {
+ McCapability slot = new InterfaceMcCapability(6.25, 1, 6);
+
+ assertFalse(slot.isCompatibleWithServiceFrequency(50));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50)));
+ assertFalse(slot.isCompatibleWithServiceFrequency(50, observer));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50), observer));
+ }
+
+ @Test
+ public void minSlotIsTooHigh() {
+ McCapability slot = new InterfaceMcCapability(6.25, 8, 16);
+
+ assertFalse(slot.isCompatibleWithServiceFrequency(37.5));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(37.5)));
+ assertFalse(slot.isCompatibleWithServiceFrequency(37.5, observer));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(37.5), observer));
+ }
+
+ @Test
+ public void slotWidthGranularityIsTooHigh() {
+ McCapability slot = new InterfaceMcCapability(100, 1, 1);
+
+ assertFalse(slot.isCompatibleWithServiceFrequency(50));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50)));
+ assertFalse(slot.isCompatibleWithServiceFrequency(50, observer));
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50), observer));
+ }
+
+ @Test
+ public void testUnknownNodesEquals() {
+ McCapability slotOne = new InterfaceMcCapability(50, 1, 1);
+ McCapability slotTwo = new InterfaceMcCapability(50, 1, 1);
+
+ assertTrue(slotOne.equals(slotTwo));
+ }
+
+ @Test
+ public void testKnownNodesEquals() {
+ McCapability slotOne = new InterfaceMcCapability("A", 50, 1, 1);
+ McCapability slotTwo = new InterfaceMcCapability("A", 50, 1, 1);
+
+ assertTrue(slotOne.equals(slotTwo));
+ }
+
+ @Test
+ public void testKnownNodesNotEquals() {
+ McCapability slotOne = new InterfaceMcCapability("A", 50, 1, 1);
+ McCapability slotTwo = new InterfaceMcCapability("B", 50, 1, 1);
+
+ assertFalse(slotOne.equals(slotTwo));
+ }
+
+ @Test
+ public void observerIsNotifiedFrequencyAsDoubleValue() {
+ McCapability slot = new InterfaceMcCapability(100, 1, 1);
+
+ Observer observerMock = mock(Observer.class);
+
+ assertFalse(slot.isCompatibleWithServiceFrequency(50, observerMock));
+
+ verify(observerMock, times(1)).error(anyString());
+ }
+
+
+ @Test
+ public void observerIsNotifiedFrequencyAsBigDecimalValue() {
+ McCapability slot = new InterfaceMcCapability(100, 1, 1);
+
+ Observer observerMock = mock(Observer.class);
+
+ assertFalse(slot.isCompatibleWithServiceFrequency(BigDecimal.valueOf(50), observerMock));
+
+ verify(observerMock, times(1)).error(anyString());
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.pce.spectrum.slot;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.jupiter.api.Test;
+
+class McCapabilityCollectionTest {
+
+ @Test
+ void add() {
+
+ McCapability mcCapability = mock(McCapability.class);
+
+ CapabilityCollection slotCollection = new McCapabilityCollection();
+ assertTrue(slotCollection.add(mcCapability));
+ }
+
+ @Test
+ void emptyCollectionIsCompatible() {
+
+ CapabilityCollection slotCollection = new McCapabilityCollection();
+ assertTrue(slotCollection.isCompatibleService(6.25, 8));
+
+ }
+
+ @Test
+ void isCompatibleService() {
+
+ McCapability mcCapability = mock(McCapability.class);
+ when(mcCapability.isCompatibleWithServiceFrequency(any(), any())).thenReturn(true);
+
+ CapabilityCollection slotCollection = new McCapabilityCollection();
+ slotCollection.add(mcCapability);
+ assertTrue(slotCollection.isCompatibleService(6.25, 8));
+
+ }
+
+ @Test
+ void isNotCompatibleService() {
+
+ McCapability mcCapability = mock(McCapability.class);
+ when(mcCapability.isCompatibleWithServiceFrequency(any())).thenReturn(false);
+
+ CapabilityCollection slotCollection = new McCapabilityCollection();
+ slotCollection.add(mcCapability);
+ assertFalse(slotCollection.isCompatibleService(6.25, 8));
+
+ }
+
+ @Test
+ void multipleWhereOneIsFalseReturnsFalse() {
+
+ CapabilityCollection slotCollection = new McCapabilityCollection();
+
+ McCapability mcCapabilityOne = mock(McCapability.class);
+ when(mcCapabilityOne.isCompatibleWithServiceFrequency(any(), any())).thenReturn(true);
+ assertTrue(slotCollection.add(mcCapabilityOne));
+
+ McCapability mcCapabilityTwo = mock(McCapability.class);
+ when(mcCapabilityTwo.isCompatibleWithServiceFrequency(any(), any())).thenReturn(true);
+ assertTrue(slotCollection.add(mcCapabilityTwo));
+
+ assertTrue(slotCollection.isCompatibleService(6.25, 8));
+
+ McCapability mcCapabilityThree = mock(McCapability.class);
+ when(mcCapabilityThree.isCompatibleWithServiceFrequency(any())).thenReturn(false);
+ assertTrue(slotCollection.add(mcCapabilityThree));
+
+ assertFalse(slotCollection.isCompatibleService(6.25, 8));
+
+ }
+
+}