New Package dealing with device rollback 29/109829/4
authorJoakim Törnqvist <joakim.tornqvist@smartoptics.com>
Wed, 17 Jan 2024 14:35:08 +0000 (14:35 +0000)
committerJoakim Törnqvist <joakim.tornqvist@smartoptics.com>
Tue, 13 Feb 2024 11:49:13 +0000 (11:49 +0000)
Add a new class (TransactionHistory) capable of keeping
track of created interfaces (during a service creation
process) and rolling them back.

The DeleteService class handles the rollback process. Passing
in a subscriber (DeleteSubscriber) capable of tracking the process
and creating a result.

Pseudo code example

public Result rollbackExample(
        OpenRoadmInterfaceFactory openRoadmInterfaceFactory,
        OpenRoadmInterfaces openRoadmInterfaces,
        CrossConnect crossConnect
) {

    History transactionHistory = new TransactionHistory();

    String supportingOchInterface = openRoadmInterfaceFactory
        .createOpenRoadmOchInterface(...);

    transactionHistory.add(
        new DeviceInterface(nodeId, supportingOchInterface)
    );
    transactionHistory.addInterfaces(
        nodeId,
        supportingOchInterface.split("#")
    );

    Result rollbackResult = new FailedRollbackResult();
    Subscriber deleteSubscriber = new DeleteSubscriber(rollbackResult);

    transactionHistory.rollback(
        new DeleteService(
            crossConnect,
            openRoadmInterfaces,
            deleteSubscriber
        )
    );

    return rollbackResult.renderRollbackOutput();

}

JIRA: TRNSPRTPCE-615
Change-Id: I723e42628e8de0b45ab9160b5b8c366497c0f550
Signed-off-by: Joakim Törnqvist <joakim.tornqvist@smartoptics.com>
15 files changed:
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/Connection.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/DeviceInterface.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/Transaction.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Delete.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/DeleteService.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/DeleteSubscriber.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/FailedRollbackResult.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Result.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Subscriber.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/History.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/NonStickHistoryMemory.java [new file with mode: 0644]
renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/TransactionHistory.java [new file with mode: 0644]
renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/ConnectionTest.java [new file with mode: 0644]
renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/DeviceInterfaceTest.java [new file with mode: 0644]
renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/TransactionHistoryTest.java [new file with mode: 0644]

diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/Connection.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/Connection.java
new file mode 100644 (file)
index 0000000..ad04ae7
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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.renderer.provisiondevice.transaction;
+
+import java.util.List;
+import java.util.Objects;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Connection transaction.
+ *
+ * <p>
+ * i.e. a class tracking a connection.
+ */
+public class Connection implements Transaction {
+
+    private static final Logger LOG = LoggerFactory.getLogger(Connection.class);
+    private final String deviceId;
+    private final String connectionNumber;
+    private final boolean isOtn;
+
+    public Connection(String deviceId, String connectionNumber, boolean isOtn) {
+        this.deviceId = deviceId;
+        this.connectionNumber = connectionNumber;
+        this.isOtn = isOtn;
+    }
+
+    @Override
+    public boolean rollback(Delete delete) {
+        List<String> supportingInterfaces = delete.deleteCrossConnect(deviceId, connectionNumber, isOtn);
+
+        if (supportingInterfaces == null || supportingInterfaces.size() == 0) {
+            return false;
+        }
+
+        LOG.info("Supporting interfaces {} affected by rollback on {} {}",
+                String.join(", ", supportingInterfaces), deviceId, connectionNumber);
+
+        return true;
+
+    }
+
+    @Override
+    public String description() {
+        return String.format("Connection %s connection number %s isOtn %s", deviceId,
+                connectionNumber, isOtn);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof Connection)) {
+            return false;
+        }
+        Connection that = (Connection) object;
+        return isOtn == that.isOtn && Objects.equals(deviceId, that.deviceId)
+                && Objects.equals(connectionNumber, that.connectionNumber);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(deviceId, connectionNumber, isOtn);
+    }
+}
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/DeviceInterface.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/DeviceInterface.java
new file mode 100644 (file)
index 0000000..8bcc2c1
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.renderer.provisiondevice.transaction;
+
+import java.util.Objects;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+
+public class DeviceInterface implements Transaction {
+
+    private final String nodeId;
+
+    private final String interfaceId;
+
+    public DeviceInterface(String nodeId, String interfaceId) {
+        this.nodeId = nodeId;
+        this.interfaceId = interfaceId;
+    }
+
+    @Override
+    public boolean rollback(Delete delete) {
+        return delete.deleteInterface(nodeId, interfaceId);
+    }
+
+    @Override
+    public String description() {
+        return String.format("Node: %s interface id: %s", nodeId, interfaceId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(nodeId, interfaceId);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof DeviceInterface)) {
+            return false;
+        }
+        DeviceInterface that = (DeviceInterface) object;
+        return Objects.equals(nodeId, that.nodeId) && Objects.equals(interfaceId,
+                that.interfaceId);
+    }
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/Transaction.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/Transaction.java
new file mode 100644 (file)
index 0000000..d1300e3
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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.renderer.provisiondevice.transaction;
+
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+
+/**
+ * Any class wishing to keep track of transactions
+ * may implement this interface.
+ */
+public interface Transaction {
+
+    /**
+     * Rollback this transaction.
+     */
+    boolean rollback(Delete delete);
+
+    String description();
+
+    int hashCode();
+
+    boolean equals(Object object);
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Delete.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Delete.java
new file mode 100644 (file)
index 0000000..95fcdff
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.renderer.provisiondevice.transaction.delete;
+
+import java.util.List;
+
+/**
+ * A class capable of deleting service connections/interfaces
+ * may implement this interface.
+ */
+public interface Delete {
+
+    /**
+     * Delete cross connection.
+     * Typically, deleted before interfaces.
+     */
+    List<String> deleteCrossConnect(String deviceId, String connectionNumber, boolean isOtn);
+
+    /**
+     * Delete an interface.
+     * Typically, deleted after the cross connection.
+     */
+    boolean deleteInterface(String nodeId, String interfaceId);
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/DeleteService.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/DeleteService.java
new file mode 100644 (file)
index 0000000..62af4bf
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.renderer.provisiondevice.transaction.delete;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.transportpce.common.crossconnect.CrossConnect;
+import org.opendaylight.transportpce.common.openroadminterfaces.OpenRoadmInterfaceException;
+import org.opendaylight.transportpce.common.openroadminterfaces.OpenRoadmInterfaces;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DeleteService implements Delete {
+
+    private final CrossConnect crossConnect;
+    private final OpenRoadmInterfaces openRoadmInterfaces;
+
+    private final Subscriber subscriber;
+
+    private static final Logger LOG = LoggerFactory.getLogger(DeleteService.class);
+
+    public DeleteService(
+            CrossConnect crossConnect,
+            OpenRoadmInterfaces openRoadmInterfaces,
+            Subscriber subscriber) {
+        this.crossConnect = crossConnect;
+        this.openRoadmInterfaces = openRoadmInterfaces;
+        this.subscriber = subscriber;
+    }
+
+    @Override
+    public @NonNull List<String> deleteCrossConnect(String deviceId, String connectionNumber,
+                                                    boolean isOtn) {
+        List<String> result = crossConnect.deleteCrossConnect(deviceId, connectionNumber, isOtn);
+
+        if (result == null) {
+            subscriber.result(false, deviceId, connectionNumber);
+            return new ArrayList<>();
+        }
+
+        subscriber.result(true, deviceId, connectionNumber);
+
+        return result;
+    }
+
+    @Override
+    public boolean deleteInterface(String nodeId, String interfaceId) {
+        try {
+            openRoadmInterfaces.deleteInterface(nodeId, interfaceId);
+
+            subscriber.result(true, nodeId, interfaceId);
+            return true;
+        } catch (OpenRoadmInterfaceException e) {
+            LOG.error("Failed rolling back {} {}", nodeId, interfaceId);
+            subscriber.result(false, nodeId, interfaceId);
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/DeleteSubscriber.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/DeleteSubscriber.java
new file mode 100644 (file)
index 0000000..886b459
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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.renderer.provisiondevice.transaction.delete;
+
+public class DeleteSubscriber implements Subscriber {
+
+    private final Result result;
+
+    public DeleteSubscriber(Result result) {
+        this.result = result;
+    }
+
+    @Override
+    public void result(Boolean success, String nodeId, String interfaceId) {
+
+        result.add(success, nodeId, interfaceId);
+
+    }
+
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/FailedRollbackResult.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/FailedRollbackResult.java
new file mode 100644 (file)
index 0000000..5637879
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.renderer.provisiondevice.transaction.delete;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.device.renderer.rev211004.RendererRollbackOutput;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.device.renderer.rev211004.RendererRollbackOutputBuilder;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.device.renderer.rev211004.renderer.rollback.output.FailedToRollback;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.device.renderer.rev211004.renderer.rollback.output.FailedToRollbackBuilder;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.device.renderer.rev211004.renderer.rollback.output.FailedToRollbackKey;
+
+public class FailedRollbackResult implements Result {
+
+    private final Map<String, Set<String>> failedRollback = Collections.synchronizedMap(
+            new HashMap<>());
+
+    @Override
+    public boolean add(boolean success, String nodeId, String interfaceId) {
+
+        if (success) {
+            return false;
+        }
+
+        if (!failedRollback.containsKey(nodeId)) {
+            failedRollback.put(nodeId, new LinkedHashSet<>());
+        }
+
+        return failedRollback.get(nodeId).add(interfaceId);
+    }
+
+    @Override
+    public RendererRollbackOutput renderRollbackOutput() {
+
+        Map<FailedToRollbackKey, FailedToRollback> failedToRollbackList = new HashMap<>();
+
+        for (Entry<String, Set<String>> entry : failedRollback.entrySet()) {
+
+            FailedToRollback failedToRollack = new FailedToRollbackBuilder()
+                    .withKey(new FailedToRollbackKey(entry.getKey()))
+                    .setNodeId(entry.getKey())
+                    .setInterface(entry.getValue())
+                    .build();
+
+            failedToRollbackList.put(failedToRollack.key(), failedToRollack);
+
+        }
+
+        return new RendererRollbackOutputBuilder()
+                .setSuccess(failedRollback.isEmpty())
+                .setFailedToRollback(failedToRollbackList)
+                .build();
+
+    }
+
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Result.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Result.java
new file mode 100644 (file)
index 0000000..fb77027
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * 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.renderer.provisiondevice.transaction.delete;
+
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.device.renderer.rev211004.RendererRollbackOutput;
+
+public interface Result {
+
+    boolean add(boolean success, String nodeId, String interfaceId);
+
+    RendererRollbackOutput renderRollbackOutput();
+
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Subscriber.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/delete/Subscriber.java
new file mode 100644 (file)
index 0000000..8e83d16
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * 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.renderer.provisiondevice.transaction.delete;
+
+public interface Subscriber {
+
+    void result(Boolean success, String nodeId, String interfaceId);
+
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/History.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/History.java
new file mode 100644 (file)
index 0000000..fd2646a
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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.renderer.provisiondevice.transaction.history;
+
+import java.util.List;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.Transaction;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+
+public interface History {
+
+    /**
+     * Add transaction.
+     *
+     * <p>
+     * Only accepts the transaction if this History
+     * object doesn't already contain the object.
+     *
+     * @return true if the transaction was added.
+     */
+    boolean add(Transaction transaction);
+
+    /**
+     * A list of transactions.
+     *
+     * <p>
+     * Will only accept unique transactions.
+     * @return true if all transactions was added. false if one or more transactions was rejected.
+     */
+    boolean add(List<Transaction> transactions);
+
+    /**
+     * Add an array of interface transactions.
+     *
+     * <p>
+     * Duplicate interface ids, null or empty strings
+     * are silently ignored.
+     * @return may return false
+     */
+    boolean addInterfaces(String nodeId, String interfaceId);
+
+    /**
+     * Add an array of interface transactions.
+     *
+     * <p>
+     * Duplicate interface ids, null or empty strings
+     * are silently ignored.
+     * @return may return false
+     */
+    boolean addInterfaces(String nodeId, String[] interfaceIds);
+
+    /**
+     * Add a list of interface transactions.
+     *
+     * <p>
+     * Duplicate interface ids, null or empty strings
+     * are silently ignored.
+     */
+    boolean addInterfaces(String nodeId, List<String> interfaceIds);
+
+    /**
+     * Rollback all transactions.
+     *
+     * <p>
+     * Typically, the transactions are rolled back in reverse
+     * order, but the implementing class may choose a different
+     * logic.
+     */
+    boolean rollback(Delete delete);
+
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/NonStickHistoryMemory.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/NonStickHistoryMemory.java
new file mode 100644 (file)
index 0000000..71d8f74
--- /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.renderer.provisiondevice.transaction.history;
+
+import java.util.List;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.Transaction;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Goldfish implementation of the History interface.
+ *
+ * <p>
+ * This implementation simply doesn't track anything.
+ * Most useful for backwards compatibility reasons.
+ */
+public class NonStickHistoryMemory implements History {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NonStickHistoryMemory.class);
+
+    @Override
+    public boolean add(Transaction transaction) {
+        LOG.warn("Transaction history disabled. Ignoring '{}'.", transaction.description());
+        return false;
+    }
+
+    @Override
+    public boolean add(List<Transaction> transactions) {
+        LOG.warn("Transaction history disabled. No rollback executed.");
+        return false;
+    }
+
+    @Override
+    public boolean addInterfaces(String nodeId, String interfaceId) {
+        LOG.warn("Transaction history disabled.");
+        return false;
+    }
+
+    @Override
+    public boolean addInterfaces(String nodeId, String[] interfaceIds) {
+        LOG.warn("Transaction history disabled.");
+        return false;
+    }
+
+    @Override
+    public boolean addInterfaces(String nodeId, List<String> interfaceIds) {
+        LOG.warn("Transaction history disabled.");
+        return false;
+    }
+
+    @Override
+    public boolean rollback(Delete delete) {
+        LOG.warn("Transaction history disabled. No rollback executed.");
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/TransactionHistory.java b/renderer/src/main/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/TransactionHistory.java
new file mode 100644 (file)
index 0000000..a1565fc
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.renderer.provisiondevice.transaction.history;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.DeviceInterface;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.Transaction;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class keeping track of transaction history.
+ *
+ * <p>
+ * A transaction can be something like an interface or a roadm connection, that may need to be
+ * rolled back in the future.
+ */
+public class TransactionHistory implements History {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TransactionHistory.class);
+    Set<Transaction> transactionHistory = Collections.synchronizedSet(new LinkedHashSet<>());
+
+    @Override
+    public boolean add(Transaction transaction) {
+
+        boolean result = transactionHistory.add(transaction);
+
+        if (result) {
+            LOG.info("Adding {}", transaction.description());
+        } else {
+            LOG.warn("Transaction {} not added.", transaction.description());
+        }
+
+        return result;
+    }
+
+    @Override
+    public boolean add(List<Transaction> transactions) {
+        Set<Boolean> results = new HashSet<>(transactions.size());
+
+        for (Transaction transaction : transactions) {
+            results.add(add(transaction));
+        }
+
+        return results.stream().allMatch(i -> (i.equals(Boolean.TRUE)));
+    }
+
+    @Override
+    public boolean addInterfaces(String nodeId, String interfaceId) {
+        return addInterfaces(nodeId, Collections.singletonList(interfaceId));
+    }
+
+    @Override
+    public boolean addInterfaces(String nodeId, String[] interfaceIds) {
+
+        return addInterfaces(nodeId, Arrays.asList(interfaceIds));
+
+    }
+
+    @Override
+    public boolean addInterfaces(String nodeId, List<String> interfaceIds) {
+
+        Set<Boolean> results = new HashSet<>();
+        Set<String> unique = new LinkedHashSet<>();
+
+        for (String interfaceId : interfaceIds) {
+            if (interfaceId != null && !interfaceId.trim().isEmpty()) {
+                unique.add(interfaceId.trim());
+            }
+        }
+
+        for (String interfaceId : unique) {
+            results.add(this.add(new DeviceInterface(nodeId, interfaceId)));
+        }
+
+        return results.stream().allMatch(i -> (i.equals(Boolean.TRUE)));
+
+    }
+
+    @Override
+    public boolean rollback(Delete delete) {
+
+        LOG.info("History contains {} items. Rolling them back in reverse order.",
+                transactionHistory.size());
+
+        List<Transaction> reverse = new ArrayList<>(transactionHistory);
+
+        Collections.reverse(reverse);
+
+        boolean success = true;
+
+        for (Transaction transaction : reverse) {
+            LOG.info("Rolling back {}", transaction.description());
+            if (!transaction.rollback(delete)) {
+                success = false;
+            }
+        }
+
+        return success;
+
+    }
+}
\ No newline at end of file
diff --git a/renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/ConnectionTest.java b/renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/ConnectionTest.java
new file mode 100644 (file)
index 0000000..a3ba1e7
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.renderer.provisiondevice.transaction;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+
+class ConnectionTest {
+
+    @Test
+    void rollback() {
+        Delete delete = Mockito.mock(Delete.class);
+        Mockito.when(delete.deleteCrossConnect("ROADM-A", "DEG1", false))
+                .thenReturn(List.of("Interface1"));
+
+        Connection n1 = new Connection("ROADM-A", "DEG1", false);
+
+        Assert.assertTrue(n1.rollback(delete));
+
+        Mockito.verify(delete, Mockito.times(1))
+                .deleteCrossConnect("ROADM-A", "DEG1", false);
+    }
+
+    @Test
+    void testTwoObjectsWithSameInformationIsEqual() {
+        Connection n1 = new Connection("ROADM-A", "DEG1", false);
+        Connection n2 = new Connection("ROADM-A", "DEG1", false);
+
+        Assert.assertTrue(n1.equals(n2));
+    }
+
+    @Test
+    void testTwoObjectsWithDifferentInformationIsNotEqual() {
+        Connection n1 = new Connection("ROADM-A", "DEG1", true);
+        Connection n2 = new Connection("ROADM-A", "DEG1", false);
+
+        Assert.assertFalse(n1.equals(n2));
+    }
+
+    @Test
+    void testTwoDifferentRoadmNodesAreNotEqual() {
+        Connection n1 = new Connection("ROADM-A", "DEG1", false);
+        Connection n2 = new Connection("ROADM-B", "DEG1", false);
+
+        Assert.assertFalse(n1.equals(n2));
+    }
+
+
+    @Test
+    void deleteReturnNull() {
+        Delete delete = Mockito.mock(Delete.class);
+        Mockito.when(delete.deleteCrossConnect("ROADM-A", "DEG1", false))
+                .thenReturn(null);
+
+        Connection n1 = new Connection("ROADM-A", "DEG1", false);
+
+        Assert.assertFalse(n1.rollback(delete));
+
+        Mockito.verify(delete, Mockito.times(1))
+                .deleteCrossConnect("ROADM-A", "DEG1", false);
+    }
+
+    @Test
+    void deleteReturnEmptyList() {
+        Delete delete = Mockito.mock(Delete.class);
+        Mockito.when(delete.deleteCrossConnect("ROADM-A", "DEG1", false))
+                .thenReturn(new ArrayList<>());
+
+        Connection n1 = new Connection("ROADM-A", "DEG1", false);
+
+        Assert.assertFalse(n1.rollback(delete));
+
+        Mockito.verify(delete, Mockito.times(1))
+                .deleteCrossConnect("ROADM-A", "DEG1", false);
+    }
+}
\ No newline at end of file
diff --git a/renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/DeviceInterfaceTest.java b/renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/DeviceInterfaceTest.java
new file mode 100644 (file)
index 0000000..d0481ae
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.renderer.provisiondevice.transaction;
+
+import org.junit.Assert;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+
+class DeviceInterfaceTest {
+
+    @Test
+    void rollback() {
+        Delete delete = Mockito.mock(Delete.class);
+        Mockito.when(delete.deleteInterface("ROADM-A", "DEG1")).thenReturn(true);
+
+        DeviceInterface n1 = new DeviceInterface("ROADM-A", "DEG1");
+        Assert.assertTrue(n1.rollback(delete));
+
+        Mockito.verify(delete, Mockito.times(1)).deleteInterface("ROADM-A", "DEG1");
+    }
+
+    @Test
+    void testTwoInterfacesAreEqual() {
+        DeviceInterface n1 = new DeviceInterface("ROADM-A", "DEG1");
+        DeviceInterface n2 = new DeviceInterface("ROADM-A", "DEG1");
+
+        Assert.assertTrue(n1.equals(n2));
+    }
+
+    @Test
+    void testTwoInterfacesAreNotEqual() {
+        DeviceInterface n1 = new DeviceInterface("ROADM-A", "DEG1");
+        DeviceInterface n2 = new DeviceInterface("ROADM-B", "DEG1");
+
+        Assert.assertFalse(n1.equals(n2));
+    }
+}
\ No newline at end of file
diff --git a/renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/TransactionHistoryTest.java b/renderer/src/test/java/org/opendaylight/transportpce/renderer/provisiondevice/transaction/history/TransactionHistoryTest.java
new file mode 100644 (file)
index 0000000..0adf418
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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.renderer.provisiondevice.transaction.history;
+
+import java.util.List;
+import org.junit.Assert;
+import org.junit.jupiter.api.Test;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.DeviceInterface;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.Transaction;
+import org.opendaylight.transportpce.renderer.provisiondevice.transaction.delete.Delete;
+
+class TransactionHistoryTest {
+
+    @Test
+    void add() {
+        Transaction transaction = Mockito.mock(Transaction.class);
+        History history = new TransactionHistory();
+
+        Assert.assertTrue(history.add(transaction));
+    }
+
+    @Test
+    void testDuplicateTransactionIsIgnored() {
+
+        Transaction t1 = new DeviceInterface("ROADM-A", "DEG1");
+        Transaction t2 = new DeviceInterface("ROADM-A", "DEG1");
+
+        History history = new TransactionHistory();
+
+        history.add(t1);
+        Assert.assertFalse(history.add(t2));
+    }
+
+    @Test
+    void testAddCollectionOfUniqueTransactions() {
+        Transaction t1 = new DeviceInterface("ROADM-A", "DEG1");
+        Transaction t2 = new DeviceInterface("ROADM-A", "DEG2");
+
+        List<Transaction> transactions = List.of(t1, t2);
+
+        History history = new TransactionHistory();
+
+        Assert.assertTrue(history.add(transactions));
+    }
+
+    @Test
+    void testAddCollectionOfDuplicateTransactions() {
+        Transaction t1 = new DeviceInterface("ROADM-A", "DEG1");
+        Transaction t2 = new DeviceInterface("ROADM-A", "DEG1");
+
+        List<Transaction> transactions = List.of(t1, t2);
+
+        History history = new TransactionHistory();
+
+        Assert.assertFalse(history.add(transactions));
+    }
+
+    @Test
+    void testAddUniqueStringOfInterfaceIds() {
+        String nodeId = "ROADM-A";
+        String[] interfaces = new String[]{"DEG1", "DEG2"};
+
+        History history = new TransactionHistory();
+
+        Assert.assertTrue(history.addInterfaces(nodeId, interfaces));
+    }
+
+    @Test
+    void testAddDuplicateStringOfInterfaceIds() {
+        String nodeId = "ROADM-A";
+        String[] interfaces = new String[]{"DEG1", "DEG1"};
+
+        History history = new TransactionHistory();
+
+        Assert.assertTrue(history.addInterfaces(nodeId, interfaces));
+
+    }
+
+    @Test
+    void testAddDuplicateListOfInterfaceIds() {
+        String nodeId = "ROADM-A";
+        List<String> interfaces = List.of("DEG1", "DEG1");
+
+        History history = new TransactionHistory();
+
+        Assert.assertTrue(history.addInterfaces(nodeId, interfaces));
+
+    }
+
+    @Test
+    void rollbackOneInterface() {
+
+        String nodeId = "ROADM-A";
+        List<String> interfaces = List.of("DEG1", "DEG1");
+
+        History history = new TransactionHistory();
+        history.addInterfaces(nodeId, interfaces);
+
+        Delete delete = Mockito.mock(Delete.class);
+        Mockito.when(delete.deleteInterface("ROADM-A", "DEG1")).thenReturn(true);
+
+        Assert.assertTrue(history.rollback(delete));
+
+        //Although the same interface was added twice, we only rollback once.
+        Mockito.verify(delete, Mockito.times(1))
+                .deleteInterface("ROADM-A", "DEG1");
+    }
+
+    @Test
+    void rollbackTwoInterfacesInReverseOrderTheyWereAdded() {
+
+        String nodeId = "ROADM-A";
+
+        //Note DEG1 is added before DEG2
+        List<String> interfaces = List.of("DEG1", "DEG2");
+
+        History history = new TransactionHistory();
+        history.addInterfaces(nodeId, interfaces);
+
+        Delete delete = Mockito.mock(Delete.class);
+        Mockito.when(delete.deleteInterface("ROADM-A", "DEG1")).thenReturn(true);
+        Mockito.when(delete.deleteInterface("ROADM-A", "DEG2")).thenReturn(true);
+
+        Assert.assertTrue(history.rollback(delete));
+
+        //The rollback occurs in the reverse order.
+        // i.e. DEG2 before DEG1.
+        InOrder inOrder = Mockito.inOrder(delete);
+        inOrder.verify(delete, Mockito.times(1))
+                .deleteInterface("ROADM-A", "DEG2");
+        inOrder.verify(delete, Mockito.times(1))
+                .deleteInterface("ROADM-A", "DEG1");
+
+    }
+}
\ No newline at end of file