-/**
+/*
* Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
checkPreconditions();
- return putDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
+ return putDataViaTransaction(domDataBroker, CONFIGURATION, path, payload, globalSchema);
}
public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
if (domDataBrokerService.isPresent()) {
- return putDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
+ return putDataViaTransaction(domDataBrokerService.get(), CONFIGURATION, path,
payload, mountPoint.getSchemaContext());
}
final String errMsg = "DOM data broker service isn't available for mount point " + path;
throw new RestconfDocumentedException(errMsg);
}
+ public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
+ final SchemaContext globalSchema) {
+ final DOMDataReadWriteTransaction patchTransaction = domDataBroker.newReadWriteTransaction();
+ List<PATCHStatusEntity> editCollection = new ArrayList<>();
+ List<RestconfError> editErrors;
+ List<RestconfError> globalErrors = null;
+ int errorCounter = 0;
+
+ for (PATCHEntity patchEntity : context.getData()) {
+ final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
+
+ switch (operation) {
+ case CREATE:
+ if (errorCounter == 0) {
+ try {
+ postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
+ patchEntity.getNode(), globalSchema);
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (RestconfDocumentedException e) {
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+ errorCounter++;
+ }
+ }
+ break;
+ case REPLACE:
+ if (errorCounter == 0) {
+ try {
+ putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode(), patchEntity.getNode(), globalSchema);
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (RestconfDocumentedException e) {
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+ errorCounter++;
+ }
+ }
+ break;
+ case DELETE:
+ if (errorCounter == 0) {
+ try {
+ deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (RestconfDocumentedException e) {
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+ errorCounter++;
+ }
+ }
+ break;
+ case REMOVE:
+ if (errorCounter == 0) {
+ try {
+ deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (RestconfDocumentedException e) {
+ LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
+ patchEntity.getEditId(), e);
+ }
+ }
+ break;
+ }
+ }
+
+ //TODO: make sure possible global errors are filled up correctly and decide transaction submission based on that
+ //globalErrors = new ArrayList<>();
+ if (errorCounter == 0) {
+ final CheckedFuture<Void, TransactionCommitFailedException> submit = patchTransaction.submit();
+ return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true,
+ globalErrors);
+ } else {
+ patchTransaction.cancel();
+ return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
+ globalErrors);
+ }
+ }
+
// POST configuration
public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
checkPreconditions();
- return postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
+ return postDataViaTransaction(domDataBroker, CONFIGURATION, path, payload, globalSchema);
}
public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
if (domDataBrokerService.isPresent()) {
- return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
+ return postDataViaTransaction(domDataBrokerService.get(), CONFIGURATION, path,
payload, mountPoint.getSchemaContext());
}
final String errMsg = "DOM data broker service isn't available for mount point " + path;
if (rpcService == null) {
throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
}
+ LOG.trace("Invoke RPC {} with input: {}", type, input);
return rpcService.invokeRpc(type, input);
}
private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
- LOG.trace("Read " + datastore.name() + " via Restconf: {}", path);
+ LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
if (listenableFuture != null) {
Optional<NormalizedNode<?, ?>> optional;
LOG.debug("Reading result data from transaction.");
optional = listenableFuture.get();
} catch (InterruptedException | ExecutionException e) {
- LOG.warn("Exception by reading " + datastore.name() + " via Restconf: {}", path, e);
+ LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, e);
throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
}
}
private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
+ final DOMDataBroker domDataBroker, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
+ // FIXME: This is doing correct post for container and list children
+ // not sure if this will work for choice case
+ DOMDataReadWriteTransaction transaction = domDataBroker.newReadWriteTransaction();
+ if(payload instanceof MapNode) {
+ LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ try {
+ transaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ } catch (RuntimeException e) {
+ // FIXME: Figure out and catch specific RunTimeExceptions thrown by NETCONF instead of generic one.
+ // to make this cleaner and easier to maintain.
+ transaction.cancel();
+ transaction = domDataBroker.newReadWriteTransaction();
+ LOG.debug("Empty subtree merge failed", e);
+ }
+ if (!ensureParentsByMerge(datastore, path, transaction, schemaContext)) {
+ transaction.cancel();
+ transaction = domDataBroker.newReadWriteTransaction();
+ }
+ for(final MapEntryNode child : ((MapNode) payload).getValue()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ checkItemDoesNotExists(transaction, datastore, childPath);
+ transaction.put(datastore, childPath, child);
+ }
+ } else {
+ checkItemDoesNotExists(transaction,datastore, path);
+ if(!ensureParentsByMerge(datastore, path, transaction, schemaContext)) {
+ transaction.cancel();
+ transaction = domDataBroker.newReadWriteTransaction();
+ }
+ transaction.put(datastore, path, payload);
+ }
+ return transaction.submit();
+ }
+
+ private void postDataWithinTransaction(
final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
// FIXME: This is doing correct post for container and list children
// not sure if this will work for choice case
if(payload instanceof MapNode) {
- LOG.trace("POST " + datastore.name() + " via Restconf: {} with payload {}", path, payload);
+ LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
rWTransaction.put(datastore, path, payload);
}
- return rWTransaction.submit();
}
private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
try {
if (futureDatastoreData.get()) {
final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
- LOG.trace(errMsg + ":{}", path);
+ LOG.trace("{}:{}", errMsg, path);
rWTransaction.cancel();
throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
ErrorTag.DATA_EXISTS);
}
} catch (InterruptedException | ExecutionException e) {
- LOG.warn("It wasn't possible to get data loaded from datastore at path " + path, e);
+ LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
}
}
private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
+ final DOMDataBroker domDataBroker, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext)
+ {
+ DOMDataReadWriteTransaction transaction = domDataBroker.newReadWriteTransaction();
+ LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
+ if (!ensureParentsByMerge(datastore, path, transaction, schemaContext)) {
+ transaction.cancel();
+ transaction = domDataBroker.newReadWriteTransaction();
+ }
+ transaction.put(datastore, path, payload);
+ return transaction.submit();
+ }
+
+ private void putDataWithinTransaction(
final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
- LOG.trace("Put " + datastore.name() + " via Restconf: {} with payload {}", path, payload);
+ LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
writeTransaction.put(datastore, path, payload);
- return writeTransaction.submit();
}
private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
final YangInstanceIdentifier path) {
- LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path);
+ LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
writeTransaction.delete(datastore, path);
return writeTransaction.submit();
}
+ private void deleteDataWithinTransaction(
+ final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path) {
+ LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
+ writeTransaction.delete(datastore, path);
+ }
+
public void setDomDataBroker(final DOMDataBroker domDataBroker) {
this.domDataBroker = domDataBroker;
}
- private void ensureParentsByMerge(final LogicalDatastoreType store,
+ private boolean ensureParentsByMerge(final LogicalDatastoreType store,
final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
+
+ boolean mergeResult = true;
final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
YangInstanceIdentifier rootNormalizedPath = null;
// No parent structure involved, no need to ensure parents
if(normalizedPathWithoutChildArgs.isEmpty()) {
- return;
+ return mergeResult;
}
Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
final NormalizedNode<?, ?> parentStructure =
ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
- rwTx.merge(store, rootNormalizedPath, parentStructure);
+ try {
+ rwTx.merge(store, rootNormalizedPath, parentStructure);
+ } catch (RuntimeException e) {
+ /*
+ * Catching the exception here, logging it and proceeding further
+ * for the following reasons.
+ *
+ * 1. For MD-SAL store if it fails we'll go with the next call
+ * anyway and let the failure happen there. 2. For NETCONF devices
+ * that can not handle these calls such as creation of empty lists
+ * etc, instead of failing we'll go with the actual call. Devices
+ * should be able to handle the actual calls made without the need
+ * to create parents. So instead of failing we will give a device a
+ * chance to configure the management entity in question. 3. If this
+ * merge call is handled properly by MD-SAL data store or a Netconf
+ * device this is a no-op.
+ */
+ // FIXME: Figure out and catch specific RunTimeExceptions thrown by NETCONF instead of generic one.
+ // to make this cleaner and easier to maintain.
+ mergeResult = false;
+ LOG.debug("Exception while creating the parent in ensureParentsByMerge. Proceeding with the actual request", e);
+ }
+ return mergeResult;
}
}