X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-rest-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fsal%2Frestconf%2Fimpl%2FRestconfImpl.java;h=cd860efab75073e13cf0cd2cfd93e12fd7200a5e;hp=fac6c80564784759e08aef3db46ebada1dba69fe;hb=531621aac4cff9d39cbd8668a53bdeba8a0e6d81;hpb=f3bc7a6b7d0326e5594604cdc144b967c2a9cdb4 diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java index fac6c80564..cd860efab7 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java @@ -9,19 +9,22 @@ package org.opendaylight.controller.sal.restconf.impl; import com.google.common.base.Objects; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.google.common.base.Splitter; import com.google.common.base.Strings; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; - +import java.math.BigInteger; import java.net.URI; +import java.net.URISyntaxException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -33,10 +36,11 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; - import org.apache.commons.lang3.StringUtils; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException; +import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint; import org.opendaylight.controller.sal.rest.api.Draft02; @@ -52,7 +56,6 @@ import org.opendaylight.controller.sal.streams.websockets.WebSocketServer; import org.opendaylight.yangtools.concepts.Codec; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; -import org.opendaylight.yangtools.yang.common.RpcError; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode; @@ -63,6 +66,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceI import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.ModifiedNodeDoesNotExistException; import org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser.CnSnToNormalizedNodeParserFactory; import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; import org.opendaylight.yangtools.yang.data.impl.NodeFactory; @@ -87,6 +91,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RestconfImpl implements RestconfService { + private enum UriParameters { PRETTY_PRINT("prettyPrint"), DEPTH("depth"); @@ -103,6 +108,8 @@ public class RestconfImpl implements RestconfService { } } + + private final static RestconfImpl INSTANCE = new RestconfImpl(); private static final int NOTIFICATION_PORT = 8181; @@ -135,13 +142,24 @@ public class RestconfImpl implements RestconfService { private static final String SCOPE_PARAM_NAME = "scope"; + private static final String NETCONF_BASE = "urn:ietf:params:xml:ns:netconf:base:1.0"; + + private static final String NETCONF_BASE_PAYLOAD_NAME = "data"; + + private static final QName NETCONF_BASE_QNAME; + static { try { EVENT_SUBSCRIPTION_AUGMENT_REVISION = new SimpleDateFormat("yyyy-MM-dd").parse("2014-07-08"); + NETCONF_BASE_QNAME = QName.create(QNameModule.create(new URI(NETCONF_BASE), null), NETCONF_BASE_PAYLOAD_NAME ); } catch (ParseException e) { throw new RestconfDocumentedException( "It wasn't possible to convert revision date of sal-remote-augment to date", ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED); + } catch (URISyntaxException e) { + throw new RestconfDocumentedException( + "It wasn't possible to create instance of URI class with "+NETCONF_BASE+" URI", ErrorType.APPLICATION, + ErrorTag.OPERATION_FAILED); } } @@ -205,7 +223,7 @@ public class RestconfImpl implements RestconfService { Set modules = null; DOMMountPoint mountPoint = null; if (identifier.contains(ControllerContext.MOUNT)) { - InstanceIdWithSchemaNode mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier); + InstanceIdentifierContext mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier); mountPoint = mountPointIdentifier.getMountPoint(); modules = this.controllerContext.getAllModules(mountPoint); } else { @@ -236,7 +254,7 @@ public class RestconfImpl implements RestconfService { Module module = null; DOMMountPoint mountPoint = null; if (identifier.contains(ControllerContext.MOUNT)) { - InstanceIdWithSchemaNode mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier); + InstanceIdentifierContext mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier); mountPoint = mountPointIdentifier.getMountPoint(); module = this.controllerContext.findModuleByNameAndRevision(mountPoint, moduleNameAndRevision); } else { @@ -267,7 +285,7 @@ public class RestconfImpl implements RestconfService { Set modules = null; DOMMountPoint mountPoint = null; if (identifier.contains(ControllerContext.MOUNT)) { - InstanceIdWithSchemaNode mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier); + InstanceIdentifierContext mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier); mountPoint = mountPointIdentifier.getMountPoint(); modules = this.controllerContext.getAllModules(mountPoint); } else { @@ -518,7 +536,7 @@ public class RestconfImpl implements RestconfService { DOMMountPoint mountPoint = null; if (identifier.contains(ControllerContext.MOUNT)) { // mounted RPC call - look up mount instance. - InstanceIdWithSchemaNode mountPointId = controllerContext.toMountPointIdentifier(identifier); + InstanceIdentifierContext mountPointId = controllerContext.toMountPointIdentifier(identifier); mountPoint = mountPointId.getMountPoint(); int startOfRemoteRpcName = identifier.lastIndexOf(ControllerContext.MOUNT) @@ -609,25 +627,14 @@ public class RestconfImpl implements RestconfService { private void checkRpcSuccessAndThrowException(final RpcResult rpcResult) { if (rpcResult.isSuccessful() == false) { - Collection rpcErrors = rpcResult.getErrors(); - if (rpcErrors == null || rpcErrors.isEmpty()) { - throw new RestconfDocumentedException( - "The operation was not successful and there were no RPC errors returned", ErrorType.RPC, - ErrorTag.OPERATION_FAILED); - } - - List errorList = Lists.newArrayList(); - for (RpcError rpcError : rpcErrors) { - errorList.add(new RestconfError(rpcError)); - } - - throw new RestconfDocumentedException(errorList); + throw new RestconfDocumentedException("The operation was not successful", null, + rpcResult.getErrors()); } } @Override - public StructuredData readConfigurationData(final String identifier, final UriInfo uriInfo) { - final InstanceIdWithSchemaNode iiWithData = controllerContext.toInstanceIdentifier(identifier); + public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) { + final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier); DOMMountPoint mountPoint = iiWithData.getMountPoint(); NormalizedNode data = null; YangInstanceIdentifier normalizedII; @@ -639,12 +646,7 @@ public class RestconfImpl implements RestconfService { normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier()); data = broker.readConfigurationData(normalizedII); } - - final CompositeNode compositeNode = datastoreNormalizedNodeToCompositeNode(data, iiWithData.getSchemaNode()); - final CompositeNode prunedCompositeNode = pruneDataAtDepth(compositeNode, parseDepthParameter(uriInfo)); - - final boolean prettyPrintMode = parsePrettyPrintParameter(uriInfo); - return new StructuredData(prunedCompositeNode, iiWithData.getSchemaNode(), mountPoint, prettyPrintMode); + return new NormalizedNodeContext(iiWithData, data); } @SuppressWarnings("unchecked") @@ -690,8 +692,8 @@ public class RestconfImpl implements RestconfService { } @Override - public StructuredData readOperationalData(final String identifier, final UriInfo info) { - final InstanceIdWithSchemaNode iiWithData = controllerContext.toInstanceIdentifier(identifier); + public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo info) { + final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier); DOMMountPoint mountPoint = iiWithData.getMountPoint(); NormalizedNode data = null; YangInstanceIdentifier normalizedII; @@ -704,11 +706,7 @@ public class RestconfImpl implements RestconfService { data = broker.readOperationalData(normalizedII); } - final CompositeNode compositeNode = datastoreNormalizedNodeToCompositeNode(data, iiWithData.getSchemaNode()); - final CompositeNode prunedCompositeNode = pruneDataAtDepth(compositeNode, parseDepthParameter(info)); - - final boolean prettyPrintMode = parsePrettyPrintParameter(info); - return new StructuredData(prunedCompositeNode, iiWithData.getSchemaNode(), mountPoint, prettyPrintMode); + return new NormalizedNodeContext(iiWithData, data); } private boolean parsePrettyPrintParameter(final UriInfo info) { @@ -718,34 +716,91 @@ public class RestconfImpl implements RestconfService { @Override public Response updateConfigurationData(final String identifier, final Node payload) { - final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier); + final InstanceIdentifierContext iiWithData = this.controllerContext.toInstanceIdentifier(identifier); validateInput(iiWithData.getSchemaNode(), payload); DOMMountPoint mountPoint = iiWithData.getMountPoint(); + validateTopLevelNodeName(payload, iiWithData.getInstanceIdentifier()); final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint); validateListKeysEqualityInPayloadAndUri(iiWithData, value); final NormalizedNode datastoreNormalizedNode = compositeNodeToDatastoreNormalizedNode(value, iiWithData.getSchemaNode()); + YangInstanceIdentifier normalizedII; + if (mountPoint != null) { + normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized( + iiWithData.getInstanceIdentifier()); + } else { + normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier()); + } - try { - if (mountPoint != null) { - normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData - .getInstanceIdentifier()); - broker.commitConfigurationDataPut(mountPoint, normalizedII, datastoreNormalizedNode).get(); - } else { - normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier()); - broker.commitConfigurationDataPut(normalizedII, datastoreNormalizedNode).get(); + /* + * There is a small window where another write transaction could be updating the same data + * simultaneously and we get an OptimisticLockFailedException. This error is likely + * transient and The WriteTransaction#submit API docs state that a retry will likely + * succeed. So we'll try again if that scenario occurs. If it fails a third time then it + * probably will never succeed so we'll fail in that case. + * + * By retrying we're attempting to hide the internal implementation of the data store and + * how it handles concurrent updates from the restconf client. The client has instructed us + * to put the data and we should make every effort to do so without pushing optimistic lock + * failures back to the client and forcing them to handle it via retry (and having to + * document the behavior). + */ + int tries = 2; + while(true) { + try { + if (mountPoint != null) { + broker.commitConfigurationDataPut(mountPoint, normalizedII, + datastoreNormalizedNode).checkedGet(); + } else { + broker.commitConfigurationDataPut(normalizedII, + datastoreNormalizedNode).checkedGet(); + } + + break; + } catch (TransactionCommitFailedException e) { + if(e instanceof OptimisticLockFailedException) { + if(--tries <= 0) { + LOG.debug("Got OptimisticLockFailedException on last try - failing"); + throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList()); + } + + LOG.debug("Got OptimisticLockFailedException - trying again"); + } else { + throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList()); + } } - } catch (Exception e) { - throw new RestconfDocumentedException("Error updating data", e); } return Response.status(Status.OK).build(); } + private void validateTopLevelNodeName(final Node node, + final YangInstanceIdentifier identifier) { + final String payloadName = getName(node); + final Iterator pathArguments = identifier.getReversePathArguments().iterator(); + + //no arguments + if (!pathArguments.hasNext()) { + //no "data" payload + if (!node.getNodeType().equals(NETCONF_BASE_QNAME)) { + throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument", + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + //any arguments + } else { + final String identifierName = pathArguments.next().getNodeType().getLocalName(); + if (!payloadName.equals(identifierName)) { + throw new RestconfDocumentedException("Payload name (" + payloadName + + ") is different from identifier name (" + identifierName + ")", ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE); + } + } + } + /** * Validates whether keys in {@code payload} are equal to values of keys in {@code iiWithData} for list schema node * @@ -753,7 +808,7 @@ public class RestconfImpl implements RestconfService { * if key values or key count in payload and URI isn't equal * */ - private void validateListKeysEqualityInPayloadAndUri(final InstanceIdWithSchemaNode iiWithData, + private void validateListKeysEqualityInPayloadAndUri(final InstanceIdentifierContext iiWithData, final CompositeNode payload) { if (iiWithData.getSchemaNode() instanceof ListSchemaNode) { final List keyDefinitions = ((ListSchemaNode) iiWithData.getSchemaNode()).getKeyDefinition(); @@ -804,7 +859,7 @@ public class RestconfImpl implements RestconfService { ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE); } - InstanceIdWithSchemaNode iiWithData = null; + InstanceIdentifierContext iiWithData = null; CompositeNode value = null; if (this.representsMountPointRootData(payload)) { // payload represents mount point data and URI represents path to the mount point @@ -820,7 +875,7 @@ public class RestconfImpl implements RestconfService { value = this.normalizeNode(payload, iiWithData.getSchemaNode(), iiWithData.getMountPoint()); } else { - final InstanceIdWithSchemaNode incompleteInstIdWithData = this.controllerContext + final InstanceIdentifierContext incompleteInstIdWithData = this.controllerContext .toInstanceIdentifier(identifier); final DataNodeContainer parentSchema = (DataNodeContainer) incompleteInstIdWithData.getSchemaNode(); DOMMountPoint mountPoint = incompleteInstIdWithData.getMountPoint(); @@ -835,7 +890,7 @@ public class RestconfImpl implements RestconfService { parentSchema, payloadName, module.getNamespace()); value = this.normalizeNode(payload, schemaNode, mountPoint); - iiWithData = addLastIdentifierFromData(incompleteInstIdWithData, value, schemaNode); + iiWithData = addLastIdentifierFromData(incompleteInstIdWithData, value, schemaNode,incompleteInstIdWithData.getSchemaContext()); } final NormalizedNode datastoreNormalizedData = compositeNodeToDatastoreNormalizedNode(value, @@ -852,6 +907,8 @@ public class RestconfImpl implements RestconfService { normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier()); broker.commitConfigurationDataPost(normalizedII, datastoreNormalizedData); } + } catch(RestconfDocumentedException e) { + throw e; } catch (Exception e) { throw new RestconfDocumentedException("Error creating data", e); } @@ -883,7 +940,7 @@ public class RestconfImpl implements RestconfService { final DataSchemaNode schemaNode = ControllerContext.findInstanceDataChildByNameAndNamespace(module, payloadName, module.getNamespace()); final CompositeNode value = this.normalizeNode(payload, schemaNode, null); - final InstanceIdWithSchemaNode iiWithData = this.addLastIdentifierFromData(null, value, schemaNode); + final InstanceIdentifierContext iiWithData = this.addLastIdentifierFromData(null, value, schemaNode,ControllerContext.getInstance().getGlobalSchema()); final NormalizedNode datastoreNormalizedData = compositeNodeToDatastoreNormalizedNode(value, schemaNode); DOMMountPoint mountPoint = iiWithData.getMountPoint(); YangInstanceIdentifier normalizedII; @@ -898,6 +955,8 @@ public class RestconfImpl implements RestconfService { normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier()); broker.commitConfigurationDataPost(normalizedII, datastoreNormalizedData); } + } catch(RestconfDocumentedException e) { + throw e; } catch (Exception e) { throw new RestconfDocumentedException("Error creating data", e); } @@ -907,7 +966,7 @@ public class RestconfImpl implements RestconfService { @Override public Response deleteConfigurationData(final String identifier) { - final InstanceIdWithSchemaNode iiWithData = controllerContext.toInstanceIdentifier(identifier); + final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier); DOMMountPoint mountPoint = iiWithData.getMountPoint(); YangInstanceIdentifier normalizedII; @@ -921,9 +980,13 @@ public class RestconfImpl implements RestconfService { broker.commitConfigurationDataDelete(normalizedII).get(); } } catch (Exception e) { - throw new RestconfDocumentedException("Error creating data", e); + final Optional searchedException = Iterables.tryFind(Throwables.getCausalChain(e), + Predicates.instanceOf(ModifiedNodeDoesNotExistException.class)); + if (searchedException.isPresent()) { + throw new RestconfDocumentedException("Data specified for deleting doesn't exist.", ErrorType.APPLICATION, ErrorTag.DATA_MISSING); + } + throw new RestconfDocumentedException("Error while deleting data", e); } - return Response.status(Status.OK).build(); } @@ -1073,8 +1136,8 @@ public class RestconfImpl implements RestconfService { return module; } - private InstanceIdWithSchemaNode addLastIdentifierFromData(final InstanceIdWithSchemaNode identifierWithSchemaNode, - final CompositeNode data, final DataSchemaNode schemaOfData) { + private InstanceIdentifierContext addLastIdentifierFromData(final InstanceIdentifierContext identifierWithSchemaNode, + final CompositeNode data, final DataSchemaNode schemaOfData, SchemaContext schemaContext) { YangInstanceIdentifier instanceIdentifier = null; if (identifierWithSchemaNode != null) { instanceIdentifier = identifierWithSchemaNode.getInstanceIdentifier(); @@ -1097,11 +1160,12 @@ public class RestconfImpl implements RestconfService { YangInstanceIdentifier instance = iiBuilder.toInstance(); DOMMountPoint mountPoint = null; + SchemaContext schemaCtx = null; if (identifierWithSchemaNode != null) { mountPoint = identifierWithSchemaNode.getMountPoint(); } - return new InstanceIdWithSchemaNode(instance, schemaOfData, mountPoint); + return new InstanceIdentifierContext(instance, schemaOfData, mountPoint,schemaContext); } private HashMap resolveKeysFromData(final ListSchemaNode listNode, final CompositeNode dataNode) { @@ -1156,8 +1220,9 @@ public class RestconfImpl implements RestconfService { private CompositeNode normalizeNode(final Node node, final DataSchemaNode schema, final DOMMountPoint mountPoint) { if (schema == null) { - QName nodeType = node == null ? null : node.getNodeType(); - String localName = nodeType == null ? null : nodeType.getLocalName(); + String localName = node == null ? null : + node instanceof NodeWrapper ? ((NodeWrapper)node).getLocalName() : + node.getNodeType().getLocalName(); throw new RestconfDocumentedException("Data schema node was not found for " + localName, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE); @@ -1488,16 +1553,16 @@ public class RestconfImpl implements RestconfService { "It wasn't possible to translate specified data to datastore readable form.")); } - private InstanceIdWithSchemaNode normalizeInstanceIdentifierWithSchemaNode( - final InstanceIdWithSchemaNode iiWithSchemaNode) { + private InstanceIdentifierContext normalizeInstanceIdentifierWithSchemaNode( + final InstanceIdentifierContext iiWithSchemaNode) { return normalizeInstanceIdentifierWithSchemaNode(iiWithSchemaNode, false); } - private InstanceIdWithSchemaNode normalizeInstanceIdentifierWithSchemaNode( - final InstanceIdWithSchemaNode iiWithSchemaNode, final boolean unwrapLastListNode) { - return new InstanceIdWithSchemaNode(instanceIdentifierToReadableFormForNormalizeNode( + private InstanceIdentifierContext normalizeInstanceIdentifierWithSchemaNode( + final InstanceIdentifierContext iiWithSchemaNode, final boolean unwrapLastListNode) { + return new InstanceIdentifierContext(instanceIdentifierToReadableFormForNormalizeNode( iiWithSchemaNode.getInstanceIdentifier(), unwrapLastListNode), iiWithSchemaNode.getSchemaNode(), - iiWithSchemaNode.getMountPoint()); + iiWithSchemaNode.getMountPoint(),iiWithSchemaNode.getSchemaContext()); } private YangInstanceIdentifier instanceIdentifierToReadableFormForNormalizeNode( @@ -1534,4 +1599,9 @@ public class RestconfImpl implements RestconfService { } return false; } + + public BigInteger getOperationalReceived() { + // TODO Auto-generated method stub + return null; + } }