Determine preferred node/port from PCRI 68/109368/9
authorJoakim Törnqvist <joakim.tornqvist@smartoptics.com>
Thu, 7 Dec 2023 14:13:13 +0000 (14:13 +0000)
committerJoakim Törnqvist <joakim.tornqvist@smartoptics.com>
Fri, 16 Feb 2024 10:49:19 +0000 (10:49 +0000)
Package intended to be used to determine preferred SRG port
usage when setting up optical services between ROADMs.

Use an instance of PathComputationRequestInput, e.g. API
input and provides a method for checking if a specific SRG port
is among the ports in the client request.

Package org.opendaylight.transportpce.pce.networkanalyzer.port:

* Preference        - Interface defining one method capable
                      of determining if a node/port is 'preferred'
                      by the client.
* ClientPreference  - Implements Preference. Contains information
                      about actual client port preference.
* NoPreference      - Implements Preference. Typically used when
                      the client has no preference regarding port.

* Factory           - Interface defining methods needed in order
                      to convert a PathComputationRequestInput
                      to an implementation of Preferenc.
* PreferenceFactory - Implements Factory. The method
                      'portPreference' should typically be used
                      implementing the feature in the application.

                      The method portPreference either returns
                      an instance of 'ClientPreference' or
                      'NoPreference'.

Example:

public void someMethod(PathComputationRequestInput input) {

    Factory portPreferenceFactory = new PreferenceFactory();

    Preference preference = portPreferenceFactory.portPreference(input);

    if (preference.preferredPort("ROADM-A-SRG1", "SRG1-PP1-TXRX")) {
        System.out.println("The port 'SRG1-PP1-TXRX' on 'ROADM-A-SRG1' "
                          +"is preferred by the client.");
    }

}

JIRA: TRNSPRTPCE-176
Change-Id: Iba7e7886678deb6d965391633f819995635caa06
Signed-off-by: Joakim Törnqvist <joakim.tornqvist@smartoptics.com>
pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/ClientPreference.java [new file with mode: 0644]
pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/Factory.java [new file with mode: 0644]
pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/NoPreference.java [new file with mode: 0644]
pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/Preference.java [new file with mode: 0644]
pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/PreferenceFactory.java [new file with mode: 0644]
pce/src/test/java/org/opendaylight/transportpce/pce/networkanalyzer/port/ClientPreferenceTest.java [new file with mode: 0644]
pce/src/test/java/org/opendaylight/transportpce/pce/networkanalyzer/port/PreferenceFactoryTest.java [new file with mode: 0644]

