throw new RestconfDocumentedException(errMsg);
}
- public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
- final SchemaContext globalSchema)
+ public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext patchContext)
throws TransactionCommitFailedException {
- final DOMDataReadWriteTransaction patchTransaction = this.domDataBroker.newReadWriteTransaction();
+ final DOMMountPoint mountPoint = patchContext.getInstanceIdentifierContext().getMountPoint();
+
+ // get new transaction and schema context on server or on mounted device
+ final SchemaContext schemaContext;
+ final DOMDataReadWriteTransaction patchTransaction;
+ if (mountPoint == null) {
+ schemaContext = patchContext.getInstanceIdentifierContext().getSchemaContext();
+ patchTransaction = this.domDataBroker.newReadWriteTransaction();
+ } else {
+ schemaContext = mountPoint.getSchemaContext();
+
+ final Optional<DOMDataBroker> optional = mountPoint.getService(DOMDataBroker.class);
+
+ if (optional.isPresent()) {
+ patchTransaction = optional.get().newReadWriteTransaction();
+ } else {
+ // if mount point does not have broker it is not possible to continue and global error is reported
+ LOG.error("Http PATCH {} has failed - device {} does not support broker service",
+ patchContext.getPatchId(), mountPoint.getIdentifier());
+ return new PATCHStatusContext(
+ patchContext.getPatchId(),
+ null,
+ false,
+ ImmutableList.of(new RestconfError(
+ ErrorType.APPLICATION,
+ ErrorTag.OPERATION_FAILED,
+ "DOM data broker service isn't available for mount point "
+ + mountPoint.getIdentifier()))
+ );
+ }
+ }
+
final List<PATCHStatusEntity> editCollection = new ArrayList<>();
List<RestconfError> editErrors;
- final List<RestconfError> globalErrors = null;
- int errorCounter = 0;
+ boolean withoutError = true;
- for (final PATCHEntity patchEntity : context.getData()) {
+ for (final PATCHEntity patchEntity : patchContext.getData()) {
final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
switch (operation) {
case CREATE:
- if (errorCounter == 0) {
+ if (withoutError) {
try {
postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
- patchEntity.getNode(), globalSchema);
+ patchEntity.getNode(), schemaContext);
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
} catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http PATCH operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
editErrors = new ArrayList<>();
editErrors.addAll(e.getErrors());
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
- errorCounter++;
+ withoutError = false;
}
}
break;
case REPLACE:
- if (errorCounter == 0) {
+ if (withoutError) {
try {
putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
- .getTargetNode(), patchEntity.getNode(), globalSchema);
+ .getTargetNode(), patchEntity.getNode(), schemaContext);
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
} catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http PATCH operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
editErrors = new ArrayList<>();
editErrors.addAll(e.getErrors());
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
- errorCounter++;
+ withoutError = false;
}
}
break;
case DELETE:
- if (errorCounter == 0) {
+ if (withoutError) {
try {
deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
.getTargetNode());
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
} catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http PATCH operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
editErrors = new ArrayList<>();
editErrors.addAll(e.getErrors());
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
- errorCounter++;
+ withoutError = false;
}
}
break;
case REMOVE:
- if (errorCounter == 0) {
+ if (withoutError) {
try {
deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
.getTargetNode());
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
} catch (final RestconfDocumentedException e) {
- LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
- patchEntity.getEditId(), e);
+ LOG.error("Error call http PATCH operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
}
}
break;
case MERGE:
- if (errorCounter == 0) {
+ if (withoutError) {
try {
mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
- patchEntity.getNode(), globalSchema);
+ patchEntity.getNode(), schemaContext);
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
} catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http PATCH operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
editErrors = new ArrayList<>();
editErrors.addAll(e.getErrors());
editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
- errorCounter++;
+ withoutError = false;
}
}
break;
}
//TODO: make sure possible global errors are filled up correctly and decide transaction submission based on that
- //globalErrors = new ArrayList<>();
- if (errorCounter == 0) {
+ if (withoutError) {
patchTransaction.submit().checkedGet();
- return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true,
- globalErrors);
+ return new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
} else {
patchTransaction.cancel();
- return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
- globalErrors);
+ return new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
}
}
return transaction.submit();
}
+ // FIXME: This is doing correct post for container and list children
+ // not sure if this will work for choice case
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 {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+ LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+
+ if (payload instanceof MapNode) {
final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
- for(final MapEntryNode child : ((MapNode) payload).getValue()) {
+ for (final MapEntryNode child : ((MapNode) payload).getValue()) {
final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
checkItemDoesNotExists(rWTransaction, datastore, childPath);
rWTransaction.put(datastore, childPath, child);
}
} else {
- checkItemDoesNotExists(rWTransaction,datastore, path);
+ checkItemDoesNotExists(rWTransaction, datastore, path);
ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
rWTransaction.put(datastore, path, payload);
}
return transaction.submit();
}
+ // FIXME: This is doing correct post for container and list children
+ // not sure if this will work for choice case
private void putDataWithinTransaction(
- final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+ final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
- LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
- ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
- writeTransaction.put(datastore, path, payload);
+ LOG.trace("PUT {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+
+ if (payload instanceof MapNode) {
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
+ for (final MapEntryNode child : ((MapNode) payload).getValue()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ checkItemDoesNotExists(rWTransaction, datastore, childPath);
+ rWTransaction.put(datastore, childPath, child);
+ }
+ } else {
+ checkItemDoesNotExists(rWTransaction, datastore, path);
+ ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
+ rWTransaction.put(datastore, path, payload);
+ }
}
private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
}
try {
- return broker.patchConfigurationDataWithinTransaction(context, controllerContext.getGlobalSchema());
- } catch (TransactionCommitFailedException e) {
+ return broker.patchConfigurationDataWithinTransaction(context);
+ } catch (Exception e) {
LOG.debug("Patch transaction failed", e);
throw new RestconfDocumentedException(e.getMessage());
}
throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
}
try {
- return broker.patchConfigurationDataWithinTransaction(context, controllerContext.getGlobalSchema());
- } catch (TransactionCommitFailedException e) {
+ return broker.patchConfigurationDataWithinTransaction(context);
+ } catch (Exception e) {
LOG.debug("Patch transaction failed", e);
throw new RestconfDocumentedException(e.getMessage());
}
assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaContext());
assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaNode());
}
+
+ protected static void checkPATCHContextMountPoint(final PATCHContext patchContext) {
+ checkPATCHContext(patchContext);
+ assertNotNull(patchContext.getInstanceIdentifierContext().getMountPoint());
+ assertNotNull(patchContext.getInstanceIdentifierContext().getMountPoint().getSchemaContext());
+ }
}
--- /dev/null
+/*
+ * 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.controller.sal.rest.impl.test.providers;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Optional;
+import java.io.InputStream;
+import javax.ws.rs.core.MediaType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.netconf.sal.rest.impl.JsonToPATCHBodyReader;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class TestJsonPATCHBodyReaderMountPoint extends AbstractBodyReaderTest {
+
+ private final JsonToPATCHBodyReader jsonPATCHBodyReader;
+ private static SchemaContext schemaContext;
+ private static final String MOUNT_POINT = "instance-identifier-module:cont/yang-ext:mount";
+
+ public TestJsonPATCHBodyReaderMountPoint() throws NoSuchFieldException, SecurityException {
+ super();
+ jsonPATCHBodyReader = new JsonToPATCHBodyReader();
+ }
+
+ @Override
+ protected MediaType getMediaType() {
+ return new MediaType(APPLICATION_JSON, null);
+ }
+
+ @BeforeClass
+ public static void initialization() {
+ schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext);
+
+ final DOMMountPoint mockMountPoint = mock(DOMMountPoint.class);
+ when(mockMountPoint.getSchemaContext()).thenReturn(schemaContext);
+ final DOMMountPointService mockMountPointService = mock(DOMMountPointService.class);
+ when(mockMountPointService.getMountPoint(any(YangInstanceIdentifier.class)))
+ .thenReturn(Optional.of(mockMountPoint));
+
+ controllerContext.setMountService(mockMountPointService);
+ controllerContext.setSchemas(schemaContext);
+ }
+
+ @Test
+ public void modulePATCHDataTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+ final InputStream inputStream = TestJsonBodyReader.class
+ .getResourceAsStream("/instanceidentifier/json/jsonPATCHdata.json");
+
+ final PATCHContext returnValue = jsonPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+
+ /**
+ * Test of successful PATCH consisting of create and delete PATCH operations.
+ */
+ @Test
+ public void modulePATCHCreateAndDeleteTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+ final InputStream inputStream = TestJsonBodyReader.class
+ .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json");
+
+ final PATCHContext returnValue = jsonPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+
+ /**
+ * Test trying to use PATCH create operation which requires value without value. Test should fail with
+ * {@link RestconfDocumentedException} with error code 400.
+ */
+ @Test
+ public void modulePATCHValueMissingNegativeTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+ final InputStream inputStream = TestJsonBodyReader.class
+ .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueMissing.json");
+
+ try {
+ jsonPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+ fail("Test should return error 400 due to missing value node when attempt to invoke create operation");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Test trying to use value with PATCH delete operation which does not support value. Test should fail with
+ * {@link RestconfDocumentedException} with error code 400.
+ */
+ @Test
+ public void modulePATCHValueNotSupportedNegativeTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+ final InputStream inputStream = TestJsonBodyReader.class
+ .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueNotSupported.json");
+
+ try {
+ jsonPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+ fail("Test should return error 400 due to present value node when attempt to invoke delete operation");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Test using PATCH when target is completely specified in request URI and thus target leaf contains only '/' sign.
+ */
+ @Test
+ public void modulePATCHCompleteTargetInURITest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont";
+ mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+ final InputStream inputStream = TestJsonBodyReader.class
+ .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json");
+
+ final PATCHContext returnValue = jsonPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+
+ /**
+ * Test of Yang PATCH merge operation on list. Test consists of two edit operations - replace and merge.
+ */
+ @Test
+ public void modulePATCHMergeOperationOnListTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+ final InputStream inputStream = TestJsonBodyReader.class
+ .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnList.json");
+
+ final PATCHContext returnValue = jsonPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+
+ /**
+ * Test of Yang PATCH merge operation on container. Test consists of two edit operations - create and merge.
+ */
+ @Test
+ public void modulePATCHMergeOperationOnContainerTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont";
+ mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+ final InputStream inputStream = TestJsonBodyReader.class
+ .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json");
+
+ final PATCHContext returnValue = jsonPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+}
}
}
-
/**
* Test of Yang PATCH with absolute target path.
*/
--- /dev/null
+/*
+ * 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.controller.sal.rest.impl.test.providers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Optional;
+import java.io.InputStream;
+import javax.ws.rs.core.MediaType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.netconf.sal.rest.impl.XmlToPATCHBodyReader;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class TestXmlPATCHBodyReaderMountPoint extends AbstractBodyReaderTest {
+
+ private final XmlToPATCHBodyReader xmlPATCHBodyReader;
+ private static SchemaContext schemaContext;
+ private static final String MOUNT_POINT = "instance-identifier-module:cont/yang-ext:mount";
+
+ public TestXmlPATCHBodyReaderMountPoint() throws NoSuchFieldException, SecurityException {
+ super();
+ xmlPATCHBodyReader = new XmlToPATCHBodyReader();
+ }
+
+ @Override
+ protected MediaType getMediaType() {
+ return new MediaType(MediaType.APPLICATION_XML, null);
+ }
+
+ @BeforeClass
+ public static void initialization() throws NoSuchFieldException, SecurityException {
+ schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext);
+
+ final DOMMountPoint mockMountPoint = mock(DOMMountPoint.class);
+ when(mockMountPoint.getSchemaContext()).thenReturn(schemaContext);
+ final DOMMountPointService mockMountPointService = mock(DOMMountPointService.class);
+ when(mockMountPointService.getMountPoint(any(YangInstanceIdentifier.class)))
+ .thenReturn(Optional.of(mockMountPoint));
+
+ controllerContext.setMountService(mockMountPointService);
+ controllerContext.setSchemas(schemaContext);
+ }
+
+ @Test
+ public void moduleDataTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, xmlPATCHBodyReader, false);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdata.xml");
+ final PATCHContext returnValue = xmlPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+
+ /**
+ * Test trying to use PATCH create operation which requires value without value. Error code 400 should be returned.
+ */
+ @Test
+ public void moduleDataValueMissingNegativeTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, xmlPATCHBodyReader, false);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueMissing.xml");
+ try {
+ xmlPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+ fail("Test should return error 400 due to missing value node when attempt to invoke create operation");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Test trying to use value with PATCH delete operation which does not support value. Error code 400 should be
+ * returned.
+ */
+ @Test
+ public void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, xmlPATCHBodyReader, false);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml");
+ try {
+ xmlPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+ fail("Test should return error 400 due to present value node when attempt to invoke delete operation");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Test of Yang PATCH with absolute target path.
+ */
+ @Test
+ public void moduleDataAbsoluteTargetPathTest() throws Exception {
+ final String uri = MOUNT_POINT;
+ mockBodyReader(uri, xmlPATCHBodyReader, false);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml");
+ final PATCHContext returnValue = xmlPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+
+ /**
+ * Test using PATCH when target is completely specified in request URI and thus target leaf contains only '/' sign.
+ */
+ @Test
+ public void modulePATCHCompleteTargetInURITest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont";
+ mockBodyReader(uri, xmlPATCHBodyReader, false);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml");
+ final PATCHContext returnValue = xmlPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+
+ /**
+ * Test of Yang PATCH merge operation on list. Test consists of two edit operations - replace and merge.
+ */
+ @Test
+ public void moduleDataMergeOperationOnListTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, xmlPATCHBodyReader, false);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml");
+ final PATCHContext returnValue = xmlPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+
+ /**
+ * Test of Yang PATCH merge operation on container. Test consists of two edit operations - create and merge.
+ */
+ @Test
+ public void moduleDataMergeOperationOnContainerTest() throws Exception {
+ final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont";
+ mockBodyReader(uri, xmlPATCHBodyReader, false);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml");
+ final PATCHContext returnValue = xmlPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContextMountPoint(returnValue);
+ }
+}
package org.opendaylight.controller.sal.restconf.impl.test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
* @author Thomas Pantelis
*/
public class BrokerFacadeTest {
- @Mock private DOMDataBroker domDataBroker;
- @Mock private DOMNotificationService domNotification;
- @Mock private ConsumerSession context;
- @Mock private DOMRpcService mockRpcService;
- @Mock private DOMMountPoint mockMountInstance;
+
+ @Mock
+ private DOMDataBroker domDataBroker;
+ @Mock
+ private DOMNotificationService domNotification;
+ @Mock
+ private ConsumerSession context;
+ @Mock
+ private DOMRpcService mockRpcService;
+ @Mock
+ private DOMMountPoint mockMountInstance;
+ @Mock
+ private DOMDataReadOnlyTransaction rTransaction;
+ @Mock
+ private DOMDataWriteTransaction wTransaction;
+ @Mock
+ private DOMDataReadWriteTransaction rwTransaction;
private final BrokerFacade brokerFacade = BrokerFacade.getInstance();
private final NormalizedNode<?, ?> dummyNode = createDummyNode("test:module", "2014-01-09", "interfaces");
private final SchemaPath type = SchemaPath.create(true, qname);
private final YangInstanceIdentifier instanceID = YangInstanceIdentifier.builder().node(qname).build();
- @Mock private DOMDataReadOnlyTransaction rTransaction;
- @Mock private DOMDataWriteTransaction wTransaction;
- @Mock private DOMDataReadWriteTransaction rwTransaction;
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
listener.close();
Notificator.removeNotificationListenerIfNoSubscriberExists(listener);
}
+
+ /**
+ * Test PATCH method on the server with no data
+ */
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testPatchConfigurationDataWithinTransactionServer() throws Exception {
+ final PATCHContext patchContext = mock(PATCHContext.class);
+ final InstanceIdentifierContext identifierContext = mock(InstanceIdentifierContext.class);
+ final CheckedFuture<Void, TransactionCommitFailedException> expFuture = Futures.immediateCheckedFuture(null);
+
+ when(patchContext.getData()).thenReturn(Lists.<PATCHEntity>newArrayList());
+ when(patchContext.getInstanceIdentifierContext()).thenReturn(identifierContext);
+
+ // no mount point
+ when(identifierContext.getMountPoint()).thenReturn(null);
+
+ when(this.rwTransaction.submit()).thenReturn(expFuture);
+
+ final PATCHStatusContext status = this.brokerFacade.patchConfigurationDataWithinTransaction(patchContext);
+
+ // assert success
+ assertTrue("PATCH operation should be successful on server", status.isOk());
+ }
+
+ /**
+ * Test PATCH method on mounted device with no data
+ */
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testPatchConfigurationDataWithinTransactionMount() throws Exception {
+ final PATCHContext patchContext = mock(PATCHContext.class);
+ final InstanceIdentifierContext identifierContext = mock(InstanceIdentifierContext.class);
+ final DOMMountPoint mountPoint = mock(DOMMountPoint.class);
+ final DOMDataBroker mountDataBroker = mock(DOMDataBroker.class);
+ final DOMDataReadWriteTransaction transaction = mock(DOMDataReadWriteTransaction.class);
+ final CheckedFuture<Void, TransactionCommitFailedException> expFuture = Futures.immediateCheckedFuture(null);
+
+ when(patchContext.getData()).thenReturn(Lists.<PATCHEntity>newArrayList());
+ when(patchContext.getInstanceIdentifierContext()).thenReturn(identifierContext);
+
+ // return mount point with broker
+ when(identifierContext.getMountPoint()).thenReturn(mountPoint);
+ when(mountPoint.getService(DOMDataBroker.class)).thenReturn(Optional.of(mountDataBroker));
+ when(mountDataBroker.newReadWriteTransaction()).thenReturn(transaction);
+ when(transaction.submit()).thenReturn(expFuture);
+
+ final PATCHStatusContext status = this.brokerFacade.patchConfigurationDataWithinTransaction(patchContext);
+
+ // assert success
+ assertTrue("PATCH operation should be successful on mounted device", status.isOk());
+ }
+
+ /**
+ * Negative test for PATCH operation when mounted device does not support {@link DOMDataBroker service.
+ * PATCH operation should fail with global error.
+ */
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testPatchConfigurationDataWithinTransactionMountFail() throws Exception {
+ final PATCHContext patchContext = mock(PATCHContext.class);
+ final InstanceIdentifierContext identifierContext = mock(InstanceIdentifierContext.class);
+ final DOMMountPoint mountPoint = mock(DOMMountPoint.class);
+ final DOMDataBroker mountDataBroker = mock(DOMDataBroker.class);
+ final DOMDataReadWriteTransaction transaction = mock(DOMDataReadWriteTransaction.class);
+ final CheckedFuture<Void, TransactionCommitFailedException> expFuture = Futures.immediateCheckedFuture(null);
+
+ when(patchContext.getData()).thenReturn(Lists.<PATCHEntity>newArrayList());
+ when(patchContext.getInstanceIdentifierContext()).thenReturn(identifierContext);
+ when(identifierContext.getMountPoint()).thenReturn(mountPoint);
+
+ // missing broker on mounted device
+ when(mountPoint.getService(DOMDataBroker.class)).thenReturn(Optional.<DOMDataBroker>absent());
+
+ when(mountDataBroker.newReadWriteTransaction()).thenReturn(transaction);
+ when(transaction.submit()).thenReturn(expFuture);
+
+ final PATCHStatusContext status = this.brokerFacade.patchConfigurationDataWithinTransaction(patchContext);
+
+ // assert not successful operation with error
+ assertNotNull(status.getGlobalErrors());
+ assertEquals(1, status.getGlobalErrors().size());
+ assertEquals(ErrorType.APPLICATION, status.getGlobalErrors().get(0).getErrorType());
+ assertEquals(ErrorTag.OPERATION_FAILED, status.getGlobalErrors().get(0).getErrorTag());
+
+ assertFalse("PATCH operation should fail on mounted device without Broker", status.isOk());
+ }
}