From: Vladyslav Marchenko Date: Wed, 10 Jun 2020 09:19:19 +0000 (+0300) Subject: Create NetconfDataTreeService with base and additional operations for netconf X-Git-Tag: release/aluminium~21 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;ds=sidebyside;h=14bd5e7ef6421c109248f4e750be330cb0337287;p=netconf.git Create NetconfDataTreeService with base and additional operations for netconf Service provided by netconf mountpoints. It provides an api for all netconf operations, instead of mapping mdsal api to netconf requests. Restconf now uses the new NetconfDataTreeService when available. If no - fall back to DOMDataBroker. JIRA: NETCONF-312 Signed-off-by: Vladyslav Marchenko Change-Id: I354449883b54c8a3a4e7ffdc12d7d532c8120b6a --- diff --git a/artifacts/pom.xml b/artifacts/pom.xml index ce8a55f9a9..b4e1cff36f 100644 --- a/artifacts/pom.xml +++ b/artifacts/pom.xml @@ -35,6 +35,11 @@ netconf-api ${project.version} + + ${project.groupId} + netconf-dom-api + ${project.version} + ${project.groupId} netconf-auth diff --git a/netconf/netconf-dom-api/pom.xml b/netconf/netconf-dom-api/pom.xml new file mode 100644 index 0000000000..d0b7d3e391 --- /dev/null +++ b/netconf/netconf-dom-api/pom.xml @@ -0,0 +1,36 @@ + + + + 4.0.0 + + + org.opendaylight.netconf + netconf-parent + 1.9.0-SNAPSHOT + ../../parent + + + org.opendaylight.netconf + netconf-dom-api + 1.9.0-SNAPSHOT + ${project.artifactId} + bundle + + + + org.opendaylight.mdsal + mdsal-dom-api + + + org.opendaylight.netconf + netconf-api + + + diff --git a/netconf/netconf-dom-api/src/main/java/org/opendaylight/netconf/dom/api/NetconfDataTreeService.java b/netconf/netconf-dom-api/src/main/java/org/opendaylight/netconf/dom/api/NetconfDataTreeService.java new file mode 100644 index 0000000000..67f063346a --- /dev/null +++ b/netconf/netconf-dom-api/src/main/java/org/opendaylight/netconf/dom/api/NetconfDataTreeService.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.netconf.dom.api; + +import com.google.common.util.concurrent.ListenableFuture; +import java.util.List; +import java.util.Optional; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMService; +import org.opendaylight.netconf.api.ModifyAction; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Interface for base and additional operations for netconf (e.g. get, get-config, edit-config, (un)lock, commit etc). + * <edit-config> operation is extended according it's attributes (merge, replace, create, delete, remove). + * According to RFC-6241. + */ +public interface NetconfDataTreeService extends DOMService { + + /** + * The <lock> operation. + * Allows the client to lock the entire configuration datastore system of a device. + * + * @return result of <lock> operation + */ + List> lock(); + + /** + * The <unlock> operation. + * Used to release a configuration lock, previously obtained with the <lock> operation. + */ + void unlock(); + + /** + * The <discard-changes> operation. + * If device supports :candidate capability, discards any uncommitted changes by resetting + * the candidate configuration with the content of the running configuration. + */ + void discardChanges(); + + /** + * The <get> operation. + * Retrieve running configuration and device state information. + * + * @return result of <get> operation + */ + ListenableFuture>> get(YangInstanceIdentifier path); + + /** + * The <get-config> operation. + * Retrieve all or part of a specified configuration datastore. + * + * @return result of <get-config> operation + */ + ListenableFuture>> getConfig(YangInstanceIdentifier path); + + /** + * The <edit-config> operation with "merge" attribute. + * The configuration data identified by the element containing this attribute is merged with the configuration + * at the corresponding level in the configuration datastore. + * + * @return result of <edit-config> operation + */ + ListenableFuture merge(LogicalDatastoreType store, YangInstanceIdentifier path, + NormalizedNode data, + Optional defaultOperation); + + /** + * The <edit-config> operation with "replace" attribute. + * The configuration data identified by the element containing this attribute replaces any related configuration + * in the configuration datastore. + * + * @return result of <edit-config> operation + */ + ListenableFuture replace(LogicalDatastoreType store, YangInstanceIdentifier path, + NormalizedNode data, + Optional defaultOperation); + + /** + * The <edit-config> operation with "create" attribute. + * The configuration data identified by the element containing this attribute is added to the configuration if + * and only if the configuration data does not already exist in the configuration datastore. + * + * @return result of <edit-config> operation + */ + ListenableFuture create(LogicalDatastoreType store, YangInstanceIdentifier path, + NormalizedNode data, + Optional defaultOperation); + + /** + * The <edit-config> operation with "create" attribute. + * The configuration data identified by the element containing this attribute is deleted from the configuration + * if and only if the configuration data currently exists in the configuration datastore. + * + * @return result of <edit-config> operation + */ + ListenableFuture delete(LogicalDatastoreType store, YangInstanceIdentifier path); + + /** + * The <edit-config> operation with "create" attribute. + * The configuration data identified by the element containing this attribute is deleted from the configuration + * if the configuration data currently exists in the configuration datastore. + * + * @return result of <edit-config> operation + */ + ListenableFuture remove(LogicalDatastoreType store, YangInstanceIdentifier path); + + /** + * The <commit> operation. + * If device supports :candidate capability, commit the candidate configuration as the device's + * new current configuration. + * + * @return result of <commit> operation + */ + ListenableFuture commit(List> resultsFutures); + + /** + * Return device identifier. + * + * @return Device's identifier, must not be null. + */ + @NonNull Object getDeviceId(); +} \ No newline at end of file diff --git a/netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/MasterSalFacade.java b/netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/MasterSalFacade.java index 03347e9c06..d80a9871c6 100644 --- a/netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/MasterSalFacade.java +++ b/netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/MasterSalFacade.java @@ -142,7 +142,7 @@ class MasterSalFacade implements AutoCloseable, RemoteDeviceHandler netconf-api + netconf-dom-api netconf-config netconf-impl mdsal-netconf-ssh diff --git a/netconf/sal-netconf-connector/pom.xml b/netconf/sal-netconf-connector/pom.xml index 91b7515238..2eda0f6d2f 100644 --- a/netconf/sal-netconf-connector/pom.xml +++ b/netconf/sal-netconf-connector/pom.xml @@ -51,6 +51,10 @@ org.opendaylight.mdsal mdsal-binding-api + + org.opendaylight.netconf + netconf-dom-api + org.opendaylight.mdsal mdsal-binding-runtime-spi diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/LockChangeListener.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/LockChangeListener.java index ee3d6a08ae..d0820e97a2 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/LockChangeListener.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/LockChangeListener.java @@ -12,6 +12,7 @@ import org.opendaylight.mdsal.binding.api.DataObjectModification; import org.opendaylight.mdsal.binding.api.DataTreeChangeListener; import org.opendaylight.mdsal.binding.api.DataTreeModification; import org.opendaylight.mdsal.dom.api.DOMDataBroker; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.netconf.node.fields.optional.topology.node.DatastoreLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,9 +22,12 @@ final class LockChangeListener implements DataTreeChangeListener private static final Logger LOG = LoggerFactory.getLogger(LockChangeListener.class); private final NetconfDeviceDataBroker netconfDeviceDataBroker; + private final NetconfDataTreeServiceImpl netconfDataTreeService; - LockChangeListener(final DOMDataBroker netconfDeviceDataBrokder) { + LockChangeListener(final DOMDataBroker netconfDeviceDataBrokder, + final NetconfDataTreeService netconfDataTreeService) { this.netconfDeviceDataBroker = (NetconfDeviceDataBroker)netconfDeviceDataBrokder; + this.netconfDataTreeService = (NetconfDataTreeServiceImpl) netconfDataTreeService; } @Override @@ -39,9 +43,11 @@ final class LockChangeListener implements DataTreeChangeListener + "the data store may interfere with data consistency."); } netconfDeviceDataBroker.setLockAllowed(rootNode.getDataAfter().isDatastoreLockAllowed()); + netconfDataTreeService.setLockAllowed(rootNode.getDataAfter().isDatastoreLockAllowed()); break; case DELETE: netconfDeviceDataBroker.setLockAllowed(true); + netconfDataTreeService.setLockAllowed(true); break; default: LOG.debug("Unsupported modification type: {}.", rootNode.getModificationType()); diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDataTreeServiceImpl.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDataTreeServiceImpl.java new file mode 100644 index 0000000000..df0c65050c --- /dev/null +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDataTreeServiceImpl.java @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.netconf.sal.connect.netconf.sal; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.netconf.api.DocumentedException; +import org.opendaylight.netconf.api.ModifyAction; +import org.opendaylight.netconf.api.NetconfDocumentedException; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; +import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences; +import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps; +import org.opendaylight.netconf.sal.connect.netconf.util.NetconfRpcFutureCallback; +import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NetconfDataTreeServiceImpl implements NetconfDataTreeService { + private static final Logger LOG = LoggerFactory.getLogger(NetconfDataTreeServiceImpl.class); + + private final RemoteDeviceId id; + private final NetconfBaseOps netconfOps; + private final boolean rollbackSupport; + private final boolean candidateSupported; + private final boolean runningWritable; + + private boolean isLockAllowed = true; + + public NetconfDataTreeServiceImpl(final RemoteDeviceId id, final MountPointContext mountContext, + final DOMRpcService rpc, + final NetconfSessionPreferences netconfSessionPreferences) { + this.id = id; + this.netconfOps = new NetconfBaseOps(rpc, mountContext); + // get specific attributes from netconf preferences and get rid of it + // no need to keep the entire preferences object, its quite big with all the capability QNames + candidateSupported = netconfSessionPreferences.isCandidateSupported(); + runningWritable = netconfSessionPreferences.isRunningWritable(); + rollbackSupport = netconfSessionPreferences.isRollbackSupported(); + Preconditions.checkArgument(candidateSupported || runningWritable, + "Device %s has advertised neither :writable-running nor :candidate capability." + + "At least one of these should be advertised. Failed to establish a session.", id.getName()); + } + + @Override + public synchronized List> lock() { + final List> resultsFutures = new ArrayList<>(); + if (candidateSupported) { + lockCandidate(resultsFutures); + if (runningWritable) { + lockRunning(resultsFutures); + } + } else { + lockRunning(resultsFutures); + } + return resultsFutures; + } + + @Override + public synchronized void unlock() { + if (candidateSupported) { + unlockCandidate(); + if (runningWritable) { + unlockRunning(); + } + } else { + unlockRunning(); + } + } + + /** + * This has to be non blocking since it is called from a callback on commit + * and its netty threadpool that is really sensitive to blocking calls. + */ + @Override + public void discardChanges() { + if (candidateSupported) { + netconfOps.discardChanges(new NetconfRpcFutureCallback("Discarding candidate", id)); + } + } + + @Override + public ListenableFuture>> get(YangInstanceIdentifier path) { + return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path)); + } + + @Override + public ListenableFuture>> getConfig(final YangInstanceIdentifier path) { + return netconfOps.getConfigRunningData( + new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path)); + } + + @Override + public synchronized ListenableFuture merge(final LogicalDatastoreType store, + final YangInstanceIdentifier path, + final NormalizedNode data, + final Optional defaultOperation) { + checkEditable(store); + final DataContainerChild editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data), + Optional.of(ModifyAction.MERGE), path); + + return editConfig(defaultOperation, editStructure); + } + + @Override + public synchronized ListenableFuture replace( + final LogicalDatastoreType store, + final YangInstanceIdentifier path, + final NormalizedNode data, + final Optional defaultOperation) { + checkEditable(store); + final DataContainerChild editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data), + Optional.of(ModifyAction.REPLACE), path); + + return editConfig(defaultOperation, editStructure); + } + + @Override + public synchronized ListenableFuture create(final LogicalDatastoreType store, + final YangInstanceIdentifier path, + final NormalizedNode data, + final Optional defaultOperation) { + checkEditable(store); + final DataContainerChild editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data), + Optional.of(ModifyAction.CREATE), path); + + return editConfig(defaultOperation, editStructure); + } + + @Override + public synchronized ListenableFuture delete(final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + final DataContainerChild editStructure = netconfOps.createEditConfigStrcture(Optional.empty(), + Optional.of(ModifyAction.DELETE), path); + + return editConfig(Optional.empty(), editStructure); + } + + @Override + public synchronized ListenableFuture remove(final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + final DataContainerChild editStructure = netconfOps.createEditConfigStrcture(Optional.empty(), + Optional.of(ModifyAction.REMOVE), path); + + return editConfig(Optional.empty(), editStructure); + } + + @Override + public ListenableFuture commit( + List> resultsFutures) { + final SettableFuture resultFuture = SettableFuture.create(); + Futures.addCallback(performCommit(resultsFutures), new FutureCallback<>() { + @Override + public void onSuccess(final RpcResult result) { + if (!result.isSuccessful()) { + final Collection errors = result.getErrors(); + resultFuture.setException(new TransactionCommitFailedException( + String.format("Commit of transaction %s failed", this), + errors.toArray(new RpcError[errors.size()]))); + return; + } + resultFuture.set(CommitInfo.empty()); + } + + @Override + public void onFailure(final Throwable failure) { + resultFuture.setException(new TransactionCommitFailedException( + String.format("Commit of transaction %s failed", this), failure)); + } + }, MoreExecutors.directExecutor()); + return resultFuture; + } + + @Override + public Object getDeviceId() { + return id; + } + + void setLockAllowed(final boolean isLockAllowedOrig) { + this.isLockAllowed = isLockAllowedOrig; + } + + private ListenableFuture editConfig(final Optional defaultOperation, + final DataContainerChild editStructure) { + if (candidateSupported) { + return editConfigCandidate(defaultOperation, editStructure); + } else { + return editConfigRunning(defaultOperation, editStructure); + } + } + + private ListenableFuture editConfigRunning(final Optional defaultOperation, + final DataContainerChild editStructure) { + final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit running", id); + if (defaultOperation.isPresent()) { + return netconfOps.editConfigRunning(callback, editStructure, defaultOperation.get(), rollbackSupport); + } else { + return netconfOps.editConfigRunning(callback, editStructure, rollbackSupport); + } + } + + private ListenableFuture editConfigCandidate(final Optional defaultOperation, + final DataContainerChild editStructure) { + final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit candidate", id); + if (defaultOperation.isPresent()) { + return netconfOps.editConfigCandidate(callback, editStructure, defaultOperation.get(), rollbackSupport); + } else { + return netconfOps.editConfigCandidate(callback, editStructure, rollbackSupport); + } + } + + private void lockRunning(List> resultsFutures) { + if (isLockAllowed) { + resultsFutures.add(netconfOps.lockRunning(new NetconfRpcFutureCallback("Lock running", id))); + } else { + LOG.trace("Lock is not allowed: {}", id); + } + } + + private void unlockRunning() { + if (isLockAllowed) { + netconfOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id)); + } else { + LOG.trace("Unlock is not allowed: {}", id); + } + } + + private void lockCandidate(List> resultsFutures) { + if (isLockAllowed) { + resultsFutures.add(netconfOps.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id) { + @Override + public void onFailure(Throwable throwable) { + super.onFailure(throwable); + discardChanges(); + } + })); + } else { + LOG.trace("Lock is not allowed: {}", id); + } + } + + private void unlockCandidate() { + if (isLockAllowed) { + netconfOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id)); + } else { + LOG.trace("Unlock is not allowed: {}", id); + } + } + + private void checkEditable(final LogicalDatastoreType store) { + checkArgument(store == LogicalDatastoreType.CONFIGURATION, + "Can edit only configuration data, not %s", store); + } + + private synchronized ListenableFuture> performCommit( + final List> resultsFutures) { + resultsFutures.add(netconfOps.commit(new NetconfRpcFutureCallback("Commit", id))); + + final ListenableFuture> txResult = resultsToTxStatus(id, resultsFutures); + Futures.addCallback(txResult, new FutureCallback<>() { + @Override + public void onSuccess(final RpcResult result) { + unlock(); + } + + @Override + public void onFailure(final Throwable throwable) { + discardChanges(); + unlock(); + } + }, MoreExecutors.directExecutor()); + + return txResult; + } + + private static ListenableFuture> resultsToTxStatus( + final RemoteDeviceId id, List> resultsFutures) { + final SettableFuture> transformed = SettableFuture.create(); + + Futures.addCallback(Futures.allAsList(resultsFutures), new FutureCallback<>() { + @Override + public void onSuccess(final List domRpcResults) { + if (!transformed.isDone()) { + extractResult(domRpcResults, transformed, id); + } + } + + @Override + public void onFailure(final Throwable throwable) { + final NetconfDocumentedException exception = + new NetconfDocumentedException( + id + ":RPC during tx returned an exception" + throwable.getMessage(), + new Exception(throwable), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.OPERATION_FAILED, + DocumentedException.ErrorSeverity.ERROR); + transformed.setException(exception); + } + }, MoreExecutors.directExecutor()); + + return transformed; + } + + @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", + justification = "https://github.com/spotbugs/spotbugs/issues/811") + private static void extractResult(final List domRpcResults, + final SettableFuture> transformed, + final RemoteDeviceId id) { + DocumentedException.ErrorType errType = DocumentedException.ErrorType.APPLICATION; + DocumentedException.ErrorSeverity errSeverity = DocumentedException.ErrorSeverity.ERROR; + StringBuilder msgBuilder = new StringBuilder(); + boolean errorsEncouneterd = false; + String errorTag = "operation-failed"; + + for (final DOMRpcResult domRpcResult : domRpcResults) { + if (!domRpcResult.getErrors().isEmpty()) { + errorsEncouneterd = true; + final RpcError error = domRpcResult.getErrors().iterator().next(); + final RpcError.ErrorType errorType = error.getErrorType(); + switch (errorType) { + case RPC: + errType = DocumentedException.ErrorType.RPC; + break; + case PROTOCOL: + errType = DocumentedException.ErrorType.PROTOCOL; + break; + case TRANSPORT: + errType = DocumentedException.ErrorType.TRANSPORT; + break; + case APPLICATION: + default: + errType = DocumentedException.ErrorType.APPLICATION; + break; + } + final RpcError.ErrorSeverity severity = error.getSeverity(); + switch (severity) { + case WARNING: + errSeverity = DocumentedException.ErrorSeverity.WARNING; + break; + case ERROR: + default: + errSeverity = DocumentedException.ErrorSeverity.ERROR; + break; + } + msgBuilder.append(error.getMessage()); + msgBuilder.append(error.getInfo()); + errorTag = error.getTag(); + } + } + if (errorsEncouneterd) { + final NetconfDocumentedException exception = new NetconfDocumentedException(id + + ":RPC during tx failed. " + msgBuilder.toString(), + errType, + DocumentedException.ErrorTag.from(errorTag), + errSeverity); + transformed.setException(exception); + return; + } + transformed.set(RpcResultBuilder.success().build()); + } +} diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java index ab214400b6..9ed9d71efd 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java @@ -18,6 +18,7 @@ import org.opendaylight.mdsal.dom.api.DOMActionService; import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMNotification; import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences; @@ -77,12 +78,14 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice final EffectiveModelContext schemaContext = mountContext.getEffectiveModelContext(); final NetconfDeviceDataBroker netconfDeviceDataBroker = new NetconfDeviceDataBroker(id, mountContext, deviceRpc, netconfSessionPreferences); - registerLockListener(netconfDeviceDataBroker); + final NetconfDataTreeService netconfService = + new NetconfDataTreeServiceImpl(id, mountContext, deviceRpc, netconfSessionPreferences); + registerLockListener(netconfDeviceDataBroker, netconfService); final NetconfDeviceNotificationService notificationService = new NetconfDeviceNotificationService(); salProvider.getMountInstance() - .onTopologyDeviceConnected(schemaContext, netconfDeviceDataBroker, deviceRpc, notificationService, - deviceAction); + .onTopologyDeviceConnected(schemaContext, netconfDeviceDataBroker, netconfService, + deviceRpc, notificationService, deviceAction); salProvider.getTopologyDatastoreAdapter() .updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities()); } @@ -134,10 +137,11 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice } } - private void registerLockListener(final NetconfDeviceDataBroker netconfDeviceDataBroker) { + private void registerLockListener(final NetconfDeviceDataBroker netconfDeviceDataBroker, + final NetconfDataTreeService netconfDataTreeService) { listenerRegistration = dataBroker.registerDataTreeChangeListener( DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, createTopologyListPath()), - new LockChangeListener(netconfDeviceDataBroker)); + new LockChangeListener(netconfDeviceDataBroker, netconfDataTreeService)); } private InstanceIdentifier createTopologyListPath() { diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java index 5dd2418603..8c77933ac3 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java @@ -23,6 +23,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMNotification; import org.opendaylight.mdsal.dom.api.DOMNotificationService; import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId; import org.opendaylight.yangtools.concepts.ObjectRegistration; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; @@ -120,11 +121,11 @@ public class NetconfDeviceSalProvider implements AutoCloseable { public void onTopologyDeviceConnected(final EffectiveModelContext initialCtx, final DOMDataBroker broker, final DOMRpcService rpc, final NetconfDeviceNotificationService newNotificationService) { - onTopologyDeviceConnected(initialCtx, broker, rpc, newNotificationService, null); + onTopologyDeviceConnected(initialCtx, broker, null, rpc, newNotificationService, null); } public synchronized void onTopologyDeviceConnected(final EffectiveModelContext initialCtx, - final DOMDataBroker broker, final DOMRpcService rpc, + final DOMDataBroker broker, final NetconfDataTreeService netconfService, final DOMRpcService rpc, final NetconfDeviceNotificationService newNotificationService, final DOMActionService deviceAction) { requireNonNull(mountService, "Closed"); checkState(topologyRegistration == null, "Already initialized"); @@ -133,12 +134,17 @@ public class NetconfDeviceSalProvider implements AutoCloseable { mountService.createMountPoint(id.getTopologyPath()); mountBuilder.addInitialSchemaContext(initialCtx); - mountBuilder.addService(DOMDataBroker.class, broker); + if (broker != null) { + mountBuilder.addService(DOMDataBroker.class, broker); + } mountBuilder.addService(DOMRpcService.class, rpc); mountBuilder.addService(DOMNotificationService.class, newNotificationService); if (deviceAction != null) { mountBuilder.addService(DOMActionService.class, deviceAction); } + if (netconfService != null) { + mountBuilder.addService(NetconfDataTreeService.class, netconfService); + } this.notificationService = newNotificationService; topologyRegistration = mountBuilder.register(); diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/MountInstanceTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/MountInstanceTest.java index afeeb90c74..d2f661a17c 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/MountInstanceTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/MountInstanceTest.java @@ -25,6 +25,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMNotification; import org.opendaylight.mdsal.dom.api.DOMNotificationService; import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.IetfNetconfService; import org.opendaylight.yangtools.concepts.ObjectRegistration; @@ -43,6 +44,8 @@ public class MountInstanceTest { @Mock private DOMDataBroker broker; @Mock + private NetconfDataTreeService netconfService; + @Mock private DOMRpcService rpcService; @Mock private NetconfDeviceNotificationService notificationService; @@ -72,7 +75,7 @@ public class MountInstanceTest { @Test - public void testOnTopologyDeviceConnected() throws Exception { + public void testOnTopologyDeviceConnected() { mountInstance.onTopologyDeviceConnected(SCHEMA_CONTEXT, broker, rpcService, notificationService); verify(mountPointBuilder).addInitialSchemaContext(SCHEMA_CONTEXT); verify(mountPointBuilder).addService(DOMDataBroker.class, broker); @@ -80,6 +83,16 @@ public class MountInstanceTest { verify(mountPointBuilder).addService(DOMNotificationService.class, notificationService); } + @Test + public void testOnTopologyDeviceConnectedWithNetconfService() { + mountInstance.onTopologyDeviceConnected(SCHEMA_CONTEXT, null, netconfService, rpcService, + notificationService, null); + verify(mountPointBuilder).addInitialSchemaContext(SCHEMA_CONTEXT); + verify(mountPointBuilder).addService(NetconfDataTreeService.class, netconfService); + verify(mountPointBuilder).addService(DOMRpcService.class, rpcService); + verify(mountPointBuilder).addService(DOMNotificationService.class, notificationService); + } + @Test public void testOnTopologyDeviceDisconnected() throws Exception { mountInstance.onTopologyDeviceConnected(SCHEMA_CONTEXT, broker, rpcService, notificationService); diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDataTreeServiceImplTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDataTreeServiceImplTest.java new file mode 100644 index 0000000000..ca2e3b7c3a --- /dev/null +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDataTreeServiceImplTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.netconf.sal.connect.netconf.sal; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME; +import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME; +import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME; +import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME; +import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_LOCK_QNAME; +import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME; +import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath; + +import com.google.common.util.concurrent.ListenableFuture; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.opendaylight.binding.runtime.spi.BindingRuntimeHelpers; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult; +import org.opendaylight.netconf.api.NetconfMessage; +import org.opendaylight.netconf.sal.connect.netconf.AbstractTestModelTest; +import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences; +import org.opendaylight.netconf.sal.connect.netconf.sal.tx.TxTestUtils; +import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.NetconfMessageTransformer; +import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.IetfNetconfService; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState; +import org.opendaylight.yangtools.rcf8528.data.util.EmptyMountPointContext; +import org.opendaylight.yangtools.util.concurrent.FluentFutures; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class NetconfDataTreeServiceImplTest extends AbstractTestModelTest { + @Mock + private DOMRpcService rpcService; + private NetconfDataTreeServiceImpl netconService; + private NetconfMessageTransformer netconfMessageTransformer; + ArgumentCaptor captor = ArgumentCaptor.forClass(ContainerNode.class); + + @Before + public void setUp() { + doReturn(FluentFutures.immediateFluentFuture(new DefaultDOMRpcResult())).when(rpcService) + .invokeRpc(any(), any()); + netconService = getNetconService(); + final EffectiveModelContext model = BindingRuntimeHelpers.createEffectiveModel(IetfNetconfService.class, + NetconfState.class); + netconfMessageTransformer = new NetconfMessageTransformer(new EmptyMountPointContext(model), true, + BASE_SCHEMAS.getBaseSchema()); + } + + @Test + public void lock() { + netconService.lock(); + verify(rpcService).invokeRpc(eq(toPath(NETCONF_LOCK_QNAME)), any(ContainerNode.class)); + } + + @Test + public void unlock() { + netconService.unlock(); + verify(rpcService).invokeRpc(eq(toPath(NETCONF_UNLOCK_QNAME)), any(ContainerNode.class)); + } + + @Test + public void discardChanges() { + doReturn(FluentFutures.immediateFluentFuture(new DefaultDOMRpcResult())).when(rpcService) + .invokeRpc(any(SchemaPath.class), isNull()); + netconService.discardChanges(); + verify(rpcService).invokeRpc(eq(toPath(NETCONF_DISCARD_CHANGES_QNAME)), isNull()); + } + + @Test + public void get() { + netconService.get(null); + verify(rpcService).invokeRpc(eq(toPath(NETCONF_GET_QNAME)), any(ContainerNode.class)); + } + + @Test + public void getConfig() { + netconService.getConfig(null); + verify(rpcService).invokeRpc(eq(toPath(NETCONF_GET_CONFIG_QNAME)), any(ContainerNode.class)); + } + + @Test + public void merge() { + netconService.merge(LogicalDatastoreType.CONFIGURATION, TxTestUtils.getLeafId(), TxTestUtils.getLeafNode(), + Optional.empty()); + verify(rpcService).invokeRpc(eq(SchemaPath.create(true, NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME)), + captor.capture()); + + final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest( + toPath(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), captor.getValue()); + Assert.assertTrue(netconfMessage.toString().contains("operation=\"merge\"")); + } + + @Test + public void replace() { + netconService.replace(LogicalDatastoreType.CONFIGURATION, TxTestUtils.getLeafId(), TxTestUtils.getLeafNode(), + Optional.empty()); + verify(rpcService).invokeRpc( + eq(SchemaPath.create(true, NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME)), captor.capture()); + + final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest( + toPath(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), captor.getValue()); + Assert.assertTrue(netconfMessage.toString().contains("operation=\"replace\"")); + } + + @Test + public void create() { + netconService.create(LogicalDatastoreType.CONFIGURATION, TxTestUtils.getLeafId(), TxTestUtils.getLeafNode(), + Optional.empty()); + verify(rpcService).invokeRpc( + eq(SchemaPath.create(true, NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME)), captor.capture()); + + final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest( + toPath(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), captor.getValue()); + Assert.assertTrue(netconfMessage.toString().contains("operation=\"create\"")); + } + + @Test + public void delete() { + netconService.delete(LogicalDatastoreType.CONFIGURATION, TxTestUtils.getLeafId().getParent()); + verify(rpcService).invokeRpc( + eq(SchemaPath.create(true, NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME)), captor.capture()); + + final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest( + toPath(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), captor.getValue()); + Assert.assertTrue(netconfMessage.toString().contains("operation=\"delete\"")); + } + + @Test + public void remove() { + netconService.remove(LogicalDatastoreType.CONFIGURATION, TxTestUtils.getLeafId().getParent()); + verify(rpcService).invokeRpc( + eq(SchemaPath.create(true, NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME)), captor.capture()); + + final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest( + toPath(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), captor.getValue()); + Assert.assertTrue(netconfMessage.toString().contains("operation=\"remove\"")); + } + + @Test + public void commit() { + List> resultsFutures = new ArrayList<>(); + netconService.commit(resultsFutures); + verify(rpcService).invokeRpc(eq(toPath(NETCONF_COMMIT_QNAME)), any(ContainerNode.class)); + } + + private NetconfDataTreeServiceImpl getNetconService() { + NetconfSessionPreferences prefs = NetconfSessionPreferences.fromStrings( + Collections.singletonList(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString())); + final RemoteDeviceId id = + new RemoteDeviceId("device-1", InetSocketAddress.createUnresolved("localhost", 17830)); + return new NetconfDataTreeServiceImpl(id, new EmptyMountPointContext(SCHEMA_CONTEXT), rpcService, prefs); + } +} \ No newline at end of file diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java index d2e2bec004..3ba4650718 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java @@ -28,6 +28,7 @@ import org.opendaylight.mdsal.binding.api.DataBroker; import org.opendaylight.mdsal.dom.api.DOMDataBroker; import org.opendaylight.mdsal.dom.api.DOMNotification; import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil; @@ -100,8 +101,8 @@ public class NetconfDeviceSalFacadeTest { null); verify(mountInstance, times(1)).onTopologyDeviceConnected(eq(schemaContext), - any(DOMDataBroker.class), eq(deviceRpc), any(NetconfDeviceNotificationService.class), - isNull()); + any(DOMDataBroker.class), any(NetconfDataTreeService.class), eq(deviceRpc), + any(NetconfDeviceNotificationService.class), isNull()); verify(netconfDeviceTopologyAdapter, times(1)).updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities()); } diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxTestUtils.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxTestUtils.java index 5e53287cde..498812a46a 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxTestUtils.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxTestUtils.java @@ -14,7 +14,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; -final class TxTestUtils { +public final class TxTestUtils { private static final QName Q_NAME_1 = QName.create("test:namespace", "2013-07-22", "c"); private static final QName Q_NAME_2 = QName.create(Q_NAME_1, "a"); @@ -29,7 +29,7 @@ final class TxTestUtils { .build(); } - static YangInstanceIdentifier getLeafId() { + public static YangInstanceIdentifier getLeafId() { return YangInstanceIdentifier.builder() .node(Q_NAME_1) .node(Q_NAME_2) @@ -42,11 +42,10 @@ final class TxTestUtils { .build(); } - static LeafNode getLeafNode() { + public static LeafNode getLeafNode() { return Builders.leafBuilder() .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(Q_NAME_2)) .withValue("data") .build(); } - } diff --git a/restconf/restconf-nb-rfc8040/pom.xml b/restconf/restconf-nb-rfc8040/pom.xml index 3fc2d9cf60..a2a188a53d 100644 --- a/restconf/restconf-nb-rfc8040/pom.xml +++ b/restconf/restconf-nb-rfc8040/pom.xml @@ -54,6 +54,10 @@ org.opendaylight.netconf netconf-api + + org.opendaylight.netconf + netconf-dom-api + org.opendaylight.yangtools diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeJsonBodyWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeJsonBodyWriter.java index 67fea4db2f..ec3df98aea 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeJsonBodyWriter.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeJsonBodyWriter.java @@ -17,6 +17,7 @@ import java.lang.reflect.Type; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import java.util.Set; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; @@ -95,6 +96,12 @@ public class NormalizedNodeJsonBodyWriter implements MessageBodyWriter entry : context.getNewHeaders().entrySet()) { + httpHeaders.add(entry.getKey(), entry.getValue()); + } + } } private static void writeNormalizedNode(final JsonWriter jsonWriter, diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeXmlBodyWriter.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeXmlBodyWriter.java index 9d849ec565..a89caa90c9 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeXmlBodyWriter.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/NormalizedNodeXmlBodyWriter.java @@ -14,6 +14,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import java.util.Set; import javanet.staxutils.IndentingXMLStreamWriter; import javax.ws.rs.Produces; @@ -84,6 +85,11 @@ public class NormalizedNodeXmlBodyWriter implements MessageBodyWriter entry : context.getNewHeaders().entrySet()) { + httpHeaders.add(entry.getKey(), entry.getValue()); + } + } XMLStreamWriter xmlWriter; try { diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java index 4bb24bc7d6..a9edb4a328 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java @@ -24,16 +24,18 @@ import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map.Entry; import java.util.Optional; +import java.util.concurrent.ExecutionException; import javax.ws.rs.Path; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMActionResult; import org.opendaylight.mdsal.dom.api.DOMDataBroker; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.context.WriterParameters; @@ -43,13 +45,16 @@ import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; import org.opendaylight.restconf.common.errors.RestconfError.ErrorType; import org.opendaylight.restconf.common.patch.PatchContext; import org.opendaylight.restconf.common.patch.PatchStatusContext; +import org.opendaylight.restconf.nb.rfc8040.Rfc8040; import org.opendaylight.restconf.nb.rfc8040.handlers.ActionServiceHandler; import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler; import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfDataService; import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.restconf.nb.rfc8040.rests.utils.DeleteDataTransactionUtil; import org.opendaylight.restconf.nb.rfc8040.rests.utils.PatchDataTransactionUtil; import org.opendaylight.restconf.nb.rfc8040.rests.utils.PlainPatchDataTransactionUtil; @@ -60,6 +65,7 @@ import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConst import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfInvokeOperationsUtil; import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter; import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil; +import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier; import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType; import org.opendaylight.yangtools.concepts.Immutable; @@ -142,10 +148,9 @@ public class RestconfDataServiceImpl implements RestconfDataService { final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(instanceIdentifier, uriInfo); final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint(); - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - instanceIdentifier, mountPoint, getTransactionChainHandler(mountPoint)); + final RestconfStrategy strategy = getRestconfStrategy(instanceIdentifier, mountPoint); final NormalizedNode node = readData(identifier, parameters.getContent(), - transactionNode, parameters.getWithDefault(), schemaContextRef, uriInfo); + strategy, parameters.getWithDefault(), schemaContextRef, uriInfo); if (identifier != null && identifier.contains(STREAM_PATH) && identifier.contains(STREAM_ACCESS_PATH_PART) && identifier.contains(STREAM_LOCATION_PATH_PART)) { final String value = (String) node.getValue(); @@ -173,62 +178,83 @@ public class RestconfDataServiceImpl implements RestconfDataService { return Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)).build(); } - /** * Read specific type of data from data store via transaction and if identifier read data from * streams then put streams from actual schema context to datastore. * - * @param identifier - * identifier of data to read - * @param content - * type of data to read (config, state, all) - * @param transactionNode - * {@link TransactionVarsWrapper} - wrapper for variables - * @param withDefa - * vaule of with-defaults parameter - * @param schemaContext - * schema context - * @param uriInfo - * uri info + * @param identifier identifier of data to read + * @param content type of data to read (config, state, all) + * @param strategy {@link RestconfStrategy} - object that perform the actual DS operations + * @param withDefa vaule of with-defaults parameter + * @param schemaContext schema context + * @param uriInfo uri info * @return {@link NormalizedNode} */ - private static NormalizedNode readData(final String identifier, final String content, - final TransactionVarsWrapper transactionNode, final String withDefa, + public static NormalizedNode readData(final String identifier, final String content, + final RestconfStrategy strategy, final String withDefa, final EffectiveModelContext schemaContext, final UriInfo uriInfo) { if (identifier != null && identifier.contains(STREAMS_PATH) && !identifier.contains(STREAM_PATH_PART)) { - createAllYangNotificationStreams(transactionNode, schemaContext, uriInfo); + createAllYangNotificationStreams(strategy, schemaContext, uriInfo); } - return ReadDataTransactionUtil.readData(content, transactionNode, withDefa, schemaContext); + return ReadDataTransactionUtil.readData(content, strategy, withDefa, schemaContext); } - private static void createAllYangNotificationStreams(final TransactionVarsWrapper transactionNode, - final EffectiveModelContext schemaContext, final UriInfo uriInfo) { - final DOMDataTreeReadWriteTransaction wTx = transactionNode.getTransactionChain().newReadWriteTransaction(); - final boolean exist = SubscribeToStreamUtil.checkExist(schemaContext, wTx); + private static void createAllYangNotificationStreams(final RestconfStrategy strategy, + final EffectiveModelContext schemaContext, + final UriInfo uriInfo) { + strategy.prepareReadWriteExecution(); + final boolean exist = checkExist(schemaContext, strategy); for (final NotificationDefinition notificationDefinition : schemaContext.getNotifications()) { final NotificationListenerAdapter notifiStreamXML = - CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext, - NotificationOutputType.XML); + CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext, + NotificationOutputType.XML); final NotificationListenerAdapter notifiStreamJSON = - CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext, - NotificationOutputType.JSON); - writeNotificationStreamToDatastore(schemaContext, uriInfo, wTx, exist, notifiStreamXML); - writeNotificationStreamToDatastore(schemaContext, uriInfo, wTx, exist, notifiStreamJSON); + CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext, + NotificationOutputType.JSON); + writeNotificationStreamToDatastore(schemaContext, uriInfo, strategy, exist, notifiStreamXML); + writeNotificationStreamToDatastore(schemaContext, uriInfo, strategy, exist, notifiStreamJSON); + } + try { + strategy.commit().get(); + } catch (final InterruptedException | ExecutionException e) { + throw new RestconfDocumentedException("Problem while putting data to DS.", e); } - SubscribeToStreamUtil.submitData(wTx); } private static void writeNotificationStreamToDatastore(final EffectiveModelContext schemaContext, - final UriInfo uriInfo, final DOMDataTreeReadWriteTransaction readWriteTransaction, final boolean exist, - final NotificationListenerAdapter listener) { + final UriInfo uriInfo, final RestconfStrategy strategy, + final boolean exist, + final NotificationListenerAdapter listener) { final URI uri = SubscribeToStreamUtil.prepareUriByStreamName(uriInfo, listener.getStreamName()); final NormalizedNode mapToStreams = - RestconfMappingNodeUtil.mapYangNotificationStreamByIetfRestconfMonitoring( - listener.getSchemaPath().getLastComponent(), schemaContext.getNotifications(), null, - listener.getOutputType(), uri, SubscribeToStreamUtil.getMonitoringModule(schemaContext), exist); - SubscribeToStreamUtil.writeDataToDS(schemaContext, - listener.getSchemaPath().getLastComponent().getLocalName(), readWriteTransaction, exist, mapToStreams); + RestconfMappingNodeUtil.mapYangNotificationStreamByIetfRestconfMonitoring( + listener.getSchemaPath().getLastComponent(), schemaContext.getNotifications(), null, + listener.getOutputType(), uri, SubscribeToStreamUtil.getMonitoringModule(schemaContext), exist); + writeDataToDS(schemaContext, + listener.getSchemaPath().getLastComponent().getLocalName(), strategy, exist, mapToStreams); + } + + private static boolean checkExist(final EffectiveModelContext schemaContext, final RestconfStrategy strategy) { + try { + return strategy.exists(LogicalDatastoreType.OPERATIONAL, + IdentifierCodec.deserialize(Rfc8040.MonitoringModule.PATH_TO_STREAMS, schemaContext)).get(); + } catch (final InterruptedException | ExecutionException exception) { + throw new RestconfDocumentedException("Problem while checking data if exists", exception); + } + } + + private static void writeDataToDS(final EffectiveModelContext schemaContext, final String name, + final RestconfStrategy strategy, final boolean exist, + final NormalizedNode mapToStreams) { + String pathId; + if (exist) { + pathId = Rfc8040.MonitoringModule.PATH_TO_STREAM_WITHOUT_KEY + name; + } else { + pathId = Rfc8040.MonitoringModule.PATH_TO_STREAMS; + } + strategy.merge(LogicalDatastoreType.OPERATIONAL, + IdentifierCodec.deserialize(pathId, schemaContext), mapToStreams); } @Override @@ -245,19 +271,12 @@ public class RestconfDataServiceImpl implements RestconfDataService { PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload); final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); - final TransactionChainHandler localTransactionChainHandler; - final EffectiveModelContext ref; - if (mountPoint == null) { - localTransactionChainHandler = this.transactionChainHandler; - ref = this.schemaContextHandler.get(); - } else { - localTransactionChainHandler = transactionChainOfMountPoint(mountPoint); - ref = mountPoint.getEffectiveModelContext(); - } + final EffectiveModelContext ref = mountPoint == null + ? this.schemaContextHandler.get() + : mountPoint.getEffectiveModelContext(); - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler); - return PutDataTransactionUtil.putData(payload, ref, transactionNode, checkedParms.insert, checkedParms.point); + final RestconfStrategy strategy = getRestconfStrategy(payload.getInstanceIdentifierContext(), mountPoint); + return PutDataTransactionUtil.putData(payload, ref, strategy, checkedParms.insert, checkedParms.point); } private static QueryParams checkQueryParameters(final UriInfo uriInfo) { @@ -321,11 +340,10 @@ public class RestconfDataServiceImpl implements RestconfDataService { } final QueryParams checkedParms = checkQueryParameters(uriInfo); - final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - payload.getInstanceIdentifierContext(), mountPoint, getTransactionChainHandler(mountPoint)); - return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode, + final RestconfStrategy strategy = getRestconfStrategy(payload.getInstanceIdentifierContext(), + payload.getInstanceIdentifierContext().getMountPoint()); + return PostDataTransactionUtil.postData(uriInfo, payload, strategy, getSchemaContext(mountPoint), checkedParms.insert, checkedParms.point); } @@ -335,16 +353,8 @@ public class RestconfDataServiceImpl implements RestconfDataService { identifier, this.schemaContextHandler.get(), Optional.of(this.mountPointServiceHandler.get())); final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint(); - final TransactionChainHandler localTransactionChainHandler; - if (mountPoint == null) { - localTransactionChainHandler = this.transactionChainHandler; - } else { - localTransactionChainHandler = transactionChainOfMountPoint(mountPoint); - } - - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint, - localTransactionChainHandler); - return DeleteDataTransactionUtil.deleteData(transactionNode); + final RestconfStrategy strategy = getRestconfStrategy(instanceIdentifier, mountPoint); + return DeleteDataTransactionUtil.deleteData(strategy); } @Override @@ -355,9 +365,8 @@ public class RestconfDataServiceImpl implements RestconfDataService { @Override public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) { final DOMMountPoint mountPoint = requireNonNull(context).getInstanceIdentifierContext().getMountPoint(); - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - context.getInstanceIdentifierContext(), mountPoint, getTransactionChainHandler(mountPoint)); - return PatchDataTransactionUtil.patchData(context, transactionNode, getSchemaContext(mountPoint)); + final RestconfStrategy strategy = getRestconfStrategy(context.getInstanceIdentifierContext(), mountPoint); + return PatchDataTransactionUtil.patchData(context, strategy, getSchemaContext(mountPoint)); } @Override @@ -372,34 +381,35 @@ public class RestconfDataServiceImpl implements RestconfDataService { PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload); final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); - final TransactionChainHandler localTransactionChainHandler; - final EffectiveModelContext ref; - if (mountPoint == null) { - localTransactionChainHandler = this.transactionChainHandler; - ref = this.schemaContextHandler.get(); - } else { - localTransactionChainHandler = transactionChainOfMountPoint(mountPoint); - ref = mountPoint.getEffectiveModelContext(); - } - - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler); - - return PlainPatchDataTransactionUtil.patchData(payload, transactionNode, ref); - } + final EffectiveModelContext ref = mountPoint == null + ? this.schemaContextHandler.get() + : mountPoint.getEffectiveModelContext(); + final RestconfStrategy strategy = getRestconfStrategy(payload.getInstanceIdentifierContext(), mountPoint); - private TransactionChainHandler getTransactionChainHandler(final DOMMountPoint mountPoint) { - return mountPoint == null ? transactionChainHandler : transactionChainOfMountPoint(mountPoint); + return PlainPatchDataTransactionUtil.patchData(payload, strategy, ref); } private EffectiveModelContext getSchemaContext(final DOMMountPoint mountPoint) { return mountPoint == null ? schemaContextHandler.get() : mountPoint.getEffectiveModelContext(); } + public synchronized RestconfStrategy getRestconfStrategy(final InstanceIdentifierContext instanceIdentifier, + final DOMMountPoint mountPoint) { + if (mountPoint != null) { + final Optional service = mountPoint.getService(NetconfDataTreeService.class); + if (service.isPresent()) { + return new NetconfRestconfStrategy(service.get(), instanceIdentifier); + } + } + final TransactionChainHandler transactionChain = mountPoint == null + ? transactionChainHandler : transactionChainOfMountPoint(mountPoint); + return new MdsalRestconfStrategy(instanceIdentifier, transactionChain); + } + /** * Prepare transaction chain to access data of mount point. - * @param mountPoint - * mount point reference + * + * @param mountPoint mount point reference * @return {@link TransactionChainHandler} */ private static TransactionChainHandler transactionChainOfMountPoint(final @NonNull DOMMountPoint mountPoint) { @@ -416,10 +426,8 @@ public class RestconfDataServiceImpl implements RestconfDataService { /** * Invoke Action operation. * - * @param payload - * {@link NormalizedNodeContext} - the body of the operation - * @param uriInfo - * URI info + * @param payload {@link NormalizedNodeContext} - the body of the operation + * @param uriInfo URI info * @return {@link NormalizedNodeContext} wrapped in {@link Response} */ public Response invokeAction(final NormalizedNodeContext payload, final UriInfo uriInfo) { diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/SubscribeToStreamUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/SubscribeToStreamUtil.java index 9eab73c316..b689753925 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/SubscribeToStreamUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/SubscribeToStreamUtil.java @@ -328,7 +328,6 @@ final class SubscribeToStreamUtil { static boolean checkExist(final SchemaContext schemaContext, final DOMDataTreeReadOperations readWriteTransaction) { - boolean exist; try { return readWriteTransaction.exists(LogicalDatastoreType.OPERATIONAL, IdentifierCodec.deserialize(MonitoringModule.PATH_TO_STREAMS, schemaContext)).get(); diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.java new file mode 100644 index 0000000000..f05f675d2e --- /dev/null +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategy.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.restconf.nb.rfc8040.rests.transactions; + +import static java.util.Objects.requireNonNull; + +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Optional; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Strategy that allow to communicate with netconf devices in terms of md-sal transactions. + * + * @see DOMTransactionChain + * @see DOMDataTreeReadWriteTransaction + */ +public class MdsalRestconfStrategy implements RestconfStrategy { + private final InstanceIdentifierContext instanceIdentifier; + private final DOMTransactionChain transactionChain; + private DOMDataTreeReadWriteTransaction rwTx; + private final TransactionChainHandler transactionChainHandler; + + public MdsalRestconfStrategy(final InstanceIdentifierContext instanceIdentifier, + final TransactionChainHandler transactionChainHandler) { + this.instanceIdentifier = requireNonNull(instanceIdentifier); + this.transactionChainHandler = requireNonNull(transactionChainHandler); + transactionChain = transactionChainHandler.get(); + } + + @Override + public void prepareReadWriteExecution() { + rwTx = transactionChain.newReadWriteTransaction(); + } + + @Override + public void cancel() { + if (rwTx != null) { + rwTx.cancel(); + } + transactionChain.close(); + } + + @Override + public ListenableFuture>> read(final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + try (DOMDataTreeReadTransaction tx = transactionChain.newReadOnlyTransaction()) { + return tx.read(store, path); + } + } + + @Override + public FluentFuture exists(LogicalDatastoreType store, YangInstanceIdentifier path) { + return rwTx.exists(store, path); + } + + @Override + public void delete(LogicalDatastoreType store, final YangInstanceIdentifier path) { + rwTx.delete(store, path); + } + + @Override + public void merge(LogicalDatastoreType store, YangInstanceIdentifier path, NormalizedNode data) { + rwTx.merge(store, path, data); + } + + @Override + public void create(LogicalDatastoreType store, YangInstanceIdentifier path, NormalizedNode data) { + rwTx.put(store, path, data); + } + + @Override + public void replace(LogicalDatastoreType store, YangInstanceIdentifier path, NormalizedNode data) { + create(store, path, data); + } + + @Override + public FluentFuture commit() { + return rwTx.commit(); + } + + @Override + public DOMTransactionChain getTransactionChain() { + return transactionChain; + } + + @Override + public InstanceIdentifierContext getInstanceIdentifier() { + return instanceIdentifier; + } + + @Override + public TransactionChainHandler getTransactionChainHandler() { + return transactionChainHandler; + } + + @Override + public RestconfStrategy buildStrategy(final InstanceIdentifierContext instanceIdentifierContext) { + return new MdsalRestconfStrategy(instanceIdentifierContext, this.transactionChainHandler); + } +} diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java new file mode 100644 index 0000000000..7231706f50 --- /dev/null +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/NetconfRestconfStrategy.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.restconf.nb.rfc8040.rests.transactions; + +import static java.util.Objects.requireNonNull; + +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import java.util.List; +import java.util.Optional; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.common.api.ReadFailedException; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Strategy that allow to communicate with netconf devices using pure netconf operations. + */ +public class NetconfRestconfStrategy implements RestconfStrategy { + private static final Logger LOG = LoggerFactory.getLogger(NetconfRestconfStrategy.class); + + private final NetconfDataTreeService netconfService; + private final InstanceIdentifierContext instanceIdentifier; + + private List> resultsFutures; + + public NetconfRestconfStrategy(final NetconfDataTreeService netconfService, + final InstanceIdentifierContext instanceIdentifier) { + this.netconfService = requireNonNull(netconfService); + this.instanceIdentifier = requireNonNull(instanceIdentifier); + } + + @Override + public void prepareReadWriteExecution() { + resultsFutures = netconfService.lock(); + } + + @Override + public void cancel() { + netconfService.discardChanges(); + netconfService.unlock(); + } + + @Override + public ListenableFuture>> read(final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + switch (store) { + case CONFIGURATION: + return netconfService.getConfig(path); + case OPERATIONAL: + return netconfService.get(path); + default: + LOG.info("Unknown datastore type: {}.", store); + throw new IllegalArgumentException(String.format( + "%s, Cannot read data %s for %s datastore, unknown datastore type", + netconfService.getDeviceId(), path, store)); + } + } + + @Override + public FluentFuture exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) { + return remapException(read(store, path)) + .transform(optionalNode -> optionalNode != null && optionalNode.isPresent(), + MoreExecutors.directExecutor()); + } + + @Override + public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) { + resultsFutures.add(netconfService.delete(store, path)); + } + + @Override + public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, + final NormalizedNode data) { + resultsFutures.add(netconfService.merge(store, path, data, Optional.empty())); + } + + @Override + public void create(final LogicalDatastoreType store, final YangInstanceIdentifier path, + final NormalizedNode data) { + resultsFutures.add(netconfService.create(store, path, data, Optional.empty())); + } + + @Override + public void replace(final LogicalDatastoreType store, final YangInstanceIdentifier path, + final NormalizedNode data) { + resultsFutures.add(netconfService.replace(store, path, data, Optional.empty())); + } + + @Override + public FluentFuture commit() { + return FluentFuture.from(netconfService.commit(resultsFutures)); + } + + /** + * As we are not using any transactions here, always return null. + */ + @Override + public DOMTransactionChain getTransactionChain() { + return null; + } + + @Override + public InstanceIdentifierContext getInstanceIdentifier() { + return instanceIdentifier; + } + + /** + * As we are not using any transactions here, always return null. + */ + @Override + public TransactionChainHandler getTransactionChainHandler() { + return null; + } + + @Override + public RestconfStrategy buildStrategy(final InstanceIdentifierContext instanceIdentifierContext) { + return new NetconfRestconfStrategy(this.netconfService, instanceIdentifierContext); + } + + private static FluentFuture remapException(final ListenableFuture input) { + final SettableFuture ret = SettableFuture.create(); + Futures.addCallback(input, new FutureCallback() { + + @Override + public void onSuccess(final T result) { + ret.set(result); + } + + @Override + public void onFailure(final Throwable cause) { + ret.setException(cause instanceof ReadFailedException ? cause + : new ReadFailedException("NETCONF operation failed", cause)); + } + }, MoreExecutors.directExecutor()); + return FluentFuture.from(ret); + } +} diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java new file mode 100644 index 0000000000..71732d1d73 --- /dev/null +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.restconf.nb.rfc8040.rests.transactions; + +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Optional; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * This interface allows interact with netconf operations in different ways. + * + * @see NetconfRestconfStrategy + * @see MdsalRestconfStrategy + */ +public interface RestconfStrategy { + + /** + * Lock the entire datastore. + */ + void prepareReadWriteExecution(); + + /** + * Rollback changes and unlock the datastore. + */ + void cancel(); + + /** + * Read data from the datastore. + * + * @param store the logical data store which should be modified + * @param path the data object path + * @return a ListenableFuture containing the result of the read + */ + ListenableFuture>> read(LogicalDatastoreType store, YangInstanceIdentifier path); + + /** + * Check if data already exists in the datastore. + * + * @param store the logical data store which should be modified + * @param path the data object path + * @return a FluentFuture containing the result of the check + */ + FluentFuture exists(LogicalDatastoreType store, YangInstanceIdentifier path); + + /** + * Delete data from the datastore. + * + * @param store the logical data store which should be modified + * @param path the data object path + */ + void delete(LogicalDatastoreType store, YangInstanceIdentifier path); + + /** + * Merges a piece of data with the existing data at a specified path. + * + * @param store the logical data store which should be modified + * @param path the data object path + * @param data the data object to be merged to the specified path + */ + void merge(LogicalDatastoreType store, YangInstanceIdentifier path, NormalizedNode data); + + /** + * Stores a piece of data at the specified path. + * + * @param store the logical data store which should be modified + * @param path the data object path + * @param data the data object to be merged to the specified path + */ + void create(LogicalDatastoreType store, YangInstanceIdentifier path, NormalizedNode data); + + /** + * Replace a piece of data at the specified path. + * + * @param store the logical data store which should be modified + * @param path the data object path + * @param data the data object to be merged to the specified path + */ + void replace(LogicalDatastoreType store, YangInstanceIdentifier path, NormalizedNode data); + + /** + * Confirm previous operations. + * + * @return a FluentFuture containing the result of the commit information + */ + FluentFuture commit(); + + /** + * Get transaction chain for creating specific transaction for specific operation. + * + * @return transaction chain or null + */ + @Nullable DOMTransactionChain getTransactionChain(); + + /** + * Get instance identifier of data. + * + * @return {@link InstanceIdentifierContext} + */ + InstanceIdentifierContext getInstanceIdentifier(); + + /** + * Get transaction chain handler for creating new transaction chain. + * + * @return {@link TransactionChainHandler} or null + */ + @Nullable TransactionChainHandler getTransactionChainHandler(); + + /** + * Create a new and same type strategy for communication with netconf interface with + * a new {@link InstanceIdentifierContext}. + * + * @return {@link RestconfStrategy} + */ + RestconfStrategy buildStrategy(InstanceIdentifierContext instanceIdentifierContext); +} diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/TransactionVarsWrapper.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/TransactionVarsWrapper.java deleted file mode 100644 index 706879f437..0000000000 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/TransactionVarsWrapper.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2016 Cisco Systems, Inc. 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.restconf.nb.rfc8040.rests.transactions; - -import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DOMMountPoint; -import org.opendaylight.mdsal.dom.api.DOMTransactionChain; -import org.opendaylight.restconf.common.context.InstanceIdentifierContext; -import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; - -/** - * This class represent delegation wrapper for transaction variables. - * - */ -public final class TransactionVarsWrapper { - - private final InstanceIdentifierContext instanceIdentifier; - private final DOMMountPoint mountPoint; - private LogicalDatastoreType configuration = null; - private final DOMTransactionChain transactionChain; - private final TransactionChainHandler transactionChainHandler; - - /** - * Set base type of variables, which ones we need for transaction. - * {@link LogicalDatastoreType} is default set to null (to read all data - * from DS - config + state). - * - * @param instanceIdentifier - * {@link InstanceIdentifierContext} of data for transaction - * @param mountPoint - * mount point if is present - * @param transactionChainHandler - * used to obtain the transaction chain for creating specific type of transaction - * in specific operation - */ - public TransactionVarsWrapper(final InstanceIdentifierContext instanceIdentifier, final DOMMountPoint mountPoint, - final TransactionChainHandler transactionChainHandler) { - this.instanceIdentifier = instanceIdentifier; - this.mountPoint = mountPoint; - this.transactionChainHandler = transactionChainHandler; - transactionChain = transactionChainHandler.get(); - } - - /** - * Get instance identifier of data. - * - * @return {@link InstanceIdentifierContext} - */ - public InstanceIdentifierContext getInstanceIdentifier() { - return this.instanceIdentifier; - } - - /** - * Get mount point. - * - * @return {@link DOMMountPoint} - */ - public DOMMountPoint getMountPoint() { - return this.mountPoint; - } - - /** - * Set {@link LogicalDatastoreType} of data for transaction. - * - * @param datastoreType - * {@link LogicalDatastoreType} - */ - public void setLogicalDatastoreType(final LogicalDatastoreType datastoreType) { - this.configuration = datastoreType; - } - - /** - * Get type of data. - * - * @return {@link LogicalDatastoreType} - */ - public LogicalDatastoreType getLogicalDatastoreType() { - return this.configuration; - } - - /** - * Get transaction chain for creating specific transaction for specific - * operation. - * - * @return transaction chain - */ - public DOMTransactionChain getTransactionChain() { - return this.transactionChain; - } - - public TransactionChainHandler getTransactionChainHandler() { - return transactionChainHandler; - } -} diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java index 201166185b..9544a07f3a 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java @@ -12,14 +12,11 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; -import org.opendaylight.mdsal.dom.api.DOMTransactionChain; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; /** * Util class for delete specific data in config DS. - * */ public final class DeleteDataTransactionUtil { @@ -30,36 +27,20 @@ public final class DeleteDataTransactionUtil { /** * Delete data from DS via transaction. * - * @param transactionNode - * Wrapper for data of transaction + * @param strategy object that perform the actual DS operations * @return {@link Response} */ - public static Response deleteData(final TransactionVarsWrapper transactionNode) { - final DOMTransactionChain transactionChain = transactionNode.getTransactionChainHandler().get(); - final FluentFuture future = submitData(transactionChain, - transactionNode.getInstanceIdentifier().getInstanceIdentifier()); + public static Response deleteData(final RestconfStrategy strategy) { + strategy.prepareReadWriteExecution(); + final YangInstanceIdentifier path = strategy.getInstanceIdentifier().getInstanceIdentifier(); + TransactionUtil.checkItemExists(strategy, LogicalDatastoreType.CONFIGURATION, path, + RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE); + strategy.delete(LogicalDatastoreType.CONFIGURATION, path); + final FluentFuture future = strategy.commit(); final ResponseFactory response = new ResponseFactory(Status.NO_CONTENT); - //This method will close transactionChain + //This method will close transactionChain if any FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE, response, - transactionChain); + strategy.getTransactionChain()); return response.build(); } - - /** - * Delete data via transaction. Return error if data to delete does not exist. - * - * @param transactionChain - * transaction chain - * @param path - * path of data to delete - * @return {@link FluentFuture} - */ - private static FluentFuture submitData( - final DOMTransactionChain transactionChain, final YangInstanceIdentifier path) { - final DOMDataTreeReadWriteTransaction readWriteTx = transactionChain.newReadWriteTransaction(); - TransactionUtil.checkItemExists(transactionChain, readWriteTx, LogicalDatastoreType.CONFIGURATION, path, - RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE); - readWriteTx.delete(LogicalDatastoreType.CONFIGURATION, path); - return readWriteTx.commit(); - } } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java index ebfb9964b6..e047dab994 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java @@ -15,9 +15,6 @@ import java.util.List; import javax.ws.rs.core.Response.Status; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; -import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; @@ -27,7 +24,7 @@ import org.opendaylight.restconf.common.patch.PatchContext; import org.opendaylight.restconf.common.patch.PatchEntity; import org.opendaylight.restconf.common.patch.PatchStatusContext; import org.opendaylight.restconf.common.patch.PatchStatusEntity; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PatchData; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; @@ -46,19 +43,19 @@ public final class PatchDataTransactionUtil { } /** - * Process edit operations of one {@link PatchContext}. Close {@link DOMTransactionChain} inside of object - * {@link TransactionVarsWrapper} provided as a parameter. - * @param context Patch context to be processed - * @param transactionNode Wrapper for transaction + * Process edit operations of one {@link PatchContext}. Close {@link DOMTransactionChain} if any inside of object + * {@link RestconfStrategy} provided as a parameter. + * + * @param context Patch context to be processed + * @param strategy object that perform the actual DS operations * @param schemaContext Global schema context * @return {@link PatchStatusContext} */ - public static PatchStatusContext patchData(final PatchContext context, final TransactionVarsWrapper transactionNode, + public static PatchStatusContext patchData(final PatchContext context, final RestconfStrategy strategy, final EffectiveModelContext schemaContext) { final List editCollection = new ArrayList<>(); boolean noError = true; - final DOMTransactionChain transactionChain = transactionNode.getTransactionChain(); - final DOMDataTreeReadWriteTransaction tx = transactionChain.newReadWriteTransaction(); + strategy.prepareReadWriteExecution(); for (final PatchEntity patchEntity : context.getData()) { if (noError) { @@ -66,7 +63,7 @@ public final class PatchDataTransactionUtil { case CREATE: try { createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, - patchEntity.getTargetNode(), patchEntity.getNode(), tx, schemaContext); + patchEntity.getTargetNode(), patchEntity.getNode(), strategy, schemaContext); editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null)); } catch (final RestconfDocumentedException e) { editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), @@ -77,7 +74,7 @@ public final class PatchDataTransactionUtil { case DELETE: try { deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(), - tx); + strategy); editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null)); } catch (final RestconfDocumentedException e) { editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), @@ -88,7 +85,7 @@ public final class PatchDataTransactionUtil { case MERGE: try { mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, - patchEntity.getTargetNode(), patchEntity.getNode(), tx, schemaContext); + patchEntity.getTargetNode(), patchEntity.getNode(), strategy, schemaContext); editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null)); } catch (final RestconfDocumentedException e) { editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), @@ -99,7 +96,7 @@ public final class PatchDataTransactionUtil { case REPLACE: try { replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, - patchEntity.getTargetNode(), patchEntity.getNode(), schemaContext, tx); + patchEntity.getTargetNode(), patchEntity.getNode(), schemaContext, strategy); editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null)); } catch (final RestconfDocumentedException e) { editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), @@ -110,7 +107,7 @@ public final class PatchDataTransactionUtil { case REMOVE: try { removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(), - tx); + strategy); editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null)); } catch (final RestconfDocumentedException e) { editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), @@ -133,21 +130,21 @@ public final class PatchDataTransactionUtil { // if no errors then submit transaction, otherwise cancel if (noError) { final ResponseFactory response = new ResponseFactory(Status.OK); - final FluentFuture future = tx.commit(); + final FluentFuture future = strategy.commit(); try { - //This method will close transactionChain - FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response, transactionChain); + //This method will close transactionChain if any + FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response, strategy.getTransactionChain()); } catch (final RestconfDocumentedException e) { // if errors occurred during transaction commit then patch failed and global errors are reported return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, Lists.newArrayList(e.getErrors())); } - return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null); + return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), + true, null); } else { - tx.cancel(); - transactionChain.close(); + strategy.cancel(); return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null); } @@ -155,130 +152,146 @@ public final class PatchDataTransactionUtil { /** * Create data within one transaction, return error if already exists. - * @param dataStore Datastore to write data to - * @param path Path for data to be created - * @param payload Data to be created - * @param rwTransaction Transaction + * + * @param dataStore Datastore to write data to + * @param path Path for data to be created + * @param payload Data to be created + * @param strategy Object that perform the actual DS operations * @param schemaContext Global schema context */ private static void createDataWithinTransaction(final LogicalDatastoreType dataStore, final YangInstanceIdentifier path, final NormalizedNode payload, - final DOMDataTreeReadWriteTransaction rwTransaction, + final RestconfStrategy strategy, final EffectiveModelContext schemaContext) { LOG.trace("POST {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload); - createData(payload, schemaContext, path, rwTransaction, dataStore, true); + createData(payload, schemaContext, path, strategy, dataStore, true); } /** * Check if data exists and remove it within one transaction. - * @param dataStore Datastore to delete data from - * @param path Path for data to be deleted - * @param readWriteTransaction Transaction + * + * @param dataStore Datastore to delete data from + * @param path Path for data to be deleted + * @param strategy Object that perform the actual DS operations */ private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore, final YangInstanceIdentifier path, - final DOMDataTreeReadWriteTransaction readWriteTransaction) { + final RestconfStrategy strategy) { LOG.trace("Delete {} within Restconf Patch: {}", dataStore.name(), path); - checkItemExistsWithinTransaction(readWriteTransaction, dataStore, path); - readWriteTransaction.delete(dataStore, path); + checkItemExistsWithinTransaction(strategy, dataStore, path); + strategy.delete(dataStore, path); } /** * Merge data within one transaction. - * @param dataStore Datastore to merge data to - * @param path Path for data to be merged - * @param payload Data to be merged - * @param writeTransaction Transaction + * + * @param dataStore Datastore to merge data to + * @param path Path for data to be merged + * @param payload Data to be merged + * @param strategy Object that perform the actual DS operations * @param schemaContext Global schema context */ private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore, final YangInstanceIdentifier path, final NormalizedNode payload, - final DOMDataTreeWriteTransaction writeTransaction, + final RestconfStrategy strategy, final EffectiveModelContext schemaContext) { LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload); - TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTransaction); - writeTransaction.merge(dataStore, path, payload); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); + strategy.merge(dataStore, path, payload); } /** * Do NOT check if data exists and remove it within one transaction. - * @param dataStore Datastore to delete data from - * @param path Path for data to be deleted - * @param writeTransaction Transaction + * + * @param dataStore Datastore to delete data from + * @param path Path for data to be deleted + * @param strategy Object that perform the actual DS operations */ private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore, final YangInstanceIdentifier path, - final DOMDataTreeWriteTransaction writeTransaction) { + final RestconfStrategy strategy) { LOG.trace("Remove {} within Restconf Patch: {}", dataStore.name(), path); - writeTransaction.delete(dataStore, path); + strategy.delete(dataStore, path); } /** * Create data within one transaction, replace if already exists. - * @param dataStore Datastore to write data to - * @param path Path for data to be created - * @param payload Data to be created - * @param path Path for data to be created - * @param rwTransaction Transaction + * + * @param dataStore Datastore to write data to + * @param path Path for data to be created + * @param payload Data to be created + * @param schemaContext Global schema context + * @param strategy Object that perform the actual DS operations */ private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore, final YangInstanceIdentifier path, final NormalizedNode payload, final EffectiveModelContext schemaContext, - final DOMDataTreeReadWriteTransaction rwTransaction) { + final RestconfStrategy strategy) { LOG.trace("PUT {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload); - createData(payload, schemaContext, path, rwTransaction, dataStore, false); + createData(payload, schemaContext, path, strategy, dataStore, false); } /** * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked * for existence before created, otherwise they will be overwritten. - * @param payload Data to be created + * + * @param payload Data to be created * @param schemaContext Global schema context - * @param path Path for data to be created - * @param rwTransaction Transaction - * @param dataStore Datastore to write data to + * @param path Path for data to be created + * @param strategy Object that perform the actual DS operations + * @param dataStore Datastore to write data to * @param errorIfExists Enable checking for existence of data (throws error if already exists) */ private static void createData(final NormalizedNode payload, final EffectiveModelContext schemaContext, final YangInstanceIdentifier path, - final DOMDataTreeReadWriteTransaction rwTransaction, + final RestconfStrategy strategy, final LogicalDatastoreType dataStore, final boolean errorIfExists) { if (payload instanceof MapNode) { final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path); - rwTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); - TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction); + strategy.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); for (final MapEntryNode child : ((MapNode) payload).getValue()) { final YangInstanceIdentifier childPath = path.node(child.getIdentifier()); if (errorIfExists) { - checkItemDoesNotExistsWithinTransaction(rwTransaction, dataStore, childPath); + checkItemDoesNotExistsWithinTransaction(strategy, dataStore, childPath); } - rwTransaction.put(dataStore, childPath, child); + if (errorIfExists) { + strategy.create(dataStore, childPath, child); + } else { + strategy.replace(dataStore, childPath, child); + } } } else { if (errorIfExists) { - checkItemDoesNotExistsWithinTransaction(rwTransaction, dataStore, path); + checkItemDoesNotExistsWithinTransaction(strategy, dataStore, path); } - TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction); - rwTransaction.put(dataStore, path, payload); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); + if (errorIfExists) { + strategy.create(dataStore, path, payload); + } else { + strategy.replace(dataStore, path, payload); + } } } /** * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if * data does NOT already exists. - * @param rwTransaction Transaction - * @param store Datastore - * @param path Path to be checked + * + * @param strategy Object that perform the actual DS operations + * @param store Datastore + * @param path Path to be checked */ - public static void checkItemExistsWithinTransaction(final DOMDataTreeReadOperations rwTransaction, - final LogicalDatastoreType store, final YangInstanceIdentifier path) { - final FluentFuture future = rwTransaction.exists(store, path); + public static void checkItemExistsWithinTransaction(final RestconfStrategy strategy, + final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + final FluentFuture future = strategy.exists(store, path); final FutureDataFactory response = new FutureDataFactory<>(); FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response); @@ -286,20 +299,22 @@ public final class PatchDataTransactionUtil { if (!response.result) { LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path); throw new RestconfDocumentedException("Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, - path); + path); } } /** * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if * data already exists. - * @param rwTransaction Transaction - * @param store Datastore - * @param path Path to be checked + * + * @param strategy Object that perform the actual DS operations + * @param store Datastore + * @param path Path to be checked */ - public static void checkItemDoesNotExistsWithinTransaction(final DOMDataTreeReadOperations rwTransaction, - final LogicalDatastoreType store, final YangInstanceIdentifier path) { - final FluentFuture future = rwTransaction.exists(store, path); + public static void checkItemDoesNotExistsWithinTransaction(final RestconfStrategy strategy, + final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + final FluentFuture future = strategy.exists(store, path); final FutureDataFactory response = new FutureDataFactory<>(); FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response); @@ -307,7 +322,7 @@ public final class PatchDataTransactionUtil { if (response.result) { LOG.trace("Operation via Restconf was not executed because data at {} already exists", path); throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, - path); + path); } } } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtil.java index 95e36e5952..371b155f1b 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtil.java @@ -13,12 +13,10 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; -import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; @@ -36,62 +34,54 @@ public final class PlainPatchDataTransactionUtil { } /** - * Prepare variables for put data to DS. Close {@link DOMTransactionChain} inside of object - * {@link TransactionVarsWrapper} provided as a parameter. + * Prepare variables for put data to DS. Close {@link DOMTransactionChain} if any inside of object + * {@link RestconfStrategy} provided as a parameter if any. * - * @param payload - * data to put - * @param schemaContext - * reference to {@link EffectiveModelContext} - * @param transactionNode - * wrapper of variables for transaction + * @param payload data to put + * @param schemaContext reference to {@link EffectiveModelContext} + * @param strategy object that perform the actual DS operations * @return {@link Response} */ public static Response patchData(final NormalizedNodeContext payload, - final TransactionVarsWrapper transactionNode, + final RestconfStrategy strategy, final EffectiveModelContext schemaContext) { - final DOMTransactionChain transactionChain = transactionNode.getTransactionChain(); - final DOMDataTreeReadWriteTransaction tx = transactionChain.newReadWriteTransaction(); - + strategy.prepareReadWriteExecution(); YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier(); NormalizedNode data = payload.getData(); try { - mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, path, data, tx, schemaContext); + mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, path, data, strategy, schemaContext); } catch (final RestconfDocumentedException e) { - tx.cancel(); - transactionChain.close(); - + strategy.cancel(); throw new IllegalArgumentException(e); } - final FluentFuture future = tx.commit(); + final FluentFuture future = strategy.commit(); final ResponseFactory response = new ResponseFactory(Status.OK); - FutureCallbackTx.addCallback(future, - RestconfDataServiceConstant.PatchData.PATCH_TX_TYPE, - response, - transactionChain); // closes transactionChain, may throw + FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PatchData.PATCH_TX_TYPE, response, + strategy.getTransactionChain()); // closes transactionChain if any, may throw return response.build(); } /** * Merge data within one transaction. - * @param dataStore Datastore to merge data to - * @param path Path for data to be merged - * @param payload Data to be merged - * @param writeTransaction Transaction + * + * @param dataStore Datastore to merge data to + * @param path Path for data to be merged + * @param payload Data to be merged + * @param strategy Object that perform the actual DS operations * @param schemaContext global schema context */ private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore, final YangInstanceIdentifier path, final NormalizedNode payload, - final DOMDataTreeWriteTransaction writeTransaction, + final RestconfStrategy strategy, final EffectiveModelContext schemaContext) { LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload); - TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTransaction); - writeTransaction.merge(dataStore, path, payload); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); + strategy.merge(dataStore, path, payload); } } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java index 1e79b9a36f..295640d01a 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java @@ -16,13 +16,12 @@ import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; @@ -47,62 +46,53 @@ public final class PostDataTransactionUtil { } /** - * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} inside of object - * {@link TransactionVarsWrapper} provided as a parameter. + * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object + * {@link RestconfStrategy} provided as a parameter. * - * @param uriInfo - * - * @param payload - * data - * @param transactionNode - * wrapper for transaction data - * @param schemaContext - * reference to current {@link EffectiveModelContext} - * @param point - * point - * @param insert - * insert + * @param uriInfo uri info + * @param payload data + * @param strategy Object that perform the actual DS operations + * @param schemaContext reference to actual {@link EffectiveModelContext} + * @param point point + * @param insert insert * @return {@link Response} */ public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload, - final TransactionVarsWrapper transactionNode, final EffectiveModelContext schemaContext, - final String insert, final String point) { + final RestconfStrategy strategy, + final EffectiveModelContext schemaContext, final String insert, + final String point) { final FluentFuture future = submitData( payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), - transactionNode, schemaContext, insert, point); - final URI location = resolveLocation(uriInfo, transactionNode, schemaContext, payload.getData()); + strategy, schemaContext, insert, point); + final URI location = resolveLocation(uriInfo, strategy.getInstanceIdentifier(), + schemaContext, payload.getData()); final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location); - //This method will close transactionChain + //This method will close transactionChain if any FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory, - transactionNode.getTransactionChain()); + strategy.getTransactionChain()); return dataFactory.build(); } /** * Post data by type. * - * @param path - * path - * @param data - * data - * @param transactionNode - * wrapper for data to transaction - * @param schemaContext - * schema context of data - * @param point - * query parameter - * @param insert - * query parameter + * @param path path + * @param data data + * @param strategy object that perform the actual DS operations + * @param schemaContext schema context of data + * @param point query parameter + * @param insert query parameter * @return {@link FluentFuture} */ private static FluentFuture submitData(final YangInstanceIdentifier path, - final NormalizedNode data, final TransactionVarsWrapper transactionNode, - final EffectiveModelContext schemaContext, final String insert, final String point) { - final DOMTransactionChain transactionChain = transactionNode.getTransactionChain(); - final DOMDataTreeReadWriteTransaction newReadWriteTransaction = transactionChain.newReadWriteTransaction(); + final NormalizedNode data, + final RestconfStrategy strategy, + final EffectiveModelContext schemaContext, + final String insert, final String point) { + strategy.prepareReadWriteExecution(); if (insert == null) { - makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + makePost(path, data, schemaContext, strategy); + return strategy.commit(); } final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path); @@ -110,92 +100,88 @@ public final class PostDataTransactionUtil { case "first": if (schemaNode instanceof ListSchemaNode) { final NormalizedNode readData = PutDataTransactionUtil.readList(path.getParent(), - schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); + schemaContext, strategy, schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + makePost(path, data, schemaContext, strategy); + return strategy.commit(); } - newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent()); - simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, - transactionChain); - makePost(path, readData, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent()); + simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy); + makePost(path, readData, schemaContext, strategy); + return strategy.commit(); } else { final NormalizedNode readData = PutDataTransactionUtil.readList(path.getParent(), - schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); + schemaContext, strategy, schemaNode); final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionChain, - newReadWriteTransaction); - return newReadWriteTransaction.commit(); + makePost(path, data, schemaContext, strategy); + return strategy.commit(); } - newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent()); - simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, - transactionChain); - makePost(path, readData, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent()); + simplePost(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, strategy); + makePost(path, readData, schemaContext, strategy); + return strategy.commit(); } case "last": - makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + makePost(path, data, schemaContext, strategy); + return strategy.commit(); case "before": if (schemaNode instanceof ListSchemaNode) { final NormalizedNode readData = PutDataTransactionUtil.readList(path.getParent(), - schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); + schemaContext, strategy, schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + makePost(path, data, schemaContext, strategy); + return strategy.commit(); } - insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, - data, schemaContext, point, readList, true, transactionChain); - return newReadWriteTransaction.commit(); + insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path, + data, schemaContext, point, readList, true, strategy); + return strategy.commit(); } else { final NormalizedNode readData = PutDataTransactionUtil.readList(path.getParent(), - schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); + schemaContext, strategy, schemaNode); final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + makePost(path, data, schemaContext, strategy); + return strategy.commit(); } - insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, - path, data, schemaContext, point, readLeafList, true, transactionChain); - return newReadWriteTransaction.commit(); + insertWithPointLeafListPost(LogicalDatastoreType.CONFIGURATION, + path, data, schemaContext, point, readLeafList, true, strategy); + return strategy.commit(); } case "after": if (schemaNode instanceof ListSchemaNode) { final NormalizedNode readData = PutDataTransactionUtil.readList(path.getParent(), - schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); + schemaContext, strategy, schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + makePost(path, data, schemaContext, strategy); + return strategy.commit(); } - insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, - data, schemaContext, point, readList, false, - transactionChain); - return newReadWriteTransaction.commit(); + insertWithPointListPost(LogicalDatastoreType.CONFIGURATION, path, + data, schemaContext, point, readList, false, strategy); + return strategy.commit(); } else { final NormalizedNode readData = PutDataTransactionUtil.readList(path.getParent(), - schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); + schemaContext, strategy, schemaNode); final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); - return newReadWriteTransaction.commit(); + makePost(path, data, schemaContext, strategy); + return strategy.commit(); } - insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, - path, data, schemaContext, point, readLeafList, true, transactionChain); - return newReadWriteTransaction.commit(); + insertWithPointLeafListPost(LogicalDatastoreType.CONFIGURATION, + path, data, schemaContext, point, readLeafList, true, strategy); + return strategy.commit(); } default: throw new RestconfDocumentedException( @@ -204,11 +190,13 @@ public final class PostDataTransactionUtil { } } - private static void insertWithPointLeafListPost(final DOMDataTreeReadWriteTransaction rwTransaction, - final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload, - final EffectiveModelContext schemaContext, final String point, final OrderedLeafSetNode readLeafList, - final boolean before, final DOMTransactionChain transactionChain) { - rwTransaction.delete(datastore, path.getParent().getParent()); + private static void insertWithPointLeafListPost(final LogicalDatastoreType datastore, + final YangInstanceIdentifier path, + final NormalizedNode payload, + final EffectiveModelContext schemaContext, final String point, + final OrderedLeafSetNode readLeafList, + final boolean before, final RestconfStrategy strategy) { + strategy.delete(datastore, path.getParent().getParent()); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty()); int lastItemPosition = 0; @@ -224,26 +212,27 @@ public final class PostDataTransactionUtil { int lastInsertedPosition = 0; final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent()); - rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); + strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); for (final LeafSetEntryNode nodeChild : readLeafList.getValue()) { if (lastInsertedPosition == lastItemPosition) { - TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path, + TransactionUtil.checkItemDoesNotExists(strategy, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE); - rwTransaction.put(datastore, path, payload); + strategy.create(datastore, path, payload); } final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier()); - TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, childPath, + TransactionUtil.checkItemDoesNotExists(strategy, datastore, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE); - rwTransaction.put(datastore, childPath, nodeChild); + strategy.create(datastore, childPath, nodeChild); lastInsertedPosition++; } } - private static void insertWithPointListPost(final DOMDataTreeReadWriteTransaction rwTransaction, - final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload, - final EffectiveModelContext schemaContext, final String point, final MapNode readList, final boolean before, - final DOMTransactionChain transactionChain) { - rwTransaction.delete(datastore, path.getParent().getParent()); + private static void insertWithPointListPost(final LogicalDatastoreType datastore, final YangInstanceIdentifier path, + final NormalizedNode payload, + final EffectiveModelContext schemaContext, final String point, + final MapNode readList, final boolean before, + final RestconfStrategy strategy) { + strategy.delete(datastore, path.getParent().getParent()); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty()); int lastItemPosition = 0; @@ -259,68 +248,62 @@ public final class PostDataTransactionUtil { int lastInsertedPosition = 0; final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent()); - rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); + strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); for (final MapEntryNode mapEntryNode : readList.getValue()) { if (lastInsertedPosition == lastItemPosition) { - TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path, + TransactionUtil.checkItemDoesNotExists(strategy, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE); - rwTransaction.put(datastore, path, payload); + strategy.create(datastore, path, payload); } final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier()); - TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, childPath, + TransactionUtil.checkItemDoesNotExists(strategy, datastore, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE); - rwTransaction.put(datastore, childPath, mapEntryNode); + strategy.create(datastore, childPath, mapEntryNode); lastInsertedPosition++; } } private static void makePost(final YangInstanceIdentifier path, final NormalizedNode data, - final SchemaContext schemaContext, final DOMTransactionChain transactionChain, - final DOMDataTreeReadWriteTransaction transaction) { + final SchemaContext schemaContext, final RestconfStrategy strategy) { if (data instanceof MapNode) { boolean merge = false; for (final MapEntryNode child : ((MapNode) data).getValue()) { final YangInstanceIdentifier childPath = path.node(child.getIdentifier()); - TransactionUtil.checkItemDoesNotExists( - transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, childPath, + TransactionUtil.checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE); if (!merge) { merge = true; - TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); final NormalizedNode emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path); - transaction.merge(LogicalDatastoreType.CONFIGURATION, + strategy.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree); } - transaction.put(LogicalDatastoreType.CONFIGURATION, childPath, child); + strategy.create(LogicalDatastoreType.CONFIGURATION, childPath, child); } } else { - TransactionUtil.checkItemDoesNotExists( - transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, path, + TransactionUtil.checkItemDoesNotExists(strategy, LogicalDatastoreType.CONFIGURATION, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE); - TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction); - transaction.put(LogicalDatastoreType.CONFIGURATION, path, data); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); + strategy.create(LogicalDatastoreType.CONFIGURATION, path, data); } } /** * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}. * - * @param uriInfo - * uri info - * @param transactionNode - * wrapper for data of transaction - * @param schemaContext - * reference to {@link SchemaContext} + * @param uriInfo uri info + * @param yangInstanceIdentifier reference to {@link InstanceIdentifierContext} + * @param schemaContext reference to {@link SchemaContext} * @return {@link URI} */ - private static URI resolveLocation(final UriInfo uriInfo, final TransactionVarsWrapper transactionNode, - final EffectiveModelContext schemaContext, final NormalizedNode data) { + private static URI resolveLocation(final UriInfo uriInfo, final InstanceIdentifierContext yangInstanceIdentifier, + final EffectiveModelContext schemaContext, final NormalizedNode data) { if (uriInfo == null) { return null; } - YangInstanceIdentifier path = transactionNode.getInstanceIdentifier().getInstanceIdentifier(); + YangInstanceIdentifier path = yangInstanceIdentifier.getInstanceIdentifier(); if (data instanceof MapNode) { final Collection children = ((MapNode) data).getValue(); @@ -335,12 +318,12 @@ public final class PostDataTransactionUtil { .build(); } - private static void simplePost(final DOMDataTreeReadWriteTransaction rwTransaction, - final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload, - final SchemaContext schemaContext, final DOMTransactionChain transactionChain) { - TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path, + private static void simplePost(final LogicalDatastoreType datastore, final YangInstanceIdentifier path, + final NormalizedNode payload, + final SchemaContext schemaContext, final RestconfStrategy strategy) { + TransactionUtil.checkItemDoesNotExists(strategy, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE); - TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction); - rwTransaction.put(datastore, path, payload); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); + strategy.create(datastore, path, payload); } } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java index c9f7d6379d..b9f20b79df 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java @@ -16,8 +16,6 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; -import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; @@ -25,8 +23,7 @@ import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; import org.opendaylight.restconf.common.errors.RestconfError.ErrorType; -import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; @@ -55,16 +52,13 @@ import org.opendaylight.yangtools.yang.model.api.SchemaNode; public final class PutDataTransactionUtil { private PutDataTransactionUtil() { - } /** * Valid input data with {@link SchemaNode}. * - * @param schemaNode - * {@link SchemaNode} - * @param payload - * input data + * @param schemaNode {@link SchemaNode} + * @param payload input data */ public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) { if (schemaNode != null && payload.getData() == null) { @@ -77,10 +71,8 @@ public final class PutDataTransactionUtil { /** * Valid top level node name. * - * @param path - * path of node - * @param payload - * data + * @param path path of node + * @param payload data */ public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) { final String payloadName = payload.getData().getNodeType().getLocalName(); @@ -104,8 +96,7 @@ public final class PutDataTransactionUtil { * Validates whether keys in {@code payload} are equal to values of keys in * {@code iiWithData} for list schema node. * - * @throws RestconfDocumentedException - * if key values or key count in payload and URI isn't equal + * @throws RestconfDocumentedException if key values or key count in payload and URI isn't equal */ public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) { final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext(); @@ -122,12 +113,12 @@ public final class PutDataTransactionUtil { } private static void isEqualUriAndPayloadKeyValues(final Map uriKeyValues, final MapEntryNode payload, - final List keyDefinitions) { + final List keyDefinitions) { final Map mutableCopyUriKeyValues = new HashMap<>(uriKeyValues); for (final QName keyDefinition : keyDefinitions) { final Object uriKeyValue = RestconfDocumentedException.throwIfNull( - mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, - "Missing key %s in URI.", keyDefinition); + mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, + "Missing key %s in URI.", keyDefinition); final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition); @@ -141,151 +132,131 @@ public final class PutDataTransactionUtil { } /** - * Check mount point and prepare variables for put data to DS. Close {@link DOMTransactionChain} inside of object - * {@link TransactionVarsWrapper} provided as a parameter. + * Check mount point and prepare variables for put data to DS. Close {@link DOMTransactionChain} if any + * inside of object {@link RestconfStrategy} provided as a parameter if any. * - * @param payload - * data to put - * @param schemaContext - * reference to {@link EffectiveModelContext} - * @param transactionNode - * wrapper of variables for transaction - * @param point - * query parameter - * @param insert - * query parameter + * @param payload data to put + * @param schemaContext reference to {@link EffectiveModelContext} + * @param strategy object that perform the actual DS operations + * @param point query parameter + * @param insert query parameter * @return {@link Response} */ public static Response putData(final NormalizedNodeContext payload, final EffectiveModelContext schemaContext, - final TransactionVarsWrapper transactionNode, final String insert, final String point) { + final RestconfStrategy strategy, final String insert, final String point) { final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier(); - final DOMDataTreeReadWriteTransaction readWriteTransaction = - transactionNode.getTransactionChain().newReadWriteTransaction(); - - final FluentFuture existsFuture = readWriteTransaction.exists(LogicalDatastoreType.CONFIGURATION, - path); + strategy.prepareReadWriteExecution(); + final FluentFuture existsFuture = strategy.exists(LogicalDatastoreType.CONFIGURATION, path); final FutureDataFactory existsResponse = new FutureDataFactory<>(); FutureCallbackTx.addCallback(existsFuture, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, existsResponse); final ResponseFactory responseFactory = new ResponseFactory(existsResponse.result ? Status.NO_CONTENT : Status.CREATED); - final FluentFuture submitData = submitData(path, schemaContext, - transactionNode.getTransactionChainHandler(), readWriteTransaction, payload.getData(), insert, point); - //This method will close transactionChain + final FluentFuture submitData = submitData(path, schemaContext, strategy, + payload.getData(), insert, point, existsResponse.result); + //This method will close transactionChain if any FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory, - transactionNode.getTransactionChain()); + strategy.getTransactionChain()); return responseFactory.build(); } /** * Put data to DS. * - * @param path - * path of data - * @param schemaContext - * {@link SchemaContext} - * @param transactionChainHandler - * write transaction - * @param data - * data - * @param point - * query parameter - * @param insert - * query parameter + * @param path path of data + * @param schemaContext {@link SchemaContext} + * @param strategy object that perform the actual DS operations + * @param data data + * @param point query parameter + * @param insert query parameter * @return {@link FluentFuture} */ - private static FluentFuture submitData(final YangInstanceIdentifier path, - final EffectiveModelContext schemaContext, final TransactionChainHandler transactionChainHandler, - final DOMDataTreeReadWriteTransaction readWriteTransaction, - final NormalizedNode data, final String insert, final String point) { + private static FluentFuture submitData( + final YangInstanceIdentifier path, + final EffectiveModelContext schemaContext, + final RestconfStrategy strategy, + final NormalizedNode data, final String insert, final String point, final boolean exists) { if (insert == null) { - return makePut(path, schemaContext, readWriteTransaction, data); + return makePut(path, schemaContext, strategy, data, exists); } final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path); switch (insert) { case "first": if (schemaNode instanceof ListSchemaNode) { - final NormalizedNode readData = - readList(path, schemaContext, transactionChainHandler, schemaNode); + final NormalizedNode readData = readList(path, schemaContext, strategy, schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - return makePut(path, schemaContext, readWriteTransaction, data); + return makePut(path, schemaContext, strategy, data, exists); } else { - readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent()); - simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction, - schemaContext, data); - listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction, - schemaContext, readList); - return readWriteTransaction.commit(); + strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent()); + simplePut(LogicalDatastoreType.CONFIGURATION, path, strategy, schemaContext, data, exists); + listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), strategy, + schemaContext, readList, exists); + return strategy.commit(); } } else { - final NormalizedNode readData = - readList(path, schemaContext, transactionChainHandler, schemaNode); + final NormalizedNode readData = readList(path, schemaContext, strategy, schemaNode); final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - return makePut(path, schemaContext, readWriteTransaction, data); + return makePut(path, schemaContext, strategy, data, exists); } else { - readWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent()); - simplePut(LogicalDatastoreType.CONFIGURATION, path, readWriteTransaction, - schemaContext, data); - listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), readWriteTransaction, - schemaContext, readLeafList); - return readWriteTransaction.commit(); + strategy.delete(LogicalDatastoreType.CONFIGURATION, path.getParent()); + simplePut(LogicalDatastoreType.CONFIGURATION, path, strategy, + schemaContext, data, exists); + listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), strategy, + schemaContext, readLeafList, exists); + return strategy.commit(); } } case "last": - return makePut(path, schemaContext, readWriteTransaction, data); + return makePut(path, schemaContext, strategy, data, exists); case "before": if (schemaNode instanceof ListSchemaNode) { - final NormalizedNode readData = - readList(path, schemaContext, transactionChainHandler, schemaNode); + final NormalizedNode readData = readList(path, schemaContext, strategy, schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - return makePut(path, schemaContext, readWriteTransaction, data); + return makePut(path, schemaContext, strategy, data, exists); } else { - insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, - data, schemaContext, point, readList, true); - return readWriteTransaction.commit(); + insertWithPointListPut(strategy, LogicalDatastoreType.CONFIGURATION, path, + data, schemaContext, point, readList, true, exists); + return strategy.commit(); } } else { - final NormalizedNode readData = - readList(path, schemaContext, transactionChainHandler, schemaNode); + final NormalizedNode readData = readList(path, schemaContext, strategy, schemaNode); final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - return makePut(path, schemaContext, readWriteTransaction, data); + return makePut(path, schemaContext, strategy, data, exists); } else { - insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, - path, data, schemaContext, point, readLeafList, true); - return readWriteTransaction.commit(); + insertWithPointLeafListPut(strategy, LogicalDatastoreType.CONFIGURATION, + path, data, schemaContext, point, readLeafList, true, exists); + return strategy.commit(); } } case "after": if (schemaNode instanceof ListSchemaNode) { - final NormalizedNode readData = - readList(path, schemaContext, transactionChainHandler, schemaNode); + final NormalizedNode readData = readList(path, schemaContext, strategy, schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - return makePut(path, schemaContext, readWriteTransaction, data); + return makePut(path, schemaContext, strategy, data, exists); } else { - insertWithPointListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, - path, data, schemaContext, point, readList, false); - return readWriteTransaction.commit(); + insertWithPointListPut(strategy, LogicalDatastoreType.CONFIGURATION, + path, data, schemaContext, point, readList, false, exists); + return strategy.commit(); } } else { - final NormalizedNode readData = - readList(path, schemaContext, transactionChainHandler, schemaNode); + final NormalizedNode readData = readList(path, schemaContext, strategy, schemaNode); final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - return makePut(path, schemaContext, readWriteTransaction, data); + return makePut(path, schemaContext, strategy, data, exists); } else { - insertWithPointLeafListPut(readWriteTransaction, LogicalDatastoreType.CONFIGURATION, - path, data, schemaContext, point, readLeafList, true); - return readWriteTransaction.commit(); + insertWithPointLeafListPut(strategy, LogicalDatastoreType.CONFIGURATION, + path, data, schemaContext, point, readLeafList, true, exists); + return strategy.commit(); } } default: @@ -296,21 +267,24 @@ public final class PutDataTransactionUtil { } public static NormalizedNode readList(final YangInstanceIdentifier path, - final EffectiveModelContext schemaContext, final TransactionChainHandler transactionChainHandler, - final DataSchemaNode schemaNode) { + final EffectiveModelContext schemaContext, + final RestconfStrategy strategy, + final DataSchemaNode schemaNode) { final InstanceIdentifierContext iid = new InstanceIdentifierContext( path.getParent(), schemaNode, null, schemaContext); - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(iid, null, transactionChainHandler); - final NormalizedNode readData = ReadDataTransactionUtil - .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext); - return readData; + final RestconfStrategy restconfStrategy = strategy.buildStrategy(iid); + return ReadDataTransactionUtil.readData( + RestconfDataServiceConstant.ReadData.CONFIG, restconfStrategy, schemaContext); } - private static void insertWithPointLeafListPut(final DOMDataTreeReadWriteTransaction rwTransaction, - final LogicalDatastoreType datastore, final YangInstanceIdentifier path, - final NormalizedNode data, final EffectiveModelContext schemaContext, final String point, - final OrderedLeafSetNode readLeafList, final boolean before) { - rwTransaction.delete(datastore, path.getParent()); + private static void insertWithPointLeafListPut(final RestconfStrategy strategy, + final LogicalDatastoreType datastore, + final YangInstanceIdentifier path, + final NormalizedNode data, + final EffectiveModelContext schemaContext, final String point, + final OrderedLeafSetNode readLeafList, final boolean before, + final boolean exists) { + strategy.delete(datastore, path.getParent()); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty()); int lastItemPosition = 0; @@ -325,22 +299,28 @@ public final class PutDataTransactionUtil { } int lastInsertedPosition = 0; final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent()); - rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); + strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); for (final LeafSetEntryNode nodeChild : readLeafList.getValue()) { if (lastInsertedPosition == lastItemPosition) { - simplePut(datastore, path, rwTransaction, schemaContext, data); + simplePut(datastore, path, strategy, schemaContext, data, exists); } final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier()); - rwTransaction.put(datastore, childPath, nodeChild); + if (exists) { + strategy.replace(datastore, childPath, nodeChild); + } else { + strategy.create(datastore, childPath, nodeChild); + } lastInsertedPosition++; } } - private static void insertWithPointListPut(final DOMDataTreeReadWriteTransaction writeTx, - final LogicalDatastoreType datastore, final YangInstanceIdentifier path, - final NormalizedNode data, final EffectiveModelContext schemaContext, final String point, - final OrderedMapNode readList, final boolean before) { - writeTx.delete(datastore, path.getParent()); + private static void insertWithPointListPut(final RestconfStrategy strategy, + final LogicalDatastoreType datastore, final YangInstanceIdentifier path, + final NormalizedNode data, + final EffectiveModelContext schemaContext, final String point, + final OrderedMapNode readList, final boolean before, + final boolean exists) { + strategy.delete(datastore, path.getParent()); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty()); int lastItemPosition = 0; @@ -355,54 +335,76 @@ public final class PutDataTransactionUtil { } int lastInsertedPosition = 0; final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent()); - writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); + strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); for (final MapEntryNode mapEntryNode : readList.getValue()) { if (lastInsertedPosition == lastItemPosition) { - simplePut(datastore, path, writeTx, schemaContext, data); + simplePut(datastore, path, strategy, schemaContext, data, exists); } final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier()); - writeTx.put(datastore, childPath, mapEntryNode); + if (exists) { + strategy.replace(datastore, childPath, mapEntryNode); + } else { + strategy.create(datastore, childPath, mapEntryNode); + } lastInsertedPosition++; } } private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path, - final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext, - final OrderedLeafSetNode payload) { + final RestconfStrategy strategy, final SchemaContext schemaContext, + final OrderedLeafSetNode payload, final boolean exists) { final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path); - writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); - TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx); + strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); for (final LeafSetEntryNode child : ((LeafSetNode) payload).getValue()) { final YangInstanceIdentifier childPath = path.node(child.getIdentifier()); - writeTx.put(datastore, childPath, child); + if (exists) { + strategy.replace(datastore, childPath, child); + } else { + strategy.create(datastore, childPath, child); + } } } private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path, - final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext, - final OrderedMapNode payload) { + final RestconfStrategy strategy, final SchemaContext schemaContext, + final OrderedMapNode payload, final boolean exists) { final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path); - writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); - TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx); + strategy.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); for (final MapEntryNode child : payload.getValue()) { final YangInstanceIdentifier childPath = path.node(child.getIdentifier()); - writeTx.put(datastore, childPath, child); + if (exists) { + strategy.replace(datastore, childPath, child); + } else { + strategy.create(datastore, childPath, child); + } } } private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path, - final DOMDataTreeWriteTransaction writeTx, final SchemaContext schemaContext, - final NormalizedNode data) { - TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx); - writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data); + final RestconfStrategy strategy, final SchemaContext schemaContext, + final NormalizedNode data, final boolean exists) { + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); + if (exists) { + strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data); + } else { + strategy.create(LogicalDatastoreType.CONFIGURATION, path, data); + } } private static FluentFuture makePut(final YangInstanceIdentifier path, - final SchemaContext schemaContext, final DOMDataTreeWriteTransaction writeTx, - final NormalizedNode data) { - TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx); - writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data); - return writeTx.commit(); + final SchemaContext schemaContext, + final RestconfStrategy strategy, + final NormalizedNode data, + final boolean exists) { + TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy); + if (exists) { + strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data); + } else { + strategy.create(LogicalDatastoreType.CONFIGURATION, path, data); + } + return strategy.commit(); } public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) { diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java index 8ba6802833..a9cdb236a0 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java @@ -8,7 +8,7 @@ package org.opendaylight.restconf.nb.rfc8040.rests.utils; import com.google.common.primitives.Ints; -import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.ListenableFuture; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -20,14 +20,13 @@ import javax.ws.rs.core.UriInfo; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.WriterParameters; import org.opendaylight.restconf.common.context.WriterParameters.WriterParametersBuilder; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; @@ -83,10 +82,8 @@ public final class ReadDataTransactionUtil { /** * Parse parameters from URI request and check their types and values. * - * @param identifier - * {@link InstanceIdentifierContext} - * @param uriInfo - * URI info + * @param identifier {@link InstanceIdentifierContext} + * @param uriInfo URI info * @return {@link WriterParameters} */ public static WriterParameters parseUriParameters(final InstanceIdentifierContext identifier, @@ -173,60 +170,50 @@ public final class ReadDataTransactionUtil { builder.setWithDefault(withDefaults.get(0)); } } - return builder.build(); } /** * Read specific type of data from data store via transaction. * - * @param valueOfContent - * type of data to read (config, state, all) - * @param transactionNode - * {@link TransactionVarsWrapper} - wrapper for variables - * @param schemaContext - * schema context + * @param valueOfContent type of data to read (config, state, all) + * @param strategy {@link RestconfStrategy} - wrapper for variables + * @param schemaContext schema context * @return {@link NormalizedNode} */ public static @Nullable NormalizedNode readData(final @NonNull String valueOfContent, - final @NonNull TransactionVarsWrapper transactionNode, final SchemaContext schemaContext) { - return readData(valueOfContent, transactionNode, null, schemaContext); + final @NonNull RestconfStrategy strategy, final SchemaContext schemaContext) { + return readData(valueOfContent, strategy, null, schemaContext); } /** - * Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} inside of object - * {@link TransactionVarsWrapper} provided as a parameter. + * Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} if any + * inside of object {@link RestconfStrategy} provided as a parameter. * - * @param valueOfContent - * type of data to read (config, state, all) - * @param transactionNode - * {@link TransactionVarsWrapper} - wrapper for variables - * @param withDefa - * value of with-defaults parameter - * @param ctx - * schema context + * @param valueOfContent type of data to read (config, state, all) + * @param strategy {@link RestconfStrategy} - object that perform the actual DS operations + * @param withDefa vaule of with-defaults parameter + * @param ctx schema context * @return {@link NormalizedNode} */ public static @Nullable NormalizedNode readData(final @NonNull String valueOfContent, - final @NonNull TransactionVarsWrapper transactionNode, final String withDefa, final SchemaContext ctx) { + final @NonNull RestconfStrategy strategy, + final String withDefa, final SchemaContext ctx) { switch (valueOfContent) { case RestconfDataServiceConstant.ReadData.CONFIG: - transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION); if (withDefa == null) { - return readDataViaTransaction(transactionNode); + return readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, true); } else { - return prepareDataByParamWithDef(readDataViaTransaction(transactionNode), - transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa, ctx); + return prepareDataByParamWithDef( + readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, true), + strategy.getInstanceIdentifier().getInstanceIdentifier(), withDefa, ctx); } case RestconfDataServiceConstant.ReadData.NONCONFIG: - transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL); - return readDataViaTransaction(transactionNode); - + return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, true); case RestconfDataServiceConstant.ReadData.ALL: - return readAllData(transactionNode, withDefa, ctx); - + return readAllData(strategy, withDefa, ctx); default: - transactionNode.getTransactionChain().close(); + strategy.cancel(); throw new RestconfDocumentedException( new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE, "Invalid content parameter: " + valueOfContent, null, @@ -358,79 +345,57 @@ public final class ReadDataTransactionUtil { } /** - * If is set specific {@link LogicalDatastoreType} in - * {@link TransactionVarsWrapper}, then read this type of data from DS. If - * don't, we have to read all data from DS (state + config). - * This method will close {@link org.opendaylight.mdsal.dom.api.DOMTransactionChain} inside of - * {@link TransactionVarsWrapper}. - * - * @param transactionNode - * {@link TransactionVarsWrapper} - wrapper for variables - * @return {@link NormalizedNode} - */ - private static @Nullable NormalizedNode readDataViaTransaction( - final @NonNull TransactionVarsWrapper transactionNode) { - return readDataViaTransaction(transactionNode, true); - } - - - /** - * If is set specific {@link LogicalDatastoreType} in - * {@link TransactionVarsWrapper}, then read this type of data from DS. If - * don't, we have to read all data from DS (state + config) + * If is set specific {@link LogicalDatastoreType} in {@link RestconfStrategy}, then read this type of data from DS. + * If don't, we have to read all data from DS (state + config) * - * @param transactionNode - * {@link TransactionVarsWrapper} - wrapper for variables - * @param closeTransactionChain - * If is set to true, after transaction it will close transactionChain in {@link TransactionVarsWrapper} + * @param strategy {@link RestconfStrategy} - object that perform the actual DS operations + * @param closeTransactionChain If is set to true, after transaction it will close transactionChain + * in {@link RestconfStrategy} if any * @return {@link NormalizedNode} */ private static @Nullable NormalizedNode readDataViaTransaction( - final @NonNull TransactionVarsWrapper transactionNode, final boolean closeTransactionChain) { + final @NonNull RestconfStrategy strategy, + final LogicalDatastoreType store, + final boolean closeTransactionChain) { final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory(); - try (DOMDataTreeReadTransaction tx = transactionNode.getTransactionChain().newReadOnlyTransaction()) { - final FluentFuture>> listenableFuture = tx.read( - transactionNode.getLogicalDatastoreType(), - transactionNode.getInstanceIdentifier().getInstanceIdentifier()); - if (closeTransactionChain) { - //Method close transactionChain inside of TransactionVarsWrapper, if is provide as a parameter. - FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX, - dataFactory, transactionNode.getTransactionChain()); - } else { - FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX, - dataFactory); - } + final ListenableFuture>> listenableFuture = strategy.read( + store, strategy.getInstanceIdentifier().getInstanceIdentifier()); + if (closeTransactionChain) { + //Method close transactionChain if any + FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX, + dataFactory, strategy.getTransactionChain()); + } else { + FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX, + dataFactory); } return dataFactory.build(); } /** * Read config and state data, then map them. Close {@link DOMTransactionChain} inside of object - * {@link TransactionVarsWrapper} provided as a parameter. + * {@link RestconfStrategy} provided as a parameter if any. * - * @param transactionNode - * {@link TransactionVarsWrapper} - wrapper for variables - * @param withDefa - * with-defaults parameter - * @param ctx - * schema context + * @param strategy {@link RestconfStrategy} - object that perform the actual DS operations + * @param withDefa with-defaults parameter + * @param ctx schema context * @return {@link NormalizedNode} */ - private static @Nullable NormalizedNode readAllData(final @NonNull TransactionVarsWrapper transactionNode, + private static @Nullable NormalizedNode readAllData(final @NonNull RestconfStrategy strategy, final String withDefa, final SchemaContext ctx) { // PREPARE STATE DATA NODE - transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL); - final NormalizedNode stateDataNode = readDataViaTransaction(transactionNode, false); + final NormalizedNode stateDataNode = readDataViaTransaction( + strategy, LogicalDatastoreType.OPERATIONAL, false); // PREPARE CONFIG DATA NODE - transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION); final NormalizedNode configDataNode; - //Here will be closed transactionChain + //Here will be closed transactionChain if any if (withDefa == null) { - configDataNode = readDataViaTransaction(transactionNode); + configDataNode = readDataViaTransaction( + strategy, LogicalDatastoreType.CONFIGURATION, true); } else { - configDataNode = prepareDataByParamWithDef(readDataViaTransaction(transactionNode), - transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa, ctx); + configDataNode = prepareDataByParamWithDef( + readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, true), + strategy.getInstanceIdentifier().getInstanceIdentifier(), withDefa, ctx); } // if no data exists @@ -455,10 +420,8 @@ public final class ReadDataTransactionUtil { /** * Merge state and config data into a single NormalizedNode. * - * @param stateDataNode - * data node of state data - * @param configDataNode - * data node of config data + * @param stateDataNode data node of state data + * @param configDataNode data node of config data * @return {@link NormalizedNode} */ private static @NonNull NormalizedNode mergeStateAndConfigData( @@ -474,10 +437,8 @@ public final class ReadDataTransactionUtil { /** * Validates whether the two NormalizedNodes can be merged. * - * @param stateDataNode - * data node of state data - * @param configDataNode - * data node of config data + * @param stateDataNode data node of state data + * @param configDataNode data node of config data */ private static void validateNodeMerge(final @NonNull NormalizedNode stateDataNode, final @NonNull NormalizedNode configDataNode) { @@ -491,10 +452,8 @@ public final class ReadDataTransactionUtil { /** * Prepare and map data for rpc. * - * @param configDataNode - * data node of config data - * @param stateDataNode - * data node of state data + * @param configDataNode data node of config data + * @param stateDataNode data node of state data * @return {@link NormalizedNode} */ private static @NonNull NormalizedNode prepareRpcData(final @NonNull NormalizedNode configDataNode, @@ -514,10 +473,8 @@ public final class ReadDataTransactionUtil { /** * Map node to map entry builder. * - * @param dataNode - * data node - * @param mapEntryBuilder - * builder for mapping data + * @param dataNode data node + * @param mapEntryBuilder builder for mapping data */ private static void mapRpcDataNode(final @NonNull NormalizedNode dataNode, final @NonNull DataContainerNodeBuilder mapEntryBuilder) { @@ -527,10 +484,8 @@ public final class ReadDataTransactionUtil { /** * Prepare and map all data from DS. * - * @param configDataNode - * data node of config data - * @param stateDataNode - * data node of state data + * @param configDataNode data node of config data + * @param stateDataNode data node of state data * @return {@link NormalizedNode} */ @SuppressWarnings("unchecked") @@ -627,12 +582,9 @@ public final class ReadDataTransactionUtil { /** * Map value from container node to builder. * - * @param configData - * collection of config data nodes - * @param stateData - * collection of state data nodes - * @param builder - * builder + * @param configData collection of config data nodes + * @param stateData collection of state data nodes + * @param builder builder */ private static > void mapValueToBuilder( final @NonNull Collection configData, final @NonNull Collection stateData, @@ -653,12 +605,9 @@ public final class ReadDataTransactionUtil { * Map data with different identifiers to builder. Data with different identifiers can be just added * as childs to parent node. * - * @param configMap - * map of config data nodes - * @param stateMap - * map of state data nodes - * @param builder - * - builder + * @param configMap map of config data nodes + * @param stateMap map of state data nodes + * @param builder - builder */ private static > void mapDataToBuilder( final @NonNull Map configMap, final @NonNull Map stateMap, @@ -673,12 +622,9 @@ public final class ReadDataTransactionUtil { * Map data with the same identifiers to builder. Data with the same identifiers cannot be just added but we need to * go one level down with {@code prepareData} method. * - * @param configMap - * immutable config data - * @param stateMap - * immutable state data - * @param builder - * - builder + * @param configMap immutable config data + * @param stateMap immutable state data + * @param builder - builder */ @SuppressWarnings("unchecked") private static > void mergeDataToBuilder( diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java index 0e4261c46a..ffe95f5366 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java @@ -7,18 +7,15 @@ */ package org.opendaylight.restconf.nb.rfc8040.rests.utils; -import com.google.common.base.Preconditions; import com.google.common.util.concurrent.FluentFuture; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; -import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; -import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; import org.opendaylight.restconf.common.errors.RestconfError.ErrorType; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; @@ -42,15 +39,12 @@ public final class TransactionUtil { /** * Merged parents of data. * - * @param path - * path of data - * @param schemaContext - * {@link SchemaContext} - * @param writeTx - * write transaction + * @param path path of data + * @param schemaContext {@link SchemaContext} + * @param strategy object that perform the actual DS operations */ public static void ensureParentsByMerge(final YangInstanceIdentifier path, final SchemaContext schemaContext, - final DOMDataTreeWriteTransaction writeTx) { + final RestconfStrategy strategy) { final List normalizedPathWithoutChildArgs = new ArrayList<>(); YangInstanceIdentifier rootNormalizedPath = null; @@ -71,35 +65,31 @@ public final class TransactionUtil { return; } - Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received"); - final NormalizedNode parentStructure = ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs)); - writeTx.merge(LogicalDatastoreType.CONFIGURATION, rootNormalizedPath, parentStructure); + strategy.merge(LogicalDatastoreType.CONFIGURATION, rootNormalizedPath, parentStructure); } /** * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if * data does NOT already exists. - * @param transactionChain Transaction chain - * @param rwTransaction Transaction - * @param store Datastore - * @param path Path to be checked + * + * @param strategy Object that perform the actual DS operations + * @param store Datastore + * @param path Path to be checked * @param operationType Type of operation (READ, POST, PUT, DELETE...) */ - public static void checkItemExists(final DOMTransactionChain transactionChain, - final DOMDataTreeReadWriteTransaction rwTransaction, + public static void checkItemExists(final RestconfStrategy strategy, final LogicalDatastoreType store, final YangInstanceIdentifier path, final String operationType) { - final FluentFuture future = rwTransaction.exists(store, path); + final FluentFuture future = strategy.exists(store, path); final FutureDataFactory response = new FutureDataFactory<>(); FutureCallbackTx.addCallback(future, operationType, response); if (!response.result) { // close transaction - rwTransaction.cancel(); - transactionChain.close(); + strategy.cancel(); // throw error LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path); throw new RestconfDocumentedException( @@ -110,25 +100,23 @@ public final class TransactionUtil { /** * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if * data already exists. - * @param transactionChain Transaction chain - * @param rwTransaction Transaction - * @param store Datastore - * @param path Path to be checked + * + * @param strategy Object that perform the actual DS operations + * @param store Datastore + * @param path Path to be checked * @param operationType Type of operation (READ, POST, PUT, DELETE...) */ - public static void checkItemDoesNotExists(final DOMTransactionChain transactionChain, - final DOMDataTreeReadWriteTransaction rwTransaction, + public static void checkItemDoesNotExists(final RestconfStrategy strategy, final LogicalDatastoreType store, final YangInstanceIdentifier path, final String operationType) { - final FluentFuture future = rwTransaction.exists(store, path); + final FluentFuture future = strategy.exists(store, path); final FutureDataFactory response = new FutureDataFactory<>(); FutureCallbackTx.addCallback(future, operationType, response); if (response.result) { // close transaction - rwTransaction.cancel(); - transactionChain.close(); + strategy.cancel(); // throw error LOG.trace("Operation via Restconf was not executed because data at {} already exists", path); throw new RestconfDocumentedException( diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImplTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImplTest.java index c99d2b7fb8..c0dc6fada2 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImplTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImplTest.java @@ -51,6 +51,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMSchemaService; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; @@ -63,6 +64,9 @@ import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; @@ -119,6 +123,8 @@ public class RestconfDataServiceImplTest { @Mock private DOMDataBroker mountDataBroker; @Mock + private NetconfDataTreeService netconfService; + @Mock private ActionServiceHandler actionServiceHandler; @Mock private DOMTransactionChain mountTransactionChain; @@ -544,4 +550,17 @@ public class RestconfDataServiceImplTest { final String errorMessage = status.getEditCollection().get(2).getEditErrors().get(0).getErrorMessage(); assertEquals("Data does not exist", errorMessage); } + + @Test + public void testGetRestconfStrategy() { + final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>( + this.iidBase, this.schemaNode, this.mountPoint, this.contextRef); + + RestconfStrategy restconfStrategy = this.dataService.getRestconfStrategy(iidContext, this.mountPoint); + assertTrue(restconfStrategy instanceof MdsalRestconfStrategy); + + doReturn(Optional.of(this.netconfService)).when(this.mountPoint).getService(NetconfDataTreeService.class); + restconfStrategy = this.dataService.getRestconfStrategy(iidContext, this.mountPoint); + assertTrue(restconfStrategy instanceof NetconfRestconfStrategy); + } } diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtilTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtilTest.java index b0fce39c48..bc60219e4d 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtilTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtilTest.java @@ -9,9 +9,12 @@ package org.opendaylight.restconf.nb.rfc8040.rests.utils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture; +import java.util.Optional; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.junit.Before; @@ -24,13 +27,17 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataBroker; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; import org.opendaylight.restconf.common.errors.RestconfError.ErrorType; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; public class DeleteDataTransactionUtilTest { @Mock @@ -41,14 +48,17 @@ public class DeleteDataTransactionUtilTest { private DOMDataTreeReadWriteTransaction readWrite; @Mock private DOMDataBroker mockDataBroker; + @Mock + private NetconfDataTreeService netconfService; private TransactionChainHandler transactionChainHandler; @Before - public void init() throws Exception { + public void init() { MockitoAnnotations.initMocks(this); Mockito.when(this.transactionChain.newReadWriteTransaction()).thenReturn(this.readWrite); Mockito.doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); + Mockito.doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); Mockito.when(this.context.getInstanceIdentifier()).thenReturn(YangInstanceIdentifier.empty()); Mockito.doReturn(transactionChain).when(mockDataBroker).createTransactionChain(Mockito.any()); @@ -59,32 +69,41 @@ public class DeleteDataTransactionUtilTest { * Test of successful DELETE operation. */ @Test - public void deleteData() throws Exception { + public void deleteData() { // assert that data to delete exists Mockito.when(this.transactionChain.newReadWriteTransaction().exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty())).thenReturn(immediateTrueFluentFuture()); - + Mockito.when(this.netconfService.getConfig(YangInstanceIdentifier.empty())) + .thenReturn(immediateFluentFuture(Optional.of(mock(NormalizedNode.class)))); // test - final Response response = DeleteDataTransactionUtil.deleteData( - new TransactionVarsWrapper(this.context, null, transactionChainHandler)); - - // assert success - assertEquals("Not expected response received", Status.NO_CONTENT.getStatusCode(), response.getStatus()); + delete(new MdsalRestconfStrategy(this.context, transactionChainHandler)); + delete(new NetconfRestconfStrategy(netconfService, this.context)); } /** * Negative test for DELETE operation when data to delete does not exist. Error DATA_MISSING is expected. */ @Test - public void deleteDataNegativeTest() throws Exception { + public void deleteDataNegativeTest() { // assert that data to delete does NOT exist Mockito.when(this.transactionChain.newReadWriteTransaction().exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty())).thenReturn(immediateFalseFluentFuture()); - + Mockito.when(this.netconfService.getConfig(YangInstanceIdentifier.empty())) + .thenReturn(immediateFluentFuture(Optional.empty())); // test and assert error + deleteFail(new MdsalRestconfStrategy(this.context, transactionChainHandler)); + deleteFail(new NetconfRestconfStrategy(netconfService, this.context)); + } + + private void delete(final RestconfStrategy strategy) { + final Response response = DeleteDataTransactionUtil.deleteData(strategy); + // assert success + assertEquals("Not expected response received", Status.NO_CONTENT.getStatusCode(), response.getStatus()); + } + + private void deleteFail(final RestconfStrategy strategy) { try { - DeleteDataTransactionUtil.deleteData(new TransactionVarsWrapper(this.context, null, - transactionChainHandler)); + DeleteDataTransactionUtil.deleteData(strategy); fail("Delete operation should fail due to missing data"); } catch (final RestconfDocumentedException e) { assertEquals(ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType()); diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtilTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtilTest.java index 2de9591b8b..0d32951f30 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtilTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtilTest.java @@ -12,6 +12,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.MockitoAnnotations.initMocks; import static org.opendaylight.restconf.common.patch.PatchEditOperation.CREATE; import static org.opendaylight.restconf.common.patch.PatchEditOperation.DELETE; @@ -19,18 +20,22 @@ import static org.opendaylight.restconf.common.patch.PatchEditOperation.MERGE; import static org.opendaylight.restconf.common.patch.PatchEditOperation.REMOVE; import static org.opendaylight.restconf.common.patch.PatchEditOperation.REPLACE; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataBroker; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.common.patch.PatchContext; @@ -39,7 +44,9 @@ import org.opendaylight.restconf.common.patch.PatchStatusContext; import org.opendaylight.restconf.common.patch.PatchStatusEntity; import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; @@ -48,23 +55,22 @@ import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; public class PatchDataTransactionUtilTest { - private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox"; - @Mock private DOMTransactionChain transactionChain; - @Mock private DOMDataTreeReadWriteTransaction rwTransaction; - @Mock private DOMDataBroker mockDataBroker; + @Mock + private NetconfDataTreeService netconfService; private TransactionChainHandler transactionChainHandler; private EffectiveModelContext refSchemaCtx; @@ -165,6 +171,7 @@ public class PatchDataTransactionUtilTest { /* Mocks */ doReturn(this.rwTransaction).when(this.transactionChain).newReadWriteTransaction(); doReturn(CommitInfo.emptyFluentFuture()).when(this.rwTransaction).commit(); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); } @Test @@ -185,22 +192,21 @@ public class PatchDataTransactionUtilTest { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.instanceIdMerge, null, null, this.refSchemaCtx); final PatchContext patchContext = new PatchContext(iidContext, entities, "patchRMRm"); - final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChainHandler); - final PatchStatusContext patchStatusContext = - PatchDataTransactionUtil.patchData(patchContext, wrapper, this.refSchemaCtx); - for (final PatchStatusEntity entity : patchStatusContext.getEditCollection()) { - assertTrue(entity.isOk()); - } - assertTrue(patchStatusContext.isOk()); + patch(patchContext, new MdsalRestconfStrategy(iidContext, transactionChainHandler), false); + patch(patchContext, new NetconfRestconfStrategy(netconfService, iidContext), false); } @Test - public void testPatchDataCreateAndDelete() throws Exception { + public void testPatchDataCreateAndDelete() { doReturn(immediateFalseFluentFuture()).when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, this.instanceIdContainer); + Mockito.when(this.netconfService.getConfig(this.instanceIdContainer)) + .thenReturn(immediateFluentFuture(Optional.empty())); doReturn(immediateTrueFluentFuture()).when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, this.targetNodeForCreateAndDelete); + Mockito.when(this.netconfService.getConfig(this.targetNodeForCreateAndDelete)) + .thenReturn(immediateFluentFuture(Optional.of(mock(NormalizedNode.class)))); final PatchEntity entityCreate = new PatchEntity("edit1", CREATE, this.instanceIdContainer, this.buildBaseContainerForTests); @@ -214,20 +220,16 @@ public class PatchDataTransactionUtilTest { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.instanceIdCreateAndDelete, null, null, this.refSchemaCtx); final PatchContext patchContext = new PatchContext(iidContext, entities, "patchCD"); - final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChainHandler); - final PatchStatusContext patchStatusContext = - PatchDataTransactionUtil.patchData(patchContext, wrapper, this.refSchemaCtx); - - for (final PatchStatusEntity entity : patchStatusContext.getEditCollection()) { - assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk()); - } - assertTrue(patchStatusContext.isOk()); + patch(patchContext, new MdsalRestconfStrategy(iidContext, transactionChainHandler), true); + patch(patchContext, new NetconfRestconfStrategy(netconfService, iidContext), true); } @Test public void deleteNonexistentDataTest() { doReturn(immediateFalseFluentFuture()).when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, this.targetNodeForCreateAndDelete); + Mockito.when(this.netconfService.getConfig(this.targetNodeForCreateAndDelete)) + .thenReturn(immediateFluentFuture(Optional.empty())); final PatchEntity entityDelete = new PatchEntity("edit", DELETE, this.targetNodeForCreateAndDelete); final List entities = new ArrayList<>(); @@ -237,19 +239,12 @@ public class PatchDataTransactionUtilTest { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.instanceIdCreateAndDelete, null, null, this.refSchemaCtx); final PatchContext patchContext = new PatchContext(iidContext, entities, "patchD"); - final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChainHandler); - final PatchStatusContext patchStatusContext = - PatchDataTransactionUtil.patchData(patchContext, wrapper, this.refSchemaCtx); - - assertFalse(patchStatusContext.isOk()); - assertEquals(RestconfError.ErrorType.PROTOCOL, - patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorType()); - assertEquals(RestconfError.ErrorTag.DATA_MISSING, - patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorTag()); + delete(patchContext, new MdsalRestconfStrategy(iidContext, transactionChainHandler)); + delete(patchContext, new NetconfRestconfStrategy(netconfService, iidContext)); } @Test - public void testPatchMergePutContainer() throws Exception { + public void testPatchMergePutContainer() { doReturn(immediateFalseFluentFuture()).doReturn(immediateTrueFluentFuture()) .when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, this.targetNodeForCreateAndDelete); @@ -262,13 +257,32 @@ public class PatchDataTransactionUtilTest { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.instanceIdCreateAndDelete, null, null, this.refSchemaCtx); final PatchContext patchContext = new PatchContext(iidContext, entities, "patchM"); - final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChainHandler); - final PatchStatusContext patchStatusContext = - PatchDataTransactionUtil.patchData(patchContext, wrapper, this.refSchemaCtx); + patch(patchContext, new MdsalRestconfStrategy(iidContext, transactionChainHandler), false); + patch(patchContext, new NetconfRestconfStrategy(netconfService, iidContext), false); + } + private void patch(final PatchContext patchContext, final RestconfStrategy strategy, + final boolean failed) { + final PatchStatusContext patchStatusContext = + PatchDataTransactionUtil.patchData(patchContext, strategy, this.refSchemaCtx); for (final PatchStatusEntity entity : patchStatusContext.getEditCollection()) { - assertTrue(entity.isOk()); + if (failed) { + assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk()); + } else { + assertTrue(entity.isOk()); + } } assertTrue(patchStatusContext.isOk()); } + + private void delete(PatchContext patchContext, RestconfStrategy strategy) { + final PatchStatusContext patchStatusContext = + PatchDataTransactionUtil.patchData(patchContext, strategy, this.refSchemaCtx); + + assertFalse(patchStatusContext.isOk()); + assertEquals(RestconfError.ErrorType.PROTOCOL, + patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorType()); + assertEquals(RestconfError.ErrorTag.DATA_MISSING, + patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorTag()); + } } diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtilTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtilTest.java index 802c7fe5ea..d80cfe764c 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtilTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtilTest.java @@ -13,6 +13,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture; +import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -25,11 +26,13 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; @@ -45,9 +48,7 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; public class PlainPatchDataTransactionUtilTest { - private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox"; - @Mock private DOMTransactionChain transactionChain; @Mock @@ -58,6 +59,8 @@ public class PlainPatchDataTransactionUtilTest { private DOMDataTreeWriteTransaction write; @Mock private DOMDataBroker mockDataBroker; + @Mock + private NetconfDataTreeService netconfService; private TransactionChainHandler transactionChainHandler; private LeafNode leafGap; @@ -169,13 +172,19 @@ public class PlainPatchDataTransactionUtilTest { doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData()); doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); PlainPatchDataTransactionUtil.patchData(payload, - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler), + new MdsalRestconfStrategy(iidContext, transactionChainHandler), this.schema); - verify(this.readWrite).merge(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData()); + + PlainPatchDataTransactionUtil.patchData(payload, + new NetconfRestconfStrategy(netconfService, iidContext), + this.schema); + verify(this.netconfService).merge(LogicalDatastoreType.CONFIGURATION, + payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), Optional.empty()); } @Test @@ -192,13 +201,17 @@ public class PlainPatchDataTransactionUtilTest { doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData()); doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); - PlainPatchDataTransactionUtil.patchData(payload, - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler), + PlainPatchDataTransactionUtil.patchData(payload, new MdsalRestconfStrategy(iidContext, transactionChainHandler), this.schema); - verify(this.readWrite).merge(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData()); + + PlainPatchDataTransactionUtil.patchData(payload, new NetconfRestconfStrategy(netconfService, iidContext), + this.schema); + verify(this.netconfService).merge(LogicalDatastoreType.CONFIGURATION, + payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), Optional.empty()); } @Test @@ -215,11 +228,16 @@ public class PlainPatchDataTransactionUtilTest { doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData()); doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); PlainPatchDataTransactionUtil.patchData(payload, - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler), + new MdsalRestconfStrategy(iidContext, transactionChainHandler), this.schema); - verify(this.readWrite).merge(LogicalDatastoreType.CONFIGURATION, this.iidJukebox, payload.getData()); + + PlainPatchDataTransactionUtil.patchData(payload, new NetconfRestconfStrategy(netconfService, iidContext), + this.schema); + verify(this.netconfService).merge(LogicalDatastoreType.CONFIGURATION, this.iidJukebox, payload.getData(), + Optional.empty()); } } diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtilTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtilTest.java index 7bd431caa4..fe22aefdab 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtilTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtilTest.java @@ -17,10 +17,12 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.Collection; +import java.util.Optional; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; @@ -34,14 +36,15 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataBroker; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; -import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; @@ -67,13 +70,11 @@ public class PostDataTransactionUtilTest { @Mock private DOMDataTreeReadTransaction read; @Mock - private DOMDataTreeWriteTransaction write; - @Mock private UriInfo uriInfo; @Mock - private UriBuilder uriBuilder; - @Mock private DOMDataBroker mockDataBroker; + @Mock + private NetconfDataTreeService netconfService; private TransactionChainHandler transactionChainHandler; private ContainerNode buildBaseCont; @@ -150,6 +151,7 @@ public class PostDataTransactionUtilTest { doReturn(immediateFalseFluentFuture()).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iid2); + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).getConfig(this.iid2); final NodeIdentifier identifier = ((ContainerNode) ((Collection) payload.getData().getValue()).iterator().next()).getIdentifier(); final YangInstanceIdentifier node = @@ -157,14 +159,21 @@ public class PostDataTransactionUtilTest { doReturn(immediateFalseFluentFuture()).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, node); doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, node, payload.getData()); doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); - final TransactionVarsWrapper wrapper = - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler); - final Response response = - PostDataTransactionUtil.postData(this.uriInfo, payload, wrapper, this.schema, null, null); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); + + Response response = PostDataTransactionUtil.postData(this.uriInfo, payload, + new MdsalRestconfStrategy(iidContext, transactionChainHandler), this.schema, null, null); assertEquals(201, response.getStatus()); verify(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iid2); verify(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData()); + + response = PostDataTransactionUtil.postData(this.uriInfo, payload, + new NetconfRestconfStrategy(netconfService, iidContext), this.schema, null, null); + assertEquals(201, response.getStatus()); + verify(this.netconfService).getConfig(this.iid2); + verify(this.netconfService).create(LogicalDatastoreType.CONFIGURATION, + payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), Optional.empty()); } @Test @@ -178,17 +187,27 @@ public class PostDataTransactionUtilTest { final YangInstanceIdentifier node = payload.getInstanceIdentifierContext().getInstanceIdentifier().node(identifier); doReturn(immediateFalseFluentFuture()).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, node); + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).getConfig(node); doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, node, payload.getData()); doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); - final TransactionVarsWrapper wrapper = - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler); - final Response response = - PostDataTransactionUtil.postData(this.uriInfo, payload, wrapper, this.schema, null, null); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); + + Response response = PostDataTransactionUtil.postData(this.uriInfo, payload, + new MdsalRestconfStrategy(iidContext, transactionChainHandler), this.schema, null, null); assertEquals(201, response.getStatus()); assertThat(URLDecoder.decode(response.getLocation().toString(), "UTF-8"), containsString(identifier.getValue(identifier.keySet().iterator().next()).toString())); verify(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, node); verify(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, node, data.getValue().iterator().next()); + + response = PostDataTransactionUtil.postData(this.uriInfo, payload, + new NetconfRestconfStrategy(netconfService, iidContext), this.schema, null, null); + assertEquals(201, response.getStatus()); + assertThat(URLDecoder.decode(response.getLocation().toString(), "UTF-8"), + containsString(identifier.getValue(identifier.keySet().iterator().next()).toString())); + verify(this.netconfService).getConfig(node); + verify(this.netconfService).create(LogicalDatastoreType.CONFIGURATION, node, data.getValue().iterator().next(), + Optional.empty()); } @Test @@ -199,19 +218,21 @@ public class PostDataTransactionUtilTest { doReturn(immediateFalseFluentFuture()).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iid2); + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).getConfig(this.iid2); final NodeIdentifier identifier = ((ContainerNode) ((Collection) payload.getData().getValue()).iterator().next()).getIdentifier(); final YangInstanceIdentifier node = payload.getInstanceIdentifierContext().getInstanceIdentifier().node(identifier); doReturn(immediateFalseFluentFuture()).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, node); + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).getConfig(node); doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, node, payload.getData()); final DOMException domException = new DOMException((short) 414, "Post request failed"); doReturn(immediateFailedFluentFuture(domException)).when(this.readWrite).commit(); - final TransactionVarsWrapper wrapper = - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler); + doReturn(immediateFailedFluentFuture(domException)).when(this.netconfService).commit(Mockito.any()); try { - PostDataTransactionUtil.postData(this.uriInfo, payload, wrapper, this.schema, null, null); + PostDataTransactionUtil.postData(this.uriInfo, payload, + new MdsalRestconfStrategy(iidContext, transactionChainHandler), this.schema, null, null); fail("Expected RestconfDocumentedException"); } catch (final RestconfDocumentedException e) { assertEquals(1, e.getErrors().size()); @@ -221,5 +242,18 @@ public class PostDataTransactionUtilTest { verify(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iid2); verify(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData()); + + try { + PostDataTransactionUtil.postData(this.uriInfo, payload, + new NetconfRestconfStrategy(netconfService, iidContext), this.schema, null, null); + fail("Expected RestconfDocumentedException"); + } catch (final RestconfDocumentedException e) { + assertEquals(1, e.getErrors().size()); + assertTrue(e.getErrors().get(0).getErrorInfo().contains(domException.getMessage())); + } + + verify(this.netconfService).getConfig(this.iid2); + verify(this.netconfService).create(LogicalDatastoreType.CONFIGURATION, + payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), Optional.empty()); } } diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtilTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtilTest.java index 3f2afde7c9..f5ca8f3a73 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtilTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtilTest.java @@ -9,9 +9,12 @@ package org.opendaylight.restconf.nb.rfc8040.rests.utils; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; +import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -24,12 +27,14 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; @@ -38,6 +43,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; @@ -45,9 +51,7 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; public class PutDataTransactionUtilTest { - private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox"; - @Mock private DOMTransactionChain transactionChain; @Mock @@ -58,6 +62,8 @@ public class PutDataTransactionUtilTest { private DOMDataTreeWriteTransaction write; @Mock private DOMDataBroker mockDataBroker; + @Mock + private NetconfDataTreeService netconfService; private TransactionChainHandler transactionChainHandler; private LeafNode buildLeaf; @@ -161,7 +167,7 @@ public class PutDataTransactionUtilTest { } @Test - public void testValidInputData() throws Exception { + public void testValidInputData() { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.iid, this.schemaNode, null, this.schema); final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildLeaf); @@ -169,7 +175,7 @@ public class PutDataTransactionUtilTest { } @Test - public void testValidTopLevelNodeName() throws Exception { + public void testValidTopLevelNodeName() { InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.iid, this.schemaNode, null, this.schema); NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildLeaf); @@ -181,7 +187,7 @@ public class PutDataTransactionUtilTest { } @Test(expected = RestconfDocumentedException.class) - public void testValidTopLevelNodeNamePathEmpty() throws Exception { + public void testValidTopLevelNodeNamePathEmpty() { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.iid, this.schemaNode, null, this.schema); final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildLeaf); @@ -189,7 +195,7 @@ public class PutDataTransactionUtilTest { } @Test(expected = RestconfDocumentedException.class) - public void testValidTopLevelNodeNameWrongTopIdentifier() throws Exception { + public void testValidTopLevelNodeNameWrongTopIdentifier() { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.iid, this.schemaNode, null, this.schema); final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildLeaf); @@ -197,7 +203,7 @@ public class PutDataTransactionUtilTest { } @Test - public void testValidateListKeysEqualityInPayloadAndUri() throws Exception { + public void testValidateListKeysEqualityInPayloadAndUri() { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.iid3, this.schemaNode3, null, this.schema); final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildListEntry); @@ -205,7 +211,7 @@ public class PutDataTransactionUtilTest { } @Test - public void testPutContainerData() throws Exception { + public void testPutContainerData() { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.iid2, this.schemaNode2, null, this.schema); final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildBaseCont); @@ -220,8 +226,7 @@ public class PutDataTransactionUtilTest { doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); PutDataTransactionUtil.putData(payload, this.schema, - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler), null, - null); + new MdsalRestconfStrategy(iidContext, transactionChainHandler), null, null); verify(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier()); verify(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, @@ -229,7 +234,40 @@ public class PutDataTransactionUtilTest { } @Test - public void testPutleafData() throws Exception { + public void testPutCreateContainerData() { + final InstanceIdentifierContext iidContext = + new InstanceIdentifierContext<>(this.iid2, this.schemaNode2, null, this.schema); + final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildBaseCont); + + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).getConfig(this.iid2); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); + + PutDataTransactionUtil.putData(payload, this.schema, + new NetconfRestconfStrategy(netconfService, iidContext), null, null); + verify(this.netconfService).getConfig(payload.getInstanceIdentifierContext().getInstanceIdentifier()); + verify(this.netconfService).create(LogicalDatastoreType.CONFIGURATION, + payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), Optional.empty()); + } + + @Test + public void testPutReplaceContainerData() { + final InstanceIdentifierContext iidContext = + new InstanceIdentifierContext<>(this.iid2, this.schemaNode2, null, this.schema); + final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildBaseCont); + + doReturn(immediateFluentFuture(Optional.of(mock(NormalizedNode.class)))) + .when(this.netconfService).getConfig(this.iid2); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); + + PutDataTransactionUtil.putData(payload, this.schema, + new NetconfRestconfStrategy(netconfService, iidContext), null, null); + verify(this.netconfService).getConfig(payload.getInstanceIdentifierContext().getInstanceIdentifier()); + verify(this.netconfService).replace(LogicalDatastoreType.CONFIGURATION, + payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), Optional.empty()); + } + + @Test + public void testPutLeafData() { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.iid, this.schemaNode, null, this.schema); final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildLeaf); @@ -244,8 +282,7 @@ public class PutDataTransactionUtilTest { doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); PutDataTransactionUtil.putData(payload, this.schema, - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler), null, - null); + new MdsalRestconfStrategy(iidContext, transactionChainHandler), null, null); verify(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, payload.getInstanceIdentifierContext().getInstanceIdentifier()); verify(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, @@ -253,7 +290,40 @@ public class PutDataTransactionUtilTest { } @Test - public void testPutListData() throws Exception { + public void testPutCreateLeafData() { + final InstanceIdentifierContext iidContext = + new InstanceIdentifierContext<>(this.iid, this.schemaNode, null, this.schema); + final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildLeaf); + + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).getConfig(this.iid); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); + + PutDataTransactionUtil.putData(payload, this.schema, + new NetconfRestconfStrategy(netconfService, iidContext), null, null); + verify(this.netconfService).getConfig(payload.getInstanceIdentifierContext().getInstanceIdentifier()); + verify(this.netconfService).create(LogicalDatastoreType.CONFIGURATION, + payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), Optional.empty()); + } + + @Test + public void testPutReplaceLeafData() { + final InstanceIdentifierContext iidContext = + new InstanceIdentifierContext<>(this.iid, this.schemaNode, null, this.schema); + final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildLeaf); + + doReturn(immediateFluentFuture(Optional.of(mock(NormalizedNode.class)))) + .when(this.netconfService).getConfig(this.iid); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); + + PutDataTransactionUtil.putData(payload, this.schema, + new NetconfRestconfStrategy(netconfService, iidContext), null, null); + verify(this.netconfService).getConfig(payload.getInstanceIdentifierContext().getInstanceIdentifier()); + verify(this.netconfService).replace(LogicalDatastoreType.CONFIGURATION, + payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(), Optional.empty()); + } + + @Test + public void testPutListData() { final InstanceIdentifierContext iidContext = new InstanceIdentifierContext<>(this.iid2, this.schemaNode2, null, this.schema); final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildBaseContWithList); @@ -267,9 +337,42 @@ public class PutDataTransactionUtilTest { payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData()); doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit(); PutDataTransactionUtil.putData(payload, this.schema, - new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler), null, - null); + new MdsalRestconfStrategy(iidContext, transactionChainHandler), null, null); verify(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iid2); verify(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, this.iid2, payload.getData()); } + + @Test + public void testPutCreateListData() { + final InstanceIdentifierContext iidContext = + new InstanceIdentifierContext<>(this.iid2, this.schemaNode2, null, this.schema); + final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildBaseContWithList); + + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService) + .getConfig(this.iid2); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); + + PutDataTransactionUtil.putData(payload, this.schema, + new NetconfRestconfStrategy(netconfService, iidContext), null, null); + verify(this.netconfService).getConfig(this.iid2); + verify(this.netconfService).create(LogicalDatastoreType.CONFIGURATION, this.iid2, + payload.getData(), Optional.empty()); + } + + @Test + public void testPutReplaceListData() { + final InstanceIdentifierContext iidContext = + new InstanceIdentifierContext<>(this.iid2, this.schemaNode2, null, this.schema); + final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildBaseContWithList); + + doReturn(immediateFluentFuture(Optional.of(mock(NormalizedNode.class)))).when(this.netconfService) + .getConfig(this.iid2); + doReturn(CommitInfo.emptyFluentFuture()).when(this.netconfService).commit(Mockito.any()); + + PutDataTransactionUtil.putData(payload, this.schema, + new NetconfRestconfStrategy(netconfService, iidContext), null, null); + verify(this.netconfService).getConfig(this.iid2); + verify(this.netconfService).replace(LogicalDatastoreType.CONFIGURATION, this.iid2, + payload.getData(), Optional.empty()); + } } diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtilTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtilTest.java index 995073f860..41103fd54a 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtilTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtilTest.java @@ -31,13 +31,16 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataBroker; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.WriterParameters; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; import org.opendaylight.restconf.common.errors.RestconfError.ErrorType; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy; +import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.ReadData; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; @@ -58,7 +61,10 @@ public class ReadDataTransactionUtilTest { private static final YangInstanceIdentifier.NodeIdentifier NODE_IDENTIFIER = new YangInstanceIdentifier .NodeIdentifier(QName.create("ns", "2016-02-28", "container")); - private TransactionVarsWrapper wrapper; + private RestconfStrategy mdsalStrategy; + private RestconfStrategy netconfStrategy; + @Mock + private NetconfDataTreeService netconfService; @Mock private DOMTransactionChain transactionChain; @Mock @@ -88,17 +94,22 @@ public class ReadDataTransactionUtilTest { DOMDataBroker mockDataBroker = Mockito.mock(DOMDataBroker.class); Mockito.doReturn(transactionChain).when(mockDataBroker).createTransactionChain(Mockito.any()); - wrapper = new TransactionVarsWrapper(this.context, null, new TransactionChainHandler(mockDataBroker)); + mdsalStrategy = new MdsalRestconfStrategy(this.context, new TransactionChainHandler(mockDataBroker)); + netconfStrategy = new NetconfRestconfStrategy(this.netconfService, this.context); } @Test public void readDataConfigTest() { doReturn(immediateFluentFuture(Optional.of(DATA.data3))).when(read) .read(LogicalDatastoreType.CONFIGURATION, DATA.path); + doReturn(immediateFluentFuture(Optional.of(DATA.data3))).when(this.netconfService).getConfig(DATA.path); doReturn(DATA.path).when(context).getInstanceIdentifier(); final String valueOfContent = RestconfDataServiceConstant.ReadData.CONFIG; - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext); + NormalizedNode normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, mdsalStrategy, schemaContext); + assertEquals(DATA.data3, normalizedNode); + + normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, netconfStrategy, schemaContext); assertEquals(DATA.data3, normalizedNode); } @@ -108,10 +119,16 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.CONFIGURATION, DATA.path); doReturn(immediateFluentFuture(Optional.empty())).when(read) .read(LogicalDatastoreType.OPERATIONAL, DATA.path); + doReturn(immediateFluentFuture(Optional.of(DATA.data3))).when(this.netconfService).getConfig(DATA.path); + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).get(DATA.path); doReturn(DATA.path).when(context).getInstanceIdentifier(); final String valueOfContent = RestconfDataServiceConstant.ReadData.ALL; - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext); + NormalizedNode normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, mdsalStrategy, schemaContext); + assertEquals(DATA.data3, normalizedNode); + + normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, netconfStrategy, schemaContext); assertEquals(DATA.data3, normalizedNode); } @@ -121,10 +138,16 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.OPERATIONAL, DATA.path2); doReturn(immediateFluentFuture(Optional.empty())).when(read) .read(LogicalDatastoreType.CONFIGURATION, DATA.path2); + doReturn(immediateFluentFuture(Optional.of(DATA.data2))).when(this.netconfService).get(DATA.path2); + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).getConfig(DATA.path2); doReturn(DATA.path2).when(context).getInstanceIdentifier(); final String valueOfContent = RestconfDataServiceConstant.ReadData.ALL; - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext); + NormalizedNode normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, mdsalStrategy, schemaContext); + assertEquals(DATA.data2, normalizedNode); + + normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, netconfStrategy, schemaContext); assertEquals(DATA.data2, normalizedNode); } @@ -132,10 +155,15 @@ public class ReadDataTransactionUtilTest { public void readDataNonConfigTest() { doReturn(immediateFluentFuture(Optional.of(DATA.data2))).when(read) .read(LogicalDatastoreType.OPERATIONAL, DATA.path2); + doReturn(immediateFluentFuture(Optional.of(DATA.data2))).when(this.netconfService).get(DATA.path2); doReturn(DATA.path2).when(context).getInstanceIdentifier(); final String valueOfContent = RestconfDataServiceConstant.ReadData.NONCONFIG; - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext); + NormalizedNode normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, mdsalStrategy, schemaContext); + assertEquals(DATA.data2, normalizedNode); + + normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, netconfStrategy, schemaContext); assertEquals(DATA.data2, normalizedNode); } @@ -145,16 +173,22 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.CONFIGURATION, DATA.path); doReturn(immediateFluentFuture(Optional.of(DATA.data4))).when(read) .read(LogicalDatastoreType.OPERATIONAL, DATA.path); + doReturn(immediateFluentFuture(Optional.of(DATA.data3))).when(this.netconfService).getConfig(DATA.path); + doReturn(immediateFluentFuture(Optional.of(DATA.data4))).when(this.netconfService).get(DATA.path); doReturn(DATA.path).when(context).getInstanceIdentifier(); final String valueOfContent = RestconfDataServiceConstant.ReadData.ALL; - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext); final ContainerNode checkingData = Builders .containerBuilder() .withNodeIdentifier(NODE_IDENTIFIER) .withChild(DATA.contentLeaf) .withChild(DATA.contentLeaf2) .build(); + NormalizedNode normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, mdsalStrategy, schemaContext); + assertEquals(checkingData, normalizedNode); + + normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, netconfStrategy, schemaContext); assertEquals(checkingData, normalizedNode); } @@ -164,15 +198,21 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.CONFIGURATION, DATA.path); doReturn(immediateFluentFuture(Optional.of(DATA.data4))).when(read) .read(LogicalDatastoreType.OPERATIONAL, DATA.path); + doReturn(immediateFluentFuture(Optional.of(DATA.data3))).when(this.netconfService).getConfig(DATA.path); + doReturn(immediateFluentFuture(Optional.of(DATA.data4))).when(this.netconfService).get(DATA.path); doReturn(DATA.path).when(context).getInstanceIdentifier(); - final NormalizedNode normalizedNode = ReadDataTransactionUtil.readData( - RestconfDataServiceConstant.ReadData.ALL, wrapper, schemaContext); final ContainerNode checkingData = Builders .containerBuilder() .withNodeIdentifier(NODE_IDENTIFIER) .withChild(DATA.contentLeaf) .withChild(DATA.contentLeaf2) .build(); + NormalizedNode normalizedNode = ReadDataTransactionUtil.readData( + RestconfDataServiceConstant.ReadData.ALL, mdsalStrategy, schemaContext); + assertEquals(checkingData, normalizedNode); + + normalizedNode = ReadDataTransactionUtil.readData( + RestconfDataServiceConstant.ReadData.ALL, netconfStrategy, schemaContext); assertEquals(checkingData, normalizedNode); } @@ -182,15 +222,21 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.OPERATIONAL, DATA.path3); doReturn(immediateFluentFuture(Optional.of(DATA.listData2))).when(read) .read(LogicalDatastoreType.CONFIGURATION, DATA.path3); + doReturn(immediateFluentFuture(Optional.of(DATA.listData))).when(this.netconfService).get(DATA.path3); + doReturn(immediateFluentFuture(Optional.of(DATA.listData2))).when(this.netconfService).getConfig(DATA.path3); doReturn(DATA.path3).when(context).getInstanceIdentifier(); final String valueOfContent = RestconfDataServiceConstant.ReadData.ALL; - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext); final MapNode checkingData = Builders .mapBuilder() .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create("ns", "2016-02-28", "list"))) .withChild(DATA.checkData) .build(); + NormalizedNode normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, mdsalStrategy, schemaContext); + assertEquals(checkingData, normalizedNode); + + normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, netconfStrategy, schemaContext); assertEquals(checkingData, normalizedNode); } @@ -200,14 +246,19 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.OPERATIONAL, DATA.path3); doReturn(immediateFluentFuture(Optional.of(DATA.orderedMapNode2))).when(read) .read(LogicalDatastoreType.CONFIGURATION, DATA.path3); + doReturn(immediateFluentFuture(Optional.of(DATA.orderedMapNode1))).when(this.netconfService).get(DATA.path3); + doReturn(immediateFluentFuture(Optional.of(DATA.orderedMapNode2))).when(this.netconfService) + .getConfig(DATA.path3); doReturn(DATA.path3).when(context).getInstanceIdentifier(); - - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.ALL, wrapper, schemaContext); - final MapNode expectedData = Builders.orderedMapBuilder() .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DATA.listQname)).withChild(DATA.checkData) .build(); + NormalizedNode normalizedNode = ReadDataTransactionUtil + .readData(RestconfDataServiceConstant.ReadData.ALL, mdsalStrategy, schemaContext); + assertEquals(expectedData, normalizedNode); + + normalizedNode = ReadDataTransactionUtil + .readData(RestconfDataServiceConstant.ReadData.ALL, netconfStrategy, schemaContext); assertEquals(expectedData, normalizedNode); } @@ -217,17 +268,22 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.OPERATIONAL, DATA.path3); doReturn(immediateFluentFuture(Optional.of(DATA.unkeyedListNode2))).when(read) .read(LogicalDatastoreType.CONFIGURATION, DATA.path3); + doReturn(immediateFluentFuture(Optional.of(DATA.unkeyedListNode1))).when(this.netconfService).get(DATA.path3); + doReturn(immediateFluentFuture(Optional.of(DATA.unkeyedListNode2))).when(this.netconfService) + .getConfig(DATA.path3); doReturn(DATA.path3).when(context).getInstanceIdentifier(); - - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.ALL, wrapper, schemaContext); - final UnkeyedListNode expectedData = Builders.unkeyedListBuilder() .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DATA.listQname)) .withChild(Builders.unkeyedListEntryBuilder().withNodeIdentifier( new YangInstanceIdentifier.NodeIdentifier(DATA.listQname)) .withChild(DATA.unkeyedListEntryNode1.getValue().iterator().next()) .withChild(DATA.unkeyedListEntryNode2.getValue().iterator().next()).build()).build(); + NormalizedNode normalizedNode = ReadDataTransactionUtil + .readData(RestconfDataServiceConstant.ReadData.ALL, mdsalStrategy, schemaContext); + assertEquals(expectedData, normalizedNode); + + normalizedNode = ReadDataTransactionUtil + .readData(RestconfDataServiceConstant.ReadData.ALL, netconfStrategy, schemaContext); assertEquals(expectedData, normalizedNode); } @@ -237,15 +293,21 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.OPERATIONAL, DATA.leafSetNodePath); doReturn(immediateFluentFuture(Optional.of(DATA.leafSetNode2))).when(read) .read(LogicalDatastoreType.CONFIGURATION, DATA.leafSetNodePath); + doReturn(immediateFluentFuture(Optional.of(DATA.leafSetNode1))).when(this.netconfService) + .get(DATA.leafSetNodePath); + doReturn(immediateFluentFuture(Optional.of(DATA.leafSetNode2))).when(this.netconfService) + .getConfig(DATA.leafSetNodePath); doReturn(DATA.leafSetNodePath).when(context).getInstanceIdentifier(); - - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.ALL, wrapper, schemaContext); - final LeafSetNode expectedData = Builders.leafSetBuilder().withNodeIdentifier( new YangInstanceIdentifier.NodeIdentifier(DATA.leafListQname)).withValue( ImmutableList.>builder().addAll(DATA.leafSetNode1.getValue()) .addAll(DATA.leafSetNode2.getValue()).build()).build(); + NormalizedNode normalizedNode = ReadDataTransactionUtil + .readData(RestconfDataServiceConstant.ReadData.ALL, mdsalStrategy, schemaContext); + assertEquals(expectedData, normalizedNode); + + normalizedNode = ReadDataTransactionUtil + .readData(RestconfDataServiceConstant.ReadData.ALL, netconfStrategy, schemaContext); assertEquals(expectedData, normalizedNode); } @@ -255,15 +317,21 @@ public class ReadDataTransactionUtilTest { .read(LogicalDatastoreType.OPERATIONAL, DATA.leafSetNodePath); doReturn(immediateFluentFuture(Optional.of(DATA.orderedLeafSetNode2))).when(read) .read(LogicalDatastoreType.CONFIGURATION, DATA.leafSetNodePath); + doReturn(immediateFluentFuture(Optional.of(DATA.orderedLeafSetNode1))).when(this.netconfService) + .get(DATA.leafSetNodePath); + doReturn(immediateFluentFuture(Optional.of(DATA.orderedLeafSetNode2))).when(this.netconfService) + .getConfig(DATA.leafSetNodePath); doReturn(DATA.leafSetNodePath).when(context).getInstanceIdentifier(); - - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.ALL, wrapper, schemaContext); - final LeafSetNode expectedData = Builders.orderedLeafSetBuilder().withNodeIdentifier( new YangInstanceIdentifier.NodeIdentifier(DATA.leafListQname)).withValue( ImmutableList.>builder().addAll(DATA.orderedLeafSetNode1.getValue()) .addAll(DATA.orderedLeafSetNode2.getValue()).build()).build(); + NormalizedNode normalizedNode = ReadDataTransactionUtil + .readData(RestconfDataServiceConstant.ReadData.ALL, mdsalStrategy, schemaContext); + assertEquals(expectedData, normalizedNode); + + normalizedNode = ReadDataTransactionUtil + .readData(RestconfDataServiceConstant.ReadData.ALL, netconfStrategy, schemaContext); assertEquals(expectedData, normalizedNode); } @@ -271,18 +339,27 @@ public class ReadDataTransactionUtilTest { public void readDataWrongPathOrNoContentTest() { doReturn(immediateFluentFuture(Optional.empty())).when(read) .read(LogicalDatastoreType.CONFIGURATION, DATA.path2); + doReturn(immediateFluentFuture(Optional.empty())).when(this.netconfService).getConfig(DATA.path2); doReturn(DATA.path2).when(context).getInstanceIdentifier(); final String valueOfContent = RestconfDataServiceConstant.ReadData.CONFIG; - final NormalizedNode normalizedNode = - ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext); + NormalizedNode normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, mdsalStrategy, schemaContext); + assertNull(normalizedNode); + + normalizedNode = + ReadDataTransactionUtil.readData(valueOfContent, netconfStrategy, schemaContext); assertNull(normalizedNode); } @Test(expected = RestconfDocumentedException.class) public void readDataFailTest() { final String valueOfContent = RestconfDataServiceConstant.ReadData.READ_TYPE_TX; - final NormalizedNode normalizedNode = ReadDataTransactionUtil.readData( - valueOfContent, wrapper, schemaContext); + NormalizedNode normalizedNode = ReadDataTransactionUtil.readData( + valueOfContent, mdsalStrategy, schemaContext); + assertNull(normalizedNode); + + normalizedNode = ReadDataTransactionUtil.readData( + valueOfContent, netconfStrategy, schemaContext); assertNull(normalizedNode); }