diff --git a/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/ClientPreference.java b/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/ClientPreference.java
new file mode 100644 (file)
index 0000000..d3749c3
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 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.networkanalyzer.port;
+
+import java.util.Map;
+import java.util.Set;
+
+public class ClientPreference implements Preference {
+
+    Map<String, Set<String>> nodePortPreference;
+
+    public ClientPreference(Map<String, Set<String>> nodePortPreference) {
+        this.nodePortPreference = nodePortPreference;
+    }
+
+    @Override
+    public boolean isPreferredPort(String node, String portName) {
+
+        //If there is no preferred port registered for the node, it means
+        //the client has no preference regarding the node.
+        //Therefore, we'll treat the node as it was preferred to
+        //prevent it from NOT being used.
+        return !nodePortPreference.containsKey(node) || nodePortPreference.get(node).contains(portName);
+
+    }
+
+}
diff --git a/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/Factory.java b/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/Factory.java
new file mode 100644 (file)
index 0000000..11721f5
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 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.networkanalyzer.port;
+
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.PathComputationRequestInput;
+
+public interface Factory {
+
+    /**
+     * Extracting preferred ports from pathComputationRequestInput.
+     *
+     * <p>
+     * This is the recommended method of determining if a node/port combination
+     * is preferred by the client.
+     *
+     * <p>
+     * Pseudocode example:
+     * <pre>
+     *     Factory.portPreference(PCRI).preferredPort("ROADM-B-SRG1", "SRG1-PP1-TXRX");
+     * </pre>
+     *
+     * @return Client port preference
+     */
+    Preference portPreference(PathComputationRequestInput pathComputationRequestInput);
+
+}
diff --git a/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/NoPreference.java b/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/NoPreference.java
new file mode 100644 (file)
index 0000000..5b96ca0
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 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.networkanalyzer.port;
+
+/**
+ * This class represents a state where the client has no port preference.
+ * In essence, all ports on all nodes will therefore be treated as 'preferred'
+ * when queried.
+ *
+ * <p>
+ * Usage of this class is of sorts the 'backwards compatible' approach. Meaning,
+ * intended to offer a path for the application to behave as it did
+ * prior to implementing client port preference.
+ */
+public class NoPreference implements Preference {
+    @Override
+    public boolean isPreferredPort(String node, String portName) {
+        return true;
+    }
+}
diff --git a/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/Preference.java b/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/Preference.java
new file mode 100644 (file)
index 0000000..3d1acb8
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 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.networkanalyzer.port;
+
+public interface Preference {
+
+    /**
+     * Return true if the portName is among the ports preferred by the client.
+     */
+    boolean isPreferredPort(String node, String portName);
+
+}
diff --git a/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/PreferenceFactory.java b/pce/src/main/java/org/opendaylight/transportpce/pce/networkanalyzer/port/PreferenceFactory.java
new file mode 100644 (file)
index 0000000..6d36d82
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 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.networkanalyzer.port;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.PathComputationRequestInput;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.path.computation.request.input.ServiceAEnd;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.path.computation.request.input.ServiceZEnd;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev230526.service.port.Port;
+import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.service.types.rev220118.service.endpoint.sp.RxDirection;
+import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.service.types.rev220118.service.endpoint.sp.TxDirection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PreferenceFactory implements Factory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PreferenceFactory.class);
+
+    private final String portNamePattern = "(?i)SRG\\d+-PP\\d+-(TXRX|TX|RX)";
+
+    @Override
+    public Preference portPreference(PathComputationRequestInput pathComputationRequestInput) {
+
+        Map<String, Set<String>> map = nodePortMap(pathComputationRequestInput);
+
+        if (map.isEmpty()) {
+            LOG.debug("No port preference found in path computation request.");
+            return new NoPreference();
+        }
+
+        LOG.debug("Port preference in path computation request: {}." , map);
+        return new ClientPreference(map);
+    }
+
+    /**
+     * Create a key value mapper from PCRI where key is the node and the value is
+     * a unique list of port names.
+     *
+     * @return Client port preference map
+     */
+    Map<String, Set<String>> nodePortMap(PathComputationRequestInput pathComputationRequestInput) {
+
+        Map<String, Set<String>> mapper = new HashMap<>();
+
+        ServiceAEnd serviceAEnd = pathComputationRequestInput.getServiceAEnd();
+        if (serviceAEnd != null) {
+
+            RxDirection rxAzDirection = serviceAEnd.getRxDirection();
+            if (rxAzDirection != null) {
+
+                Port rxAZport = rxAzDirection.getPort();
+                if (rxAZport != null) {
+                    add(rxAZport.getPortDeviceName(), rxAZport.getPortName(), mapper);
+                }
+            }
+
+            TxDirection txAzDirection = serviceAEnd.getTxDirection();
+            if (txAzDirection != null) {
+
+                Port txAZport = txAzDirection.getPort();
+                if (txAZport != null) {
+                    add(txAZport.getPortDeviceName(), txAZport.getPortName(), mapper);
+                }
+            }
+        }
+
+        ServiceZEnd serviceZEnd = pathComputationRequestInput.getServiceZEnd();
+        if (serviceZEnd != null) {
+
+            RxDirection rxZaDirection = serviceZEnd.getRxDirection();
+            if (rxZaDirection != null) {
+
+                Port rxZAport = rxZaDirection.getPort();
+                if (rxZAport != null) {
+                    add(rxZAport.getPortDeviceName(), rxZAport.getPortName(), mapper);
+                }
+            }
+
+            TxDirection txZaDirection = serviceZEnd.getTxDirection();
+            if (txZaDirection != null) {
+
+                Port txZAport = txZaDirection.getPort();
+                if (txZAport != null) {
+                    add(txZAport.getPortDeviceName(), txZAport.getPortName(), mapper);
+                }
+            }
+        }
+
+        return mapper;
+    }
+
+    /**
+     * Add node/port name to key value map. Mutable method, modifies the argument nodePortMap.
+     */
+    boolean add(String node, String port, Map<String, Set<String>> nodePortMap) {
+
+        if (node == null || port == null) {
+            return false;
+        }
+
+        String nodeTrimmed = node.trim();
+        String portTrimmed = port.trim();
+
+        if (nodeTrimmed.isEmpty() || portTrimmed.isEmpty()) {
+            return false;
+        }
+
+        if (!portTrimmed.matches(portNamePattern)) {
+            LOG.warn("Preferred port name '{}' on node {} doesn't match pattern '{}'",
+                portTrimmed,
+                nodeTrimmed,
+                portNamePattern
+            );
+        }
+
+        if (nodePortMap.containsKey(nodeTrimmed)) {
+            boolean added = nodePortMap.get(nodeTrimmed).add(portTrimmed);
+            if (added) {
+                LOG.debug("Preferred port '{}' for node '{}' registered.", portTrimmed, nodeTrimmed);
+            } else {
+                LOG.debug("Failed registering port '{}' for node '{}'.", portTrimmed, nodeTrimmed);
+            }
+            return added;
+        }
+
+        nodePortMap.put(nodeTrimmed, new HashSet<>(Arrays.asList(portTrimmed)));
+
+        return true;
+    }
+}
diff --git a/pce/src/test/java/org/opendaylight/transportpce/pce/networkanalyzer/port/ClientPreferenceTest.java b/pce/src/test/java/org/opendaylight/transportpce/pce/networkanalyzer/port/ClientPreferenceTest.java
new file mode 100644 (file)
index 0000000..2466221
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.networkanalyzer.port;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class ClientPreferenceTest {
+
+    @Test
+    void preferredPort_returnTrue() {
+
+        Map<String, Set<String>> nodePortPreference = new HashMap<>();
+        nodePortPreference.put("ROADM-B-SRG1", Set.of("SRG1-PP1-TXRX"));
+
+        Preference clientPreference = new ClientPreference(nodePortPreference);
+
+        Assertions.assertTrue(clientPreference.isPreferredPort("ROADM-B-SRG1", "SRG1-PP1-TXRX"));
+    }
+
+    /**
+     * The client prefer to use SRG1-PP1-TXRX on ROADM-B-SRG1.
+     * Therefore, preferredPort returns false on SRG1-PP2-TXRX.
+     */
+    @Test
+    void nonPreferredPort_returnFalse() {
+
+        Map<String, Set<String>> nodePortPreference = new HashMap<>();
+        nodePortPreference.put("ROADM-B-SRG1", Set.of("SRG1-PP1-TXRX"));
+
+        Preference clientPreference = new ClientPreference(nodePortPreference);
+
+        Assertions.assertFalse(clientPreference.isPreferredPort("ROADM-B-SRG1", "SRG1-PP2-TXRX"));
+    }
+
+    /**
+     * In this scenario ROADM-A-SRG1 is missing from the client preferred list.
+     * We treat this as the client has no opinion on what port
+     * to use on ROADM-A-SRG1. Meaning, as far as the client goes, all
+     * ports on ROADM-A-SRG1 are fine.
+     */
+    @Test
+    void nodeMissingInPreferredList_returnTrue() {
+
+        Map<String, Set<String>> nodePortPreference = new HashMap<>();
+        nodePortPreference.put("ROADM-B-SRG1", Set.of("SRG1-PP1-TXRX"));
+
+        Preference clientPreference = new ClientPreference(nodePortPreference);
+
+        Assertions.assertTrue(clientPreference.isPreferredPort("ROADM-A-SRG1", "SRG1-PP2-TXRX"));
+
+    }
+}
\ No newline at end of file
diff --git a/pce/src/test/java/org/opendaylight/transportpce/pce/networkanalyzer/port/PreferenceFactoryTest.java b/pce/src/test/java/org/opendaylight/transportpce/pce/networkanalyzer/port/PreferenceFactoryTest.java
new file mode 100644 (file)
index 0000000..34ebb5c
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 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.networkanalyzer.port;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.PathComputationRequestInput;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.path.computation.request.input.ServiceAEnd;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.path.computation.request.input.ServiceZEnd;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev230526.service.port.PortBuilder;
+import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.service.types.rev220118.service.endpoint.sp.RxDirection;
+import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.service.types.rev220118.service.endpoint.sp.TxDirection;
+
+class PreferenceFactoryTest {
+
+    @Test
+    void emptyPathComputationRequest_returnEmptyHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceAEndRxDirectionWithoutDeviceAndPort_returnEmptyHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceAEnd serviceAEnd = Mockito.mock(ServiceAEnd.class);
+        RxDirection rxDirection = Mockito.mock(RxDirection.class);
+
+        Mockito.when(rxDirection.getPort()).thenReturn(new PortBuilder().build());
+        Mockito.when(serviceAEnd.getRxDirection()).thenReturn(rxDirection);
+        Mockito.when(pathComputationRequestInput.getServiceAEnd()).thenReturn(serviceAEnd);
+
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceAEndRxDirectionWithoutPort_returnEmptyHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceAEnd serviceAEnd = Mockito.mock(ServiceAEnd.class);
+        RxDirection rxDirection = Mockito.mock(RxDirection.class);
+
+        Mockito.when(rxDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .build()
+        );
+        Mockito.when(serviceAEnd.getRxDirection()).thenReturn(rxDirection);
+        Mockito.when(pathComputationRequestInput.getServiceAEnd()).thenReturn(serviceAEnd);
+
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceAEndRxDirectionTxRx_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceAEnd serviceAEnd = Mockito.mock(ServiceAEnd.class);
+        RxDirection rxDirection = Mockito.mock(RxDirection.class);
+
+        Mockito.when(rxDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName("SRG1-PP1-TXRX")
+                .build()
+        );
+        Mockito.when(serviceAEnd.getRxDirection()).thenReturn(rxDirection);
+        Mockito.when(pathComputationRequestInput.getServiceAEnd()).thenReturn(serviceAEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        expected.put("ROADM-B-SRG1", Set.of("SRG1-PP1-TXRX"));
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceAEndRxDirectionTx_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceAEnd serviceAEnd = Mockito.mock(ServiceAEnd.class);
+        RxDirection rxDirection = Mockito.mock(RxDirection.class);
+
+        Mockito.when(rxDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName("SRG1-PP1-TX")
+                .build()
+        );
+        Mockito.when(serviceAEnd.getRxDirection()).thenReturn(rxDirection);
+        Mockito.when(pathComputationRequestInput.getServiceAEnd()).thenReturn(serviceAEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        expected.put("ROADM-B-SRG1", Set.of("SRG1-PP1-TX"));
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceAEndRxDirectionRx_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceAEnd serviceAEnd = Mockito.mock(ServiceAEnd.class);
+        TxDirection txDirection = Mockito.mock(TxDirection.class);
+
+        Mockito.when(txDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName("SRG1-PP1-RX")
+                .build()
+        );
+        Mockito.when(serviceAEnd.getTxDirection()).thenReturn(txDirection);
+        Mockito.when(pathComputationRequestInput.getServiceAEnd()).thenReturn(serviceAEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        expected.put("ROADM-B-SRG1", Set.of("SRG1-PP1-RX"));
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceZEndRx_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceZEnd serviceZEnd = Mockito.mock(ServiceZEnd.class);
+        RxDirection rxDirection = Mockito.mock(RxDirection.class);
+
+        Mockito.when(rxDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName("SRG1-PP1-TXRX")
+                .build()
+        );
+        Mockito.when(serviceZEnd.getRxDirection()).thenReturn(rxDirection);
+        Mockito.when(pathComputationRequestInput.getServiceZEnd()).thenReturn(serviceZEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        expected.put("ROADM-B-SRG1", Set.of("SRG1-PP1-TXRX"));
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceZEndTxDirectionTxRx_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceZEnd serviceZEnd = Mockito.mock(ServiceZEnd.class);
+        TxDirection txDirection = Mockito.mock(TxDirection.class);
+
+        Mockito.when(txDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName("SRG1-PP1-TXRX")
+                .build()
+        );
+        Mockito.when(serviceZEnd.getTxDirection()).thenReturn(txDirection);
+        Mockito.when(pathComputationRequestInput.getServiceZEnd()).thenReturn(serviceZEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        expected.put("ROADM-B-SRG1", Set.of("SRG1-PP1-TXRX"));
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceZEndTxDirectionTx_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceZEnd serviceZEnd = Mockito.mock(ServiceZEnd.class);
+        TxDirection txDirection = Mockito.mock(TxDirection.class);
+
+        Mockito.when(txDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName("SRG1-PP1-TX")
+                .build()
+        );
+        Mockito.when(serviceZEnd.getTxDirection()).thenReturn(txDirection);
+        Mockito.when(pathComputationRequestInput.getServiceZEnd()).thenReturn(serviceZEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        expected.put("ROADM-B-SRG1", Set.of("SRG1-PP1-TX"));
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathComputationRequestServiceZEndTxDirectionRx_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceZEnd serviceZEnd = Mockito.mock(ServiceZEnd.class);
+        TxDirection txDirection = Mockito.mock(TxDirection.class);
+
+        Mockito.when(txDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName("SRG1-PP1-RX")
+                .build()
+        );
+        Mockito.when(serviceZEnd.getTxDirection()).thenReturn(txDirection);
+        Mockito.when(pathComputationRequestInput.getServiceZEnd()).thenReturn(serviceZEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        expected.put("ROADM-B-SRG1", Set.of("SRG1-PP1-RX"));
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathEmptyComputationRequestServiceZEndTx_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceZEnd serviceZEnd = Mockito.mock(ServiceZEnd.class);
+        TxDirection txDirection = Mockito.mock(TxDirection.class);
+
+        Mockito.when(txDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName(" ")
+                .build()
+        );
+        Mockito.when(serviceZEnd.getTxDirection()).thenReturn(txDirection);
+        Mockito.when(pathComputationRequestInput.getServiceZEnd()).thenReturn(serviceZEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void pathUnexpectedPortName_returnHashmap() {
+
+        PathComputationRequestInput pathComputationRequestInput = Mockito.mock(PathComputationRequestInput.class);
+        ServiceZEnd serviceZEnd = Mockito.mock(ServiceZEnd.class);
+        TxDirection txDirection = Mockito.mock(TxDirection.class);
+
+        Mockito.when(txDirection.getPort()).thenReturn(
+            new PortBuilder()
+                .setPortDeviceName("ROADM-B-SRG1")
+                .setPortName("FUBAR")
+                .build()
+        );
+        Mockito.when(serviceZEnd.getTxDirection()).thenReturn(txDirection);
+        Mockito.when(pathComputationRequestInput.getServiceZEnd()).thenReturn(serviceZEnd);
+
+        Map<String, Set<String>> expected = new HashMap<>();
+        expected.put("ROADM-B-SRG1", Set.of("FUBAR"));
+
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+        Assertions.assertEquals(expected, portPreferenceFactory.nodePortMap(pathComputationRequestInput));
+
+    }
+
+    @Test
+    void addingMultiplePort() {
+
+        PreferenceFactory portPreferenceFactory = new PreferenceFactory();
+        Map<String, Set<String>> mapper = new HashMap<>();
+
+        //New ports
+        Assertions.assertTrue(portPreferenceFactory.add("ROADM-B-SRG1", "SRG1-PP1-TXRX", mapper));
+        Assertions.assertTrue(portPreferenceFactory.add("ROADM-B-SRG1", "SRG1-PP2-TXRX", mapper));
+        Assertions.assertTrue(portPreferenceFactory.add("ROADM-B-SRG1", "SRG1-PP3-RX", mapper));
+        Assertions.assertTrue(portPreferenceFactory.add("ROADM-B-SRG1", "SRG1-PP3-TX", mapper));
+
+        //This port already exists, should return false.
+        Assertions.assertFalse(portPreferenceFactory.add("ROADM-B-SRG1", "SRG1-PP2-TXRX", mapper));
+
+        Assertions.assertEquals(
+            Set.of("SRG1-PP1-TXRX", "SRG1-PP2-TXRX", "SRG1-PP3-RX", "SRG1-PP3-TX"),
+            mapper.get("ROADM-B-SRG1")
+        );
+    }
+}
\ No newline at end of file