import org.opendaylight.controller.sal.core.api.Broker;
import org.opendaylight.controller.sal.core.api.Broker.ProviderSession;
import org.opendaylight.controller.sal.core.api.Provider;
+import org.opendaylight.netconf.api.NetconfMessage;
import org.opendaylight.netconf.client.NetconfClientDispatcher;
import org.opendaylight.netconf.client.NetconfClientSessionListener;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
import org.opendaylight.netconf.sal.connect.netconf.NetconfStateSchemas;
+import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
}
final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node);
-
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setReconnectOnSchemasChange(reconnectOnChangedSchema)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(processingExecutor.getExecutor())
- .setId(remoteDeviceId)
- .setSalFacade(salFacade)
- .build();
+ final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> device;
+ if (node.isSchemaless()) {
+ device = new SchemalessNetconfDevice(remoteDeviceId, salFacade);
+ } else {
+ device = new NetconfDeviceBuilder()
+ .setReconnectOnSchemasChange(reconnectOnChangedSchema)
+ .setSchemaResourcesDTO(schemaResourcesDTO)
+ .setGlobalProcessingExecutor(processingExecutor.getExecutor())
+ .setId(remoteDeviceId)
+ .setSalFacade(salFacade)
+ .build();
+ }
final Optional<NetconfSessionPreferences> userCapabilities = getUserCapabilities(node);
.setReconnectOnChangedSchema(true)
.setDefaultRequestTimeoutMillis(1000L)
.setBetweenAttemptsTimeoutMillis(100)
+ .setSchemaless(false)
.build();
AbstractNetconfTopology.NetconfConnectorDTO connectorDTO = topology.createDeviceCommunicator(NODE_ID, testingNode);
.setDefaultRequestTimeoutMillis(1000L)
.setBetweenAttemptsTimeoutMillis(100)
.setKeepaliveDelay(1L)
+ .setSchemaless(false)
.build();
AbstractNetconfTopology.NetconfConnectorDTO connectorDTO = topology.createDeviceCommunicator(NODE_ID, testingNode);
.setBetweenAttemptsTimeoutMillis(100)
.setKeepaliveDelay(1000L)
.setTcpOnly(true)
+ .setSchemaless(false)
.setCredentials(new LoginPasswordBuilder().setUsername("testuser").setPassword("testpassword").build())
.build();
Node nd = mock(Node.class);
.setBetweenAttemptsTimeoutMillis(100)
.setKeepaliveDelay(1000L)
.setTcpOnly(true)
+ .setSchemaless(false)
.setCredentials(new LoginPasswordBuilder().setUsername("testuser").setPassword("testpassword").build())
.build();
Node nd = mock(Node.class);
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceRpc;
import org.opendaylight.netconf.sal.connect.netconf.schema.NetconfRemoteSchemaYangSourceProvider;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseSchema;
import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.NetconfMessageTransformer;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
* Create rpc implementation capable of handling RPC for monitoring and notifications even before the schemas of remote device are downloaded
*/
static NetconfDeviceRpc getRpcForInitialization(final NetconfDeviceCommunicator listener, final boolean notificationSupport) {
- NetconfMessageTransformer.BaseSchema baseSchema = notificationSupport ?
- NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS :
- NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX;
+ BaseSchema baseSchema = notificationSupport ?
+ BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS :
+ BaseSchema.BASE_NETCONF_CTX;
return new NetconfDeviceRpc(baseSchema.getSchemaContext(), listener, new NetconfMessageTransformer(baseSchema.getSchemaContext(), false, baseSchema));
}
}
protected void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, final DOMRpcService deviceRpc) {
- NetconfMessageTransformer.BaseSchema baseSchema =
+ BaseSchema baseSchema =
remoteSessionCapabilities.isNotificationsSupported() ?
- NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS :
- NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX;
+ BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS :
+ BaseSchema.BASE_NETCONF_CTX;
messageTransformer = new NetconfMessageTransformer(result, true, baseSchema);
updateTransformer(messageTransformer);
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceRpc;
-import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.NetconfMessageTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseSchema;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
private static final ContainerNode GET_SCHEMAS_RPC;
static {
final DataContainerChild<?, ?> filter = NetconfMessageTransformUtil.toFilterStructure(STATE_SCHEMAS_IDENTIFIER,
- NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext());
+ BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext());
GET_SCHEMAS_RPC
= Builders.containerBuilder().withNodeIdentifier(toId(NETCONF_GET_QNAME)).withChild(filter).build();
}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf;
+
+import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
+import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
+import org.opendaylight.netconf.sal.connect.netconf.sal.SchemalessNetconfDeviceRpc;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseSchema;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+
+public class SchemalessNetconfDevice implements
+ RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> {
+
+ private RemoteDeviceId id;
+ private RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
+
+ public SchemalessNetconfDevice(final RemoteDeviceId id,
+ final RemoteDeviceHandler<NetconfSessionPreferences> salFacade) {
+ this.id = id;
+ this.salFacade = salFacade;
+ }
+
+ @Override public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
+ final NetconfDeviceCommunicator netconfDeviceCommunicator) {
+
+ final SchemalessNetconfDeviceRpc schemalessNetconfDeviceRpc = new SchemalessNetconfDeviceRpc(id,
+ netconfDeviceCommunicator);
+
+ salFacade.onDeviceConnected(BaseSchema.BASE_NETCONF_CTX.getSchemaContext(),
+ remoteSessionCapabilities, schemalessNetconfDeviceRpc);
+
+ }
+
+ @Override public void onRemoteSessionDown() {
+ salFacade.onDeviceDisconnected();
+ }
+
+ @Override public void onRemoteSessionFailed(final Throwable throwable) {
+ salFacade.onDeviceFailed(throwable);
+ }
+
+ @Override public void onNotification(final NetconfMessage notification) {
+ // TODO support for notifications
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf.sal;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException;
+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.api.NetconfMessage;
+import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
+import org.opendaylight.netconf.sal.connect.api.RemoteDeviceCommunicator;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseRpcSchemalessTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
+import org.opendaylight.netconf.sal.connect.util.MessageCounter;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Invokes RPC by sending netconf message via listener. Also transforms result from NetconfMessage to CompositeNode.
+ */
+public final class SchemalessNetconfDeviceRpc implements DOMRpcService {
+
+ private final RemoteDeviceCommunicator<NetconfMessage> listener;
+ private final BaseRpcSchemalessTransformer baseRpcTransformer;
+ private final SchemalessMessageTransformer schemalessTransformer;
+ private final RemoteDeviceId deviceId;
+
+ public SchemalessNetconfDeviceRpc(final RemoteDeviceId deviceId,
+ final RemoteDeviceCommunicator<NetconfMessage> listener) {
+ this.deviceId = deviceId;
+ this.listener = listener;
+ final MessageCounter counter = new MessageCounter();
+ baseRpcTransformer = new BaseRpcSchemalessTransformer(counter);
+ schemalessTransformer = new SchemalessMessageTransformer(counter);
+ }
+
+ @Nonnull
+ @Override
+ public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(@Nonnull final SchemaPath type,
+ @Nullable final NormalizedNode<?, ?> input) {
+ final MessageTransformer<NetconfMessage> transformer;
+ if (input instanceof AnyXmlNode) {
+ transformer = schemalessTransformer;
+ } else if (isBaseRpc(type)) {
+ transformer = baseRpcTransformer;
+ } else {
+ return Futures.immediateFailedCheckedFuture(new DOMRpcImplementationNotAvailableException("Unable to invoke rpc %s", type));
+ }
+ return handleRpc(type, input, transformer);
+ }
+
+ private CheckedFuture<DOMRpcResult, DOMRpcException> handleRpc(@Nonnull final SchemaPath type,
+ @Nullable final NormalizedNode<?, ?> input,
+ final MessageTransformer<NetconfMessage> transformer) {
+ final NetconfMessage netconfMessage = transformer.toRpcRequest(type, input);
+ final ListenableFuture<RpcResult<NetconfMessage>> rpcResultListenableFuture = listener.sendRequest(netconfMessage, type.getLastComponent());
+
+ final ListenableFuture<DOMRpcResult> transformed = Futures.transform(rpcResultListenableFuture, new Function<RpcResult<NetconfMessage>, DOMRpcResult>() {
+ @Override
+ public DOMRpcResult apply(final RpcResult<NetconfMessage> input) {
+ if (input.isSuccessful()) {
+ return transformer.toRpcResult(input.getResult(), type);
+ } else {
+ return new DefaultDOMRpcResult(input.getErrors());
+ }
+ }
+ });
+
+ return Futures.makeChecked(transformed, new Function<Exception, DOMRpcException>() {
+ @Nullable
+ @Override
+ public DOMRpcException apply(@Nullable final Exception e) {
+ return new DOMRpcImplementationNotAvailableException(e, "Unable to invoke rpc %s on device %s", type, deviceId);
+ }
+ });
+ }
+
+
+ private boolean isBaseRpc(final SchemaPath type) {
+ return NetconfMessageTransformUtil.NETCONF_URI.equals(type.getLastComponent().getNamespace());
+ }
+
+ @Nonnull
+ @Override
+ public <T extends DOMRpcAvailabilityListener> ListenerRegistration<T> registerRpcListener(@Nonnull final T listener) {
+ throw new UnsupportedOperationException("Not available for netconf 1.0");
+ }
+
+}
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.CheckedFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
-import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
import org.opendaylight.yangtools.util.concurrent.MappingCheckedFuture;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readConfigurationData(
final YangInstanceIdentifier path) {
- final ListenableFuture<DOMRpcResult> configRunning = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path));
+ final ListenableFuture<Optional<NormalizedNode<?, ?>>> configRunning = netconfOps.getConfigRunningData(loggingCallback, Optional.fromNullable(path));
- final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(configRunning, new Function<DOMRpcResult, Optional<NormalizedNode<?, ?>>>() {
- @Override
- public Optional<NormalizedNode<?, ?>> apply(final DOMRpcResult result) {
- checkReadSuccess(result, path);
-
- final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> dataNode = findDataNode(result);
- return NormalizedNodes.findNode(dataNode, path.getPathArguments());
- }
- });
-
- return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER);
- }
-
- private DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> findDataNode(final DOMRpcResult result) {
- return ((ContainerNode) result.getResult()).getChild(NetconfMessageTransformUtil.toId(NetconfMessageTransformUtil.NETCONF_DATA_QNAME)).get();
- }
-
- private void checkReadSuccess(final DOMRpcResult result, final YangInstanceIdentifier path) {
- try {
- Preconditions.checkArgument(AbstractWriteTx.isSuccess(result), "%s: Unable to read data: %s, errors: %s", id, path, result.getErrors());
- } catch (final IllegalArgumentException e) {
- LOG.warn("{}: Unable to read data: {}, errors: {}", id, path, result.getErrors());
- throw e;
- }
+ return MappingCheckedFuture.create(configRunning, ReadFailedException.MAPPER);
}
private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readOperationalData(
final YangInstanceIdentifier path) {
- final ListenableFuture<DOMRpcResult> configCandidate = netconfOps.get(loggingCallback, Optional.fromNullable(path));
-
- // Find data node and normalize its content
- final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(configCandidate, new Function<DOMRpcResult, Optional<NormalizedNode<?, ?>>>() {
- @Override
- public Optional<NormalizedNode<?, ?>> apply(final DOMRpcResult result) {
- checkReadSuccess(result, path);
-
- final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> dataNode = findDataNode(result);
- return NormalizedNodes.findNode(dataNode, path.getPathArguments());
- }
- });
+ final ListenableFuture<Optional<NormalizedNode<?, ?>>> configCandidate = netconfOps.getData(loggingCallback, Optional.fromNullable(path));
- return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER);
+ return MappingCheckedFuture.create(configCandidate, ReadFailedException.MAPPER);
}
@Override
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf.schema.mapping;
+
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.util.Map;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
+import org.opendaylight.netconf.sal.connect.util.MessageCounter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Transforms base netconf rpcs
+ */
+public class BaseRpcSchemalessTransformer implements MessageTransformer<NetconfMessage> {
+
+ private static final Map<QName, RpcDefinition> mappedRpcs = BaseSchema.BASE_NETCONF_CTX.getMappedRpcs();
+ private static final SchemaContext schemaContext = BaseSchema.BASE_NETCONF_CTX.getSchemaContext();
+
+ private final MessageCounter counter;
+
+ public BaseRpcSchemalessTransformer(final MessageCounter counter) {
+ this.counter = counter;
+ }
+
+ @Override
+ public DOMNotification toNotification(final NetconfMessage message) {
+ throw new UnsupportedOperationException("Notifications not supported.");
+ }
+
+ @Override
+ public NetconfMessage toRpcRequest(final SchemaPath rpc, final NormalizedNode<?, ?> payload) {
+ // In case no input for rpc is defined, we can simply construct the payload here
+ final QName rpcQName = rpc.getLastComponent();
+
+ Preconditions.checkNotNull(mappedRpcs.get(rpcQName), "Unknown rpc %s, available rpcs: %s", rpcQName, mappedRpcs.keySet());
+ final DOMResult domResult = NetconfMessageTransformUtil.prepareDomResultForRpcRequest(rpcQName, counter);
+ if(mappedRpcs.get(rpcQName).getInput() == null) {
+ return new NetconfMessage(domResult.getNode().getOwnerDocument());
+ }
+
+ Preconditions.checkNotNull(payload, "Transforming an rpc with input: %s, payload cannot be null", rpcQName);
+ Preconditions.checkArgument(payload instanceof ContainerNode,
+ "Transforming an rpc with input: %s, payload has to be a container, but was: %s", rpcQName, payload);
+
+ // Set the path to the input of rpc for the payload stream writer
+ final SchemaPath inputPath = rpc.createChild(QName.create(rpcQName, "input").intern());
+ final DOMResult result = domResult;
+
+ try {
+ NetconfMessageTransformUtil.writeNormalizedRpc(((ContainerNode) payload), result, inputPath, schemaContext);
+ } catch (final XMLStreamException | IOException | IllegalStateException e) {
+ throw new IllegalStateException("Unable to serialize " + inputPath, e);
+ }
+
+ final Document node = result.getNode().getOwnerDocument();
+
+ return new NetconfMessage(node);
+ }
+
+ @Override
+ public DOMRpcResult toRpcResult(final NetconfMessage message, final SchemaPath rpc) {
+ final NormalizedNode<?, ?> normalizedNode;
+ final QName rpcQName = rpc.getLastComponent();
+ if (NetconfMessageTransformUtil.isDataRetrievalOperation(rpcQName)) {
+ final Element xmlData = NetconfMessageTransformUtil.getDataSubtree(message.getDocument());
+ final Document data = XmlUtil.newDocument();
+ data.appendChild(data.importNode(xmlData, true));
+ AnyXmlNode xmlDataNode = Builders.anyXmlBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NetconfMessageTransformUtil.NETCONF_DATA_QNAME))
+ .withValue(new DOMSource(data))
+ .build();
+
+ normalizedNode = Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME))
+ .withChild(xmlDataNode).build();
+ } else {
+ //other base rpcs don't have any output, we can simply construct the payload here
+ Preconditions.checkArgument(isOkPresent(message.getDocument()),
+ "Unexpected content in response of rpc: %s, %s", rpc.getLastComponent(), message);
+ normalizedNode = null;
+
+ }
+ return new DefaultDOMRpcResult(normalizedNode);
+ }
+
+ // FIXME this should go to some util class
+ static boolean isOkPresent(final Document doc) {
+ return XmlElement.fromDomDocument(doc).getOnlyChildElementWithSameNamespaceOptionally("ok").isPresent();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf.schema.mapping;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import java.util.List;
+import java.util.Map;
+import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
+import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public enum BaseSchema {
+
+ BASE_NETCONF_CTX(
+ Lists.newArrayList(
+ org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.$YangModuleInfoImpl.getInstance(),
+ org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.$YangModuleInfoImpl.getInstance()
+ )
+ ),
+ BASE_NETCONF_CTX_WITH_NOTIFICATIONS(
+ Lists.newArrayList(
+ org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.$YangModuleInfoImpl.getInstance(),
+ org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.$YangModuleInfoImpl.getInstance(),
+ org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.$YangModuleInfoImpl.getInstance(),
+ org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.$YangModuleInfoImpl.getInstance()
+ )
+ );
+
+ private final Map<QName, RpcDefinition> mappedRpcs;
+ private final SchemaContext schemaContext;
+
+ BaseSchema(final List<YangModuleInfo> modules) {
+ try {
+ final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
+ moduleInfoBackedContext.addModuleInfos(modules);
+ schemaContext = moduleInfoBackedContext.tryToCreateSchemaContext().get();
+ mappedRpcs = Maps.uniqueIndex(schemaContext.getOperations(), RpcDefinition::getQName);
+ } catch (final RuntimeException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ Map<QName, RpcDefinition> getMappedRpcs() {
+ return mappedRpcs;
+ }
+
+ public SchemaContext getSchemaContext() {
+ return schemaContext;
+ }
+
+}
package org.opendaylight.netconf.sal.connect.netconf.schema.mapping;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS;
-import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RPC_QNAME;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_URI;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
-import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.dom.DOMResult;
import org.opendaylight.controller.config.util.xml.MissingNameSpaceException;
import org.opendaylight.controller.config.util.xml.XmlElement;
-import org.opendaylight.controller.config.util.xml.XmlUtil;
import org.opendaylight.controller.md.sal.dom.api.DOMEvent;
import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.netconf.sal.connect.util.MessageCounter;
-import org.opendaylight.netconf.util.NetconfUtil;
-import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
-import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
-import org.opendaylight.yangtools.yang.data.impl.schema.SchemaOrderedNormalizedNodeWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
public class NetconfMessageTransformer implements MessageTransformer<NetconfMessage> {
- public enum BaseSchema {
-
- BASE_NETCONF_CTX(
- Lists.newArrayList(
- org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.$YangModuleInfoImpl.getInstance(),
- org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.$YangModuleInfoImpl.getInstance()
- )
- ),
- BASE_NETCONF_CTX_WITH_NOTIFICATIONS(
- Lists.newArrayList(
- org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.$YangModuleInfoImpl.getInstance(),
- org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.$YangModuleInfoImpl.getInstance(),
- org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.$YangModuleInfoImpl.getInstance(),
- org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.$YangModuleInfoImpl.getInstance()
- )
- );
-
- private final Map<QName, RpcDefinition> mappedRpcs;
- private final SchemaContext schemaContext;
-
- BaseSchema(List<YangModuleInfo> modules) {
- try {
- final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
- moduleInfoBackedContext.addModuleInfos(modules);
- schemaContext = moduleInfoBackedContext.tryToCreateSchemaContext().get();
- mappedRpcs = Maps.uniqueIndex(schemaContext.getOperations(), QNAME_FUNCTION);
- } catch (final RuntimeException e) {
- LOG.error("Unable to prepare schema context for base netconf ops", e);
- throw new ExceptionInInitializerError(e);
- }
- }
-
- private Map<QName, RpcDefinition> getMappedRpcs() {
- return mappedRpcs;
- }
-
- public SchemaContext getSchemaContext() {
- return schemaContext;
- }
- }
-
- public static final String MESSAGE_ID_PREFIX = "m";
-
- private static final Logger LOG= LoggerFactory.getLogger(NetconfMessageTransformer.class);
-
+ private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageTransformer.class);
private static final Function<SchemaNode, QName> QNAME_FUNCTION = new Function<SchemaNode, QName>() {
@Override
Preconditions.checkNotNull(currentMappedRpcs.get(rpcQName), "Unknown rpc %s, available rpcs: %s", rpcQName, currentMappedRpcs.keySet());
if(currentMappedRpcs.get(rpcQName).getInput() == null) {
- return new NetconfMessage(prepareDomResultForRpcRequest(rpcQName).getNode().getOwnerDocument());
+ return new NetconfMessage(NetconfMessageTransformUtil.prepareDomResultForRpcRequest(rpcQName, counter).getNode().getOwnerDocument());
}
Preconditions.checkNotNull(payload, "Transforming an rpc with input: %s, payload cannot be null", rpcQName);
// Set the path to the input of rpc for the node stream writer
rpc = rpc.createChild(QName.create(rpcQName, "input").intern());
- final DOMResult result = prepareDomResultForRpcRequest(rpcQName);
+ final DOMResult result = NetconfMessageTransformUtil.prepareDomResultForRpcRequest(rpcQName, counter);
try {
// If the schema context for netconf device does not contain model for base netconf operations, use default pre build context with just the base model
// This way operations like lock/unlock are supported even if the source for base model was not provided
SchemaContext ctx = needToUseBaseCtx ? baseSchema.getSchemaContext() : schemaContext;
- writeNormalizedRpc(((ContainerNode) payload), result, rpc, ctx);
+ NetconfMessageTransformUtil.writeNormalizedRpc(((ContainerNode) payload), result, rpc, ctx);
} catch (final XMLStreamException | IOException | IllegalStateException e) {
throw new IllegalStateException("Unable to serialize " + rpc, e);
}
rpc.getNamespace().equals(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME.getNamespace());
}
- private DOMResult prepareDomResultForRpcRequest(final QName rpcQName) {
- final Document document = XmlUtil.newDocument();
- final Element rpcNS = document.createElementNS(NETCONF_RPC_QNAME.getNamespace().toString(), NETCONF_RPC_QNAME.getLocalName());
- // set msg id
- rpcNS.setAttribute(NetconfMessageTransformUtil.MESSAGE_ID_ATTR, counter.getNewMessageId(MESSAGE_ID_PREFIX));
- final Element elementNS = document.createElementNS(rpcQName.getNamespace().toString(), rpcQName.getLocalName());
- rpcNS.appendChild(elementNS);
- document.appendChild(rpcNS);
- return new DOMResult(elementNS);
- }
-
- private static void writeNormalizedRpc(final ContainerNode normalized, final DOMResult result,
- final SchemaPath schemaPath, final SchemaContext baseNetconfCtx) throws IOException, XMLStreamException {
- final XMLStreamWriter writer = NetconfUtil.XML_FACTORY.createXMLStreamWriter(result);
- try {
- try (final NormalizedNodeStreamWriter normalizedNodeStreamWriter =
- XMLStreamNormalizedNodeStreamWriter.create(writer, baseNetconfCtx, schemaPath)) {
- try (final SchemaOrderedNormalizedNodeWriter normalizedNodeWriter =
- new SchemaOrderedNormalizedNodeWriter(normalizedNodeStreamWriter, baseNetconfCtx, schemaPath)) {
- Collection<DataContainerChild<?, ?>> value = normalized.getValue();
- normalizedNodeWriter.write(value);
- normalizedNodeWriter.flush();
- }
- }
- } finally {
- try {
- writer.close();
- } catch (final Exception e) {
- LOG.warn("Unable to close resource properly", e);
- }
- }
- }
@Override
public synchronized DOMRpcResult toRpcResult(final NetconfMessage message, final SchemaPath rpc) {
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf.schema.mapping;
+
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
+import org.opendaylight.netconf.sal.connect.util.MessageCounter;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Transforms anyxml rpcs for schemaless netconf devices.
+ */
+public class SchemalessMessageTransformer implements MessageTransformer<NetconfMessage> {
+
+ private static final YangInstanceIdentifier.NodeIdentifier REPLY_ID =
+ new YangInstanceIdentifier.NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME);
+
+ private final MessageCounter counter;
+
+ public SchemalessMessageTransformer(final MessageCounter counter) {
+ this.counter = counter;
+ }
+
+ @Override
+ public DOMNotification toNotification(final NetconfMessage message) {
+ //TODO add support for notifications
+ throw new UnsupportedOperationException("Notifications not supported.");
+ }
+
+ @Override
+ public NetconfMessage toRpcRequest(final SchemaPath rpc, final NormalizedNode<?, ?> input) {
+ final DOMSource payload = (DOMSource) input.getValue();
+ wrapPayload((Document) payload.getNode());
+ return new NetconfMessage(((Document) ((AnyXmlNode) input).getValue().getNode()));
+ }
+
+ /**
+ * Transforms reply message to anyXml node. In case, that rpc-reply doesn't contain data and contains only <ok/>
+ * element, returns null.
+ * @param rpcReply reply message
+ * @return anyxml
+ */
+ @Override
+ public DOMRpcResult toRpcResult(final NetconfMessage rpcReply, final SchemaPath rpc) {
+ final Document document = rpcReply.getDocument();
+ final AnyXmlNode result;
+ if(BaseRpcSchemalessTransformer.isOkPresent(document)) {
+ result = null;
+ } else {
+ result = Builders.anyXmlBuilder()
+ .withNodeIdentifier(REPLY_ID)
+ .withValue(new DOMSource(rpcReply.getDocument()))
+ .build();
+ }
+ return new DefaultDOMRpcResult(result);
+ }
+
+ private void wrapPayload(final Document doc) {
+ final Element payload = doc.getDocumentElement();
+ doc.removeChild(payload);
+ final Element rpcNS = doc.createElementNS(NetconfMessageTransformUtil.NETCONF_RPC_QNAME.getNamespace().toString(),
+ NetconfMessageTransformUtil.NETCONF_RPC_QNAME.getLocalName());
+ // set msg id
+ rpcNS.setAttribute(NetconfMessageTransformUtil.MESSAGE_ID_ATTR, counter.getNewMessageId(NetconfMessageTransformUtil.MESSAGE_ID_PREFIX));
+ rpcNS.appendChild(payload);
+ doc.appendChild(rpcNS);
+ }
+}
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toId;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath;
+import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.netconf.sal.connect.netconf.sal.SchemalessNetconfDeviceRpc;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.copy.config.input.target.ConfigTarget;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.edit.config.input.EditContent;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.get.config.input.source.ConfigSource;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.ModifyAction;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
private final DOMRpcService rpc;
private final SchemaContext schemaContext;
+ private final RpcStructureTransformer transformer;
public NetconfBaseOps(final DOMRpcService rpc, final SchemaContext schemaContext) {
this.rpc = rpc;
this.schemaContext = schemaContext;
+ if (rpc instanceof SchemalessNetconfDeviceRpc) {
+ this.transformer = new SchemalessRpcStructureTransformer();
+ } else {
+ this.transformer = new NetconfRpcStructureTransformer(schemaContext);
+ }
}
public ListenableFuture<DOMRpcResult> lock(final FutureCallback<DOMRpcResult> callback, final QName datastore) {
final ListenableFuture<DOMRpcResult> future;
if (isFilterPresent(filterPath)) {
- final DataContainerChild<?, ?> node = toFilterStructure(filterPath.get(), schemaContext);
+ final DataContainerChild<?, ?> node = transformer.toFilterStructure(filterPath.get());
future = rpc.invokeRpc(toPath(NETCONF_GET_CONFIG_QNAME),
NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, getSourceNode(datastore), node));
} else {
return future;
}
+ public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfigRunningData(final FutureCallback<DOMRpcResult> callback,
+ final Optional<YangInstanceIdentifier> filterPath) {
+ final ListenableFuture<DOMRpcResult> configRunning = getConfigRunning(callback, filterPath);
+ return extractData(filterPath, configRunning);
+ }
+
+ public ListenableFuture<Optional<NormalizedNode<?, ?>>> getData(final FutureCallback<DOMRpcResult> callback,
+ final Optional<YangInstanceIdentifier> filterPath) {
+ final ListenableFuture<DOMRpcResult> configRunning = get(callback, filterPath);
+ return extractData(filterPath, configRunning);
+ }
+
+ private ListenableFuture<Optional<NormalizedNode<?, ?>>> extractData(final Optional<YangInstanceIdentifier> path,
+ final ListenableFuture<DOMRpcResult> configRunning) {
+ return Futures.transform(configRunning, new Function<DOMRpcResult, Optional<NormalizedNode<?, ?>>>() {
+ @Override
+ public Optional<NormalizedNode<?, ?>> apply(final DOMRpcResult result) {
+ Preconditions.checkArgument(result.getErrors().isEmpty(), "Unable to read data: %s, errors: %s", path, result.getErrors());
+ final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> dataNode =
+ ((ContainerNode) result.getResult()).getChild(NetconfMessageTransformUtil.toId(NetconfMessageTransformUtil.NETCONF_DATA_QNAME)).get();
+ return transformer.selectFromDataStructure(dataNode, path.get());
+ }
+ });
+ }
+
public ListenableFuture<DOMRpcResult> getConfigRunning(final FutureCallback<DOMRpcResult> callback, final Optional<YangInstanceIdentifier> filterPath) {
return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath);
}
}
public DataContainerChild<?, ?> createEditConfigStrcture(final Optional<NormalizedNode<?, ?>> lastChild, final Optional<ModifyAction> operation, final YangInstanceIdentifier dataPath) {
- return NetconfMessageTransformUtil.createEditConfigStructure(schemaContext, dataPath, operation, lastChild);
+ final AnyXmlNode configContent = transformer.createEditConfigStructure(lastChild, dataPath, operation);
+ return Builders.choiceBuilder().withNodeIdentifier(toId(EditContent.QNAME)).withChild(configContent).build();
}
private ContainerNode getEditConfigContent(final QName datastore, final DataContainerChild<?, ?> editStructure, final Optional<ModifyAction> defaultOperation, final boolean rollback) {
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.AbstractMap;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import org.opendaylight.controller.config.util.xml.DocumentedException;
import org.opendaylight.netconf.api.NetconfDocumentedException;
import org.opendaylight.netconf.api.NetconfMessage;
import org.opendaylight.netconf.notifications.NetconfNotification;
+import org.opendaylight.netconf.sal.connect.util.MessageCounter;
import org.opendaylight.netconf.util.NetconfUtil;
import org.opendaylight.netconf.util.messages.NetconfMessageUtil;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.edit.config.input.EditContent;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaOrderedNormalizedNodeWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
public class NetconfMessageTransformUtil {
- private static final Logger LOG= LoggerFactory.getLogger(NetconfMessageTransformUtil.class);
+ private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageTransformUtil.class);
+ public static final String MESSAGE_ID_PREFIX = "m";
public static final String MESSAGE_ID_ATTR = "message-id";
public static final QName CREATE_SUBSCRIPTION_RPC_QNAME = QName.cachedReference(QName.create(CreateSubscriptionInput.QNAME, "create-subscription"));
return Builders.containerBuilder().withNodeIdentifier(toId(name)).withValue(ImmutableList.copyOf(node)).build();
}
- public static DataContainerChild<?, ?> createEditConfigStructure(final SchemaContext ctx, final YangInstanceIdentifier dataPath,
+ public static AnyXmlNode createEditConfigAnyxml(final SchemaContext ctx, final YangInstanceIdentifier dataPath,
final Optional<ModifyAction> operation, final Optional<NormalizedNode<?, ?>> lastChildOverride) {
final NormalizedNode<?, ?> configContent;
}
final DOMSource value = new DOMSource(element);
- return Builders.choiceBuilder().withNodeIdentifier(toId(EditContent.QNAME)).withChild(
- Builders.anyXmlBuilder().withNodeIdentifier(toId(NETCONF_CONFIG_QNAME)).withValue(value).build()).build();
+ return Builders.anyXmlBuilder().withNodeIdentifier(toId(NETCONF_CONFIG_QNAME)).withValue(value).build();
+ }
+
+ public static DataContainerChild<?, ?> createEditConfigStructure(final SchemaContext ctx, final YangInstanceIdentifier dataPath,
+ final Optional<ModifyAction> operation, final Optional<NormalizedNode<?, ?>> lastChildOverride) {
+ return Builders.choiceBuilder().withNodeIdentifier(toId(EditContent.QNAME))
+ .withChild(createEditConfigAnyxml(ctx, dataPath, operation, lastChildOverride)).build();
}
public static SchemaPath toPath(final QName rpc) {
return new AbstractMap.SimpleEntry<>(NetconfNotification.UNKNOWN_EVENT_TIME, notificationElement);
}
}
+
+ public static DOMResult prepareDomResultForRpcRequest(final QName rpcQName, final MessageCounter counter) {
+ final Document document = XmlUtil.newDocument();
+ final Element rpcNS =
+ document.createElementNS(NETCONF_RPC_QNAME.getNamespace().toString(), NETCONF_RPC_QNAME.getLocalName());
+ // set msg id
+ rpcNS.setAttribute(MESSAGE_ID_ATTR, counter.getNewMessageId(MESSAGE_ID_PREFIX));
+ final Element elementNS = document.createElementNS(rpcQName.getNamespace().toString(), rpcQName.getLocalName());
+ rpcNS.appendChild(elementNS);
+ document.appendChild(rpcNS);
+ return new DOMResult(elementNS);
+ }
+
+ public static void writeNormalizedRpc(final ContainerNode normalized, final DOMResult result,
+ final SchemaPath schemaPath, final SchemaContext baseNetconfCtx) throws IOException, XMLStreamException {
+ final XMLStreamWriter writer = NetconfUtil.XML_FACTORY.createXMLStreamWriter(result);
+ try {
+ try (final NormalizedNodeStreamWriter normalizedNodeStreamWriter =
+ XMLStreamNormalizedNodeStreamWriter.create(writer, baseNetconfCtx, schemaPath)) {
+ try (final SchemaOrderedNormalizedNodeWriter normalizedNodeWriter =
+ new SchemaOrderedNormalizedNodeWriter(normalizedNodeStreamWriter, baseNetconfCtx, schemaPath)) {
+ Collection<DataContainerChild<?, ?>> value = normalized.getValue();
+ normalizedNodeWriter.write(value);
+ normalizedNodeWriter.flush();
+ }
+ }
+ } finally {
+ try {
+ writer.close();
+ } catch (final Exception e) {
+ LOG.warn("Unable to close resource properly", e);
+ }
+ }
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf.util;
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Transforms rpc structures to normalized nodes and vice versa.
+ */
+class NetconfRpcStructureTransformer implements RpcStructureTransformer {
+
+ private final SchemaContext schemaContext;
+
+ NetconfRpcStructureTransformer(final SchemaContext schemaContext) {
+ this.schemaContext = schemaContext;
+ }
+
+ @Override
+ public Optional<NormalizedNode<?, ?>> selectFromDataStructure(final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> data,
+ final YangInstanceIdentifier path) {
+ return NormalizedNodes.findNode(data, path.getPathArguments());
+ }
+
+ @Override
+ public AnyXmlNode createEditConfigStructure(final Optional<NormalizedNode<?, ?>> data,
+ final YangInstanceIdentifier dataPath, Optional<ModifyAction> operation) {
+ return NetconfMessageTransformUtil.createEditConfigAnyxml(schemaContext, dataPath, operation, data);
+ }
+
+ @Override
+ public DataContainerChild<?, ?> toFilterStructure(final YangInstanceIdentifier path) {
+ return NetconfMessageTransformUtil.toFilterStructure(path, schemaContext);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf.util;
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * Transforms rpc structures to normalized nodes and vice versa.
+ */
+interface RpcStructureTransformer {
+
+ /**
+ * Transforms data and path to the config element structure. It means creating of parent xml structure
+ * specified by path and appending data to the structure. Operation is set as attribute on data element.
+ * @param data data
+ * @param dataPath path, where data will be written
+ * @param operation operation
+ * @return config structure
+ */
+ AnyXmlNode createEditConfigStructure(Optional<NormalizedNode<?, ?>> data,
+ YangInstanceIdentifier dataPath, Optional<ModifyAction> operation);
+
+ /**
+ * Transforms path to filter structure.
+ * @param path path
+ * @return filter structure
+ */
+ DataContainerChild<?,?> toFilterStructure(YangInstanceIdentifier path);
+
+ /**
+ * Selects data specified by path from data node. Data must be product of get-config rpc with filter created by
+ * {@link #toFilterStructure(YangInstanceIdentifier)} with same path.
+ * @param data data
+ * @param path path to select
+ * @return selected data
+ */
+ Optional<NormalizedNode<?, ?>> selectFromDataStructure(DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> data,
+ YangInstanceIdentifier path);
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf.util;
+
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * Transforms rpc structures to anyxml and vice versa. Provides support for devices, which don't expose their schema.
+ */
+class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
+
+ /**
+ * Selects elements in anyxml data node, which matches path arguments QNames. Since class in not context aware,
+ * method searches for all elements as they are named in path.
+ * @param data data, must be of type {@link AnyXmlNode}
+ * @param path path to select
+ * @return selected data
+ */
+ @Override
+ public Optional<NormalizedNode<?, ?>> selectFromDataStructure(final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> data,
+ final YangInstanceIdentifier path) {
+ Preconditions.checkArgument(data instanceof AnyXmlNode);
+ final List<XmlElement> xmlElements = selectMatchingNodes(getSourceElement(((AnyXmlNode)data).getValue()), path);
+ final Document result = XmlUtil.newDocument();
+ final QName dataQName = NetconfMessageTransformUtil.NETCONF_DATA_QNAME;
+ final Element dataElement = result.createElementNS(dataQName.getNamespace().toString(), dataQName.getLocalName());
+ result.appendChild(dataElement);
+ for (XmlElement xmlElement : xmlElements) {
+ dataElement.appendChild(result.importNode(xmlElement.getDomElement(), true));
+ }
+ final AnyXmlNode resultAnyxml = Builders.anyXmlBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(dataQName))
+ .withValue(new DOMSource(result))
+ .build();
+ return Optional.of(resultAnyxml);
+ }
+
+ /**
+ * This class in not context aware. All elements are present in resulting structure, which are present in data path.
+ * @see RpcStructureTransformer#createEditConfigStructure(Optional, YangInstanceIdentifier, Optional)
+ * @param data data
+ * @param dataPath path, where data will be written
+ * @param operation operation
+ * @return config structure
+ */
+ @Override
+ public AnyXmlNode createEditConfigStructure(final Optional<NormalizedNode<?, ?>> data,
+ final YangInstanceIdentifier dataPath, Optional<ModifyAction> operation) {
+ Preconditions.checkArgument(data.isPresent());
+ Preconditions.checkArgument(data.get() instanceof AnyXmlNode);
+
+ final AnyXmlNode anxmlData = (AnyXmlNode) data.get();
+ final Document document = XmlUtil.newDocument();
+ final Element dataNode = (Element) document.importNode(getSourceElement(anxmlData.getValue()), true);
+ checkDataValidForPath(dataPath, dataNode);
+
+ final Element configElement = document.createElementNS(NETCONF_CONFIG_QNAME.getNamespace().toString(),
+ NETCONF_CONFIG_QNAME.getLocalName());
+ document.appendChild(configElement);
+
+ final Element parentXmlStructure;
+ if (dataPath.equals(YangInstanceIdentifier.EMPTY)) {
+ parentXmlStructure = dataNode;
+ configElement.appendChild(parentXmlStructure);
+ } else {
+ final List<YangInstanceIdentifier.PathArgument> pathArguments = dataPath.getPathArguments();
+ //last will be appended later
+ final List<YangInstanceIdentifier.PathArgument> pathWithoutLast = pathArguments.subList(0, pathArguments.size() - 1);
+ parentXmlStructure = instanceIdToXmlStructure(pathWithoutLast, configElement);
+ }
+ if (operation.isPresent()) {
+ setOperationAttribute(operation, document, dataNode);
+ }
+ //append data
+ parentXmlStructure.appendChild(document.importNode(dataNode, true));
+ return Builders.anyXmlBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NETCONF_CONFIG_QNAME))
+ .withValue(new DOMSource(document.getDocumentElement()))
+ .build();
+ }
+
+ /**
+ * This class in not context aware. All elements are present in resulting structure, which are present in data path.
+ * @see RpcStructureTransformer#toFilterStructure(YangInstanceIdentifier)
+ * @param path path
+ * @return filter structure
+ */
+ @Override
+ public DataContainerChild<?, ?> toFilterStructure(final YangInstanceIdentifier path) {
+ final Document document = XmlUtil.newDocument();
+ final QName filterQname = NetconfMessageTransformUtil.NETCONF_FILTER_QNAME;
+ final Element filter = document.createElementNS(filterQname.getNamespace().toString(), filterQname.getLocalName());
+ final Attr a = document.createAttributeNS(filterQname.getNamespace().toString(), "type");
+ a.setTextContent("subtree");
+ filter.setAttributeNode(a);
+ document.appendChild(filter);
+ instanceIdToXmlStructure(path.getPathArguments(), filter);
+ return Builders.anyXmlBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(filterQname))
+ .withValue(new DOMSource(document.getDocumentElement()))
+ .build();
+ }
+
+ private void checkDataValidForPath(final YangInstanceIdentifier dataPath, final Element dataNode) {
+ //if datapath is empty, consider dataNode to be a root node
+ if (dataPath.equals(YangInstanceIdentifier.EMPTY)) {
+ return;
+ }
+ final XmlElement dataElement = XmlElement.fromDomElement(dataNode);
+ final YangInstanceIdentifier.PathArgument lastPathArgument = dataPath.getLastPathArgument();
+ final QName nodeType = lastPathArgument.getNodeType();
+ if (!nodeType.getNamespace().toString().equals(dataNode.getNamespaceURI()) ||
+ !nodeType.getLocalName().equals(dataElement.getName())) {
+ throw new IllegalStateException(
+ String.format("Can't write data '%s' to path %s", dataNode.getTagName(), dataPath));
+ }
+ if (lastPathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
+ checkKeyValuesValidForPath(dataElement, lastPathArgument);
+ }
+
+ }
+
+ private void checkKeyValuesValidForPath(final XmlElement dataElement,
+ final YangInstanceIdentifier.PathArgument lastPathArgument) {
+ final YangInstanceIdentifier.NodeIdentifierWithPredicates keyedId =
+ (YangInstanceIdentifier.NodeIdentifierWithPredicates) lastPathArgument;
+ final Map<QName, Object> keyValues = keyedId.getKeyValues();
+ for (QName qName : keyValues.keySet()) {
+ final List<XmlElement> key =
+ dataElement.getChildElementsWithinNamespace(qName.getLocalName(), qName.getNamespace().toString());
+ if (key.isEmpty()) {
+ throw new IllegalStateException("No key present in xml");
+ }
+ if (key.size() > 1) {
+ throw new IllegalStateException("Multiple values for same key present");
+ }
+ final String textContent;
+ try {
+ textContent = key.get(0).getTextContent();
+ } catch (DocumentedException e) {
+ throw new IllegalStateException("Key value not present in key element");
+ }
+ if (!keyValues.get(qName).equals(textContent)) {
+ throw new IllegalStateException("Key value in path not equal to key value in xml");
+ }
+ }
+ }
+
+ private void setOperationAttribute(final Optional<ModifyAction> operation, final Document document,
+ final Element dataNode) {
+ final QName operationQname = NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME;
+ final Attr operationAttribute =
+ document.createAttributeNS(operationQname.getNamespace().toString(), operationQname.getLocalName());
+ operationAttribute.setTextContent(toOperationString(operation.get()));
+ dataNode.setAttributeNode(operationAttribute);
+ }
+
+ private static Element instanceIdToXmlStructure(final List<YangInstanceIdentifier.PathArgument> pathArguments,
+ final Element data) {
+ final Document doc = data.getOwnerDocument();
+ Element parent = data;
+ for (YangInstanceIdentifier.PathArgument pathArgument : pathArguments) {
+ final QName nodeType = pathArgument.getNodeType();
+ final Element element = doc.createElementNS(nodeType.getNamespace().toString(), nodeType.getLocalName());
+ parent.appendChild(element);
+ //if path argument is list id, add also keys to resulting xml
+ if (pathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
+ YangInstanceIdentifier.NodeIdentifierWithPredicates listNode = (YangInstanceIdentifier.NodeIdentifierWithPredicates) pathArgument;
+ for (Map.Entry<QName, Object> key : listNode.getKeyValues().entrySet()) {
+ final Element keyElement = doc.createElementNS(key.getKey().getNamespace().toString(), key.getKey().getLocalName());
+ keyElement.setTextContent(key.getValue().toString());
+ element.appendChild(keyElement);
+ }
+ }
+ parent = element;
+ }
+ return parent;
+ }
+
+ private List<XmlElement> selectMatchingNodes(final Element domElement, final YangInstanceIdentifier path) {
+ XmlElement element = XmlElement.fromDomElement(domElement);
+ for (YangInstanceIdentifier.PathArgument pathArgument : path.getPathArguments()) {
+ List<XmlElement> childElements = element.getChildElements(pathArgument.getNodeType().getLocalName());
+ if (childElements.size() == 1) {
+ element = childElements.get(0);
+ } else {
+ return childElements;
+ }
+ }
+ return Collections.singletonList(element);
+ }
+
+ private static String toOperationString(final ModifyAction operation) {
+ return operation.name().toLowerCase();
+ }
+
+ private static Element getSourceElement(final DOMSource source) {
+ final Node node = source.getNode();
+ switch (node.getNodeType()) {
+ case Node.DOCUMENT_NODE:
+ return ((Document)node).getDocumentElement();
+ case Node.ELEMENT_NODE:
+ return (Element) node;
+ default:
+ throw new IllegalStateException("DOMSource node must be document or element.");
+ }
+ }
+
+}
type boolean;
}
+ leaf schemaless {
+ type boolean;
+ default false;
+ }
+
container yang-module-capabilities {
config true;
leaf override {
import java.util.Set;
import org.junit.Test;
import org.opendaylight.controller.config.util.xml.XmlUtil;
-import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.NetconfMessageTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseSchema;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
@Test
public void testCreate() throws Exception {
- final SchemaContext schemaContext = NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext();
+ final SchemaContext schemaContext = BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext();
final DataSchemaNode schemasNode = ((ContainerSchemaNode) schemaContext.getDataChildByName("netconf-state")).getDataChildByName("schemas");
final Document schemasXml = XmlUtil.readXmlToDocument(getClass().getResourceAsStream("/netconf-state.schemas.payload.xml"));
--- /dev/null
+package org.opendaylight.netconf.sal.connect.netconf.sal;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.net.InetSocketAddress;
+import javax.xml.transform.dom.DOMSource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.sal.connect.api.RemoteDeviceCommunicator;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+public class SchemalessNetconfDeviceRpcTest {
+
+ @Mock
+ private RemoteDeviceCommunicator<NetconfMessage> listener;
+
+ private SchemalessNetconfDeviceRpc deviceRpc;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ RpcResult<NetconfMessage> msg = null;
+ ListenableFuture<RpcResult<NetconfMessage>> future = Futures.immediateFuture(msg);
+ doReturn(future).when(listener).sendRequest(any(), any());
+ deviceRpc = new SchemalessNetconfDeviceRpc(new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("0.0.0.0", 17830)), listener);
+
+ }
+
+ @Test
+ public void testInvokeRpc() throws Exception {
+ final QName qName = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "2011-06-01", "get-config");
+ SchemaPath type = SchemaPath.create(true, qName);
+ DOMSource src = new DOMSource(XmlUtil.readXmlToDocument("<get-config xmlns=\"dd\">\n" +
+ " <source>\n" +
+ " <running/>\n" +
+ " </source>\n" +
+ " <filter type=\"subtree\">\n" +
+ " <mainroot xmlns=\"urn:dummy:mod-0\">\n" +
+ " <maincontent/>\n" +
+ "<choiceList></choiceList>\n" +
+ " </mainroot>\n" +
+ " </filter>\n" +
+ " </get-config>"));
+ NormalizedNode<?, ?> input = Builders.anyXmlBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(qName))
+ .withValue(src)
+ .build();
+
+ deviceRpc.invokeRpc(type, input);
+ ArgumentCaptor<NetconfMessage> msgCaptor = ArgumentCaptor.forClass(NetconfMessage.class);
+ ArgumentCaptor<QName> qNameCaptor = ArgumentCaptor.forClass(QName.class);
+ verify(listener).sendRequest(msgCaptor.capture(), qNameCaptor.capture());
+ System.out.println(XmlUtil.toString(msgCaptor.getValue().getDocument()));
+ }
+}
\ No newline at end of file
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
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.schema.mapping.NetconfMessageTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseSchema;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
.doReturn(Futures.immediateFailedCheckedFuture(new IllegalStateException("Failed tx")))
.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()),
+ final WriteRunningTx tx = new WriteRunningTx(id, new NetconfBaseOps(rpc, BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext()),
false);
try {
tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
final NetconfMessageTransformer transformer = new NetconfMessageTransformer(
partialSchema,
true,
- NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS
+ BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS
);
NetconfMessage netconfMessage = transformer.toRpcRequest(
toPath(CREATE_SUBSCRIPTION_RPC_QNAME),
final MapEntryNode schemaNode = Builders.mapEntryBuilder().withNodeIdentifier(identifierWithPredicates).withValue(values).build();
final YangInstanceIdentifier id = YangInstanceIdentifier.builder().node(NetconfState.QNAME).node(Schemas.QNAME).node(Schema.QNAME).nodeWithKey(Schema.QNAME, keys).build();
- final DataContainerChild<?, ?> editConfigStructure = createEditConfigStructure(NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext(), id, Optional.<ModifyAction>absent(), Optional.<NormalizedNode<?, ?>>fromNullable(schemaNode));
+ final DataContainerChild<?, ?> editConfigStructure = createEditConfigStructure(BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext(), id, Optional.<ModifyAction>absent(), Optional.<NormalizedNode<?, ?>>fromNullable(schemaNode));
final DataContainerChild<?, ?> target = NetconfBaseOps.getTargetNode(NETCONF_CANDIDATE_QNAME);
--- /dev/null
+package org.opendaylight.netconf.sal.connect.netconf.util;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import javax.xml.transform.dom.DOMSource;
+import org.custommonkey.xmlunit.Diff;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+@RunWith(Parameterized.class)
+public class SchemalessRpcStructureTransformerTest {
+
+ private static final String NAMESPACE = "http://example.com/schema/1.2/config";
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+ private final Class<? extends Exception> expectedException;
+
+ private final String expectedConfig;
+ private final String expectedFilter;
+ private final String getConfigData;
+ private final YangInstanceIdentifier path;
+ private final DOMSource source;
+
+ private final SchemalessRpcStructureTransformer adapter = new SchemalessRpcStructureTransformer();
+ private final String testDataset;
+
+ public SchemalessRpcStructureTransformerTest(YangInstanceIdentifier path, String testDataset, Class<? extends Exception> expectedException) throws IOException, SAXException, URISyntaxException {
+ this.path = path;
+ this.testDataset = testDataset;
+ this.expectedException = expectedException;
+ this.source = new DOMSource(XmlUtil.readXmlToDocument(getClass().getResourceAsStream("/schemaless/data/" + testDataset)).getDocumentElement());
+ this.expectedConfig = new String(Files.readAllBytes(Paths.get(getClass().getResource("/schemaless/edit-config/" + testDataset).toURI())));
+ this.expectedFilter = new String(Files.readAllBytes(Paths.get(getClass().getResource("/schemaless/filter/" + testDataset).toURI())));
+ this.getConfigData = new String(Files.readAllBytes(Paths.get(getClass().getResource("/schemaless/get-config/" + testDataset).toURI())));
+ }
+
+ @Parameterized.Parameters
+ public static Collection parameters() {
+ Object[][] params = {
+ {YangInstanceIdentifier.builder()
+ .node(createNodeId("top"))
+ .node(createNodeId("users"))
+ .build(), "container.xml", null},
+ {YangInstanceIdentifier.builder()
+ .node(createNodeId("top"))
+ .node(createNodeId("users"))
+ .node(createListNodeId("user", "key", "k1"))
+ .build(), "keyed-list.xml", null},
+ {YangInstanceIdentifier.builder()
+ .node(createNodeId("top"))
+ .node(createNodeId("users"))
+ .node(createListNodeId("user", ImmutableMap.of(QName.create(NAMESPACE, "key1"), "k1", QName.create(NAMESPACE, "key2"), "k2")))
+ .build(), "keyed-list-compound-key.xml", null},
+ {YangInstanceIdentifier.builder()
+ .node(createNodeId("top"))
+ .node(createNodeId("users"))
+ .node(createListNodeId("user", "key", "k2"))
+ .build(), "keyed-list-bad-key.xml", IllegalStateException.class}
+ };
+ return Arrays.asList(params);
+ }
+
+ @BeforeClass
+ public static void suiteSetup() {
+ XMLUnit.setIgnoreWhitespace(true);
+ }
+
+ @Test
+ public void testCreateEditConfigStructure() throws Exception {
+ if(expectedException != null) {
+ thrown.expect(expectedException);
+ }
+ AnyXmlNode data = Builders.anyXmlBuilder()
+ .withNodeIdentifier(createNodeId(path.getLastPathArgument().getNodeType().getLocalName()))
+ .withValue(source)
+ .build();
+ final AnyXmlNode anyXmlNode = adapter.createEditConfigStructure(Optional.of(data), path, Optional.of(ModifyAction.REPLACE));
+ final String s = XmlUtil.toString((Element) anyXmlNode.getValue().getNode());
+ Diff diff = new Diff(expectedConfig, s);
+ Assert.assertTrue(String.format("Input %s: %s", testDataset, diff.toString()), diff.similar());
+ }
+
+ @Test
+ public void testToFilterStructure() throws Exception {
+ final AnyXmlNode anyXmlNode = (AnyXmlNode) adapter.toFilterStructure(path);
+ final String s = XmlUtil.toString((Element) anyXmlNode.getValue().getNode());
+ Diff diff = new Diff(expectedFilter, s);
+ Assert.assertTrue(String.format("Input %s: %s", testDataset, diff.toString()), diff.similar());
+ }
+
+ @Test
+ public void testSelectFromDataStructure() throws Exception {
+ AnyXmlNode data = Builders.anyXmlBuilder()
+ .withNodeIdentifier(createNodeId(path.getLastPathArgument().getNodeType().getLocalName()))
+ .withValue(new DOMSource(XmlUtil.readXmlToDocument(getConfigData).getDocumentElement()))
+ .build();
+ final AnyXmlNode dataStructure = (AnyXmlNode) adapter.selectFromDataStructure(data, path).get();
+ final XmlElement s = XmlElement.fromDomDocument((Document) dataStructure.getValue().getNode());
+ final String dataFromReply = XmlUtil.toString(s.getOnlyChildElement().getDomElement());
+ final String expectedData = XmlUtil.toString((Element) source.getNode());
+ Diff diff = new Diff(expectedData, dataFromReply);
+ Assert.assertTrue(String.format("Input %s: %s", testDataset, diff.toString()), diff.similar());
+ }
+
+ private static YangInstanceIdentifier.NodeIdentifier createNodeId(String name) {
+ return new YangInstanceIdentifier.NodeIdentifier(QName.create(NAMESPACE, name));
+ }
+
+ private static YangInstanceIdentifier.NodeIdentifierWithPredicates createListNodeId(String nodeName, String keyName, String id) {
+ return new YangInstanceIdentifier.NodeIdentifierWithPredicates(QName.create(NAMESPACE, nodeName), QName.create(NAMESPACE, keyName), id);
+ }
+
+ private static YangInstanceIdentifier.NodeIdentifierWithPredicates createListNodeId(String nodeName, Map<QName, Object> keys) {
+ return new YangInstanceIdentifier.NodeIdentifierWithPredicates(QName.create(NAMESPACE, nodeName), keys);
+ }
+}
\ No newline at end of file
--- /dev/null
+<users xmlns="http://example.com/schema/1.2/config">
+ <user>
+ <name>fred</name>
+ </user>
+</users>
\ No newline at end of file
--- /dev/null
+<user xmlns="http://example.com/schema/1.2/config">
+ <key>k1</key>
+ <name>fred</name>
+</user>
\ No newline at end of file
--- /dev/null
+<user xmlns="http://example.com/schema/1.2/config">
+ <key1>k1</key1>
+ <key2>k2</key2>
+ <name>fred</name>
+</user>
\ No newline at end of file
--- /dev/null
+<user xmlns="http://example.com/schema/1.2/config">
+ <key>k1</key>
+ <name>fred</name>
+</user>
\ No newline at end of file
--- /dev/null
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="replace">
+ <user>
+ <name>fred</name>
+ </user>
+ </users>
+ </top>
+</config>
\ No newline at end of file
--- /dev/null
+empty
\ No newline at end of file
--- /dev/null
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="replace">
+ <key1>k1</key1>
+ <key2>k2</key2>
+ <name>fred</name>
+ </user>
+ </users>
+ </top>
+</config>
\ No newline at end of file
--- /dev/null
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="replace">
+ <key>k1</key>
+ <name>fred</name>
+ </user>
+ </users>
+ </top>
+</config>
\ No newline at end of file
--- /dev/null
+<filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ </users>
+ </top>
+</filter>
\ No newline at end of file
--- /dev/null
+<filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <key>k2</key>
+ </user>
+ </users>
+ </top>
+</filter>
--- /dev/null
+<filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <key1>k1</key1>
+ <key2>k2</key2>
+ </user>
+ </users>
+ </top>
+</filter>
\ No newline at end of file
--- /dev/null
+<filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <key>k1</key>
+ </user>
+ </users>
+ </top>
+</filter>
--- /dev/null
+<data operation="replace" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <name>fred</name>
+ </user>
+ </users>
+ </top>
+</data>
\ No newline at end of file
--- /dev/null
+<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <key>k1</key>
+ <name>fred</name>
+ </user>
+ </users>
+ </top>
+</data>
\ No newline at end of file
--- /dev/null
+<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <key1>k1</key1>
+ <key2>k2</key2>
+ <name>fred</name>
+ </user>
+ </users>
+ </top>
+</data>
--- /dev/null
+<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="http://example.com/schema/1.2/config">
+ <users>
+ <user>
+ <key>k1</key>
+ <name>fred</name>
+ </user>
+ </users>
+ </top>
+</data>
\ No newline at end of file