From: Tomas Cere Date: Tue, 12 Apr 2016 12:13:49 +0000 (+0000) Subject: Merge "add partial match for non-module capabilities - to enable deviations in base... X-Git-Tag: release/boron~149 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=6dcc85f57e6ca3b074bd8f48d53f661daf47fe2c;hp=5b87e56cfc9ed379f0a469ce0d2f25a94651863c;p=netconf.git Merge "add partial match for non-module capabilities - to enable deviations in base capabilities" --- diff --git a/netconf/mdsal-netconf-notification/src/main/java/org/opendaylight/netconf/mdsal/notification/NetconfNotificationOperationService.java b/netconf/mdsal-netconf-notification/src/main/java/org/opendaylight/netconf/mdsal/notification/NetconfNotificationOperationService.java index 94678c9d55..9824e1e57b 100644 --- a/netconf/mdsal-netconf-notification/src/main/java/org/opendaylight/netconf/mdsal/notification/NetconfNotificationOperationService.java +++ b/netconf/mdsal-netconf-notification/src/main/java/org/opendaylight/netconf/mdsal/notification/NetconfNotificationOperationService.java @@ -30,6 +30,14 @@ public class NetconfNotificationOperationService implements NetconfOperationServ @Override public void close() { - + for (NetconfOperation netconfOperation : netconfOperations) { + if (netconfOperation instanceof AutoCloseable) { + try { + ((AutoCloseable) netconfOperation).close(); + } catch (Exception e) { + throw new IllegalStateException("Exception while closing " + netconfOperation, e); + } + } + } } } diff --git a/netconf/netconf-impl/src/test/java/org/opendaylight/netconf/impl/MessageHeaderTest.java b/netconf/netconf-impl/src/test/java/org/opendaylight/netconf/impl/MessageHeaderTest.java deleted file mode 100644 index 40ab00c42e..0000000000 --- a/netconf/netconf-impl/src/test/java/org/opendaylight/netconf/impl/MessageHeaderTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2013 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.impl; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.opendaylight.netconf.util.messages.NetconfMessageHeader; - -@Deprecated -public class MessageHeaderTest { - @Test - public void testFromBytes() { - final byte[] raw = new byte[] { (byte) 0x0a, (byte) 0x23, (byte) 0x35, (byte) 0x38, (byte) 0x0a }; - NetconfMessageHeader header = NetconfMessageHeader.fromBytes(raw); - assertEquals(58, header.getLength()); - } - - @Test - public void testToBytes() { - NetconfMessageHeader header = new NetconfMessageHeader(123); - assertArrayEquals(new byte[] { (byte) 0x0a, (byte) 0x23, (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x0a }, - header.toBytes()); - } -} diff --git a/netconf/netconf-impl/src/test/java/org/opendaylight/netconf/impl/MessageParserTest.java b/netconf/netconf-impl/src/test/java/org/opendaylight/netconf/impl/MessageParserTest.java index df34c45c5f..562d4a4ba9 100644 --- a/netconf/netconf-impl/src/test/java/org/opendaylight/netconf/impl/MessageParserTest.java +++ b/netconf/netconf-impl/src/test/java/org/opendaylight/netconf/impl/MessageParserTest.java @@ -15,12 +15,15 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import com.google.common.base.Charsets; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.embedded.EmbeddedChannel; +import java.nio.ByteBuffer; import java.util.Queue; import org.junit.Before; import org.junit.Test; +import org.opendaylight.netconf.api.NetconfMessage; import org.opendaylight.netconf.nettyutil.handler.ChunkedFramingMechanismEncoder; import org.opendaylight.netconf.nettyutil.handler.FramingMechanismHandlerFactory; import org.opendaylight.netconf.nettyutil.handler.NetconfChunkAggregator; @@ -29,9 +32,7 @@ import org.opendaylight.netconf.nettyutil.handler.NetconfMessageToXMLEncoder; import org.opendaylight.netconf.nettyutil.handler.NetconfXMLToMessageDecoder; import org.opendaylight.netconf.util.messages.FramingMechanism; import org.opendaylight.netconf.util.messages.NetconfMessageConstants; -import org.opendaylight.netconf.util.messages.NetconfMessageHeader; import org.opendaylight.netconf.util.test.XmlFileLoader; -import org.opendaylight.netconf.api.NetconfMessage; public class MessageParserTest { @@ -78,8 +79,7 @@ public class MessageParserTest { byte[] header = new byte[String.valueOf(exptHeaderLength).length() + NetconfMessageConstants.MIN_HEADER_LENGTH - 1]; recievedOutbound.getBytes(0, header); - NetconfMessageHeader messageHeader = NetconfMessageHeader.fromBytes(header); - assertEquals(exptHeaderLength, messageHeader.getLength()); + assertEquals(exptHeaderLength, getHeaderLength(header)); testChunkChannel.writeInbound(recievedOutbound); } @@ -108,4 +108,10 @@ public class MessageParserTest { assertNotNull(receivedMessage); assertXMLEqual(this.msg.getDocument(), receivedMessage.getDocument()); } + + private static long getHeaderLength(byte[] bytes) { + byte[] HEADER_START = new byte[] { (byte) 0x0a, (byte) 0x23 }; + return Long.parseLong(Charsets.US_ASCII.decode( + ByteBuffer.wrap(bytes, HEADER_START.length, bytes.length - HEADER_START.length - 1)).toString()); + } } diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java index bfc919e595..7a0d0f7dbb 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java @@ -266,11 +266,11 @@ public abstract class AbstractNetconfTopology implements NetconfTopology, Bindin RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address); RemoteDeviceHandler salFacade = - createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker, defaultRequestTimeoutMillis); + createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker); if (keepaliveDelay > 0) { LOG.warn("Adding keepalive facade, for device {}", nodeId); - salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay); + salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay, defaultRequestTimeoutMillis); } final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node); @@ -393,7 +393,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology, Bindin .build(); } - protected abstract RemoteDeviceHandler createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis); + protected abstract RemoteDeviceHandler createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker); @Override public abstract ConnectionStatusListenerRegistration registerConnectionStatusListener(NodeId node, RemoteDeviceHandler listener); diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/ClusteredNetconfTopology.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/ClusteredNetconfTopology.java index 04c95b49aa..1612c7d9fc 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/ClusteredNetconfTopology.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/ClusteredNetconfTopology.java @@ -149,11 +149,11 @@ public class ClusteredNetconfTopology extends AbstractNetconfTopology implements RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address); RemoteDeviceHandler salFacade = - createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker, defaultRequestTimeoutMillis); + createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker); if (keepaliveDelay > 0) { LOG.warn("Adding keepalive facade, for device {}", nodeId); - salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay); + salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay, defaultRequestTimeoutMillis); } final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node); @@ -165,8 +165,8 @@ public class ClusteredNetconfTopology extends AbstractNetconfTopology implements } @Override - protected RemoteDeviceHandler createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis) { - return new TopologyMountPointFacade(topologyId, id, domBroker, bindingBroker, defaultRequestTimeoutMillis); + protected RemoteDeviceHandler createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker) { + return new TopologyMountPointFacade(topologyId, id, domBroker, bindingBroker); } @Override diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java index 33b468ce2b..c1e41189e3 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java @@ -80,8 +80,8 @@ public class NetconfTopologyImpl extends AbstractNetconfTopology implements Data } @Override - protected RemoteDeviceHandler createSalFacade(RemoteDeviceId id, Broker domBroker, BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis) { - return new NetconfDeviceSalFacade(id, domBroker, bindingAwareBroker, defaultRequestTimeoutMillis); + protected RemoteDeviceHandler createSalFacade(RemoteDeviceId id, Broker domBroker, BindingAwareBroker bindingBroker) { + return new NetconfDeviceSalFacade(id, domBroker, bindingAwareBroker); } @Override diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/NetconfDeviceMasterDataBroker.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/NetconfDeviceMasterDataBroker.java index f210820ccd..dc292d33eb 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/NetconfDeviceMasterDataBroker.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/NetconfDeviceMasterDataBroker.java @@ -57,9 +57,9 @@ public class NetconfDeviceMasterDataBroker implements ProxyNetconfDeviceDataBrok public NetconfDeviceMasterDataBroker(final ActorSystem actorSystem, final RemoteDeviceId id, final SchemaContext schemaContext, final DOMRpcService rpc, - final NetconfSessionPreferences netconfSessionPreferences, final long requestTimeoutMillis) { + final NetconfSessionPreferences netconfSessionPreferences) { this.id = id; - delegateBroker = new NetconfDeviceDataBroker(id, schemaContext, rpc, netconfSessionPreferences, requestTimeoutMillis); + delegateBroker = new NetconfDeviceDataBroker(id, schemaContext, rpc, netconfSessionPreferences); this.actorSystem = actorSystem; // only ever need 1 readTx since it doesnt need to be closed diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/TopologyMountPointFacade.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/TopologyMountPointFacade.java index 269db6907b..c79627be5a 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/TopologyMountPointFacade.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/TopologyMountPointFacade.java @@ -43,7 +43,6 @@ public class TopologyMountPointFacade implements AutoCloseable, RemoteDeviceHand private final RemoteDeviceId id; private final Broker domBroker; private final BindingAwareBroker bindingBroker; - private final long defaultRequestTimeoutMillis; private SchemaContext remoteSchemaContext = null; private NetconfSessionPreferences netconfSessionPreferences = null; @@ -58,13 +57,11 @@ public class TopologyMountPointFacade implements AutoCloseable, RemoteDeviceHand public TopologyMountPointFacade(final String topologyId, final RemoteDeviceId id, final Broker domBroker, - final BindingAwareBroker bindingBroker, - long defaultRequestTimeoutMillis) { + final BindingAwareBroker bindingBroker) { this.topologyId = topologyId; this.id = id; this.domBroker = domBroker; this.bindingBroker = bindingBroker; - this.defaultRequestTimeoutMillis = defaultRequestTimeoutMillis; this.salProvider = new ClusteredNetconfDeviceMountInstanceProxy(id); registerToSal(domBroker); } @@ -124,7 +121,7 @@ public class TopologyMountPointFacade implements AutoCloseable, RemoteDeviceHand deviceDataBroker = TypedActor.get(context).typedActorOf(new TypedProps<>(ProxyNetconfDeviceDataBroker.class, new Creator() { @Override public NetconfDeviceMasterDataBroker create() throws Exception { - return new NetconfDeviceMasterDataBroker(actorSystem, id, remoteSchemaContext, deviceRpc, netconfSessionPreferences, defaultRequestTimeoutMillis); + return new NetconfDeviceMasterDataBroker(actorSystem, id, remoteSchemaContext, deviceRpc, netconfSessionPreferences); } }), MOUNT_POINT); LOG.debug("Master data broker registered on path {}", TypedActor.get(actorSystem).getActorRefFor(deviceDataBroker).path()); diff --git a/netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/AbstractNetconfTopologyTest.java b/netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/AbstractNetconfTopologyTest.java index a29d95eb1a..2e16cca1cb 100644 --- a/netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/AbstractNetconfTopologyTest.java +++ b/netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/AbstractNetconfTopologyTest.java @@ -287,7 +287,7 @@ public class AbstractNetconfTopologyTest { } @Override - protected RemoteDeviceHandler createSalFacade(RemoteDeviceId id, Broker domBroker, BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis) { + protected RemoteDeviceHandler createSalFacade(RemoteDeviceId id, Broker domBroker, BindingAwareBroker bindingBroker) { return salFacade; } diff --git a/netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/pipeline/TopologyMountPointFacadeTest.java b/netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/pipeline/TopologyMountPointFacadeTest.java index 2b4b37418a..68342a56e5 100644 --- a/netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/pipeline/TopologyMountPointFacadeTest.java +++ b/netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/pipeline/TopologyMountPointFacadeTest.java @@ -48,7 +48,7 @@ public class TopologyMountPointFacadeTest { MockitoAnnotations.initMocks(this); - mountPointFacade = new TopologyMountPointFacade(TOPOLOGY_ID, REMOTE_DEVICE_ID, domBroker, bindingBroker, 1L); + mountPointFacade = new TopologyMountPointFacade(TOPOLOGY_ID, REMOTE_DEVICE_ID, domBroker, bindingBroker); mountPointFacade.registerConnectionStatusListener(connectionStatusListener1); mountPointFacade.registerConnectionStatusListener(connectionStatusListener2); diff --git a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/messages/NetconfMessageHeader.java b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/messages/NetconfMessageHeader.java deleted file mode 100644 index 8a425c2193..0000000000 --- a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/messages/NetconfMessageHeader.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2013 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.util.messages; - -import com.google.common.base.Charsets; -import com.google.common.base.Preconditions; -import java.nio.ByteBuffer; - -/** - * Netconf message header is used only when chunked framing mechanism is - * supported. The header consists of only the length field. - */ -@Deprecated -public final class NetconfMessageHeader { - // \n#\n - private static final byte[] HEADER_START = new byte[] { (byte) 0x0a, (byte) 0x23 }; - private static final byte HEADER_END = (byte) 0x0a; - private final long length; - - public NetconfMessageHeader(final long length) { - Preconditions.checkArgument(length < Integer.MAX_VALUE && length > 0); - this.length = length; - } - - public byte[] toBytes() { - return toBytes(this.length); - } - - // FIXME: improve precision to long - public int getLength() { - return (int) this.length; - } - - public static NetconfMessageHeader fromBytes(final byte[] bytes) { - // the length is variable therefore bytes between headerBegin and - // headerEnd mark the length - // the length should be only numbers and therefore easily parsed with - // ASCII - long length = Long.parseLong(Charsets.US_ASCII.decode( - ByteBuffer.wrap(bytes, HEADER_START.length, bytes.length - HEADER_START.length - 1)).toString()); - - return new NetconfMessageHeader(length); - } - - public static byte[] toBytes(final long length) { - final byte[] l = String.valueOf(length).getBytes(Charsets.US_ASCII); - final byte[] h = new byte[HEADER_START.length + l.length + 1]; - System.arraycopy(HEADER_START, 0, h, 0, HEADER_START.length); - System.arraycopy(l, 0, h, HEADER_START.length, l.length); - System.arraycopy(new byte[] { HEADER_END }, 0, h, HEADER_START.length + l.length, 1); - return h; - } -} diff --git a/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/messages/NetconfMessageHeaderTest.java b/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/messages/NetconfMessageHeaderTest.java deleted file mode 100644 index 1c09b959b8..0000000000 --- a/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/messages/NetconfMessageHeaderTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2014 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.util.messages; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -import com.google.common.base.Charsets; -import org.junit.Test; - -@Deprecated -public class NetconfMessageHeaderTest { - @Test - public void testGet() throws Exception { - NetconfMessageHeader header = new NetconfMessageHeader(10); - assertEquals(header.getLength(), 10); - - byte[] expectedValue = "\n#10\n".getBytes(Charsets.US_ASCII); - assertArrayEquals(expectedValue, header.toBytes()); - } -} diff --git a/netconf/sal-netconf-connector/pom.xml b/netconf/sal-netconf-connector/pom.xml index ee1640e33d..40fe3b2f70 100644 --- a/netconf/sal-netconf-connector/pom.xml +++ b/netconf/sal-netconf-connector/pom.xml @@ -191,18 +191,6 @@ org.hamcrest hamcrest-core - - org.powermock - powermock-api-mockito - 1.6.4 - test - - - org.powermock - powermock-module-junit4 - 1.6.4 - test - diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java index 785be9954d..d7222c1bb2 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -192,14 +192,14 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co final ExecutorService globalProcessingExecutor = processingExecutor.getExecutor(); RemoteDeviceHandler salFacade - = new NetconfDeviceSalFacade(id, domRegistry, bindingRegistry, getDefaultRequestTimeoutMillis()); + = new NetconfDeviceSalFacade(id, domRegistry, bindingRegistry); final Long keepaliveDelay = getKeepaliveDelay(); if (shouldSendKeepalive()) { // Keepalive executor is optional for now and a default instance is supported final ScheduledExecutorService executor = keepaliveExecutor == null ? DEFAULT_KEEPALIVE_EXECUTOR : keepaliveExecutor.getExecutor(); - salFacade = new KeepaliveSalFacade(id, salFacade, executor, keepaliveDelay); + salFacade = new KeepaliveSalFacade(id, salFacade, executor, keepaliveDelay, getDefaultRequestTimeoutMillis()); } // Setup information related to the SchemaRegistry, SchemaResourceFactory, etc. diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/KeepaliveSalFacade.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/KeepaliveSalFacade.java index dc2b5dff42..c450ac68fe 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/KeepaliveSalFacade.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/KeepaliveSalFacade.java @@ -51,28 +51,33 @@ public final class KeepaliveSalFacade implements RemoteDeviceHandler salFacade; private final ScheduledExecutorService executor; private final long keepaliveDelaySeconds; private final ResetKeepalive resetKeepaliveTask; + private final long defaultRequestTimeoutMillis; private volatile NetconfDeviceCommunicator listener; private volatile ScheduledFuture currentKeepalive; private volatile DOMRpcService currentDeviceRpc; public KeepaliveSalFacade(final RemoteDeviceId id, final RemoteDeviceHandler salFacade, - final ScheduledExecutorService executor, final long keepaliveDelaySeconds) { + final ScheduledExecutorService executor, final long keepaliveDelaySeconds, final long defaultRequestTimeoutMillis) { this.id = id; this.salFacade = salFacade; this.executor = executor; this.keepaliveDelaySeconds = keepaliveDelaySeconds; + this.defaultRequestTimeoutMillis = defaultRequestTimeoutMillis; this.resetKeepaliveTask = new ResetKeepalive(); } public KeepaliveSalFacade(final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final ScheduledExecutorService executor) { - this(id, salFacade, executor, DEFAULT_DELAY); + this(id, salFacade, executor, DEFAULT_DELAY, DEFAULT_TRANSACTION_TIMEOUT_MILLI); } /** @@ -118,7 +123,7 @@ public final class KeepaliveSalFacade implements RemoteDeviceHandler { + private class ResetKeepalive implements FutureCallback { @Override public void onSuccess(@Nullable final DOMRpcResult result) { // No matter what response we got, rpc-reply or rpc-error, we got it from device so the netconf session is OK @@ -223,24 +228,51 @@ public final class KeepaliveSalFacade implements RemoteDeviceHandler rpcResultFuture; + + public RequestTimeoutTask(final CheckedFuture rpcResultFuture) { + this.rpcResultFuture = rpcResultFuture; + } + + @Override + public void run() { + if (!rpcResultFuture.isDone()) { + rpcResultFuture.cancel(true); + } + } + } + /** - * DOMRpcService proxy that attaches reset-keepalive-task to each RPC invocation. + * DOMRpcService proxy that attaches reset-keepalive-task and schedule + * request-timeout-task to each RPC invocation. */ private static final class KeepaliveDOMRpcService implements DOMRpcService { private final DOMRpcService deviceRpc; private ResetKeepalive resetKeepaliveTask; + private final long defaultRequestTimeoutMillis; + private final ScheduledExecutorService executor; - public KeepaliveDOMRpcService(final DOMRpcService deviceRpc, final ResetKeepalive resetKeepaliveTask) { + public KeepaliveDOMRpcService(final DOMRpcService deviceRpc, final ResetKeepalive resetKeepaliveTask, + final long defaultRequestTimeoutMillis, final ScheduledExecutorService executor) { this.deviceRpc = deviceRpc; this.resetKeepaliveTask = resetKeepaliveTask; + this.defaultRequestTimeoutMillis = defaultRequestTimeoutMillis; + this.executor = executor; } @Nonnull @@ -248,6 +280,10 @@ public final class KeepaliveSalFacade implements RemoteDeviceHandler invokeRpc(@Nonnull final SchemaPath type, final NormalizedNode input) { final CheckedFuture domRpcResultDOMRpcExceptionCheckedFuture = deviceRpc.invokeRpc(type, input); Futures.addCallback(domRpcResultDOMRpcExceptionCheckedFuture, resetKeepaliveTask); + + final RequestTimeoutTask timeoutTask = new RequestTimeoutTask(domRpcResultDOMRpcExceptionCheckedFuture); + executor.schedule(timeoutTask, defaultRequestTimeoutMillis, TimeUnit.MILLISECONDS); + return domRpcResultDOMRpcExceptionCheckedFuture; } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBroker.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBroker.java index 7283cae9b8..421e52da71 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBroker.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBroker.java @@ -36,16 +36,14 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; public final class NetconfDeviceDataBroker implements DOMDataBroker { private final RemoteDeviceId id; private final NetconfBaseOps netconfOps; - private final long requestTimeoutMillis; private final boolean rollbackSupport; private boolean candidateSupported; private boolean runningWritable; - public NetconfDeviceDataBroker(final RemoteDeviceId id, final SchemaContext schemaContext, final DOMRpcService rpc, final NetconfSessionPreferences netconfSessionPreferences, long requestTimeoutMillis) { + public NetconfDeviceDataBroker(final RemoteDeviceId id, final SchemaContext schemaContext, final DOMRpcService rpc, final NetconfSessionPreferences netconfSessionPreferences) { this.id = id; this.netconfOps = new NetconfBaseOps(rpc, schemaContext); - this.requestTimeoutMillis = requestTimeoutMillis; // get specific attributes from netconf preferences and get rid of it // no need to keep the entire preferences object, its quite big with all the capability QNames candidateSupported = netconfSessionPreferences.isCandidateSupported(); @@ -57,7 +55,7 @@ public final class NetconfDeviceDataBroker implements DOMDataBroker { @Override public DOMDataReadOnlyTransaction newReadOnlyTransaction() { - return new ReadOnlyTx(netconfOps, id, requestTimeoutMillis); + return new ReadOnlyTx(netconfOps, id); } @Override @@ -69,12 +67,12 @@ public final class NetconfDeviceDataBroker implements DOMDataBroker { public DOMDataWriteTransaction newWriteOnlyTransaction() { if(candidateSupported) { if(runningWritable) { - return new WriteCandidateRunningTx(id, netconfOps, rollbackSupport, requestTimeoutMillis); + return new WriteCandidateRunningTx(id, netconfOps, rollbackSupport); } else { - return new WriteCandidateTx(id, netconfOps, rollbackSupport, requestTimeoutMillis); + return new WriteCandidateTx(id, netconfOps, rollbackSupport); } } else { - return new WriteRunningTx(id, netconfOps, rollbackSupport, requestTimeoutMillis); + return new WriteRunningTx(id, netconfOps, rollbackSupport); } } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java index 7bb3df08a5..2a817db7ec 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java @@ -30,14 +30,12 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice private final RemoteDeviceId id; private final NetconfDeviceSalProvider salProvider; - private final long defaultRequestTimeoutMillis; private final List salRegistrations = Lists.newArrayList(); - public NetconfDeviceSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis) { + public NetconfDeviceSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker) { this.id = id; this.salProvider = new NetconfDeviceSalProvider(id); - this.defaultRequestTimeoutMillis = defaultRequestTimeoutMillis; registerToSal(domBroker, bindingBroker); } @@ -55,7 +53,7 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice public synchronized void onDeviceConnected(final SchemaContext schemaContext, final NetconfSessionPreferences netconfSessionPreferences, final DOMRpcService deviceRpc) { - final DOMDataBroker domBroker = new NetconfDeviceDataBroker(id, schemaContext, deviceRpc, netconfSessionPreferences, defaultRequestTimeoutMillis); + final DOMDataBroker domBroker = new NetconfDeviceDataBroker(id, schemaContext, deviceRpc, netconfSessionPreferences); final NetconfDeviceNotificationService notificationService = new NetconfDeviceNotificationService(); diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/AbstractWriteTx.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/AbstractWriteTx.java index a12ba3a02a..3aeff93da4 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/AbstractWriteTx.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/AbstractWriteTx.java @@ -11,11 +11,8 @@ package org.opendaylight.netconf.sal.connect.netconf.sal.tx; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; @@ -29,7 +26,6 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.MixinNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,15 +33,13 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction { private static final Logger LOG = LoggerFactory.getLogger(AbstractWriteTx.class); - protected final long defaultRequestTimeoutMillis; protected final RemoteDeviceId id; protected final NetconfBaseOps netOps; protected final boolean rollbackSupport; // Allow commit to be called only once protected boolean finished = false; - public AbstractWriteTx(final long requestTimeoutMillis, final NetconfBaseOps netOps, final RemoteDeviceId id, final boolean rollbackSupport) { - this.defaultRequestTimeoutMillis = requestTimeoutMillis; + public AbstractWriteTx(final NetconfBaseOps netOps, final RemoteDeviceId id, final boolean rollbackSupport) { this.netOps = netOps; this.id = id; this.rollbackSupport = rollbackSupport; @@ -176,18 +170,4 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction { } protected abstract void editConfig(DataContainerChild editStructure, Optional defaultOperation) throws NetconfDocumentedException; - - - protected ListenableFuture perfomRequestWithTimeout(String operation, ListenableFuture future) { - try { - future.get(defaultRequestTimeoutMillis, TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException e) { - LOG.error("{}: {} failed with error", operation, id, e); - return Futures.immediateFailedCheckedFuture(new RuntimeException(id + ": " + operation + " failed")); - } catch (TimeoutException e) { - LOG.warn("{}: Unable to {} after {} milliseconds", id, operation, defaultRequestTimeoutMillis, e); - return Futures.immediateFailedCheckedFuture(new SchemaSourceException(e.getMessage())); - } - return future; - } } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/ReadOnlyTx.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/ReadOnlyTx.java index 28d75cdaa4..dda5485109 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/ReadOnlyTx.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/ReadOnlyTx.java @@ -16,9 +16,6 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; @@ -44,12 +41,9 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction { private final RemoteDeviceId id; private final FutureCallback loggingCallback; - private final long requestTimeoutMillis; - - public ReadOnlyTx(final NetconfBaseOps netconfOps, final RemoteDeviceId id, final long requestTimeoutMillis) { + public ReadOnlyTx(final NetconfBaseOps netconfOps, final RemoteDeviceId id) { this.netconfOps = netconfOps; this.id = id; - this.requestTimeoutMillis = requestTimeoutMillis; // Simple logging callback to log result of read operation loggingCallback = new FutureCallback() { @@ -84,10 +78,6 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction { } }); - if(!readWithTimeout("readConfigurationData", configRunning)) { - return null; - } - return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER); } @@ -119,10 +109,6 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction { } }); - if(!readWithTimeout("readOperationalData", configCandidate)) { - return null; - } - return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER); } @@ -161,18 +147,4 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction { public Object getIdentifier() { return this; } - - private boolean readWithTimeout(String operation, ListenableFuture future) { - try { - future.get(requestTimeoutMillis, TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException e) { - LOG.error("{}: {} failed with error", id, operation, e); - throw new RuntimeException(id + ": readOperationalData failed"); - } catch (TimeoutException e) { - LOG.warn("{}: Unable to {} after {} milliseconds", id, operation, requestTimeoutMillis, e); - future.cancel(true); - return false; - } - return true; - } } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java index 0b9bb98e9c..c076d0b7a8 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java @@ -29,8 +29,8 @@ public class WriteCandidateRunningTx extends WriteCandidateTx { private static final Logger LOG = LoggerFactory.getLogger(WriteCandidateRunningTx.class); - public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final boolean rollbackSupport, final long requestTimeoutMillis) { - super(id, netOps, rollbackSupport, requestTimeoutMillis); + public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final boolean rollbackSupport) { + super(id, netOps, rollbackSupport); } @Override @@ -46,13 +46,11 @@ public class WriteCandidateRunningTx extends WriteCandidateTx { } private void lockRunning() { - final String operation = "Lock Running"; try { - invokeBlocking(operation, new Function>() { + invokeBlocking("Lock running", new Function>() { @Override public ListenableFuture apply(final NetconfBaseOps input) { - return perfomRequestWithTimeout(operation, input.lockRunning(new NetconfRpcFutureCallback(operation, id))); - + return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id)); } }); } catch (final NetconfDocumentedException e) { diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteCandidateTx.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteCandidateTx.java index f607089dc0..b143a3bf8e 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteCandidateTx.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteCandidateTx.java @@ -71,8 +71,8 @@ public class WriteCandidateTx extends AbstractWriteTx { } }; - public WriteCandidateTx(final RemoteDeviceId id, final NetconfBaseOps rpc, final boolean rollbackSupport, long requestTimeoutMillis) { - super(requestTimeoutMillis, rpc, id, rollbackSupport); + public WriteCandidateTx(final RemoteDeviceId id, final NetconfBaseOps rpc, final boolean rollbackSupport) { + super(rpc, id, rollbackSupport); } @Override @@ -95,12 +95,11 @@ public class WriteCandidateTx extends AbstractWriteTx { } private void lock() throws NetconfDocumentedException { - final String operation = "Lock candidate"; try { - invokeBlocking(operation, new Function>() { + invokeBlocking("Lock candidate", new Function>() { @Override public ListenableFuture apply(final NetconfBaseOps input) { - return perfomRequestWithTimeout(operation, input.lockCandidate(new NetconfRpcFutureCallback(operation, id))); + return input.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id)); } }); } catch (final NetconfDocumentedException e) { @@ -186,16 +185,14 @@ public class WriteCandidateTx extends AbstractWriteTx { @Override protected void editConfig(final DataContainerChild editStructure, final Optional defaultOperation) throws NetconfDocumentedException { - final String operation = "Edit candidate"; - invokeBlocking(operation, new Function>() { + invokeBlocking("Edit candidate", new Function>() { @Override public ListenableFuture apply(final NetconfBaseOps input) { - - return perfomRequestWithTimeout(operation, defaultOperation.isPresent() - ? input.editConfigCandidate(new NetconfRpcFutureCallback(operation, id), editStructure, defaultOperation.get(), - rollbackSupport) - : input.editConfigCandidate(new NetconfRpcFutureCallback(operation, id), editStructure, - rollbackSupport)); + return defaultOperation.isPresent() + ? input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure, defaultOperation.get(), + rollbackSupport) + : input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure, + rollbackSupport); } }); } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteRunningTx.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteRunningTx.java index 1d0ff653ef..cf7594a7bc 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteRunningTx.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/WriteRunningTx.java @@ -51,8 +51,8 @@ public class WriteRunningTx extends AbstractWriteTx { private static final Logger LOG = LoggerFactory.getLogger(WriteRunningTx.class); public WriteRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, - final boolean rollbackSupport, long requestTimeoutMillis) { - super(requestTimeoutMillis, netOps, id, rollbackSupport); + final boolean rollbackSupport) { + super(netOps, id, rollbackSupport); } @Override @@ -61,12 +61,11 @@ public class WriteRunningTx extends AbstractWriteTx { } private void lock() { - final String operation = "Lock running"; try { - invokeBlocking(operation, new Function>() { + invokeBlocking("Lock running", new Function>() { @Override public ListenableFuture apply(final NetconfBaseOps input) { - return perfomRequestWithTimeout(operation, input.lockRunning(new NetconfRpcFutureCallback(operation, id))); + return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id)); } }); } catch (final NetconfDocumentedException e) { @@ -97,14 +96,14 @@ public class WriteRunningTx extends AbstractWriteTx { @Override public synchronized CheckedFuture submit() { - final ListenableFuture commitFutureAsVoid = Futures.transform(commit(), new Function, Void>() { + final ListenableFuture commmitFutureAsVoid = Futures.transform(commit(), new Function, Void>() { @Override public Void apply(final RpcResult input) { return null; } }); - return Futures.makeChecked(commitFutureAsVoid, new Function() { + return Futures.makeChecked(commmitFutureAsVoid, new Function() { @Override public TransactionCommitFailedException apply(final Exception input) { return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input); @@ -120,26 +119,24 @@ public class WriteRunningTx extends AbstractWriteTx { @Override protected void editConfig(final DataContainerChild editStructure, final Optional defaultOperation) throws NetconfDocumentedException { - final String operation = "Edit running"; - invokeBlocking(operation, new Function>() { + invokeBlocking("Edit running", new Function>() { @Override public ListenableFuture apply(final NetconfBaseOps input) { - return perfomRequestWithTimeout(operation, defaultOperation.isPresent() - ? input.editConfigRunning(new NetconfRpcFutureCallback(operation, id), editStructure, defaultOperation.get(), - rollbackSupport) - : input.editConfigRunning(new NetconfRpcFutureCallback(operation, id), editStructure, - rollbackSupport)); + return defaultOperation.isPresent() + ? input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure, defaultOperation.get(), + rollbackSupport) + : input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure, + rollbackSupport); } }); } private void unlock() { - final String operation = "Unlocking running"; try { - invokeBlocking(operation, new Function>() { + invokeBlocking("Unlocking running", new Function>() { @Override public ListenableFuture apply(final NetconfBaseOps input) { - return perfomRequestWithTimeout(operation, input.unlockRunning(new NetconfRpcFutureCallback(operation, id))); + return input.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id)); } }); } catch (final NetconfDocumentedException e) { diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/KeepaliveSalFacadeTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/KeepaliveSalFacadeTest.java index 2a6ba4301f..81e09708f3 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/KeepaliveSalFacadeTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/KeepaliveSalFacadeTest.java @@ -106,7 +106,7 @@ public class KeepaliveSalFacadeTest { doReturn(Futures.immediateCheckedFuture(result)).when(deviceRpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class)); final KeepaliveSalFacade keepaliveSalFacade = - new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 1L); + new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 1L, 1L); keepaliveSalFacade.setListener(listener); keepaliveSalFacade.onDeviceConnected(null, null, deviceRpc); @@ -133,7 +133,7 @@ public class KeepaliveSalFacadeTest { .when(deviceRpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class)); final KeepaliveSalFacade keepaliveSalFacade = - new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 1L); + new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 1L, 1L); keepaliveSalFacade.setListener(listener); keepaliveSalFacade.onDeviceConnected(null, null, deviceRpc); @@ -190,7 +190,7 @@ public class KeepaliveSalFacadeTest { .when(deviceRpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class)); final KeepaliveSalFacade keepaliveSalFacade = - new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 100L); + new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 100L, 1L); keepaliveSalFacade.setListener(listener); keepaliveSalFacade.onDeviceConnected(null, null, deviceRpc); diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBrokerTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBrokerTest.java index 1e15e115f3..c32f4fea33 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBrokerTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBrokerTest.java @@ -103,7 +103,7 @@ public class NetconfDeviceDataBrokerTest { private NetconfDeviceDataBroker getDataBroker(String... caps) { NetconfSessionPreferences prefs = NetconfSessionPreferences.fromStrings(Arrays.asList(caps)); final RemoteDeviceId id = new RemoteDeviceId("device-1", InetSocketAddress.createUnresolved("localhost", 17830)); - return new NetconfDeviceDataBroker(id, schemaContext, rpcService, prefs, 1000); + return new NetconfDeviceDataBroker(id, schemaContext, rpcService, prefs); } } \ No newline at end of file diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java index 82fe4b23e7..ecb0eeb779 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java @@ -73,7 +73,7 @@ public class NetconfDeviceWriteOnlyTxTest { @Test public void testIgnoreNonVisibleData() { final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, mock(SchemaContext.class)), - false, 60000L); + false); final MapNode emptyList = ImmutableNodes.mapNodeBuilder(NETCONF_FILTER_QNAME).build(); tx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(new YangInstanceIdentifier.NodeIdentifier(NETCONF_FILTER_QNAME)), emptyList); tx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(new YangInstanceIdentifier.NodeIdentifier(NETCONF_FILTER_QNAME)), emptyList); @@ -84,7 +84,7 @@ public class NetconfDeviceWriteOnlyTxTest { @Test public void testDiscardChanges() { final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, mock(SchemaContext.class)), - false, 60000L); + false); final CheckedFuture submitFuture = tx.submit(); try { submitFuture.checkedGet(); @@ -110,7 +110,7 @@ public class NetconfDeviceWriteOnlyTxTest { .doReturn(rpcErrorFuture).when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class)); final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, mock(SchemaContext.class)), - false, 60000L); + false); final CheckedFuture submitFuture = tx.submit(); try { @@ -129,7 +129,7 @@ public class NetconfDeviceWriteOnlyTxTest { .when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class)); final WriteRunningTx tx = new WriteRunningTx(id, new NetconfBaseOps(rpc, NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext()), - false, 60000L); + false); try { tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId); } catch (final Exception e) { diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/ReadOnlyTxTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/ReadOnlyTxTest.java index c005b5bf23..1e9c0fcd86 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/ReadOnlyTxTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/ReadOnlyTxTest.java @@ -13,27 +13,17 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import com.google.common.base.Optional; -import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import java.net.InetSocketAddress; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException; -import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult; import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps; @@ -43,12 +33,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaPath; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -@PrepareForTest({NetconfBaseOps.class}) -@RunWith(PowerMockRunner.class) public class ReadOnlyTxTest { private static final YangInstanceIdentifier path = YangInstanceIdentifier.create(); @@ -70,33 +55,11 @@ public class ReadOnlyTxTest { public void testRead() throws Exception { final NetconfBaseOps netconfOps = new NetconfBaseOps(rpc, mock(SchemaContext.class)); - final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)), 60000L); + final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196))); readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create()); verify(rpc).invokeRpc(Mockito.eq(NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME)), any(NormalizedNode.class)); readOnlyTx.read(LogicalDatastoreType.OPERATIONAL, path); verify(rpc).invokeRpc(Mockito.eq(NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.NETCONF_GET_QNAME)), any(NormalizedNode.class)); } - - @SuppressWarnings("unchecked") - @Test - public void testReadTimeout() throws Exception { - final ListenableFuture future = mock(ListenableFuture.class); - - Mockito.when(future.get(Mockito.anyLong(), any(TimeUnit.class))).then(new Answer() { - @Override - public DOMRpcResult answer(InvocationOnMock invocation) - throws Throwable { - throw new TimeoutException("Processing Timeout"); - } - }); - - final NetconfBaseOps netconfOps = PowerMockito.mock(NetconfBaseOps.class); - Mockito.when(netconfOps.getConfigRunning(any(FutureCallback.class), any(Optional.class))).thenReturn(future); - - - final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)), 100L); - Assert.assertNull("Read operation didn't correctly timeout", readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create())); - readOnlyTx.close(); - } } \ No newline at end of file diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/client/http/perf/RestPerfClient.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/client/http/perf/RestPerfClient.java index 5f6f490afc..6dc684780f 100644 --- a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/client/http/perf/RestPerfClient.java +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/client/http/perf/RestPerfClient.java @@ -130,30 +130,37 @@ public class RestPerfClient { final ExecutorService executorService = Executors.newFixedThreadPool(threadAmount); LOG.info("Starting performance test"); + boolean allThreadsCompleted = true; final Stopwatch started = Stopwatch.createStarted(); try { final List> futures = executorService.invokeAll(callables, parameters.timeout, TimeUnit.MINUTES); for (int i = 0; i < futures.size(); i++) { Future future = futures.get(i); if (future.isCancelled()) { + allThreadsCompleted = false; LOG.info("{}. thread timed out.", i + 1); } else { try { future.get(); } catch (final ExecutionException e) { + allThreadsCompleted = false; LOG.info("{}. thread failed.", i + 1, e); } } } } catch (final InterruptedException e) { + allThreadsCompleted = false; LOG.warn("Unable to execute requests", e); } executorService.shutdownNow(); started.stop(); LOG.info("FINISHED. Execution time: {}", started); - LOG.info("Requests per second: {}", (parameters.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS))); - + // If some threads failed or timed out, skip calculation of requests per second value + // and do not log it + if(allThreadsCompleted) { + LOG.info("Requests per second: {}", (parameters.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS))); + } System.exit(0); } diff --git a/restconf/sal-rest-connector/pom.xml b/restconf/sal-rest-connector/pom.xml index aee9c961eb..86c8a3dd67 100644 --- a/restconf/sal-rest-connector/pom.xml +++ b/restconf/sal-rest-connector/pom.xml @@ -124,7 +124,6 @@ net.java.dev.stax-utils stax-utils - 20070216 @@ -199,7 +198,6 @@ org.opendaylight.aaa.filterchain.filters, org.apache.shiro.web.env - stax-utils /restconf diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java index fac2b1872e..17731de775 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java @@ -21,7 +21,11 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; +import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes; +import org.opendaylight.netconf.sal.rest.impl.PATCH; import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext; /** * The URI hierarchy for the RESTCONF resources consists of an entry point container, 4 top-level resources, and 1 @@ -151,4 +155,16 @@ public interface RestconfService { MediaType.APPLICATION_XML, MediaType.TEXT_XML }) public NormalizedNodeContext getAvailableStreams(@Context UriInfo uriInfo); + @PATCH + @Path("/config/{identifier:.+}") + @Consumes({MediaTypes.PATCH + JSON, MediaTypes.PATCH + XML}) + @Produces({MediaTypes.PATCH_STATUS + JSON, MediaTypes.PATCH_STATUS + XML}) + PATCHStatusContext patchConfigurationData(@Encoded @PathParam("identifier") String identifier, PATCHContext + context, @Context UriInfo uriInfo); + + @PATCH + @Path("/config") + @Consumes({MediaTypes.PATCH + JSON, MediaTypes.PATCH + XML}) + @Produces({MediaTypes.PATCH_STATUS + JSON, MediaTypes.PATCH_STATUS + XML}) + PATCHStatusContext patchConfigurationData(PATCHContext context, @Context UriInfo uriInfo); } diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPATCHBodyReader.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPATCHBodyReader.java new file mode 100644 index 0000000000..6d3f7ea34f --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPATCHBodyReader.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2015 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.rest.impl; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.gson.stream.JsonReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.ws.rs.Consumes; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; +import org.opendaylight.netconf.sal.rest.api.Draft02; +import org.opendaylight.netconf.sal.rest.api.RestconfService; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity; +import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException; +import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag; +import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; +import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException; +import org.opendaylight.yangtools.yang.data.util.AbstractStringInstanceIdentifierCodec; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Provider +@Consumes({Draft02.MediaTypes.PATCH + RestconfService.JSON}) +public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader { + + private final static Logger LOG = LoggerFactory.getLogger(JsonToPATCHBodyReader.class); + private String patchId; + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return true; + } + + @Override + public PATCHContext readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { + try { + return readFrom(getInstanceIdentifierContext(), entityStream); + } catch (final Exception e) { + throw propagateExceptionAs(e); + } + } + + private static RuntimeException propagateExceptionAs(Exception e) throws RestconfDocumentedException { + if(e instanceof RestconfDocumentedException) { + throw (RestconfDocumentedException)e; + } + + if(e instanceof ResultAlreadySetException) { + LOG.debug("Error parsing json input:", e); + + throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. "); + } + + throw new RestconfDocumentedException("Error parsing json input: " + e.getMessage(), ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE, e); + } + + public PATCHContext readFrom(final String uriPath, final InputStream entityStream) throws + RestconfDocumentedException { + try { + return readFrom(ControllerContext.getInstance().toInstanceIdentifier(uriPath), entityStream); + } catch (final Exception e) { + propagateExceptionAs(e); + return null; // no-op + } + } + + private PATCHContext readFrom(final InstanceIdentifierContext path, final InputStream entityStream) throws IOException { + if (entityStream.available() < 1) { + return new PATCHContext(path, null, null); + } + + final JsonReader jsonReader = new JsonReader(new InputStreamReader(entityStream)); + final List resultList = read(jsonReader, path); + jsonReader.close(); + + return new PATCHContext(path, resultList, patchId); + } + + private List read(final JsonReader in, InstanceIdentifierContext path) throws + IOException { + + boolean inEdit = false; + boolean inValue = false; + String operation = null; + String target = null; + String editId = null; + List resultCollection = new ArrayList<>(); + + while (in.hasNext()) { + switch (in.peek()) { + case STRING: + case NUMBER: + in.nextString(); + break; + case BOOLEAN: + Boolean.toString(in.nextBoolean()); + break; + case NULL: + in.nextNull(); + break; + case BEGIN_ARRAY: + in.beginArray(); + break; + case BEGIN_OBJECT: + if (inEdit && operation != null & target != null & inValue) { + //let's do the stuff - find out target node +// StringInstanceIdentifierCodec codec = new StringInstanceIdentifierCodec(path +// .getSchemaContext()); +// if (path.getInstanceIdentifier().toString().equals("/")) { +// final YangInstanceIdentifier deserialized = codec.deserialize(target); +// } + DataSchemaNode targetNode = ((DataNodeContainer)(path.getSchemaNode())).getDataChildByName + (target.replace("/", "")); + if (targetNode == null) { + LOG.debug("Target node {} not found in path {} ", target, path.getSchemaNode()); + throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE); + } else { + + final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); + final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder); + + //keep on parsing json from place where target points + final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(), + path.getSchemaNode()); + jsonParser.parse(in); + + final YangInstanceIdentifier targetII = path.getInstanceIdentifier().node(targetNode.getQName()); + resultCollection.add(new PATCHEntity(editId, operation, targetII, resultHolder.getResult + ())); + inValue = false; + + operation = null; + target = null; + } + in.endObject(); + } else { + in.beginObject(); + } + break; + case END_DOCUMENT: + break; + case NAME: + final String name = in.nextName(); + + switch (name) { + case "edit" : inEdit = true; + break; + case "operation" : operation = in.nextString(); + break; + case "target" : target = in.nextString(); + break; + case "value" : inValue = true; + break; + case "patch-id" : patchId = in.nextString(); + break; + case "edit-id" : editId = in.nextString(); + break; + } + break; + case END_OBJECT: + in.endObject(); + break; + case END_ARRAY: + in.endArray(); + break; + + default: + break; + } + } + + return ImmutableList.copyOf(resultCollection); + } + + private class StringInstanceIdentifierCodec extends AbstractStringInstanceIdentifierCodec { + + private final DataSchemaContextTree dataContextTree; + private final SchemaContext context; + + StringInstanceIdentifierCodec(SchemaContext context) { + this.context = Preconditions.checkNotNull(context); + this.dataContextTree = DataSchemaContextTree.from(context); + } + + @Nonnull + @Override + protected DataSchemaContextTree getDataContextTree() { + return dataContextTree; + } + + @Nullable + @Override + protected String prefixForNamespace(@Nonnull URI namespace) { + final Module module = context.findModuleByNamespaceAndRevision(namespace, null); + return module == null ? null : module.getName(); + } + + @Nullable + @Override + protected QName createQName(@Nonnull String prefix, @Nonnull String localName) { + throw new UnsupportedOperationException("Not implemented"); + } + + } +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCH.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCH.java new file mode 100644 index 0000000000..f2829712ae --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCH.java @@ -0,0 +1,23 @@ +/* + * 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.rest.impl; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.ws.rs.HttpMethod; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@HttpMethod("PATCH") +@Documented +public @interface PATCH { +} \ No newline at end of file diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCHJsonBodyWriter.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCHJsonBodyWriter.java new file mode 100644 index 0000000000..9f9ab94933 --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCHJsonBodyWriter.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2015 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.rest.impl; + +import com.google.common.base.Charsets; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.List; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes; +import org.opendaylight.netconf.sal.rest.api.RestconfService; +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.yangtools.yang.data.codec.gson.JsonWriterFactory; + +@Provider +@Produces({MediaTypes.PATCH_STATUS + RestconfService.JSON}) +public class PATCHJsonBodyWriter implements MessageBodyWriter { + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type.equals(PATCHStatusContext.class); + } + + @Override + public long getSize(PATCHStatusContext patchStatusContext, Class type, Type genericType, Annotation[] + annotations, MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(PATCHStatusContext patchStatusContext, Class type, Type genericType, Annotation[] + annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + + final JsonWriter jsonWriter = createJsonWriter(entityStream); + jsonWriter.beginObject().name("ietf-yang-patch:yang-patch-status"); + jsonWriter.beginObject(); + jsonWriter.name("patch-id").value(patchStatusContext.getPatchId()); + if (patchStatusContext.isOk()) { + jsonWriter.name("ok").nullValue(); + } else { + if (patchStatusContext.getGlobalErrors() != null) { + reportErrors(patchStatusContext.getGlobalErrors(), jsonWriter); + } + + jsonWriter.name("edit-status"); + jsonWriter.beginObject(); + jsonWriter.name("edit"); + jsonWriter.beginArray(); + for (PATCHStatusEntity patchStatusEntity : patchStatusContext.getEditCollection()) { + jsonWriter.beginObject(); + jsonWriter.name("edit-id").value(patchStatusEntity.getEditId()); + if (patchStatusEntity.getEditErrors() != null) { + reportErrors(patchStatusEntity.getEditErrors(), jsonWriter); + } else { + if (patchStatusEntity.isOk()) { + jsonWriter.name("ok").nullValue(); + } + } + jsonWriter.endObject(); + } + jsonWriter.endArray(); + jsonWriter.endObject(); + } + jsonWriter.endObject(); + jsonWriter.endObject(); + jsonWriter.flush(); + + } + + private static void reportErrors(List errors, JsonWriter jsonWriter) throws IOException { + jsonWriter.name("errors"); + jsonWriter.beginObject(); + jsonWriter.name("error"); + jsonWriter.beginArray(); + + for (RestconfError restconfError : errors) { + jsonWriter.beginObject(); + jsonWriter.name("error-type").value(restconfError.getErrorType().getErrorTypeTag()); + jsonWriter.name("error-tag").value(restconfError.getErrorTag().getTagValue()); + //TODO: fix error-path reporting (separate error-path from error-message) + //jsonWriter.name("error-path").value(restconfError.getErrorPath()); + jsonWriter.name("error-message").value(restconfError.getErrorMessage()); + jsonWriter.endObject(); + } + + jsonWriter.endArray(); + jsonWriter.endObject(); + } + + private static JsonWriter createJsonWriter(final OutputStream entityStream) { + return JsonWriterFactory.createJsonWriter(new OutputStreamWriter(entityStream, Charsets.UTF_8)); + } +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCHXmlBodyWriter.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCHXmlBodyWriter.java new file mode 100644 index 0000000000..2d094a6439 --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCHXmlBodyWriter.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2015 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.rest.impl; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.List; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes; +import org.opendaylight.netconf.sal.rest.api.RestconfService; +import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusEntity; +import org.opendaylight.netconf.sal.restconf.impl.RestconfError; + +@Provider +@Produces({ MediaTypes.PATCH_STATUS + RestconfService.XML}) +public class PATCHXmlBodyWriter implements MessageBodyWriter { + + private static final XMLOutputFactory XML_FACTORY; + + static { + XML_FACTORY = XMLOutputFactory.newFactory(); + XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type.equals(PATCHStatusContext.class); + } + + @Override + public long getSize(PATCHStatusContext patchStatusContext, Class type, Type genericType, Annotation[] + annotations, MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(PATCHStatusContext patchStatusContext, Class type, Type genericType, Annotation[] + annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + + try { + XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream); + writeDocument(xmlWriter, patchStatusContext); + } catch (final XMLStreamException e) { + throw new IllegalStateException(e); + } catch (final FactoryConfigurationError e) { + throw new IllegalStateException(e); + } + + } + + private static void writeDocument(XMLStreamWriter writer, PATCHStatusContext context) throws XMLStreamException, IOException { + writer.writeStartElement("", "yang-patch-status", "urn:ietf:params:xml:ns:yang:ietf-yang-patch"); + writer.writeStartElement("patch-id"); + writer.writeCharacters(context.getPatchId()); + writer.writeEndElement(); + + if (context.isOk()) { + writer.writeEmptyElement("ok"); + } else { + if (context.getGlobalErrors() != null) { + reportErrors(context.getGlobalErrors(), writer); + } + writer.writeStartElement("edit-status"); + for (PATCHStatusEntity patchStatusEntity : context.getEditCollection()) { + writer.writeStartElement("edit"); + writer.writeStartElement("edit-id"); + writer.writeCharacters(patchStatusEntity.getEditId()); + writer.writeEndElement(); + if (patchStatusEntity.getEditErrors() != null) { + reportErrors(patchStatusEntity.getEditErrors(), writer); + } else { + if (patchStatusEntity.isOk()) { + writer.writeEmptyElement("ok"); + } + } + writer.writeEndElement(); + } + writer.writeEndElement(); + + } + writer.writeEndElement(); + + writer.flush(); + } + + private static void reportErrors(List errors, XMLStreamWriter writer) throws IOException, XMLStreamException { + writer.writeStartElement("errors"); + + for (RestconfError restconfError : errors) { + writer.writeStartElement("error-type"); + writer.writeCharacters(restconfError.getErrorType().getErrorTypeTag()); + writer.writeEndElement(); + writer.writeStartElement("error-tag"); + writer.writeCharacters(restconfError.getErrorTag().getTagValue()); + writer.writeEndElement(); + //TODO: fix error-path reporting (separate error-path from error-message) +// writer.writeStartElement("error-path"); +// writer.writeCharacters(restconfError.getErrorPath()); +// writer.writeEndElement(); + writer.writeStartElement("error-message"); + writer.writeCharacters(restconfError.getErrorMessage()); + writer.writeEndElement(); + } + + writer.writeEndElement(); + } +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java index bfcd826f5d..46b8743832 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java @@ -27,6 +27,10 @@ public class RestconfApplication extends Application { .add(RestconfDocumentedExceptionMapper.class) .add(XmlNormalizedNodeBodyReader.class) .add(JsonNormalizedNodeBodyReader.class) + .add(JsonToPATCHBodyReader.class) + .add(XmlToPATCHBodyReader.class) + .add(PATCHJsonBodyWriter.class) + .add(PATCHXmlBodyWriter.class) .add(NormalizedNodeJsonBodyWriter.class) .add(NormalizedNodeXmlBodyWriter.class) .add(SchemaExportContentYinBodyWriter.class) diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java index 4015d9ac22..df56bc4c0e 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java @@ -9,12 +9,15 @@ package org.opendaylight.netconf.sal.rest.impl; import com.google.common.base.Preconditions; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContext; import org.opendaylight.netconf.md.sal.rest.schema.SchemaRetrievalService; import org.opendaylight.netconf.sal.rest.api.RestconfService; import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext; public class RestconfCompositeWrapper implements RestconfService, SchemaRetrievalService { @@ -107,6 +110,16 @@ public class RestconfCompositeWrapper implements RestconfService, SchemaRetrieva return restconf.getAvailableStreams(uriInfo); } + @Override + public PATCHStatusContext patchConfigurationData(final String identifier, PATCHContext payload, UriInfo uriInfo) { + return restconf.patchConfigurationData(identifier, payload, uriInfo); + } + + @Override + public PATCHStatusContext patchConfigurationData(final PATCHContext context, final UriInfo uriInfo) { + return restconf.patchConfigurationData(context, uriInfo); + } + @Override public SchemaExportContext getSchema(final String mountId) { return schema.getSchema(mountId); diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPATCHBodyReader.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPATCHBodyReader.java new file mode 100644 index 0000000000..fe63f31373 --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPATCHBodyReader.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2015 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.rest.impl; + +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.ws.rs.Consumes; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes; +import org.opendaylight.netconf.sal.rest.api.RestconfService; +import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException; +import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag; +import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType; +import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +@Provider +@Consumes({MediaTypes.PATCH + RestconfService.XML}) +public class XmlToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements + MessageBodyReader { + + private final static Logger LOG = LoggerFactory.getLogger(XmlToPATCHBodyReader.class); + private static final DocumentBuilderFactory BUILDERFACTORY; + + static { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try { + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + } catch (final ParserConfigurationException e) { + throw new ExceptionInInitializerError(e); + } + factory.setNamespaceAware(true); + factory.setCoalescing(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setIgnoringComments(true); + BUILDERFACTORY = factory; + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return true; + } + + @Override + public PATCHContext readFrom(Class type, Type genericType, Annotation[] annotations, MediaType + mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, + WebApplicationException { + + try { + final InstanceIdentifierContext path = getInstanceIdentifierContext(); + + if (entityStream.available() < 1) { + // represent empty nopayload input + return new PATCHContext(path, null, null); + } + + final DocumentBuilder dBuilder; + try { + dBuilder = BUILDERFACTORY.newDocumentBuilder(); + } catch (final ParserConfigurationException e) { + throw new IllegalStateException("Failed to parse XML document", e); + } + final Document doc = dBuilder.parse(entityStream); + + return parse(path, doc); + } catch (final RestconfDocumentedException e) { + throw e; + } catch (final Exception e) { + LOG.debug("Error parsing xml input", e); + + throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE); + } + } + + private PATCHContext parse(final InstanceIdentifierContext pathContext, final Document doc) { + final List resultCollection = new ArrayList<>(); + final String patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue(); + final NodeList editNodes = doc.getElementsByTagName("edit"); + final DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode(); + final DomToNormalizedNodeParserFactory parserFactory = + DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, + pathContext.getSchemaContext()); + + for (int i = 0; i < editNodes.getLength(); i++) { + Element element = (Element) editNodes.item(i); + final String operation = element.getElementsByTagName("operation").item(0).getFirstChild().getNodeValue(); + final String editId = element.getElementsByTagName("edit-id").item(0).getFirstChild().getNodeValue(); + final String target = element.getElementsByTagName("target").item(0).getFirstChild().getNodeValue(); + DataSchemaNode targetNode = ((DataNodeContainer)(pathContext.getSchemaNode())).getDataChildByName + (target.replace("/", "")); + if (targetNode == null) { + LOG.debug("Target node {} not found in path {} ", target, pathContext.getSchemaNode()); + throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE); + } else { + final YangInstanceIdentifier targetII = pathContext.getInstanceIdentifier().node(targetNode.getQName()); + final NodeList valueNodes = element.getElementsByTagName("value").item(0).getChildNodes(); + Element value = null; + for (int j = 0; j < valueNodes.getLength(); j++) { + if (valueNodes.item(j) instanceof Element) { + value = (Element) valueNodes.item(j); + break; + } + } + NormalizedNode parsed = null; + if (schemaNode instanceof ContainerSchemaNode) { + parsed = parserFactory.getContainerNodeParser().parse(Collections.singletonList(value), + (ContainerSchemaNode) targetNode); + } else if (schemaNode instanceof ListSchemaNode) { + NormalizedNode parsedValue = parserFactory.getMapEntryNodeParser().parse(Collections + .singletonList(value), (ListSchemaNode) targetNode); + parsed = ImmutableNodes.mapNodeBuilder().withNodeIdentifier(new NodeIdentifier + (targetNode.getQName())).withChild((MapEntryNode) parsedValue).build(); + } + + resultCollection.add(new PATCHEntity(editId, operation, targetII, parsed)); + } + } + + return new PATCHContext(pathContext, ImmutableList.copyOf(resultCollection), patchId); + } +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java index fcf82e5623..e2097310f2 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java @@ -11,6 +11,7 @@ import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastor import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL; import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.ListenableFuture; import java.util.ArrayList; @@ -126,6 +127,88 @@ public class BrokerFacade { throw new RestconfDocumentedException(errMsg); } + public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context, + final SchemaContext globalSchema) { + final DOMDataReadWriteTransaction patchTransaction = domDataBroker.newReadWriteTransaction(); + List editCollection = new ArrayList<>(); + List editErrors; + List globalErrors = null; + int errorCounter = 0; + + for (PATCHEntity patchEntity : context.getData()) { + final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase()); + + switch (operation) { + case CREATE: + if (errorCounter == 0) { + try { + postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(), + patchEntity.getNode(), globalSchema); + editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null)); + } catch (RestconfDocumentedException e) { + editErrors = new ArrayList<>(); + editErrors.addAll(e.getErrors()); + editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors)); + errorCounter++; + } + } + break; + case REPLACE: + if (errorCounter == 0) { + try { + putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity + .getTargetNode(), patchEntity.getNode(), globalSchema); + editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null)); + } catch (RestconfDocumentedException e) { + editErrors = new ArrayList<>(); + editErrors.addAll(e.getErrors()); + editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors)); + errorCounter++; + } + } + break; + case DELETE: + if (errorCounter == 0) { + try { + deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity + .getTargetNode()); + editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null)); + } catch (RestconfDocumentedException e) { + editErrors = new ArrayList<>(); + editErrors.addAll(e.getErrors()); + editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors)); + errorCounter++; + } + } + break; + case REMOVE: + if (errorCounter == 0) { + try { + deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity + .getTargetNode()); + editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null)); + } catch (RestconfDocumentedException e) { + LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(), + patchEntity.getEditId(), e); + } + } + break; + } + } + + //TODO: make sure possible global errors are filled up correctly and decide transaction submission based on that + //globalErrors = new ArrayList<>(); + if (errorCounter == 0) { + final CheckedFuture submit = patchTransaction.submit(); + return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, + globalErrors); + } else { + patchTransaction.cancel(); + return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, + globalErrors); + } + } + // POST configuration public CheckedFuture commitConfigurationDataPost( final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode payload) { @@ -190,7 +273,7 @@ public class BrokerFacade { private NormalizedNode readDataViaTransaction(final DOMDataReadTransaction transaction, final LogicalDatastoreType datastore, final YangInstanceIdentifier path) { - LOG.trace("Read " + datastore.name() + " via Restconf: {}", path); + LOG.trace("Read {} via Restconf: {}", datastore.name(), path); final ListenableFuture>> listenableFuture = transaction.read(datastore, path); if (listenableFuture != null) { Optional> optional; @@ -198,7 +281,7 @@ public class BrokerFacade { LOG.debug("Reading result data from transaction."); optional = listenableFuture.get(); } catch (InterruptedException | ExecutionException e) { - LOG.warn("Exception by reading " + datastore.name() + " via Restconf: {}", path, e); + LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, e); throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause()); } @@ -217,7 +300,7 @@ public class BrokerFacade { // FIXME: This is doing correct post for container and list children // not sure if this will work for choice case if(payload instanceof MapNode) { - LOG.trace("POST " + datastore.name() + " via Restconf: {} with payload {}", path, payload); + LOG.trace("POST {} 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); @@ -234,18 +317,40 @@ public class BrokerFacade { return rWTransaction.submit(); } + private void postDataWithinTransaction( + final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore, + final YangInstanceIdentifier path, final NormalizedNode payload, final SchemaContext schemaContext) { + // FIXME: This is doing correct post for container and list children + // not sure if this will work for choice case + if(payload instanceof MapNode) { + LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload); + final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path); + rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); + ensureParentsByMerge(datastore, path, rWTransaction, schemaContext); + for(final MapEntryNode child : ((MapNode) payload).getValue()) { + final YangInstanceIdentifier childPath = path.node(child.getIdentifier()); + checkItemDoesNotExists(rWTransaction, datastore, childPath); + rWTransaction.put(datastore, childPath, child); + } + } else { + checkItemDoesNotExists(rWTransaction,datastore, path); + ensureParentsByMerge(datastore, path, rWTransaction, schemaContext); + rWTransaction.put(datastore, path, payload); + } + } + private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) { final ListenableFuture futureDatastoreData = rWTransaction.exists(store, path); try { if (futureDatastoreData.get()) { final String errMsg = "Post Configuration via Restconf was not executed because data already exists"; - LOG.trace(errMsg + ":{}", path); + LOG.trace("{}:{}", errMsg, path); rWTransaction.cancel(); throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS); } } catch (InterruptedException | ExecutionException e) { - LOG.warn("It wasn't possible to get data loaded from datastore at path " + path, e); + LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e); } } @@ -253,20 +358,35 @@ public class BrokerFacade { private CheckedFuture putDataViaTransaction( final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload, final SchemaContext schemaContext) { - LOG.trace("Put " + datastore.name() + " via Restconf: {} with payload {}", path, payload); + LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload); ensureParentsByMerge(datastore, path, writeTransaction, schemaContext); writeTransaction.put(datastore, path, payload); return writeTransaction.submit(); } + 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); + } + private CheckedFuture deleteDataViaTransaction( final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore, final YangInstanceIdentifier path) { - LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path); + LOG.trace("Delete {} via Restconf: {}", datastore.name(), path); writeTransaction.delete(datastore, path); return writeTransaction.submit(); } + private void deleteDataWithinTransaction( + final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore, + final YangInstanceIdentifier path) { + LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path); + writeTransaction.delete(datastore, path); + } + public void setDomDataBroker(final DOMDataBroker domDataBroker) { this.domDataBroker = domDataBroker; } diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHContext.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHContext.java new file mode 100644 index 0000000000..adba67d7c5 --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHContext.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2015 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.restconf.impl; + +import com.google.common.base.Preconditions; +import java.util.List; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; + +public class PATCHContext { + + private final InstanceIdentifierContext context; + private final List data; + private final String patchId; + + public PATCHContext(final InstanceIdentifierContext context, + final List data, final String patchId) { + this.context = Preconditions.checkNotNull(context); + this.data = Preconditions.checkNotNull(data); + this.patchId = Preconditions.checkNotNull(patchId); + } + + public InstanceIdentifierContext getInstanceIdentifierContext() { + return context; + } + + public List getData() { + return data; + } + + public String getPatchId() { + return patchId; + } +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHEditOperation.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHEditOperation.java new file mode 100644 index 0000000000..e0bdfd5893 --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHEditOperation.java @@ -0,0 +1,26 @@ +/* + * 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.restconf.impl; + +/** + * + * Each YANG patch edit specifies one edit operation on the target data + * node. The set of operations is aligned with the NETCONF edit + * operations, but also includes some new operations. + * + */ +enum PATCHEditOperation { + CREATE, //post + DELETE, //delete + INSERT, //post + MERGE, + MOVE, //delete+post + REPLACE, //put + REMOVE //delete +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHEntity.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHEntity.java new file mode 100644 index 0000000000..ae16edc9d4 --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHEntity.java @@ -0,0 +1,46 @@ +/* + * 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.restconf.impl; + +import com.google.common.base.Preconditions; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +public class PATCHEntity { + + private final String operation; + private final String editId; + private final YangInstanceIdentifier targetNode; + private final NormalizedNode node; + + public PATCHEntity(final String editId, final String operation, final YangInstanceIdentifier targetNode, final + NormalizedNode node) { + this.editId = Preconditions.checkNotNull(editId); + this.operation = Preconditions.checkNotNull(operation); + this.targetNode = Preconditions.checkNotNull(targetNode); + this.node = Preconditions.checkNotNull(node); + } + + public String getOperation() { + return operation; + } + + public String getEditId() { + return editId; + } + + public YangInstanceIdentifier getTargetNode() { + return targetNode; + } + + public NormalizedNode getNode() { + return node; + } + +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHStatusContext.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHStatusContext.java new file mode 100644 index 0000000000..f93dca10ff --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHStatusContext.java @@ -0,0 +1,43 @@ +/* + * 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.restconf.impl; + +import java.util.List; + +public class PATCHStatusContext { + + private final String patchId; + private final List editCollection; + private boolean ok; + private List globalErrors; + + public PATCHStatusContext(final String patchId, final List editCollection, + final boolean ok, final List globalErrors) { + this.patchId = patchId; + this.editCollection = editCollection; + this.ok = ok; + this.globalErrors = globalErrors; + } + + public String getPatchId() { + return patchId; + } + + public List getEditCollection() { + return editCollection; + } + + public boolean isOk() { + return ok; + } + + public List getGlobalErrors() { + return globalErrors; + } +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHStatusEntity.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHStatusEntity.java new file mode 100644 index 0000000000..f2329079b3 --- /dev/null +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHStatusEntity.java @@ -0,0 +1,36 @@ +/* + * 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.restconf.impl; + +import java.util.List; + +public class PATCHStatusEntity { + + private final String editId; + private final List editErrors; + private final boolean ok; + + public PATCHStatusEntity(final String editId, final boolean ok, final List editErrors) { + this.editId = editId; + this.ok = ok; + this.editErrors = editErrors; + } + + public String getEditId() { + return editId; + } + + public boolean isOk() { + return ok; + } + + public List getEditErrors() { + return editErrors; + } +} diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java index 42057463d6..fd39da65ba 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2014, 2015 Brocade Communication Systems, Inc., Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the @@ -36,6 +36,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; @@ -981,6 +982,22 @@ public class RestconfImpl implements RestconfService { return Response.status(Status.OK).location(uriToWebsocketServer).build(); } + @Override + public PATCHStatusContext patchConfigurationData(String identifier, PATCHContext context, UriInfo uriInfo) { + if (context == null) { + throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + return broker.patchConfigurationDataWithinTransaction(context, controllerContext.getGlobalSchema()); + } + + @Override + public PATCHStatusContext patchConfigurationData(PATCHContext context, @Context UriInfo uriInfo) { + if (context == null) { + throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + return broker.patchConfigurationDataWithinTransaction(context, controllerContext.getGlobalSchema()); + } + /** * Load parameter for subscribing to stream from input composite node * diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java index 808a4afde6..75af8fe320 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java @@ -9,6 +9,7 @@ package org.opendaylight.netconf.sal.restconf.impl; import java.math.BigInteger; import java.util.concurrent.atomic.AtomicLong; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; @@ -211,6 +212,17 @@ public class StatisticsRestconfServiceWrapper implements RestconfService { return delegate.getAvailableStreams(uriInfo); } + @Override + public PATCHStatusContext patchConfigurationData(final String identifier, final PATCHContext payload, final UriInfo + uriInfo) { + return delegate.patchConfigurationData(identifier, payload, uriInfo); + } + + @Override + public PATCHStatusContext patchConfigurationData(final PATCHContext payload, final UriInfo uriInfo) { + return delegate.patchConfigurationData(payload, uriInfo); + } + public BigInteger getConfigDelete() { return BigInteger.valueOf(configDelete.get()); } diff --git a/restconf/sal-rest-connector/src/main/yang/instance-identifier-patch-module.yang b/restconf/sal-rest-connector/src/main/yang/instance-identifier-patch-module.yang new file mode 100644 index 0000000000..1eb39fe7cc --- /dev/null +++ b/restconf/sal-rest-connector/src/main/yang/instance-identifier-patch-module.yang @@ -0,0 +1,50 @@ +module instance-identifier-patch-module { + namespace "instance:identifier:patch:module"; + + prefix "iipmodule"; + revision 2015-11-21 { + } + + container patch-cont { + container patch-cont2 { + leaf cont-leaf { + type string; + } + } + + list my-list1 { + + description "PATCH /restconf/config/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + + key name; + + leaf name { + type string; + } + + leaf my-leaf11 { + type string; + } + + leaf my-leaf12 { + type string; + } + + list my-list2 { + key name; + + leaf name { + type string; + } + + leaf my-leaf21 { + type string; + } + + leaf my-leaf22 { + type string; + } + } + } + } +} \ No newline at end of file diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/md/sal/rest/common/TestRestconfUtils.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/md/sal/rest/common/TestRestconfUtils.java index 9dea0b95cd..029c3c9bf7 100644 --- a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/md/sal/rest/common/TestRestconfUtils.java +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/md/sal/rest/common/TestRestconfUtils.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java index f9a31955a5..6a728dc40c 100644 --- a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the @@ -26,6 +26,7 @@ import org.opendaylight.netconf.sal.rest.api.RestconfConstants; import org.opendaylight.netconf.sal.rest.impl.AbstractIdentifierAwareJaxRsProvider; import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** @@ -100,4 +101,11 @@ public abstract class AbstractBodyReaderTest { .getSchemaContext()); assertNotNull(nnContext.getInstanceIdentifierContext().getSchemaNode()); } + + protected static void checkPATCHContext(final PATCHContext patchContext) { + assertNotNull(patchContext.getData()); + assertNotNull(patchContext.getInstanceIdentifierContext().getInstanceIdentifier()); + assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaContext()); + assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaNode()); + } } diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPATCHBodyReader.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPATCHBodyReader.java new file mode 100644 index 0000000000..bcceb072c4 --- /dev/null +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPATCHBodyReader.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 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 java.io.InputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.netconf.sal.rest.impl.JsonToPATCHBodyReader; +import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class TestJsonPATCHBodyReader extends AbstractBodyReaderTest { + + private final JsonToPATCHBodyReader jsonPATCHBodyReader; + private static SchemaContext schemaContext; + + public TestJsonPATCHBodyReader() 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); + controllerContext.setSchemas(schemaContext); + } + + @Test + public void modulePATCHDataTest() throws Exception { + final String uri = "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); + checkPATCHContext(returnValue); + } +} diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPATCHBodyReader.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPATCHBodyReader.java new file mode 100644 index 0000000000..9b98dd8caf --- /dev/null +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPATCHBodyReader.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 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 java.io.InputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.netconf.sal.rest.impl.XmlToPATCHBodyReader; +import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class TestXmlPATCHBodyReader extends AbstractBodyReaderTest { + + private final XmlToPATCHBodyReader xmlPATCHBodyReader; + private static SchemaContext schemaContext; + + public TestXmlPATCHBodyReader() 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); + controllerContext.setSchemas(schemaContext); + } + + @Test + public void moduleDataTest() throws Exception { + final String uri = "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); + checkPATCHContext(returnValue); + } +} diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdata.json b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdata.json new file mode 100644 index 0000000000..cf1530ec4c --- /dev/null +++ b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdata.json @@ -0,0 +1,34 @@ +{ + "ietf-yang-patch:yang-patch" : { + + "patch-id" : "test-patch", + "comment" : "this is test patch", + "edit" : [ + { + "edit-id": "edit1", + "operation": "create", + "target": "/my-list2", + "value": { + "my-list2": { + "name": "my-leaf20", + "my-leaf21": "I am leaf21-0", + "my-leaf22": "I am leaf22-0" + } + } + }, + + { + "edit-id": "edit2", + "operation": "create", + "target": "/my-list2", + "value": { + "my-list2": { + "name": "my-leaf21", + "my-leaf21": "I am leaf21-1", + "my-leaf22": "I am leaf22-1" + } + } + } + ] + } +} diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdata.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdata.xml new file mode 100644 index 0000000000..d7d3a6bea6 --- /dev/null +++ b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdata.xml @@ -0,0 +1,28 @@ + + test-patch + this is test patch + + edit1 + create + /my-list2 + + + my-leaf20 + I am leaf21-0 + I am leaf22-0 + + + + + edit2 + create + /my-list2 + + + my-leaf21 + I am leaf21-1 + I am leaf22-1 + + + + \ No newline at end of file diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/instance-identifier-patch-module.yang b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/instance-identifier-patch-module.yang new file mode 100644 index 0000000000..11b4684da1 --- /dev/null +++ b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/instance-identifier-patch-module.yang @@ -0,0 +1,44 @@ +module instance-identifier-patch-module { + namespace "instance:identifier:patch:module"; + + prefix "iipmodule"; + revision 2015-11-21 { + } + + container patch-cont { + list my-list1 { + + description "PATCH /restconf/config/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + + key name; + + leaf name { + type string; + } + + leaf my-leaf11 { + type string; + } + + leaf my-leaf12 { + type string; + } + + list my-list2 { + key name; + + leaf name { + type string; + } + + leaf my-leaf21 { + type string; + } + + leaf my-leaf22 { + type string; + } + } + } + } +} \ No newline at end of file