<classifier>features</classifier>
<type>xml</type>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-remote</artifactId>
- <version>${controller.mdsal.version}</version>
- </dependency>
<dependency>
<groupId>org.opendaylight.netconf</groupId>
<feature version='${controller.mdsal.version}'>odl-mdsal-broker</feature>
<feature version='[4.0.30,4.1.0)'>odl-netty</feature>
<feature>war</feature>
- <!-- presently we need sal-remote to be listed BEFORE sal-rest-connector because sal-rest-connector
- has a yang file which augments a yang file in sal-remote, and order seems to matter -->
- <bundle>mvn:org.opendaylight.controller/sal-remote/{{VERSION}}</bundle>
<bundle>mvn:org.opendaylight.netconf/sal-rest-connector/{{VERSION}}</bundle>
<bundle>mvn:com.google.code.gson/gson/{{VERSION}}</bundle>
<bundle>mvn:org.opendaylight.yangtools/yang-data-codec-gson/{{VERSION}}</bundle>
--- /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.netconf.confignetconfconnector.osgi;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.Set;
+import org.junit.Test;
+import org.opendaylight.controller.config.facade.xml.ConfigSubsystemFacade;
+import org.opendaylight.netconf.confignetconfconnector.operations.Commit;
+import org.opendaylight.netconf.confignetconfconnector.operations.DiscardChanges;
+import org.opendaylight.netconf.confignetconfconnector.operations.Lock;
+import org.opendaylight.netconf.confignetconfconnector.operations.UnLock;
+import org.opendaylight.netconf.confignetconfconnector.operations.Validate;
+import org.opendaylight.netconf.confignetconfconnector.operations.editconfig.EditConfig;
+import org.opendaylight.netconf.confignetconfconnector.operations.get.Get;
+import org.opendaylight.netconf.confignetconfconnector.operations.getconfig.GetConfig;
+import org.opendaylight.netconf.confignetconfconnector.operations.runtimerpc.RuntimeRpc;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+
+public class NetconfOperationServiceImplTest {
+
+ @Test
+ public void testOperationService() {
+ final ConfigSubsystemFacade configSubsystemFacade = mock(ConfigSubsystemFacade.class);
+ final NetconfOperationService netconfOperationService =
+ new NetconfOperationServiceImpl(configSubsystemFacade, "reportingID");
+
+ // testing operations in Set from NetconfOperationProvider
+
+ Set<NetconfOperation> operations = netconfOperationService.getNetconfOperations();
+
+ assertTrue(containInstance(operations, GetConfig.class));
+ assertTrue(containInstance(operations, EditConfig.class));
+ assertTrue(containInstance(operations, Commit.class));
+ assertTrue(containInstance(operations, Lock.class));
+ assertTrue(containInstance(operations, UnLock.class));
+ assertTrue(containInstance(operations, Get.class));
+ assertTrue(containInstance(operations, DiscardChanges.class));
+ assertTrue(containInstance(operations, Validate.class));
+ assertTrue(containInstance(operations, RuntimeRpc.class));
+
+ // verify closing service
+
+ doNothing().when(configSubsystemFacade).close();
+ netconfOperationService.close();
+
+ verify(configSubsystemFacade, times(1)).close();
+ }
+
+ private boolean containInstance(final Set<NetconfOperation> operations, final Class<?> cls) {
+ return operations.stream().filter(cls::isInstance).findFirst().isPresent();
+ }
+}
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.ModifyAction;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+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.impl.schema.transform.dom.DomUtils;
import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
final XmlElement configElement = getElement(operationElement, CONFIG_KEY);
- for (XmlElement element : configElement.getChildElements()) {
+ for (final XmlElement element : configElement.getChildElements()) {
final String ns = element.getNamespace();
final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get();
}
private void executeChange(final DOMDataReadWriteTransaction rwtx, final DataTreeChange change) throws DocumentedException {
+ final YangInstanceIdentifier path = YangInstanceIdentifier.create(change.getPath());
+ final NormalizedNode<?, ?> changeData = change.getChangeRoot();
switch (change.getAction()) {
case NONE:
return;
case MERGE:
- rwtx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
+ createMapNodeIfNonExistent(rwtx, path, changeData);
+ rwtx.merge(LogicalDatastoreType.CONFIGURATION, path, changeData);
break;
case CREATE:
try {
- final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath())).checkedGet();
+ final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, path).checkedGet();
if (readResult.isPresent()) {
throw new DocumentedException("Data already exists, cannot execute CREATE operation", ErrorType.protocol, ErrorTag.data_exists, ErrorSeverity.error);
}
- rwtx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
- } catch (ReadFailedException e) {
+ createMapNodeIfNonExistent(rwtx, path, changeData);
+ rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
+ } catch (final ReadFailedException e) {
LOG.warn("Read from datastore failed when trying to read data for create operation", change, e);
}
break;
case REPLACE:
- rwtx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
+ createMapNodeIfNonExistent(rwtx, path, changeData);
+ rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
break;
case DELETE:
try {
- final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath())).checkedGet();
+ final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, path).checkedGet();
if (!readResult.isPresent()) {
throw new DocumentedException("Data is missing, cannot execute DELETE operation", ErrorType.protocol, ErrorTag.data_missing, ErrorSeverity.error);
}
- rwtx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()));
- } catch (ReadFailedException e) {
+ rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
+ } catch (final ReadFailedException e) {
LOG.warn("Read from datastore failed when trying to read data for delete operation", change, e);
}
break;
case REMOVE:
- rwtx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()));
+ rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
break;
default:
LOG.warn("Unknown/not implemented operation, not executing");
}
}
+ private void createMapNodeIfNonExistent(final DOMDataReadWriteTransaction rwtx, final YangInstanceIdentifier path,
+ final NormalizedNode change) throws DocumentedException {
+ if (!(change instanceof MapEntryNode)) {
+ return;
+ }
+ final YangInstanceIdentifier mapNodeYid = path.getParent();
+ try {
+ final Boolean mapNodeExists = rwtx.exists(LogicalDatastoreType.CONFIGURATION, mapNodeYid).checkedGet();
+ if (!mapNodeExists) {
+ final MapNode mixinNode = Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(mapNodeYid.getLastPathArgument().getNodeType()))
+ .build();
+ rwtx.put(LogicalDatastoreType.CONFIGURATION, mapNodeYid, mixinNode);
+ }
+ } catch (final ReadFailedException e) {
+ throw new DocumentedException("List node existence check failed", e, ErrorType.protocol, ErrorTag.data_missing, ErrorSeverity.error);
+
+ }
+ }
+
private NormalizedNode parseIntoNormalizedNode(final DataSchemaNode schemaNode, final XmlElement element,
final DomToNormalizedNodeParserFactory.BuildingStrategyProvider editOperationStrategyProvider) {
throw new NetconfDocumentedException("Unable to find module by namespace: " + namespace,
ErrorType.application, ErrorTag.unknown_namespace, ErrorSeverity.error);
}
- DataSchemaNode schemaNode =
+ final DataSchemaNode schemaNode =
module.getDataChildByName(QName.create(module.getQNameModule(), element.getName()));
if (schemaNode != null) {
dataSchemaNode = Optional.of(schemaNode);
ErrorTag.unknown_namespace,
ErrorSeverity.error);
}
- } catch (URISyntaxException e) {
+ } catch (final URISyntaxException e) {
LOG.debug("Unable to create URI for namespace : {}", namespace);
}
}
- private XmlElement getElement(final XmlElement operationElement, String elementName) throws DocumentedException {
+ private XmlElement getElement(final XmlElement operationElement, final String elementName) throws DocumentedException {
final Optional<XmlElement> childNode = operationElement.getOnlyChildElementOptionally(elementName);
if (!childNode.isPresent()) {
throw new DocumentedException(elementName + " element is missing",
deleteDatastore();
}
+ @Test
+ public void testReplaceMapEntry() throws Exception {
+ verifyResponse(edit("messages/mapping/editConfigs/edit-config-replace-map-entry.xml"), RPC_REPLY_OK);
+ verifyResponse(commit(), RPC_REPLY_OK);
+ verifyResponse(getConfigRunning(), XmlFileLoader.xmlFileToDocument("messages/mapping/get-config-map-entry.xml"));
+ }
+
+ @Test
+ public void testMergeMapEntry() throws Exception {
+ verifyResponse(edit("messages/mapping/editConfigs/edit-config-merge-map-entry.xml"), RPC_REPLY_OK);
+ verifyResponse(commit(), RPC_REPLY_OK);
+ verifyResponse(getConfigRunning(), XmlFileLoader.xmlFileToDocument("messages/mapping/get-config-map-entry.xml"));
+ }
+
@Test
public void testFiltering() throws Exception {
--- /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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <edit-config>
+ <target>
+ <candidate/>
+ </target>
+ <default-operation>none</default-operation>
+ <config>
+ <mapping-nodes xmlns="urn:opendaylight:mdsal:mapping:test">
+ <mapping-node xmlns:a="urn:ietf:params:xml:ns:netconf:base:1.0" a:operation="merge">
+ <id>id1</id>
+ <content>content1</content>
+ </mapping-node>
+ </mapping-nodes>
+ </config>
+ </edit-config>
+</rpc>
\ No newline at end of file
--- /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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <edit-config>
+ <target>
+ <candidate/>
+ </target>
+ <default-operation>none</default-operation>
+ <config>
+ <mapping-nodes xmlns="urn:opendaylight:mdsal:mapping:test">
+ <mapping-node xmlns:a="urn:ietf:params:xml:ns:netconf:base:1.0" a:operation="replace">
+ <id>id1</id>
+ <content>content1</content>
+ </mapping-node>
+ </mapping-nodes>
+ </config>
+ </edit-config>
+</rpc>
\ No newline at end of file
--- /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
+ -->
+
+<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" a="64" id="a" message-id="101" xmlnx="a:b:c:d">
+ <data>
+ <mapping-nodes xmlns="urn:opendaylight:mdsal:mapping:test">
+ <mapping-node>
+ <id>id1</id>
+ <content>content1</content>
+ </mapping-node>
+ </mapping-nodes>
+ </data>
+</rpc-reply>
\ No newline at end of file
Capabilities newCapabilities = new CapabilitiesBuilder().setCapability(newCapabilitiesList).build();
Map<InstanceIdentifier<?>, DataObject> createdData = Maps.newHashMap();
createdData.put(capabilitiesIdentifier, newCapabilities);
- verifyDataTreeChange(null, newCapabilities, changedCapabilitesFrom(newCapabilitiesList, Collections.<Uri>emptyList()));
+ verifyDataTreeChange(DataObjectModification.ModificationType.WRITE, null, newCapabilities,
+ changedCapabilitesFrom(newCapabilitiesList, Collections.<Uri>emptyList()));
}
@Test
final List<Uri> updatedCapabilitiesList = Lists.newArrayList(new Uri("originalCapability"), new Uri("newCapability"));
Capabilities originalCapabilities = new CapabilitiesBuilder().setCapability(originalCapabilitiesList).build();
Capabilities updatedCapabilities = new CapabilitiesBuilder().setCapability(updatedCapabilitiesList).build();
- verifyDataTreeChange(originalCapabilities, updatedCapabilities, changedCapabilitesFrom(
+ verifyDataTreeChange(DataObjectModification.ModificationType.WRITE, originalCapabilities, updatedCapabilities, changedCapabilitesFrom(
Lists.newArrayList(new Uri("newCapability")), Lists.newArrayList(new Uri("anotherOriginalCapability"))));
}
+ @Test
+ public void testOnDataChangedDelete() {
+ final List<Uri> originalCapabilitiesList = Lists.newArrayList(new Uri("originalCapability"),
+ new Uri("anotherOriginalCapability"));
+ final Capabilities originalCapabilities =
+ new CapabilitiesBuilder().setCapability(originalCapabilitiesList).build();
+ verifyDataTreeChange(DataObjectModification.ModificationType.DELETE, originalCapabilities, null,
+ changedCapabilitesFrom(Collections.emptyList(), originalCapabilitiesList));
+ }
+
@SuppressWarnings("unchecked")
- private void verifyDataTreeChange(Capabilities originalCapabilities, Capabilities updatedCapabilities,
+ private void verifyDataTreeChange(final DataObjectModification.ModificationType modificationType,
+ Capabilities originalCapabilities, Capabilities updatedCapabilities,
NetconfCapabilityChange expectedChange) {
final DataTreeModification<Capabilities> treeChange2 = mock(DataTreeModification.class);
final DataObjectModification<Capabilities> objectChange2 = mock(DataObjectModification.class);
- doReturn(DataObjectModification.ModificationType.WRITE).when(objectChange2).getModificationType();
+ doReturn(modificationType).when(objectChange2).getModificationType();
doReturn(objectChange2).when(treeChange2).getRootNode();
doReturn(originalCapabilities).when(objectChange2).getDataBefore();
doReturn(updatedCapabilities).when(objectChange2).getDataAfter();
--- /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.netconf.api.monitoring;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+public class SessionEventTest {
+
+ @Test
+ public void test() {
+ final NetconfManagementSession session = mock(NetconfManagementSession.class);
+
+ assertEquals(SessionEvent.Type.IN_RPC_FAIL, SessionEvent.inRpcFail(session).getType());
+ assertEquals(SessionEvent.Type.IN_RPC_SUCCESS, SessionEvent.inRpcSuccess(session).getType());
+ assertEquals(SessionEvent.Type.NOTIFICATION, SessionEvent.notification(session).getType());
+ assertEquals(SessionEvent.Type.OUT_RPC_ERROR, SessionEvent.outRpcError(session).getType());
+ }
+}
package org.opendaylight.netconf.notifications.impl.osgi;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.Dictionary;
operationaServiceRegistration = null;
}
}
+
+ @VisibleForTesting
+ NetconfNotificationManager getNetconfNotificationManager() {
+ return netconfNotificationManager;
+ }
}
--- /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.netconf.notifications.impl.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.opendaylight.controller.config.util.capability.BasicCapability;
+import org.opendaylight.controller.config.util.capability.Capability;
+import org.opendaylight.netconf.api.monitoring.CapabilityListener;
+import org.opendaylight.netconf.api.util.NetconfConstants;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
+import org.opendaylight.netconf.notifications.NetconfNotification;
+import org.opendaylight.netconf.notifications.NetconfNotificationCollector;
+import org.opendaylight.netconf.notifications.impl.NetconfNotificationManager;
+import org.opendaylight.netconf.notifications.impl.ops.CreateSubscription;
+import org.opendaylight.netconf.notifications.impl.ops.Get;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+public class ActivatorTest {
+
+ @Test
+ public void testActivator() throws Exception {
+ final Activator activator = new Activator();
+ final BundleContext context = mock(BundleContext.class);
+
+
+ final ServiceRegistration netconfNotificationCollectorServiceRegistration = mock(ServiceRegistration.class);
+ final ServiceRegistration operationaServiceRegistration = mock(ServiceRegistration.class);
+
+ // test registering services
+ doReturn(netconfNotificationCollectorServiceRegistration).when(context).
+ registerService(eq(NetconfNotificationCollector.class), any(NetconfNotificationManager.class), any());
+ doReturn(operationaServiceRegistration).when(context).
+ registerService(eq(NetconfOperationServiceFactory.class), any(NetconfOperationServiceFactory.class), any());
+
+ activator.start(context);
+
+ verify(context, times(1)).registerService(eq(NetconfNotificationCollector.class),
+ any(NetconfNotificationManager.class), eq(new Hashtable<>()));
+
+ final ArgumentCaptor<NetconfOperationServiceFactory> serviceFactoryArgumentCaptor =
+ ArgumentCaptor.forClass(NetconfOperationServiceFactory.class);
+
+ final Dictionary<String, String> properties = new Hashtable<>();
+ properties.put(NetconfConstants.SERVICE_NAME, NetconfConstants.NETCONF_MONITORING);
+
+ verify(context, times(1)).registerService(eq(NetconfOperationServiceFactory.class),
+ serviceFactoryArgumentCaptor.capture(), eq(properties));
+
+ // test service factory argument requisites
+ final NetconfOperationServiceFactory serviceFactory = serviceFactoryArgumentCaptor.getValue();
+
+ final Set<Capability> capabilities = Collections.singleton(new BasicCapability(NetconfNotification.NOTIFICATION_NAMESPACE));
+
+ assertEquals(capabilities.iterator().next().getCapabilityUri(), serviceFactory.getCapabilities().iterator().next().getCapabilityUri());
+ assertEquals(capabilities.iterator().next().getCapabilitySchema(), serviceFactory.getCapabilities().iterator().next().getCapabilitySchema());
+ assertEquals(capabilities.iterator().next().getModuleNamespace(), serviceFactory.getCapabilities().iterator().next().getModuleNamespace());
+ assertEquals(capabilities.iterator().next().getModuleName(), serviceFactory.getCapabilities().iterator().next().getModuleName());
+
+ final CapabilityListener listener = mock(CapabilityListener.class);
+
+ doNothing().when(listener).onCapabilitiesChanged(any(), any());
+
+ serviceFactory.registerCapabilityListener(listener);
+
+ verify(listener).onCapabilitiesChanged(serviceFactory.getCapabilities(), Collections.emptySet());
+
+ final NetconfOperationService netconfOperationService = serviceFactory.createService("id");
+ final Set<NetconfOperation> netconfOperations = netconfOperationService.getNetconfOperations();
+
+ final CreateSubscription createSubscription = new CreateSubscription("id", activator.getNetconfNotificationManager());
+
+ netconfOperations.forEach(
+ operation -> {
+ if (operation instanceof CreateSubscription) {
+ assertEquals(createSubscription.toString(), operation.toString());
+ }
+ if (operation instanceof Get) {
+ assertEquals("id", ((Get) operation).getNetconfSessionIdForReporting());
+ }
+ }
+ );
+
+ // test unregister after stop
+ doNothing().when(netconfNotificationCollectorServiceRegistration).unregister();
+ doNothing().when(operationaServiceRegistration).unregister();
+
+ activator.stop(context);
+
+ verify(netconfNotificationCollectorServiceRegistration, times(1)).unregister();
+ verify(operationaServiceRegistration, times(1)).unregister();
+
+ }
+
+
+}
<artifactId>sal-distributed-datastore</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<scm>
*/
package org.opendaylight.netconf.sal.connect.netconf;
+import com.google.common.annotations.VisibleForTesting;
import org.opendaylight.netconf.api.NetconfMessage;
import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
messageTransformer = new SchemalessMessageTransformer(counter);
}
+ @VisibleForTesting
+ SchemalessNetconfDevice(final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
+ final SchemalessMessageTransformer messageTransformer) {
+ this.id = id;
+ this.salFacade = salFacade;
+ final MessageCounter counter = new MessageCounter();
+ rpcTransformer = new BaseRpcSchemalessTransformer(counter);
+ this.messageTransformer = messageTransformer;
+ }
+
@Override public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
final NetconfDeviceCommunicator netconfDeviceCommunicator) {
final SchemalessNetconfDeviceRpc schemalessNetconfDeviceRpc = new SchemalessNetconfDeviceRpc(id,
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.opendaylight.controller.config.util.xml.XmlElement;
private Future<?> initFuture;
private SettableFuture<NetconfDeviceCapabilities> firstConnectionFuture;
+ // isSessionClosing indicates a close operation on the session is issued and
+ // tearDown will surely be called later to finish the close.
+ // Used to allow only one thread to enter tearDown and other threads should
+ // NOT enter it simultaneously and should end its close operation without
+ // calling tearDown to release the locks they hold to avoid deadlock.
+ private volatile AtomicBoolean isSessionClosing = new AtomicBoolean(false);
+
+ public Boolean isSessionClosing() {
+ return isSessionClosing.get();
+ }
+
public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> remoteDevice,
final UserPreferences NetconfSessionPreferences, final int rpcMessageLimit) {
this(id, remoteDevice, Optional.of(NetconfSessionPreferences), rpcMessageLimit);
}
public void disconnect() {
- if(session != null) {
+ // If session is already in closing, no need to close it again
+ if(session != null && isSessionClosing.compareAndSet(false, true)) {
session.close();
}
}
private void tearDown( String reason ) {
+ if (!isSessionClosing()) {
+ LOG.warn("It's curious that no one to close the session but tearDown is called!");
+ }
LOG.debug("Tearing down {}", reason);
List<UncancellableFuture<RpcResult<NetconfMessage>>> futuresToCancel = Lists.newArrayList();
sessionLock.lock();
future.set( createErrorRpcResult( RpcError.ErrorType.TRANSPORT, reason ) );
}
}
+
+ isSessionClosing.set(false);
}
private RpcResult<NetconfMessage> createSessionDownRpcResult() {
@Override
public void onSessionDown(final NetconfClientSession session, final Exception e) {
- LOG.warn("{}: Session went down", id, e);
- tearDown( null );
+ // If session is already in closing, no need to call tearDown again.
+ if (isSessionClosing.compareAndSet(false, true)) {
+ LOG.warn("{}: Session went down", id, e);
+ tearDown( null );
+ }
}
@Override
public void onSessionTerminated(final NetconfClientSession session, final NetconfTerminationReason reason) {
+ // onSessionTerminated is called directly by disconnect, no need to compare and set isSessionClosing.
LOG.warn("{}: Session terminated {}", id, reason);
tearDown( reason.getErrorMessage() );
}
*/
package org.opendaylight.netconf.sal.connect.netconf.sal;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import java.util.List;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
registerToSal(domBroker, bindingBroker);
}
+ @VisibleForTesting
+ NetconfDeviceSalFacade(final RemoteDeviceId id, NetconfDeviceSalProvider salProvider,
+ final Broker domBroker, final BindingAwareBroker bindingBroker) {
+ this.id = id;
+ this.salProvider = salProvider;
+ registerToSal(domBroker, bindingBroker);
+ }
+
public void registerToSal(final Broker domRegistryDependency, final BindingAwareBroker bindingBroker) {
domRegistryDependency.registerProvider(salProvider);
bindingBroker.registerProvider(salProvider);
--- /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.netconf.sal.connect.netconf;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.collect.Lists;
+import java.lang.reflect.Field;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
+import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceRpc;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class SchemalessNetconfDeviceTest {
+
+ private static final String TEST_NAMESPACE = "test:namespace";
+ private static final String TEST_MODULE = "test-module";
+ private static final String TEST_REVISION = "2013-07-22";
+
+ @Test
+ public void testSessionOnMethods() throws Exception {
+ final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
+ final NetconfDeviceCommunicator listener = mockCloseableClass(NetconfDeviceCommunicator.class);
+ final SchemalessMessageTransformer messageTransformer = mock(SchemalessMessageTransformer.class);
+ final RemoteDeviceId remoteDeviceId = new RemoteDeviceId("test-D",
+ InetSocketAddress.createUnresolved("localhost", 22));
+ final Throwable throwable = new Throwable();
+
+ final SchemalessNetconfDevice device = new SchemalessNetconfDevice(remoteDeviceId, facade, messageTransformer);
+
+ final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
+ Lists.newArrayList(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION));
+
+ final NetconfMessage netconfMessage = mock(NetconfMessage.class);
+
+ device.onRemoteSessionUp(sessionCaps, listener);
+ verify(facade).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(DOMRpcService.class));
+
+ device.onNotification(netconfMessage);
+ verify(facade).onNotification(any(DOMNotification.class));
+
+ device.onRemoteSessionDown();
+ verify(facade).onDeviceDisconnected();
+
+ device.onRemoteSessionFailed(throwable);
+ verify(facade).onDeviceFailed(throwable);
+ }
+
+ @SuppressWarnings("unchecked")
+ private RemoteDeviceHandler<NetconfSessionPreferences> getFacade() throws Exception {
+ final RemoteDeviceHandler<NetconfSessionPreferences> remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class);
+ doNothing().when(remoteDeviceHandler).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class));
+ doNothing().when(remoteDeviceHandler).onDeviceDisconnected();
+ doNothing().when(remoteDeviceHandler).onNotification(any(DOMNotification.class));
+ return remoteDeviceHandler;
+ }
+
+ private <T extends AutoCloseable> T mockCloseableClass(final Class<T> remoteDeviceHandlerClass) throws Exception {
+ final T mock = mockClass(remoteDeviceHandlerClass);
+ doNothing().when(mock).close();
+ return mock;
+ }
+
+ private static <T> T mockClass(final Class<T> remoteDeviceHandlerClass) {
+ final T mock = mock(remoteDeviceHandlerClass);
+ Mockito.doReturn(remoteDeviceHandlerClass.getSimpleName()).when(mock).toString();
+ return mock;
+ }
+
+ private NetconfSessionPreferences getSessionCaps(final boolean addMonitor, final Collection<String> additionalCapabilities) {
+ final ArrayList<String> capabilities = Lists.newArrayList(
+ XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0,
+ XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1);
+
+ if(addMonitor) {
+ capabilities.add(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString());
+ }
+
+ capabilities.addAll(additionalCapabilities);
+
+ return NetconfSessionPreferences.fromStrings(
+ capabilities);
+ }
+}
--- /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.netconf.sal.connect.netconf.sal;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.opendaylight.controller.sal.core.api.Broker;
+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;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({NetconfDeviceTopologyAdapter.class, NetconfDeviceSalProvider.MountInstance.class, NetconfSessionPreferences.class})
+public class NetconfDeviceSalFacadeTest {
+
+ private NetconfDeviceSalFacade deviceFacade;
+
+ private NetconfDeviceTopologyAdapter netconfDeviceTopologyAdapter;
+ private NetconfDeviceSalProvider.MountInstance mountInstance;
+
+ @Mock
+ private NetconfDeviceSalProvider salProvider;
+
+ @Before
+ public void setUp() throws Exception{
+ initMocks(this);
+ final InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
+ final RemoteDeviceId remoteDeviceId = new RemoteDeviceId("test", address);
+
+ final Broker domRegistry = mock(Broker.class);
+ final BindingAwareBroker bindingRegistry = mock(BindingAwareBroker.class);
+ deviceFacade = new NetconfDeviceSalFacade(remoteDeviceId, salProvider, domRegistry, bindingRegistry);
+
+ netconfDeviceTopologyAdapter = PowerMockito.mock(NetconfDeviceTopologyAdapter.class);
+ mountInstance = PowerMockito.mock(NetconfDeviceSalProvider.MountInstance.class);
+
+ doReturn(netconfDeviceTopologyAdapter).when(salProvider).getTopologyDatastoreAdapter();
+ doNothing().when(netconfDeviceTopologyAdapter).updateDeviceData(any(Boolean.class), any(NetconfDeviceCapabilities.class));
+
+ doReturn(mountInstance).when(salProvider).getMountInstance();
+ doNothing().when(mountInstance).onTopologyDeviceDisconnected();
+ }
+
+ @Test
+ public void testOnDeviceDisconnected() {
+ deviceFacade.onDeviceDisconnected();
+
+ verify(netconfDeviceTopologyAdapter).updateDeviceData(eq(false), any(NetconfDeviceCapabilities.class));
+ verify(mountInstance, times(1)).onTopologyDeviceDisconnected();
+
+ }
+
+ @Test
+ public void testOnDeviceFailed() {
+ final Throwable throwable = new Throwable();
+ deviceFacade.onDeviceFailed(throwable);
+
+ verify(netconfDeviceTopologyAdapter).setDeviceAsFailed(throwable);
+ verify(mountInstance, times(1)).onTopologyDeviceDisconnected();
+ }
+
+ @Test
+ public void testOnDeviceClose() throws Exception {
+ deviceFacade.close();
+ verify(salProvider).close();
+ }
+
+ @Test
+ public void testOnDeviceConnected() {
+ final SchemaContext schemaContext = mock(SchemaContext.class);
+
+ final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(getCapabilities());
+
+ final DOMRpcService deviceRpc = mock(DOMRpcService.class);
+ deviceFacade.onDeviceConnected(schemaContext, netconfSessionPreferences, deviceRpc);
+
+ verify(mountInstance, times(1)).onTopologyDeviceConnected(eq(schemaContext), any(DOMDataBroker.class), eq(deviceRpc), any(NetconfDeviceNotificationService.class));
+ verify(netconfDeviceTopologyAdapter, times(1)).updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities());
+ }
+
+ @Test
+ public void testOnDeviceNotification() throws Exception {
+ final DOMNotification domNotification = mock(DOMNotification.class);
+ deviceFacade.onNotification(domNotification);
+ verify(mountInstance).publish(domNotification);
+ }
+
+ private List<String> getCapabilities(){
+ return Arrays.asList(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString());
+ }
+}
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import javassist.ClassPool;
-import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.internal.util.collections.Sets;
import org.opendaylight.controller.cluster.databroker.ConcurrentDOMDataBroker;
-import org.opendaylight.controller.cluster.datastore.node.utils.AugmentationIdentifierGenerator;
-import org.opendaylight.controller.md.sal.binding.impl.BindingDOMDataBrokerAdapter;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
-import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
-import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.binding.impl.BindingDOMDataBrokerAdapter;
import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStoreFactory;
import org.opendaylight.controller.sal.core.spi.data.DOMStore;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeFields;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
-import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
-import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
import org.opendaylight.yangtools.binding.data.codec.gen.impl.DataObjectSerializerGenerator;
import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry;
};
}
+ @Test
+ public void testRemoveDeviceConfiguration() throws Exception {
+ doReturn(Futures.immediateCheckedFuture(null)).when(writeTx).submit();
+
+ NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(id, txChain);
+ adapter.close();
+
+ verify(txChain, times(2)).newWriteOnlyTransaction();
+ verify(writeTx).delete(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath());
+ verify(writeTx, times(2)).submit();
+ }
+
}
\ No newline at end of file
--- /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.netconf.sal.connect.netconf.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import java.net.InetSocketAddress;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+
+public class RemoteDeviceIdTest {
+
+ @Test
+ public void testEquals() {
+ final InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
+ final ModuleIdentifier identifier = new ModuleIdentifier("test", "test");
+
+ final RemoteDeviceId remoteDeviceId = new RemoteDeviceId("test", address);
+ final RemoteDeviceId remoteDeviceIdEqualName = new RemoteDeviceId(identifier, address);
+ final RemoteDeviceId remoteDeviceIdDiffName = new RemoteDeviceId("test-diff", address);
+
+ assertEquals(true, remoteDeviceId.equals(remoteDeviceId));
+ assertEquals(false, remoteDeviceId.equals(this));
+ assertEquals(false, remoteDeviceId.equals(remoteDeviceIdDiffName));
+ assertEquals(true, remoteDeviceId.equals(remoteDeviceIdEqualName));
+ }
+
+ @Test
+ public void testHashCode() {
+ final String name = "name";
+ final InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
+ final RemoteDeviceId remoteDeviceId = new RemoteDeviceId(name, address);
+ final RemoteDeviceId remoteDeviceIdEqualName = new RemoteDeviceId(name, address);
+ final RemoteDeviceId remoteDeviceIdDiffName = new RemoteDeviceId("test-diff", address);
+
+ assertEquals(remoteDeviceIdEqualName.hashCode(), remoteDeviceId.hashCode());
+ assertNotEquals(remoteDeviceIdDiffName.hashCode(), remoteDeviceId.hashCode());
+ }
+}
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
\ No newline at end of file
import java.io.IOException;
import org.opendaylight.yanglib.api.YangLibService;
import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
-import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class YangLibServiceImpl implements YangLibService {
private static final Logger LOG = LoggerFactory.getLogger(YangLibServiceImpl.class);
- private SharedSchemaRepository schemaRepository;
+ private SchemaRepository schemaRepository;
public YangLibServiceImpl() {
}
- public void setSchemaRepository(final SharedSchemaRepository schemaRepository) {
+ public void setSchemaRepository(final SchemaRepository schemaRepository) {
LOG.debug("Setting schema repository {}", schemaRepository);
this.schemaRepository = schemaRepository;
}
--- /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.yanglib.impl;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.BDDMockito;
+import org.opendaylight.yanglib.api.YangLibRestAppService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(FrameworkUtil.class)
+public class YangLibRestAppTest {
+
+ @Test
+ public void testYangLibRestApp() {
+ PowerMockito.mockStatic(FrameworkUtil.class);
+
+ final BundleContext bundleContext = mock(BundleContext.class);
+ final Bundle bundle = mock(Bundle.class);
+
+ BDDMockito.given(FrameworkUtil.getBundle(any())).willReturn(bundle);
+ when(bundle.getBundleContext()).thenReturn(bundleContext);
+
+ final YangLibRestApp yangLibRestApp = new YangLibRestApp();
+ final Set singleton = yangLibRestApp.getSingletons();
+
+ assertTrue(singleton.contains(yangLibRestApp.getYangLibService()));
+
+ verify(bundleContext, times(1)).registerService(eq(YangLibRestAppService.class.getName()), eq(yangLibRestApp), eq(null));
+ }
+}
--- /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.yanglib.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+
+public class YangLibServiceImplTest {
+
+ private final static String TEST_OUTPUT_STRING = "hello world";
+
+ @Test
+ public void testSchema() throws SchemaSourceException {
+
+ final SchemaRepository schemaRepository = mock(SchemaRepository.class);
+ final YangLibServiceImpl yangLibService = new YangLibServiceImpl();
+ yangLibService.setSchemaRepository(schemaRepository);
+
+ final SourceIdentifier sourceIdentifier = RevisionSourceIdentifier.create("name", "2016-01-01");
+
+ final YangTextSchemaSource yangTextSchemaSource = new YangTextSchemaSource(sourceIdentifier) {
+ @Override
+ protected MoreObjects.ToStringHelper addToStringAttributes(MoreObjects.ToStringHelper toStringHelper) {
+ return null;
+ }
+
+ @Override
+ public InputStream openStream() throws IOException {
+ return new ByteArrayInputStream(TEST_OUTPUT_STRING.getBytes());
+ }
+ };
+
+ final CheckedFuture<YangTextSchemaSource, SchemaSourceException> sourceFuture =
+ Futures.immediateCheckedFuture(yangTextSchemaSource);
+ doReturn(sourceFuture).when(schemaRepository).getSchemaSource(any(SourceIdentifier.class),
+ eq(YangTextSchemaSource.class));
+
+ final String outputStream = yangLibService.getSchema("name", "2016-01-01");
+ assertEquals(TEST_OUTPUT_STRING, outputStream);
+ }
+
+}
</dependencies>
</dependencyManagement>
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-checkstyle-plugin</artifactId>
- <dependencies>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>checkstyle</artifactId>
- <version>0.4.0-SNAPSHOT</version>
- </dependency>
- </dependencies>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
-
<profiles>
<profile>
<id>integrationtests</id>
<groupId>org.opendaylight.controller</groupId>
<artifactId>sal-binding-broker-impl</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-remote</artifactId>
- </dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-data-api</artifactId>
import java.net.URI;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opendaylight.restconf.utils.patch.Draft16StringModuleInstanceIdentifierCodec;
import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
import org.opendaylight.yangtools.yang.model.api.Module;
/**
* @deprecated This class will be replaced by
- * {@link org.opendaylight.restconf.utils.patch.Draft11StringModuleInstanceIdentifierCodec}
+ * {@link Draft16StringModuleInstanceIdentifierCodec}
*/
@Deprecated
-final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+public final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
private final DataSchemaContextTree dataContextTree;
private final SchemaContext context;
private final String defaultPrefix;
- StringModuleInstanceIdentifierCodec(SchemaContext context) {
+ public StringModuleInstanceIdentifierCodec(final SchemaContext context) {
this.context = Preconditions.checkNotNull(context);
this.dataContextTree = DataSchemaContextTree.from(context);
this.defaultPrefix = "";
}
- StringModuleInstanceIdentifierCodec(SchemaContext context, @Nonnull String defaultPrefix) {
+ StringModuleInstanceIdentifierCodec(final SchemaContext context, @Nonnull final String defaultPrefix) {
this.context = Preconditions.checkNotNull(context);
this.dataContextTree = DataSchemaContextTree.from(context);
this.defaultPrefix = defaultPrefix;
}
@Override
- protected Module moduleForPrefix(@Nonnull String prefix) {
+ protected Module moduleForPrefix(@Nonnull final String prefix) {
if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
return this.context.findModuleByName(this.defaultPrefix, null);
} else {
@Nullable
@Override
- protected String prefixForNamespace(@Nonnull URI namespace) {
+ protected String prefixForNamespace(@Nonnull final URI namespace) {
final Module module = this.context.findModuleByNamespaceAndRevision(namespace, null);
return module == null ? null : module.getName();
}
} else if (schemaNodeContext instanceof DataSchemaNode) {
schemaNode = (DataSchemaNode) schemaNodeContext;
} else {
- throw new IllegalStateException("Unknow SchemaNode");
+ throw new IllegalStateException("Unknown SchemaNode");
}
final String docRootElm = doc.getDocumentElement().getLocalName();
+ final String docRootNamespace = doc.getDocumentElement().getNamespaceURI();
final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
InstanceIdentifierContext<? extends SchemaNode> outIIContext;
DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, pathContext.getSchemaContext());
if (isPost() && !isRpc) {
- final Deque<Object> foundSchemaNodes = findPathToSchemaNodeByName(schemaNode, docRootElm);
+ final Deque<Object> foundSchemaNodes = findPathToSchemaNodeByName(schemaNode, docRootElm, docRootNamespace);
if (foundSchemaNodes.isEmpty()) {
throw new IllegalStateException(String.format("Child \"%s\" was not found in parent schema node \"%s\"",
docRootElm, schemaNode.getQName()));
NormalizedNode<?, ?> parsed = null;
- if(schemaNode instanceof ContainerSchemaNode) {
+ if (schemaNode instanceof ContainerSchemaNode) {
parsed = parserFactory.getContainerNodeParser().parse(Collections.singletonList(doc.getDocumentElement()), (ContainerSchemaNode) schemaNode);
} else if(schemaNode instanceof ListSchemaNode) {
final ListSchemaNode casted = (ListSchemaNode) schemaNode;
return new NormalizedNodeContext(outIIContext, parsed);
}
- private static Deque<Object> findPathToSchemaNodeByName(final DataSchemaNode schemaNode, final String elementName) {
+ private static Deque<Object> findPathToSchemaNodeByName(final DataSchemaNode schemaNode, final String elementName,
+ final String namespace) {
final Deque<Object> result = new ArrayDeque<>();
final ArrayList<ChoiceSchemaNode> choiceSchemaNodes = new ArrayList<>();
final Collection<DataSchemaNode> children = ((DataNodeContainer) schemaNode).getChildNodes();
for (final DataSchemaNode child : children) {
if (child instanceof ChoiceSchemaNode) {
choiceSchemaNodes.add((ChoiceSchemaNode) child);
- } else if (child.getQName().getLocalName().equalsIgnoreCase(elementName)) {
+ } else if (child.getQName().getLocalName().equalsIgnoreCase(elementName)
+ && child.getQName().getNamespace().toString().equalsIgnoreCase(namespace)) {
+ // add child to result
result.push(child);
+
+ // find augmentation
if (child.isAugmenting()) {
final AugmentationSchema augment = findCorrespondingAugment(schemaNode, child);
if (augment != null) {
result.push(augment);
}
}
+
+ // return result
return result;
}
}
for (final ChoiceSchemaNode choiceNode : choiceSchemaNodes) {
for (final ChoiceCaseNode caseNode : choiceNode.getCases()) {
- final Deque<Object> resultFromRecursion = findPathToSchemaNodeByName(caseNode, elementName);
+ final Deque<Object> resultFromRecursion = findPathToSchemaNodeByName(caseNode, elementName, namespace);
if (!resultFromRecursion.isEmpty()) {
resultFromRecursion.push(choiceNode);
if (choiceNode.isAugmenting()) {
import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
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.collect.Lists;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
throw new RestconfDocumentedException(errMsg);
}
- public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
- final SchemaContext globalSchema)
- throws Exception {
- final DOMDataReadWriteTransaction patchTransaction = this.domDataBroker.newReadWriteTransaction();
- final List<PATCHStatusEntity> editCollection = new ArrayList<>();
+ public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext patchContext) throws Exception {
+ 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;
- 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;
+ default:
+ LOG.error("Unsupported http PATCH operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+ break;
}
}
// if errors then cancel transaction and return error status
- if (errorCounter != 0) {
+ if (!withoutError) {
patchTransaction.cancel();
- return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
+ return new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
}
// if no errors commit transaction
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable final Void result) {
- status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
+ status.setStatus(new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
true, null));
waiter.countDown();
}
@Override
public void onFailure(final Throwable t) {
// if commit failed it is global error
- status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
- false, Lists.newArrayList(
+ LOG.error("Http PATCH {} transaction commit has failed", patchContext.getPatchId());
+ status.setStatus(new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
+ false, ImmutableList.of(
new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, t.getMessage()))));
waiter.countDown();
}
return readData.getResult();
}
+ /**
+ * POST data and submit transaction {@link DOMDataReadWriteTransaction}
+ * @return
+ */
private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
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 {} via Restconf: {} 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);
- 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);
- }
+ LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
+ postData(rWTransaction, datastore, path, payload, schemaContext);
return rWTransaction.submit();
}
+ /**
+ * POST data and do NOT submit transaction {@link DOMDataReadWriteTransaction}
+ */
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);
+ postData(rWTransaction, datastore, path, payload, schemaContext);
+ }
+
+ // FIXME: This is doing correct post for container and list children, not sure if this will work for choice case
+ private void postData(final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
+ final SchemaContext schemaContext) {
+ 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);
}
}
}
+ /**
+ * PUT data and submit {@link DOMDataReadWriteTransaction}
+ */
private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
- final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+ final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
- ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
- writeTransaction.put(datastore, path, payload);
- return writeTransaction.submit();
+ putData(readWriteTransaction, datastore, path, payload, schemaContext);
+ return readWriteTransaction.submit();
}
+ /**
+ * PUT data and do NOT submit {@link DOMDataReadWriteTransaction}
+ */
private void putDataWithinTransaction(
final DOMDataReadWriteTransaction writeTransaction, 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);
+ putData(writeTransaction, datastore, path, payload, schemaContext);
+ }
+
+ // FIXME: This is doing correct put for container and list children, not sure if this will work for choice case
+ private void putData(final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
+ if (payload instanceof MapNode) {
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ writeTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
+ for (final MapEntryNode child : ((MapNode) payload).getValue()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ writeTransaction.put(datastore, childPath, child);
+ }
+ } else {
+ ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
+ writeTransaction.put(datastore, path, payload);
+ }
}
private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
import java.util.Map;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
import org.opendaylight.netconf.sal.rest.impl.RestUtil;
+import org.opendaylight.netconf.sal.rest.impl.StringModuleInstanceIdentifierCodec;
import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO.IdentityValue;
import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO.Predicate;
import org.opendaylight.yangtools.concepts.Codec;
private final TypeDefinition<?> type;
private ObjectCodec(final TypeDefinition<?> typeDefinition, final DOMMountPoint mountPoint) {
- type = RestUtil.resolveBaseTypeFrom(typeDefinition);
- if (type instanceof IdentityrefTypeDefinition) {
- identityrefCodec = new IdentityrefCodecImpl(mountPoint);
+ this.type = RestUtil.resolveBaseTypeFrom(typeDefinition);
+ if (this.type instanceof IdentityrefTypeDefinition) {
+ this.identityrefCodec = new IdentityrefCodecImpl(mountPoint);
} else {
- identityrefCodec = null;
+ this.identityrefCodec = null;
}
- if (type instanceof InstanceIdentifierTypeDefinition) {
- instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint);
+ if (this.type instanceof InstanceIdentifierTypeDefinition) {
+ this.instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint);
} else {
- instanceIdentifier = null;
+ this.instanceIdentifier = null;
}
}
@Override
public Object deserialize(final Object input) {
try {
- if (type instanceof IdentityrefTypeDefinition) {
+ if (this.type instanceof IdentityrefTypeDefinition) {
if (input instanceof IdentityValuesDTO) {
- return identityrefCodec.deserialize(input);
+ return this.identityrefCodec.deserialize(input);
}
if(LOG.isDebugEnabled()) {
LOG.debug(
input == null ? "null" : input.getClass(), String.valueOf(input));
}
return null;
- } else if (type instanceof InstanceIdentifierTypeDefinition) {
+ } else if (this.type instanceof InstanceIdentifierTypeDefinition) {
if (input instanceof IdentityValuesDTO) {
- return instanceIdentifier.deserialize(input);
+ return this.instanceIdentifier.deserialize(input);
+ } else {
+ final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+ ControllerContext.getInstance().getGlobalSchema());
+ return codec.deserialize((String) input);
}
- LOG.info(
- "Value is not instance of InstanceIdentifierTypeDefinition but is {}. Therefore NULL is used as translation of - {}",
- input == null ? "null" : input.getClass(), String.valueOf(input));
- return null;
} else {
final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec = TypeDefinitionAwareCodec
- .from(type);
+ .from(this.type);
if (typeAwarecodec != null) {
if (input instanceof IdentityValuesDTO) {
return typeAwarecodec.deserialize(((IdentityValuesDTO) input).getOriginValue());
}
return typeAwarecodec.deserialize(String.valueOf(input));
} else {
- LOG.debug("Codec for type \"" + type.getQName().getLocalName()
+ LOG.debug("Codec for type \"" + this.type.getQName().getLocalName()
+ "\" is not implemented yet.");
return null;
}
@Override
public Object serialize(final Object input) {
try {
- if (type instanceof IdentityrefTypeDefinition) {
- return identityrefCodec.serialize(input);
- } else if (type instanceof LeafrefTypeDefinition) {
+ if (this.type instanceof IdentityrefTypeDefinition) {
+ return this.identityrefCodec.serialize(input);
+ } else if (this.type instanceof LeafrefTypeDefinition) {
return LEAFREF_DEFAULT_CODEC.serialize(input);
- } else if (type instanceof InstanceIdentifierTypeDefinition) {
- return instanceIdentifier.serialize(input);
+ } else if (this.type instanceof InstanceIdentifierTypeDefinition) {
+ return this.instanceIdentifier.serialize(input);
} else {
final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec = TypeDefinitionAwareCodec
- .from(type);
+ .from(this.type);
if (typeAwarecodec != null) {
return typeAwarecodec.serialize(input);
} else {
if(LOG.isDebugEnabled()) {
- LOG.debug("Codec for type \"" + type.getQName().getLocalName()
+ LOG.debug("Codec for type \"" + this.type.getQName().getLocalName()
+ "\" is not implemented yet.");
}
return null;
@Override
public QName deserialize(final IdentityValuesDTO data) {
final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
- final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint);
+ final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), this.mountPoint);
if (module == null) {
LOG.info("Module was not found for namespace {}", valueWithNamespace.getNamespace());
LOG.info("Idenetityref will be translated as NULL for data - {}", String.valueOf(valueWithNamespace));
final IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO();
for (final PathArgument pathArgument : data.getPathArguments()) {
final IdentityValue identityValue = qNameToIdentityValue(pathArgument.getNodeType());
- if (pathArgument instanceof NodeIdentifierWithPredicates && identityValue != null) {
+ if ((pathArgument instanceof NodeIdentifierWithPredicates) && (identityValue != null)) {
final List<Predicate> predicates = keyValuesToPredicateList(((NodeIdentifierWithPredicates) pathArgument)
.getKeyValues());
identityValue.setPredicates(predicates);
- } else if (pathArgument instanceof NodeWithValue && identityValue != null) {
+ } else if ((pathArgument instanceof NodeWithValue) && (identityValue != null)) {
final List<Predicate> predicates = new ArrayList<>();
final String value = String.valueOf(((NodeWithValue) pathArgument).getValue());
predicates.add(new Predicate(null, value));
public YangInstanceIdentifier deserialize(final IdentityValuesDTO data) {
final List<PathArgument> result = new ArrayList<PathArgument>();
final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
- final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint);
+ final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), this.mountPoint);
if (module == null) {
LOG.info("Module by namespace '{}' of first node in instance-identifier was not found.",
valueWithNamespace.getNamespace());
final List<IdentityValue> identities = data.getValuesWithNamespaces();
for (int i = 0; i < identities.size(); i++) {
final IdentityValue identityValue = identities.get(i);
- URI validNamespace = resolveValidNamespace(identityValue.getNamespace(), mountPoint);
+ URI validNamespace = resolveValidNamespace(identityValue.getNamespace(), this.mountPoint);
final DataSchemaNode node = ControllerContext.findInstanceDataChildByNameAndNamespace(
parentContainer, identityValue.getValue(), validNamespace);
if (node == null) {
final DataNodeContainer listNode = (DataNodeContainer) node;
final Map<QName, Object> predicatesMap = new HashMap<>();
for (final Predicate predicate : identityValue.getPredicates()) {
- validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), mountPoint);
+ validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), this.mountPoint);
final DataSchemaNode listKey = ControllerContext
.findInstanceDataChildByNameAndNamespace(listNode, predicate.getName().getValue(),
validNamespace);
}
}
result.add(pathArgument);
- if (i < identities.size() - 1) { // last element in instance-identifier can be other than
+ if (i < (identities.size() - 1)) { // last element in instance-identifier can be other than
// DataNodeContainer
if (node instanceof DataNodeContainer) {
parentContainer = (DataNodeContainer) node;
}
try {
- return this.broker.patchConfigurationDataWithinTransaction(context,
- this.controllerContext.getGlobalSchema());
+ return this.broker.patchConfigurationDataWithinTransaction(context);
} catch (final Exception e) {
LOG.debug("Patch transaction failed", e);
throw new RestconfDocumentedException(e.getMessage());
}
try {
- return this.broker.patchConfigurationDataWithinTransaction(context,
- this.controllerContext.getGlobalSchema());
+ return this.broker.patchConfigurationDataWithinTransaction(context);
} catch (final Exception e) {
LOG.debug("Patch transaction failed", e);
throw new RestconfDocumentedException(e.getMessage());
public final class PatchDataTransactionUtil {
private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
- public PatchDataTransactionUtil() {
+ private PatchDataTransactionUtil() {
throw new UnsupportedOperationException("Util class.");
}
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-final class Draft16StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+public final class Draft16StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
private final DataSchemaContextTree dataContextTree;
private final SchemaContext context;
private final String defaultPrefix;
- Draft16StringModuleInstanceIdentifierCodec(SchemaContext context) {
+ public Draft16StringModuleInstanceIdentifierCodec(final SchemaContext context) {
this.context = Preconditions.checkNotNull(context);
this.dataContextTree = DataSchemaContextTree.from(context);
this.defaultPrefix = "";
}
- Draft16StringModuleInstanceIdentifierCodec(SchemaContext context, @Nonnull String defaultPrefix) {
+ Draft16StringModuleInstanceIdentifierCodec(final SchemaContext context, @Nonnull final String defaultPrefix) {
this.context = Preconditions.checkNotNull(context);
this.dataContextTree = DataSchemaContextTree.from(context);
this.defaultPrefix = defaultPrefix;
}
@Override
- protected Module moduleForPrefix(@Nonnull String prefix) {
+ protected Module moduleForPrefix(@Nonnull final String prefix) {
if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
return this.context.findModuleByName(this.defaultPrefix, null);
} else {
@Nullable
@Override
- protected String prefixForNamespace(@Nonnull URI namespace) {
+ protected String prefixForNamespace(@Nonnull final URI namespace) {
final Module module = this.context.findModuleByNamespaceAndRevision(namespace, null);
return module == null ? null : module.getName();
}
--- /dev/null
+module sal-remote {
+
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
+ prefix "sal-remote";
+
+ organization "Cisco Systems, Inc.";
+ contact "Martin Bobak <mbobak@cisco.com>";
+
+ description
+ "This module contains the definition of methods related to
+ sal remote model.
+
+ Copyright (c)2013 Cisco Systems, Inc. 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";
+
+ revision "2014-01-14" {
+ description
+ "Initial revision";
+ }
+
+
+ typedef q-name {
+ type string;
+ reference
+ "http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#QName";
+ }
+
+ rpc create-data-change-event-subscription {
+ input {
+ leaf path {
+ type instance-identifier;
+ description "Subtree path. ";
+ }
+ }
+ output {
+ leaf stream-name {
+ type string;
+ description "Notification stream name.";
+ }
+ }
+ }
+
+ notification data-changed-notification {
+ description "Data change notification.";
+ list data-change-event {
+ key path;
+ leaf path {
+ type instance-identifier;
+ }
+ leaf store {
+ type enumeration {
+ enum config;
+ enum operation;
+ }
+ }
+ leaf operation {
+ type enumeration {
+ enum created;
+ enum updated;
+ enum deleted;
+ }
+ }
+ anyxml data{
+ description "DataObject ";
+ }
+ }
+ }
+
+ rpc create-notification-stream {
+ input {
+ leaf-list notifications {
+ type q-name;
+ description "Notification QNames";
+ }
+ }
+ output {
+ leaf notification-stream-identifier {
+ type string;
+ description "Unique notification stream identifier, in which notifications will be propagated";
+ }
+ }
+ }
+
+ rpc begin-transaction {
+ output {
+ anyxml data-modification-transaction{
+ description "DataModificationTransaction xml";
+ }
+ }
+ }
+
+}
\ No newline at end of file
--- /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;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class InstanceIdentifierTypeLeafTest {
+
+ @Test
+ public void stringToInstanceIdentifierTest() throws Exception {
+ final SchemaContext schemaContext = TestRestconfUtils.loadSchemaContext("/instanceidentifier");
+ ControllerContext.getInstance().setGlobalSchema(schemaContext);
+ final InstanceIdentifierContext<?> instanceIdentifier =
+ ControllerContext.getInstance().toInstanceIdentifier(
+ "/iid-value-module:cont-iid/iid-list/%2Fiid-value-module%3Acont-iid%2Fiid-value-module%3A"
+ + "values-iid%5Biid-value-module:value-iid='value'%5D");
+ final YangInstanceIdentifier yiD = instanceIdentifier.getInstanceIdentifier();
+ Assert.assertNotNull(yiD);
+ final PathArgument lastPathArgument = yiD.getLastPathArgument();
+ Assert.assertTrue(lastPathArgument.getNodeType().getNamespace().toString().equals("iid:value:module"));
+ Assert.assertTrue(lastPathArgument.getNodeType().getLocalName().equals("iid-list"));
+
+ final NodeIdentifierWithPredicates list = (NodeIdentifierWithPredicates) lastPathArgument;
+ final YangInstanceIdentifier value = (YangInstanceIdentifier) list.getKeyValues()
+ .get(QName.create(lastPathArgument.getNodeType(), "iid-leaf"));
+ final PathArgument lastPathArgumentOfValue = value.getLastPathArgument();
+ Assert.assertTrue(lastPathArgumentOfValue.getNodeType().getNamespace().toString().equals("iid:value:module"));
+ Assert.assertTrue(lastPathArgumentOfValue.getNodeType().getLocalName().equals("values-iid"));
+
+ final NodeIdentifierWithPredicates valueList = (NodeIdentifierWithPredicates) lastPathArgumentOfValue;
+ final String valueIid = (String) valueList.getKeyValues()
+ .get(QName.create(lastPathArgumentOfValue.getNodeType(), "value-iid"));
+ Assert.assertEquals("value", valueIid);
+ }
+
+}
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);
+ }
+}
assertEquals(dataNodeIdent, nnContext.getInstanceIdentifierContext().getInstanceIdentifier());
assertNotNull(NormalizedNodes.findNode(nnContext.getData(), dataNodeIdent));
}
+
+ /**
+ * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be
+ * used to distinguish between them to find correct one. Check if container was found not only according to its name
+ * but also by correct namespace used in payload.
+ */
+ @Test
+ public void findFooContainerUsingNamespaceTest() throws Exception {
+ mockBodyReader("", xmlBodyReader, true);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlDataFindFooContainer.xml");
+ final NormalizedNodeContext returnValue = xmlBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+
+ // check return value
+ checkNormalizedNodeContext(returnValue);
+ // check if container was found both according to its name and namespace
+ assertEquals("Not correct container found, name was ignored",
+ "foo-bar-container", returnValue.getData().getNodeType().getLocalName());
+ assertEquals("Not correct container found, namespace was ignored",
+ "foo:module", returnValue.getData().getNodeType().getNamespace().toString());
+ }
+
+ /**
+ * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be
+ * used to distinguish between them to find correct one. Check if container was found not only according to its name
+ * but also by correct namespace used in payload.
+ */
+ @Test
+ public void findBarContainerUsingNamespaceTest() throws Exception {
+ mockBodyReader("", xmlBodyReader, true);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlDataFindBarContainer.xml");
+ final NormalizedNodeContext returnValue = xmlBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+
+ // check return value
+ checkNormalizedNodeContext(returnValue);
+ // check if container was found both according to its name and namespace
+ assertEquals("Not correct container found, name was ignored",
+ "foo-bar-container", returnValue.getData().getNodeType().getLocalName());
+ assertEquals("Not correct container found, namespace was ignored",
+ "bar:module", returnValue.getData().getNodeType().getNamespace().toString());
+ }
}
package org.opendaylight.controller.sal.rest.impl.test.providers;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
assertNotNull(NormalizedNodes.findNode(nnContext.getData(),
dataNodeIdent));
}
+
+ /**
+ * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be
+ * used to distinguish between them to find correct one. Check if container was found not only according to its name
+ * but also by correct namespace used in payload.
+ */
+ @Test
+ public void findFooContainerUsingNamespaceTest() throws Exception {
+ mockBodyReader("instance-identifier-module:cont/yang-ext:mount", xmlBodyReader, true);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlDataFindFooContainer.xml");
+ final NormalizedNodeContext returnValue = xmlBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+
+ // check return value
+ checkMountPointNormalizedNodeContext(returnValue);
+ // check if container was found both according to its name and namespace
+ assertEquals("Not correct container found, name was ignored",
+ "foo-bar-container", returnValue.getData().getNodeType().getLocalName());
+ assertEquals("Not correct container found, namespace was ignored",
+ "foo:module", returnValue.getData().getNodeType().getNamespace().toString());
+ }
+
+ /**
+ * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be
+ * used to distinguish between them to find correct one. Check if container was found not only according to its name
+ * but also by correct namespace used in payload.
+ */
+ @Test
+ public void findBarContainerUsingNamespaceTest() throws Exception {
+ mockBodyReader("instance-identifier-module:cont/yang-ext:mount", xmlBodyReader, true);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlDataFindBarContainer.xml");
+ final NormalizedNodeContext returnValue = xmlBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+
+ // check return value
+ checkMountPointNormalizedNodeContext(returnValue);
+ // check if container was found both according to its name and namespace
+ assertEquals("Not correct container found, name was ignored",
+ "foo-bar-container", returnValue.getData().getNodeType().getLocalName());
+ assertEquals("Not correct container found, namespace was ignored",
+ "bar:module", returnValue.getData().getNodeType().getNamespace().toString());
+ }
}
}
}
-
/**
* 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.PATCHStatusContext;
import org.opendaylight.netconf.sal.restconf.impl.PutResult;
import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
* @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.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.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.newArrayList());
+ when(patchContext.getInstanceIdentifierContext()).thenReturn(identifierContext);
+ when(identifierContext.getMountPoint()).thenReturn(mountPoint);
+
+ // missing broker on mounted device
+ when(mountPoint.getService(DOMDataBroker.class)).thenReturn(Optional.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());
+ }
}
--- /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.restconf.restful.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.util.concurrent.Futures;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+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.PATCHStatusEntity;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.RestConnectorProvider;
+import org.opendaylight.restconf.common.references.SchemaContextRef;
+import org.opendaylight.restconf.handlers.TransactionChainHandler;
+import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+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.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+public class PatchDataTransactionUtilTest {
+
+ private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
+
+ @Mock
+ private DOMTransactionChain transactionChain;
+
+ @Mock
+ private DOMDataReadWriteTransaction rWTransaction;
+
+ private SchemaContextRef refSchemaCtx;
+ private YangInstanceIdentifier iIDCreateAndDelete;
+ private YangInstanceIdentifier iIDMerge;
+ private ContainerNode buildBaseContainerForTests;
+ private YangInstanceIdentifier targetNodeForCreateAndDelete;
+ private YangInstanceIdentifier targetNodeMerge;
+ private MapNode buildArtistList;
+
+ // Fields used when delete operation fails to reset transaction chain
+ private static Field handler;
+ private static Field broker;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+
+ PatchDataTransactionUtilTest.handler = RestConnectorProvider.class.getDeclaredField("transactionChainHandler");
+ PatchDataTransactionUtilTest.broker = RestConnectorProvider.class.getDeclaredField("dataBroker");
+
+ PatchDataTransactionUtilTest.handler.setAccessible(true);
+ PatchDataTransactionUtilTest.handler.set(RestConnectorProvider.class, mock(TransactionChainHandler.class));
+
+ PatchDataTransactionUtilTest.broker.setAccessible(true);
+ PatchDataTransactionUtilTest.broker.set(RestConnectorProvider.class, mock(DOMDataBroker.class));
+
+ refSchemaCtx = new SchemaContextRef(TestRestconfUtils.loadSchemaContext(PATH_FOR_NEW_SCHEMA_CONTEXT));
+ final QName baseQName = QName.create("http://example.com/ns/example-jukebox", "2015-04-04", "jukebox");
+ final QName containerPlayerQName = QName.create(baseQName, "player");
+ final QName leafGapQName = QName.create(baseQName, "gap");
+ final QName containerLibraryQName = QName.create(baseQName, "library");
+ final QName listArtistQName = QName.create(baseQName, "artist");
+ final QName leafNameQName = QName.create(baseQName, "name");
+ final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeWithKey =
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(listArtistQName, leafNameQName, "name of artist");
+
+ /** instance identifier for accessing leaf node "gap" */
+ iIDCreateAndDelete = YangInstanceIdentifier.builder()
+ .node(baseQName)
+ .node(containerPlayerQName)
+ .node(leafGapQName)
+ .build();
+
+ /** values that are used for creating leaf for testPatchDataCreateAndDelete test */
+ final LeafNode buildGapLeaf = Builders.leafBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(leafGapQName))
+ .withValue(0.2)
+ .build();
+
+ final ContainerNode buildPlayerContainer = Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(containerPlayerQName))
+ .withChild(buildGapLeaf)
+ .build();
+
+ buildBaseContainerForTests = Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(baseQName))
+ .withChild(buildPlayerContainer)
+ .build();
+
+ targetNodeForCreateAndDelete = YangInstanceIdentifier.builder(iIDCreateAndDelete)
+ .node(containerPlayerQName)
+ .node(leafGapQName)
+ .build();
+
+ /** instance identifier for accessing leaf node "name" in list "artist" */
+ iIDMerge = YangInstanceIdentifier.builder()
+ .node(baseQName)
+ .node(containerLibraryQName)
+ .node(listArtistQName)
+ .nodeWithKey(listArtistQName, QName.create(listArtistQName, "name"), "name of artist")
+ .node(leafNameQName)
+ .build();
+
+ /** values that are used for creating leaf for testPatchDataReplaceMergeAndRemove test */
+ final LeafNode<Object> contentName = Builders.leafBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(baseQName, "name")))
+ .withValue("name of artist")
+ .build();
+
+ final LeafNode<Object> contentDescription = Builders.leafBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(baseQName, "description")))
+ .withValue("description of artist")
+ .build();
+
+ final MapEntryNode mapEntryNode = Builders.mapEntryBuilder()
+ .withNodeIdentifier(nodeWithKey)
+ .withChild(contentName)
+ .withChild(contentDescription)
+ .build();
+
+ buildArtistList = Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listArtistQName))
+ .withChild(mapEntryNode)
+ .build();
+
+ targetNodeMerge = YangInstanceIdentifier.builder()
+ .node(baseQName)
+ .node(containerLibraryQName)
+ .node(listArtistQName)
+ .nodeWithKey(listArtistQName, leafNameQName, "name of artist")
+ .build();
+
+ /** Mocks */
+ doReturn(rWTransaction).when(transactionChain).newReadWriteTransaction();
+ doReturn(Futures.immediateCheckedFuture(null)).when(rWTransaction).submit();
+ }
+
+ @Test
+ public void testPatchDataReplaceMergeAndRemove() {
+ doReturn(Futures.immediateCheckedFuture(false)).doReturn(Futures.immediateCheckedFuture(true))
+ .when(rWTransaction).exists(LogicalDatastoreType.CONFIGURATION, targetNodeMerge);
+
+ final PATCHEntity entityReplace = new PATCHEntity("edit1", "REPLACE", targetNodeMerge, buildArtistList);
+ final PATCHEntity entityMerge = new PATCHEntity("edit2", "MERGE", targetNodeMerge, buildArtistList);
+ final PATCHEntity entityRemove = new PATCHEntity("edit3", "REMOVE", targetNodeMerge);
+ final List<PATCHEntity> entities = new ArrayList<>();
+
+ entities.add(entityReplace);
+ entities.add(entityMerge);
+ entities.add(entityRemove);
+
+ final InstanceIdentifierContext<? extends SchemaNode> iidContext =
+ new InstanceIdentifierContext<>(iIDMerge, null, null, refSchemaCtx.get());
+ final PATCHContext patchContext = new PATCHContext(iidContext, entities, "patchRMRm");
+ final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChain);
+ final PATCHStatusContext patchStatusContext =
+ PatchDataTransactionUtil.patchData(patchContext, wrapper, refSchemaCtx);
+
+ for (PATCHStatusEntity entity : patchStatusContext.getEditCollection()) {
+ assertTrue(entity.isOk());
+ }
+ assertTrue(patchStatusContext.isOk());
+ }
+
+ @Test
+ public void testPatchDataCreateAndDelete() throws Exception {
+ doReturn(Futures.immediateCheckedFuture(false)).doReturn(Futures.immediateCheckedFuture(true))
+ .when(rWTransaction).exists(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
+
+ final PATCHEntity entityCreate =
+ new PATCHEntity("edit1", "CREATE", targetNodeForCreateAndDelete, buildBaseContainerForTests);
+ final PATCHEntity entityDelete =
+ new PATCHEntity("edit2", "DELETE", targetNodeForCreateAndDelete);
+ final List<PATCHEntity> entities = new ArrayList<>();
+
+ entities.add(entityCreate);
+ entities.add(entityDelete);
+
+ final InstanceIdentifierContext<? extends SchemaNode> iidContext =
+ new InstanceIdentifierContext<>(iIDCreateAndDelete, null, null, refSchemaCtx.get());
+ final PATCHContext patchContext = new PATCHContext(iidContext, entities, "patchCD");
+ final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChain);
+ final PATCHStatusContext patchStatusContext = PatchDataTransactionUtil.patchData(patchContext, wrapper, refSchemaCtx);
+
+ for (PATCHStatusEntity entity : patchStatusContext.getEditCollection()) {
+ assertTrue(entity.isOk());
+ }
+ assertTrue(patchStatusContext.isOk());
+ }
+
+ @Test
+ public void deleteNonexistentDataTest() {
+ doReturn(Futures.immediateCheckedFuture(false))
+ .when(rWTransaction).exists(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
+
+ final PATCHEntity entityDelete =
+ new PATCHEntity("edit", "DELETE", targetNodeForCreateAndDelete);
+ final List<PATCHEntity> entities = new ArrayList<>();
+
+ entities.add(entityDelete);
+
+ final InstanceIdentifierContext<? extends SchemaNode> iidContext =
+ new InstanceIdentifierContext<>(iIDCreateAndDelete, null, null, refSchemaCtx.get());
+ final PATCHContext patchContext = new PATCHContext(iidContext, entities, "patchD");
+ final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChain);
+ final PATCHStatusContext patchStatusContext = PatchDataTransactionUtil.patchData(patchContext, wrapper, 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());
+ }
+
+ @Test
+ public void testPatchMergePutContainer() throws Exception {
+ doReturn(Futures.immediateCheckedFuture(false)).doReturn(Futures.immediateCheckedFuture(true))
+ .when(rWTransaction).exists(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
+
+ final PATCHEntity entityMerge =
+ new PATCHEntity("edit1", "MERGE", targetNodeForCreateAndDelete, buildBaseContainerForTests);
+ final List<PATCHEntity> entities = new ArrayList<>();
+
+ entities.add(entityMerge);
+
+ final InstanceIdentifierContext<? extends SchemaNode> iidContext =
+ new InstanceIdentifierContext<>(iIDCreateAndDelete, null, null, refSchemaCtx.get());
+ final PATCHContext patchContext = new PATCHContext(iidContext, entities, "patchM");
+ final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChain);
+ final PATCHStatusContext patchStatusContext = PatchDataTransactionUtil.patchData(patchContext, wrapper, refSchemaCtx);
+
+ for (PATCHStatusEntity entity : patchStatusContext.getEditCollection()) {
+ assertTrue(entity.isOk());
+ }
+ assertTrue(patchStatusContext.isOk());
+ }
+}
\ No newline at end of file
--- /dev/null
+module iid-value-module {
+ namespace "iid:value:module";
+
+ prefix "iidvm";
+ revision 2016-09-12 {
+ }
+
+ container cont-iid {
+ list iid-list{
+ key "iid-leaf";
+ leaf iid-leaf{
+ type instance-identifier;
+ }
+ }
+
+ list values-iid{
+ key "value-iid";
+ leaf value-iid{
+ type string;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /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
+ -->
+
+<foo-bar-container xmlns="bar:module">
+</foo-bar-container>
\ No newline at end of file
--- /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
+ -->
+
+<foo-bar-container xmlns="foo:module">
+</foo-bar-container>
\ No newline at end of file
--- /dev/null
+module bar-module {
+ namespace "bar:module";
+
+ prefix "bar-module";
+ revision 2016-09-29 {
+ }
+
+ /* This container has the same name as container in foo-module */
+ container foo-bar-container {
+ }
+}
\ No newline at end of file
--- /dev/null
+module foo-module {
+ namespace "foo:module";
+
+ prefix "foo-module";
+ revision 2016-09-29 {
+ }
+
+ /* This container has the same name as container in bar-module */
+ container foo-bar-container {
+ }
+}
\ No newline at end of file
<artifactId>sal-core-api</artifactId>
</dependency>
<dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-remote</artifactId>
+ <groupId>org.opendaylight.netconf</groupId>
+ <artifactId>sal-rest-connector</artifactId>
</dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>