From fc71e17ca46e029ad6535613498842ff2854868d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jakub=20T=C3=B3th?= Date: Mon, 6 Aug 2018 16:11:29 +0200 Subject: [PATCH] Prepare netconf to support YANG 1.1 actions MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This allows implementation of a factory for creating of DOMActionService of a connected device. This service is going to be part of services provided by the device's mountpoint. Change-Id: I1c79b4bc8f1b1c63f7ffb2306f07ea9872f15882 Signed-off-by: Jakub Tóth Signed-off-by: Robert Varga --- .../callhome/mount/BaseCallHomeTopology.java | 6 +- .../mount/CallHomeMountDispatcher.java | 15 +- .../callhome/mount/CallHomeTopology.java | 17 +- .../topology/AbstractNetconfTopology.java | 23 +- .../topology/impl/NetconfTopologyImpl.java | 24 ++- .../sal/connect/api/DeviceActionFactory.java | 29 +++ .../sal/connect/api/MessageTransformer.java | 25 +++ .../sal/connect/api/RemoteDeviceHandler.java | 27 ++- .../sal/connect/netconf/NetconfDevice.java | 42 ++-- .../connect/netconf/NetconfDeviceBuilder.java | 18 +- .../netconf/sal/NetconfDeviceSalFacade.java | 7 +- .../netconf/sal/NetconfDeviceSalProvider.java | 34 +-- .../mapping/NetconfMessageTransformer.java | 145 +++++++++++-- .../util/NetconfMessageTransformUtil.java | 52 +++++ .../connect/netconf/NetconfDeviceTest.java | 21 +- .../sal/NetconfDeviceSalFacadeTest.java | 6 +- .../NetconfMessageTransformerTest.java | 199 +++++++++++++++++- .../schemas/example-server-farm.yang | 51 +++++ 18 files changed, 652 insertions(+), 89 deletions(-) create mode 100644 netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/DeviceActionFactory.java create mode 100644 netconf/sal-netconf-connector/src/test/resources/schemas/example-server-farm.yang diff --git a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/BaseCallHomeTopology.java b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/BaseCallHomeTopology.java index d4f9e132b8..2d174ea0ba 100644 --- a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/BaseCallHomeTopology.java +++ b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/BaseCallHomeTopology.java @@ -15,6 +15,7 @@ import org.opendaylight.controller.config.threadpool.ThreadPool; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService; import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory; import org.opendaylight.netconf.topology.AbstractNetconfTopology; import org.opendaylight.netconf.topology.api.SchemaRepositoryProvider; @@ -26,9 +27,10 @@ abstract class BaseCallHomeTopology extends AbstractNetconfTopology { final SchemaRepositoryProvider schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountPointService, - final AAAEncryptionService encryptionService) { + final AAAEncryptionService encryptionService, + final DeviceActionFactory deviceActionFactory) { super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor, schemaRepositoryProvider, dataBroker, mountPointService, - encryptionService); + encryptionService, deviceActionFactory); } } diff --git a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcher.java b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcher.java index d661a9e5a3..95d4b7be7b 100644 --- a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcher.java +++ b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcher.java @@ -25,6 +25,7 @@ import org.opendaylight.netconf.client.NetconfClientDispatcher; import org.opendaylight.netconf.client.NetconfClientSession; import org.opendaylight.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration; +import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory; import org.opendaylight.netconf.topology.api.SchemaRepositoryProvider; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; @@ -55,16 +56,28 @@ public class CallHomeMountDispatcher implements NetconfClientDispatcher, CallHom } }; + private DeviceActionFactory deviceActionFactory; + public CallHomeMountDispatcher(final String topologyId, final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor, final SchemaRepositoryProvider schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountService, final AAAEncryptionService encryptionService) { + this(topologyId, eventExecutor, keepaliveExecutor, processingExecutor, schemaRepositoryProvider, dataBroker, + mountService, encryptionService, null); + } + + public CallHomeMountDispatcher(final String topologyId, final EventExecutor eventExecutor, + final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor, + final SchemaRepositoryProvider schemaRepositoryProvider, final DataBroker dataBroker, + final DOMMountPointService mountService, + final AAAEncryptionService encryptionService, DeviceActionFactory deviceActionFactory) { this.topologyId = topologyId; this.eventExecutor = eventExecutor; this.keepaliveExecutor = keepaliveExecutor; this.processingExecutor = processingExecutor; this.schemaRepositoryProvider = schemaRepositoryProvider; + this.deviceActionFactory = deviceActionFactory; this.sessionManager = new CallHomeMountSessionManager(); this.dataBroker = dataBroker; this.mountService = mountService; @@ -93,7 +106,7 @@ public class CallHomeMountDispatcher implements NetconfClientDispatcher, CallHom void createTopology() { this.topology = new CallHomeTopology(topologyId, this, eventExecutor, keepaliveExecutor, processingExecutor, - schemaRepositoryProvider, dataBroker, mountService, encryptionService); + schemaRepositoryProvider, dataBroker, mountService, encryptionService, deviceActionFactory); } @Override diff --git a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java index 41d121eac3..d918def01b 100644 --- a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java +++ b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java @@ -15,6 +15,7 @@ import org.opendaylight.controller.config.threadpool.ThreadPool; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService; import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory; import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceSalFacade; @@ -23,15 +24,27 @@ import org.opendaylight.netconf.topology.api.SchemaRepositoryProvider; public class CallHomeTopology extends BaseCallHomeTopology { + public CallHomeTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher, + final EventExecutor eventExecutor, + final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor, + final SchemaRepositoryProvider schemaRepositoryProvider, + final DataBroker dataBroker, final DOMMountPointService mountPointService, + final AAAEncryptionService encryptionService) { + this(topologyId, clientDispatcher, eventExecutor, + keepaliveExecutor, processingExecutor, schemaRepositoryProvider, + dataBroker, mountPointService, encryptionService, null); + } + public CallHomeTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher, final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor, final SchemaRepositoryProvider schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountPointService, - final AAAEncryptionService encryptionService) { + final AAAEncryptionService encryptionService, + final DeviceActionFactory deviceActionFactory) { super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor, schemaRepositoryProvider, - dataBroker, mountPointService, encryptionService); + dataBroker, mountPointService, encryptionService, deviceActionFactory); } @Override 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 342d3e9cda..818a3895dc 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 @@ -51,6 +51,7 @@ import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurati import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder; import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler; import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler; +import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory; import org.opendaylight.netconf.sal.connect.api.RemoteDevice; import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas; @@ -200,15 +201,16 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { } } - protected final String topologyId; private final NetconfClientDispatcher clientDispatcher; private final EventExecutor eventExecutor; + private final DeviceActionFactory deviceActionFactory; + private final NetconfKeystoreAdapter keystoreAdapter; protected final ScheduledThreadPool keepaliveExecutor; protected final ThreadPool processingExecutor; protected final SharedSchemaRepository sharedSchemaRepository; protected final DataBroker dataBroker; protected final DOMMountPointService mountPointService; - private final NetconfKeystoreAdapter keystoreAdapter; + protected final String topologyId; protected SchemaSourceRegistry schemaRegistry = DEFAULT_SCHEMA_REPOSITORY; protected SchemaRepository schemaRepository = DEFAULT_SCHEMA_REPOSITORY; protected SchemaContextFactory schemaContextFactory = DEFAULT_SCHEMA_CONTEXT_FACTORY; @@ -222,12 +224,14 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { final ThreadPool processingExecutor, final SchemaRepositoryProvider schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountPointService, - final AAAEncryptionService encryptionService) { + final AAAEncryptionService encryptionService, + final DeviceActionFactory deviceActionFactory) { this.topologyId = topologyId; this.clientDispatcher = clientDispatcher; this.eventExecutor = eventExecutor; this.keepaliveExecutor = keepaliveExecutor; this.processingExecutor = processingExecutor; + this.deviceActionFactory = deviceActionFactory; this.sharedSchemaRepository = schemaRepositoryProvider.getSharedSchemaRepository(); this.dataBroker = dataBroker; this.mountPointService = mountPointService; @@ -320,7 +324,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { if (keepaliveDelay > 0) { LOG.warn("Adding keepalive facade, for device {}", nodeId); - salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), + salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, this.keepaliveExecutor.getExecutor(), keepaliveDelay, defaultRequestTimeoutMillis); } @@ -356,13 +360,16 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { if (node.isSchemaless()) { device = new SchemalessNetconfDevice(remoteDeviceId, salFacade); } else { - device = new NetconfDeviceBuilder() + NetconfDeviceBuilder netconfDeviceBuilder = new NetconfDeviceBuilder() .setReconnectOnSchemasChange(reconnectOnChangedSchema) .setSchemaResourcesDTO(schemaResourcesDTO) - .setGlobalProcessingExecutor(processingExecutor.getExecutor()) + .setGlobalProcessingExecutor(this.processingExecutor.getExecutor()) .setId(remoteDeviceId) - .setSalFacade(salFacade) - .build(); + .setSalFacade(salFacade); + if (this.deviceActionFactory != null) { + netconfDeviceBuilder.setDeviceActionFactory(this.deviceActionFactory); + } + device = netconfDeviceBuilder.build(); } final Optional userCapabilities = getUserCapabilities(node); 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 98c45544e2..ebfd283e76 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 @@ -26,6 +26,7 @@ import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory; import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceSalFacade; @@ -52,13 +53,24 @@ public class NetconfTopologyImpl extends AbstractNetconfTopology private ListenerRegistration datastoreListenerRegistration = null; public NetconfTopologyImpl(final String topologyId, final NetconfClientDispatcher clientDispatcher, - final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, - final ThreadPool processingExecutor, - final SchemaRepositoryProvider schemaRepositoryProvider, - final DataBroker dataBroker, final DOMMountPointService mountPointService, - final AAAEncryptionService encryptionService) { + final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, + final ThreadPool processingExecutor, + final SchemaRepositoryProvider schemaRepositoryProvider, + final DataBroker dataBroker, final DOMMountPointService mountPointService, + final AAAEncryptionService encryptionService) { + this(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor, + schemaRepositoryProvider, dataBroker, mountPointService, encryptionService, null); + } + + public NetconfTopologyImpl(final String topologyId, final NetconfClientDispatcher clientDispatcher, + final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, + final ThreadPool processingExecutor, + final SchemaRepositoryProvider schemaRepositoryProvider, + final DataBroker dataBroker, final DOMMountPointService mountPointService, + final AAAEncryptionService encryptionService, + final DeviceActionFactory deviceActionFactory) { super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor, - schemaRepositoryProvider, dataBroker, mountPointService, encryptionService); + schemaRepositoryProvider, dataBroker, mountPointService, encryptionService, deviceActionFactory); } @Override diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/DeviceActionFactory.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/DeviceActionFactory.java new file mode 100644 index 0000000000..e8df4d4dc8 --- /dev/null +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/DeviceActionFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Pantheon Technologies s.r.o. 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.api; + +import org.opendaylight.controller.md.sal.dom.api.DOMActionService; +import org.opendaylight.netconf.api.NetconfMessage; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public interface DeviceActionFactory { + + /** + * Allows user to create DOMActionService for specific device. + * + * @param messageTransformer - message transformer (for action in this case) + * @param listener - allows specific service to send and receive messages to/from device + * @param schemaContext - schema context of device + * @return {@link DOMActionService} of specific device + */ + default DOMActionService createDeviceAction(MessageTransformer messageTransformer, + RemoteDeviceCommunicator listener, SchemaContext schemaContext) { + return null; + } +} + diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/MessageTransformer.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/MessageTransformer.java index 1d1acaa0ff..0cbc2fd556 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/MessageTransformer.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/MessageTransformer.java @@ -9,6 +9,8 @@ package org.opendaylight.netconf.sal.connect.api; import org.opendaylight.controller.md.sal.dom.api.DOMNotification; import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMActionResult; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.SchemaPath; @@ -20,4 +22,27 @@ public interface MessageTransformer { DOMRpcResult toRpcResult(M message, SchemaPath rpc); + /** + * Parse action into message for request. + * + * @param action - action schema path + * @param domDataTreeIdentifier - identifier of action + * @param payload - input of action + * @return message + */ + default M toActionRequest(SchemaPath action, DOMDataTreeIdentifier domDataTreeIdentifier, NormalizedNode payload) { + throw new UnsupportedOperationException(); + } + + /** + * Parse result of invoking action into DOM result. + * + * @param action - action schema path + * @param message - message to parsing + * @return {@link DOMActionResult} + */ + default DOMActionResult toActionResult(SchemaPath action, M message) { + throw new UnsupportedOperationException(); + } } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java index 699c39945b..5db885e77e 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java @@ -7,14 +7,37 @@ */ package org.opendaylight.netconf.sal.connect.api; +import org.opendaylight.controller.md.sal.dom.api.DOMActionService; import org.opendaylight.controller.md.sal.dom.api.DOMNotification; import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; import org.opendaylight.yangtools.yang.model.api.SchemaContext; public interface RemoteDeviceHandler extends AutoCloseable { - void onDeviceConnected(SchemaContext remoteSchemaContext, - PREF netconfSessionPreferences, DOMRpcService deviceRpc); + /** + * When device connected, init new mount point with specific schema context and DOM services. + * + * @param remoteSchemaContext - schema context of connected device + * @param netconfSessionPreferences - session of device + * @param deviceRpc - {@link DOMRpcService} of device + */ + default void onDeviceConnected(SchemaContext remoteSchemaContext, PREF netconfSessionPreferences, + DOMRpcService deviceRpc) { + // DO NOTHING + } + + /** + * When device connected, init new mount point with specific schema context and DOM services. + * + * @param remoteSchemaContext - schema context of connected device + * @param netconfSessionPreferences - session of device + * @param deviceRpc - {@link DOMRpcService} of device + * @param deviceAction - {@link DOMActionService} of device + */ + default void onDeviceConnected(SchemaContext remoteSchemaContext, PREF netconfSessionPreferences, + DOMRpcService deviceRpc, DOMActionService deviceAction) { + // DO NOTHING + } void onDeviceDisconnected(); diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java index fc100b3db8..993b1230bd 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java @@ -36,6 +36,7 @@ import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; import org.opendaylight.netconf.api.NetconfMessage; +import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory; import org.opendaylight.netconf.sal.connect.api.MessageTransformer; import org.opendaylight.netconf.sal.connect.api.NetconfDeviceSchemas; import org.opendaylight.netconf.sal.connect.api.NetconfDeviceSchemasResolver; @@ -81,17 +82,20 @@ public class NetconfDevice private static final Logger LOG = LoggerFactory.getLogger(NetconfDevice.class); protected final RemoteDeviceId id; - private final boolean reconnectOnSchemasChange; - protected final SchemaContextFactory schemaContextFactory; - private final RemoteDeviceHandler salFacade; - private final ListeningExecutorService processingExecutor; protected final SchemaSourceRegistry schemaRegistry; protected final SchemaRepository schemaRepository; - private final NetconfDeviceSchemasResolver stateSchemasResolver; - private final NotificationHandler notificationHandler; + protected final List> sourceRegistrations = new ArrayList<>(); + + private final RemoteDeviceHandler salFacade; + private final ListeningExecutorService processingExecutor; + private final DeviceActionFactory deviceActionFactory; + private final NetconfDeviceSchemasResolver stateSchemasResolver; + private final NotificationHandler notificationHandler; + private final boolean reconnectOnSchemasChange; + @GuardedBy("this") private boolean connected = false; @@ -115,8 +119,16 @@ public class NetconfDevice public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final ExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange) { + this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null); + } + + public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, + final RemoteDeviceHandler salFacade, + final ExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange, + final DeviceActionFactory deviceActionFactory) { this.id = id; this.reconnectOnSchemasChange = reconnectOnSchemasChange; + this.deviceActionFactory = deviceActionFactory; this.schemaRegistry = schemaResourcesDTO.getSchemaRegistry(); this.schemaRepository = schemaResourcesDTO.getSchemaRepository(); this.schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory(); @@ -215,19 +227,22 @@ public class NetconfDevice private synchronized void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, - final DOMRpcService deviceRpc) { + final DOMRpcService deviceRpc, + final RemoteDeviceCommunicator listener) { //NetconfDevice.SchemaSetup can complete after NetconfDeviceCommunicator was closed. In that case do nothing, //since salFacade.onDeviceDisconnected was already called. if (connected) { final BaseSchema baseSchema = remoteSessionCapabilities.isNotificationsSupported() ? BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS : BaseSchema.BASE_NETCONF_CTX; - messageTransformer = new NetconfMessageTransformer(result, true, baseSchema); + this.messageTransformer = new NetconfMessageTransformer(result, true, baseSchema); - updateTransformer(messageTransformer); + updateTransformer(this.messageTransformer); // salFacade.onDeviceConnected has to be called before the notification handler is initialized - salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc); - notificationHandler.onRemoteSchemaUp(messageTransformer); + this.salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc, + this.deviceActionFactory == null ? null : this.deviceActionFactory.createDeviceAction( + this.messageTransformer, listener, result)); + this.notificationHandler.onRemoteSchemaUp(this.messageTransformer); LOG.info("{}: Netconf connector initialized successfully", id); } else { @@ -478,6 +493,8 @@ public class NetconfDevice /** * Build schema context, in case of success or final failure notify device. + * + * @param requiredSources required sources */ @SuppressWarnings("checkstyle:IllegalCatch") private void setUpSchema(Collection requiredSources) { @@ -501,7 +518,8 @@ public class NetconfDevice remoteSessionCapabilities.getNonModuleBasedCapsOrigin().get(entry)).build()) .collect(Collectors.toList())); - handleSalInitializationSuccess(result, remoteSessionCapabilities, getDeviceSpecificRpc(result)); + handleSalInitializationSuccess(result, remoteSessionCapabilities, getDeviceSpecificRpc(result), + listener); return; } catch (final ExecutionException e) { // schemaBuilderFuture.checkedGet() throws only SchemaResolutionException diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java index f5521e61f4..199f0c09f7 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java @@ -10,6 +10,7 @@ package org.opendaylight.netconf.sal.connect.netconf; import com.google.common.base.Preconditions; import java.util.concurrent.ExecutorService; +import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory; import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId; @@ -21,6 +22,7 @@ public class NetconfDeviceBuilder { private RemoteDeviceId id; private RemoteDeviceHandler salFacade; private ExecutorService globalProcessingExecutor; + private DeviceActionFactory deviceActionFactory; public NetconfDeviceBuilder() { } @@ -50,15 +52,21 @@ public class NetconfDeviceBuilder { return this; } + public NetconfDeviceBuilder setDeviceActionFactory(DeviceActionFactory deviceActionFactory) { + this.deviceActionFactory = deviceActionFactory; + return this; + } + public NetconfDevice build() { validation(); - return new NetconfDevice(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange); + return new NetconfDevice(this.schemaResourcesDTO, this.id, this.salFacade, this.globalProcessingExecutor, + this.reconnectOnSchemasChange, this.deviceActionFactory); } private void validation() { - Preconditions.checkNotNull(id, "RemoteDeviceId is not initialized"); - Preconditions.checkNotNull(salFacade, "RemoteDeviceHandler is not initialized"); - Preconditions.checkNotNull(globalProcessingExecutor, "ExecutorService is not initialized"); - Preconditions.checkNotNull(schemaResourcesDTO, "SchemaResourceDTO is not initialized"); + Preconditions.checkNotNull(this.id, "RemoteDeviceId is not initialized"); + Preconditions.checkNotNull(this.salFacade, "RemoteDeviceHandler is not initialized"); + Preconditions.checkNotNull(this.globalProcessingExecutor, "ExecutorService is not initialized"); + Preconditions.checkNotNull(this.schemaResourcesDTO, "SchemaResourceDTO is not initialized"); } } 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 5d42d3de4d..41e282ff88 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 @@ -11,6 +11,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import java.util.List; import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.dom.api.DOMActionService; import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService; import org.opendaylight.controller.md.sal.dom.api.DOMNotification; @@ -32,7 +33,7 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice private final List salRegistrations = Lists.newArrayList(); public NetconfDeviceSalFacade(final RemoteDeviceId id, final DOMMountPointService mountPointService, - final DataBroker dataBroker) { + final DataBroker dataBroker) { this.id = id; this.salProvider = new NetconfDeviceSalProvider(id, mountPointService, dataBroker); } @@ -51,7 +52,7 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice @Override public synchronized void onDeviceConnected(final SchemaContext schemaContext, final NetconfSessionPreferences netconfSessionPreferences, - final DOMRpcService deviceRpc) { + final DOMRpcService deviceRpc, DOMActionService deviceAction) { final DOMDataBroker domBroker = new NetconfDeviceDataBroker(id, schemaContext, deviceRpc, netconfSessionPreferences); @@ -59,7 +60,7 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice final NetconfDeviceNotificationService notificationService = new NetconfDeviceNotificationService(); salProvider.getMountInstance() - .onTopologyDeviceConnected(schemaContext, domBroker, deviceRpc, notificationService); + .onTopologyDeviceConnected(schemaContext, domBroker, deviceRpc, notificationService, deviceAction); salProvider.getTopologyDatastoreAdapter() .updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities()); } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java index b24eb89c99..b215f882d2 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java @@ -13,6 +13,7 @@ import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction; import org.opendaylight.controller.md.sal.common.api.data.TransactionChain; import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener; +import org.opendaylight.controller.md.sal.dom.api.DOMActionService; import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint; import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService; @@ -53,20 +54,19 @@ public class NetconfDeviceSalProvider implements AutoCloseable { } }; - public NetconfDeviceSalProvider(final RemoteDeviceId deviceId, final DOMMountPointService mountService, - final DataBroker dataBroker) { - this.id = deviceId; - mountInstance = new MountInstance(mountService, id); - this.dataBroker = dataBroker; - txChain = Preconditions.checkNotNull(dataBroker).createTransactionChain(transactionChainListener); - - topologyDatastoreAdapter = new NetconfDeviceTopologyAdapter(id, txChain); + public NetconfDeviceSalProvider(final RemoteDeviceId deviceId, final DOMMountPointService mountService) { + this(deviceId, mountService, null); } - public NetconfDeviceSalProvider(final RemoteDeviceId deviceId, final DOMMountPointService mountService) { + public NetconfDeviceSalProvider(final RemoteDeviceId deviceId, final DOMMountPointService mountService, + final DataBroker dataBroker) { this.id = deviceId; mountInstance = new MountInstance(mountService, id); - this.dataBroker = null; + this.dataBroker = dataBroker; + if (dataBroker != null) { + txChain = Preconditions.checkNotNull(dataBroker).createTransactionChain(transactionChainListener); + topologyDatastoreAdapter = new NetconfDeviceTopologyAdapter(id, txChain); + } } public MountInstance getMountInstance() { @@ -106,8 +106,8 @@ public class NetconfDeviceSalProvider implements AutoCloseable { private final DOMMountPointService mountService; private final RemoteDeviceId id; - private NetconfDeviceNotificationService notificationService; + private NetconfDeviceNotificationService notificationService; private ObjectRegistration topologyRegistration; MountInstance(final DOMMountPointService mountService, final RemoteDeviceId id) { @@ -115,9 +115,15 @@ public class NetconfDeviceSalProvider implements AutoCloseable { this.id = Preconditions.checkNotNull(id); } - public synchronized void onTopologyDeviceConnected(final SchemaContext initialCtx, + public void onTopologyDeviceConnected(final SchemaContext initialCtx, final DOMDataBroker broker, final DOMRpcService rpc, final NetconfDeviceNotificationService newNotificationService) { + onTopologyDeviceConnected(initialCtx, broker, rpc, newNotificationService, null); + } + + public synchronized void onTopologyDeviceConnected(final SchemaContext initialCtx, + final DOMDataBroker broker, final DOMRpcService rpc, + final NetconfDeviceNotificationService newNotificationService, DOMActionService deviceAction) { Preconditions.checkNotNull(mountService, "Closed"); Preconditions.checkState(topologyRegistration == null, "Already initialized"); @@ -128,11 +134,13 @@ public class NetconfDeviceSalProvider implements AutoCloseable { mountBuilder.addService(DOMDataBroker.class, broker); mountBuilder.addService(DOMRpcService.class, rpc); mountBuilder.addService(DOMNotificationService.class, newNotificationService); + if (deviceAction != null) { + mountBuilder.addService(DOMActionService.class, deviceAction); + } this.notificationService = newNotificationService; topologyRegistration = mountBuilder.register(); LOG.debug("{}: TOPOLOGY Mountpoint exposed into MD-SAL {}", id, topologyRegistration); - } @SuppressWarnings("checkstyle:IllegalCatch") diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java index 8b68b22b2d..2bd1ce4b4a 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java @@ -11,7 +11,10 @@ import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTr 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.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; @@ -21,6 +24,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Map; +import java.util.Set; import javax.annotation.Nonnull; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLStreamException; @@ -30,6 +34,9 @@ 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.controller.md.sal.dom.spi.DefaultDOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMActionResult; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult; import org.opendaylight.netconf.api.NetconfMessage; import org.opendaylight.netconf.api.xml.MissingNameSpaceException; import org.opendaylight.netconf.api.xml.XmlElement; @@ -38,6 +45,7 @@ import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransform import org.opendaylight.netconf.sal.connect.util.MessageCounter; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.RpcError; 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.NormalizedNode; @@ -46,8 +54,13 @@ import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; +import org.opendaylight.yangtools.yang.model.api.ActionDefinition; +import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; +import org.opendaylight.yangtools.yang.model.api.OperationDefinition; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; @@ -67,8 +80,8 @@ public class NetconfMessageTransformer implements MessageTransformer mappedRpcs; private final Multimap mappedNotifications; - private final boolean strictParsing; + private final Set actions; public NetconfMessageTransformer(final SchemaContext schemaContext, final boolean strictParsing) { this(schemaContext, strictParsing, BaseSchema.BASE_NETCONF_CTX); @@ -78,13 +91,39 @@ public class NetconfMessageTransformer implements MessageTransformer node.getQName().withoutRevision()); this.baseSchema = baseSchema; this.strictParsing = strictParsing; } + @VisibleForTesting + Set getActions() { + Builder builder = ImmutableSet.builder(); + for (DataSchemaNode dataSchemaNode : schemaContext.getChildNodes()) { + if (dataSchemaNode instanceof ActionNodeContainer) { + findAction(dataSchemaNode, builder); + } + } + return builder.build(); + } + + private void findAction(DataSchemaNode dataSchemaNode, Builder builder) { + if (dataSchemaNode instanceof ActionNodeContainer) { + final ActionNodeContainer containerSchemaNode = (ActionNodeContainer) dataSchemaNode; + for (ActionDefinition actionDefinition : containerSchemaNode.getActions()) { + builder.add(actionDefinition); + } + } + if (dataSchemaNode instanceof DataNodeContainer) { + for (DataSchemaNode innerDataSchemaNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) { + findAction(innerDataSchemaNode, builder); + } + } + } + @Override public synchronized DOMNotification toNotification(final NetconfMessage message) { final Map.Entry stripped = NetconfMessageTransformUtil.stripNotification(message); @@ -150,9 +189,9 @@ public class NetconfMessageTransformer implements MessageTransformer payload) { + ActionDefinition actionDefinition = null; + SchemaPath schemaPath = action; + for (ActionDefinition actionDef : actions) { + if (actionDef.getPath().getLastComponent().equals(action.getLastComponent())) { + actionDefinition = actionDef; + schemaPath = actionDef.getPath(); + } + } + Preconditions.checkNotNull(actionDefinition, "Action does not exist: %s", action.getLastComponent()); + + if (actionDefinition.getInput().getChildNodes().isEmpty()) { + return new NetconfMessage(NetconfMessageTransformUtil.prepareDomResultForActionRequest( + domDataTreeIdentifier, action, counter, actionDefinition.getQName().getLocalName()) + .getNode().getOwnerDocument()); + } + + Preconditions.checkNotNull(payload, "Transforming an action with input: %s, payload cannot be null", + action.getLastComponent()); + Preconditions.checkArgument(payload instanceof ContainerNode, + "Transforming an rpc with input: %s, payload has to be a container, but was: %s", + action.getLastComponent(), payload); + // Set the path to the input of rpc for the node stream writer + action = action.createChild(QName.create(action.getLastComponent(), "input").intern()); + final DOMResult result = NetconfMessageTransformUtil.prepareDomResultForActionRequest( + domDataTreeIdentifier, action, counter, actionDefinition.getQName().getLocalName()); + + try { + NetconfMessageTransformUtil.writeNormalizedRpc((ContainerNode) payload, result, + schemaPath.createChild(QName.create(action.getLastComponent(), "input").intern()), schemaContext); + } catch (final XMLStreamException | IOException | IllegalStateException e) { + throw new IllegalStateException("Unable to serialize " + action, e); + } + + final Document node = result.getNode().getOwnerDocument(); + + return new NetconfMessage(node); + } + private static boolean isBaseOrNotificationRpc(final QName rpc) { return rpc.getNamespace().equals(NETCONF_URI) || rpc.getNamespace().equals(IETF_NETCONF_NOTIFICATIONS.getNamespace()) @@ -221,29 +301,48 @@ public class NetconfMessageTransformer implements MessageTransformeremptyList()); + } + + private NormalizedNode parseResult(final NetconfMessage message, + final OperationDefinition operationDefinition) { + if (operationDefinition.getOutput().getChildNodes().isEmpty()) { + Preconditions.checkArgument(XmlElement.fromDomDocument( + message.getDocument()).getOnlyChildElementWithSameNamespaceOptionally("ok").isPresent(), + "Unexpected content in response of rpc: %s, %s", operationDefinition.getQName(), message); + return null; + } else { + final Element element = message.getDocument().getDocumentElement(); + try { + final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); + final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder); + final XmlParserStream xmlParser = XmlParserStream.create(writer, schemaContext, + operationDefinition.getOutput(), strictParsing); + xmlParser.traverse(new DOMSource(element)); + return resultHolder.getResult(); + } catch (XMLStreamException | URISyntaxException | IOException | ParserConfigurationException + | SAXException e) { + throw new IllegalArgumentException(String.format("Failed to parse RPC response %s", element), e); + } + } + } + static class NetconfDeviceNotification implements DOMNotification, DOMEvent { private final ContainerNode content; private final SchemaPath schemaPath; diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/util/NetconfMessageTransformUtil.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/util/NetconfMessageTransformUtil.java index b7f9dfb964..7f5e31d140 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/util/NetconfMessageTransformUtil.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/util/NetconfMessageTransformUtil.java @@ -18,6 +18,7 @@ import java.util.AbstractMap; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -25,6 +26,7 @@ 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.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.netconf.api.DocumentedException; import org.opendaylight.netconf.api.FailedNetconfMessage; import org.opendaylight.netconf.api.NetconfDocumentedException; @@ -47,6 +49,8 @@ import org.opendaylight.yangtools.yang.common.RpcResultBuilder; import org.opendaylight.yangtools.yang.data.api.ModifyAction; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; @@ -128,6 +132,9 @@ public final class NetconfMessageTransformUtil { public static final QName NETCONF_FILTER_QNAME = QName.create(NETCONF_QNAME, "filter").intern(); public static final QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get").intern(); public static final QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc").intern(); + public static final QName YANG_QNAME = null; + public static final URI NETCONF_ACTION_NAMESPACE = URI.create("urn:ietf:params:xml:ns:yang:1"); + public static final String NETCONF_ACTION = "action"; public static final URI NETCONF_ROLLBACK_ON_ERROR_URI = URI .create("urn:ietf:params:netconf:capability:rollback-on-error:1.0"); @@ -384,6 +391,51 @@ public final class NetconfMessageTransformUtil { return new DOMResult(elementNS); } + public static DOMResult prepareDomResultForActionRequest(DOMDataTreeIdentifier domDataTreeIdentifier, + final SchemaPath actionSchemaPath, final MessageCounter counter, String action) { + 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 actionNS = document.createElementNS(NETCONF_ACTION_NAMESPACE.toString(), NETCONF_ACTION); + + final Element actionData = prepareActionData(actionNS, + domDataTreeIdentifier.getRootIdentifier().getPathArguments().iterator(), document); + + Element specificActionElement = document.createElement(action); + actionData.appendChild(specificActionElement); + rpcNS.appendChild(actionNS); + document.appendChild(rpcNS); + return new DOMResult(specificActionElement); + } + + private static Element prepareActionData(Element actionNS, Iterator iterator, Document document) { + if (iterator.hasNext()) { + PathArgument next = iterator.next(); + final QName actualNS = next.getNodeType(); + + final Element actualElement = document.createElementNS(actualNS.getNamespace().toString(), + actualNS.getLocalName()); + if (next instanceof NodeWithValue) { + actualElement.setNodeValue(((NodeWithValue) next).getValue().toString()); + } else if (next instanceof NodeIdentifierWithPredicates) { + for (Entry entry : ((NodeIdentifierWithPredicates) next).getKeyValues().entrySet()) { + final Element entryElement = document.createElementNS(entry.getKey().getNamespace().toString(), + entry.getKey().getLocalName()); + entryElement.setTextContent(entry.getValue().toString()); + entryElement.setNodeValue(entry.getValue().toString()); + actualElement.appendChild(entryElement); + } + } + actionNS.appendChild(actualElement); + return prepareActionData(actualElement, iterator, document); + } else { + return actionNS; + } + } + @SuppressWarnings("checkstyle:IllegalCatch") public static void writeNormalizedRpc(final ContainerNode normalized, final DOMResult result, final SchemaPath schemaPath, diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceTest.java index 3618bf67d3..0d3ca2dab4 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceTest.java @@ -39,6 +39,7 @@ import java.util.concurrent.Executors; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.opendaylight.controller.md.sal.dom.api.DOMActionService; 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.api.DOMRpcService; @@ -160,7 +161,8 @@ public class NetconfDeviceTest { device.onRemoteSessionUp(sessionCaps, listener); Mockito.verify(facade, Mockito.timeout(5000)).onDeviceConnected( - any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class)); + any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class), + any(DOMActionService.class)); Mockito.verify(schemaFactory, times(2)).createSchemaContext(anyCollectionOf(SourceIdentifier.class)); } @@ -247,7 +249,8 @@ public class NetconfDeviceTest { device.onRemoteSessionUp(sessionCaps, listener); Mockito.verify(facade, Mockito.timeout(5000)).onDeviceConnected( - any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class)); + any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class), + any(DOMActionService.class)); Mockito.verify(schemaFactory, times(1)).createSchemaContext(anyCollectionOf(SourceIdentifier.class)); } @@ -325,7 +328,8 @@ public class NetconfDeviceTest { verify(schemaContextProviderFactory, timeout(5000)).createSchemaContext(any(Collection.class)); verify(facade, timeout(5000)).onDeviceConnected( - any(SchemaContext.class), any(NetconfSessionPreferences.class), any(DOMRpcService.class)); + any(SchemaContext.class), any(NetconfSessionPreferences.class), any(DOMRpcService.class), + any(DOMActionService.class)); device.onRemoteSessionDown(); verify(facade, timeout(5000)).onDeviceDisconnected(); @@ -334,7 +338,8 @@ public class NetconfDeviceTest { verify(schemaContextProviderFactory, timeout(5000).times(2)).createSchemaContext(any(Collection.class)); verify(facade, timeout(5000).times(2)).onDeviceConnected( - any(SchemaContext.class), any(NetconfSessionPreferences.class), any(DOMRpcService.class)); + any(SchemaContext.class), any(NetconfSessionPreferences.class), any(DOMRpcService.class), + any(DOMActionService.class)); } @Test @@ -364,7 +369,7 @@ public class NetconfDeviceTest { //complete schema setup schemaFuture.set(getSchema()); //facade.onDeviceDisconnected() was called, so facade.onDeviceConnected() shouldn't be called anymore - verify(facade, after(1000).never()).onDeviceConnected(any(), any(), any()); + verify(facade, after(1000).never()).onDeviceConnected(any(), any(), any(), any(DOMActionService.class)); } @Test @@ -397,7 +402,8 @@ public class NetconfDeviceTest { final ArgumentCaptor argument = ArgumentCaptor.forClass(NetconfSessionPreferences.class); verify(facade, timeout(5000)) - .onDeviceConnected(any(SchemaContext.class), argument.capture(), any(DOMRpcService.class)); + .onDeviceConnected(any(SchemaContext.class), argument.capture(), any(DOMRpcService.class), + any(DOMActionService.class)); final NetconfDeviceCapabilities netconfDeviceCaps = argument.getValue().getNetconfDeviceCapabilities(); netconfDeviceCaps.getResolvedCapabilities() @@ -421,7 +427,8 @@ public class NetconfDeviceTest { final RemoteDeviceHandler remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class); doNothing().when(remoteDeviceHandler).onDeviceConnected( - any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class)); + any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class), + any(DOMActionService.class)); doNothing().when(remoteDeviceHandler).onDeviceDisconnected(); doNothing().when(remoteDeviceHandler).onNotification(any(DOMNotification.class)); return remoteDeviceHandler; diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java index 05678f14ff..c96a6741d1 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java @@ -23,6 +23,7 @@ import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.opendaylight.controller.md.sal.dom.api.DOMActionService; import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; import org.opendaylight.controller.md.sal.dom.api.DOMNotification; import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; @@ -92,10 +93,11 @@ public class NetconfDeviceSalFacadeTest { NetconfSessionPreferences.fromStrings(getCapabilities()); final DOMRpcService deviceRpc = mock(DOMRpcService.class); - deviceFacade.onDeviceConnected(schemaContext, netconfSessionPreferences, deviceRpc); + deviceFacade.onDeviceConnected(schemaContext, netconfSessionPreferences, deviceRpc, null); verify(mountInstance, times(1)).onTopologyDeviceConnected(eq(schemaContext), - any(DOMDataBroker.class), eq(deviceRpc), any(NetconfDeviceNotificationService.class)); + any(DOMDataBroker.class), eq(deviceRpc), any(NetconfDeviceNotificationService.class), + any(DOMActionService.class)); verify(netconfDeviceTopologyAdapter, times(1)).updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities()); } diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java index a884a887f2..96a59a18bf 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java @@ -35,10 +35,14 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.xml.transform.dom.DOMSource; +import org.apache.xerces.dom.TextImpl; import org.custommonkey.xmlunit.Diff; import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier; import org.custommonkey.xmlunit.XMLUnit; @@ -47,6 +51,8 @@ import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; import org.opendaylight.mdsal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.mdsal.dom.api.DOMActionResult; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.netconf.api.NetconfMessage; import org.opendaylight.netconf.api.xml.XmlUtil; import org.opendaylight.netconf.sal.connect.netconf.schema.NetconfRemoteSchemaYangSourceProvider; @@ -59,17 +65,32 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.mon import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.Schema; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.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.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.ActionDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; +import org.w3c.dom.Node; import org.xml.sax.SAXException; public class NetconfMessageTransformerTest { + private static final String REVISION_EXAMPLE_SERVER_FARM = "2018-08-07"; + private static final String URN_EXAMPLE_SERVER_FARM = "urn:example:server-farm"; + + private NetconfMessageTransformer actionNetconfMessageTransformer; private NetconfMessageTransformer netconfMessageTransformer; private SchemaContext schema; @@ -81,7 +102,7 @@ public class NetconfMessageTransformerTest { schema = getSchema(true); netconfMessageTransformer = getTransformer(schema); - + actionNetconfMessageTransformer = getActionMessageTransformer(); } @Test @@ -131,7 +152,7 @@ public class NetconfMessageTransformerTest { } @Test - public void tesGetSchemaRequest() throws Exception { + public void testGetSchemaRequest() throws Exception { final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(toPath(GET_SCHEMA_QNAME), NetconfRemoteSchemaYangSourceProvider.createGetSchemaRequest("module", Optional.of("2012-12-12"))); assertSimilarXml(netconfMessage, "\n" @@ -144,7 +165,7 @@ public class NetconfMessageTransformerTest { } @Test - public void tesGetSchemaResponse() throws Exception { + public void testGetSchemaResponse() throws Exception { final NetconfMessageTransformer transformer = getTransformer(getSchema(true)); final NetconfMessage response = new NetconfMessage(XmlUtil.readXmlToDocument( " qnames = new HashSet<>(Arrays.asList(reset, start, open)); + Set actions = actionNetconfMessageTransformer.getActions(); + assertTrue(!actions.isEmpty()); + for (ActionDefinition actionDefinition : actions) { + QName qname = actionDefinition.getQName(); + assertTrue(qnames.contains(qname)); + qnames.remove(qname); + } + } + + @Test + public void toActionRequestListTopLevelTest() { + QName qname = QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "server"); + QName nameQname = QName.create(qname, "name"); + QName actionResetQName = QName.create(qname, "reset"); + + Set nodeIdentifiers = + Collections.singleton(new NodeIdentifierWithPredicates(qname, nameQname, "test")); + DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers); + + ContainerNode data = initInputAction(QName.create(qname, "reset-at"), "now"); + + NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest( + SchemaPath.create(true, actionResetQName), domDataTreeIdentifier, data); + + Node childAction = checkBasePartOfActionRequest(actionRequest); + + Node childServer = childAction.getFirstChild(); + checkNode(childServer, "server", "server", qname.getNamespace().toString()); + + Node childName = childServer.getFirstChild(); + checkNode(childName, "name", "name", qname.getNamespace().toString()); + + TextImpl childTest = (TextImpl) childName.getFirstChild(); + assertEquals(childTest.getData(), "test"); + + checkAction(actionResetQName, childName.getNextSibling(), "reset-at", "reset-at", "now"); + } + + @Test + public void toActionRequestContainerTopLevelTest() { + QName qname = QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "device"); + QName actionStartQName = QName.create(qname, "start"); + + Set nodeIdentifiers = Collections.singleton(NodeIdentifier.create(qname)); + DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers); + + NormalizedNode payload = initInputAction(QName.create(qname, "start-at"), "now"); + NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest( + SchemaPath.create(true, actionStartQName), domDataTreeIdentifier, payload); + + Node childAction = checkBasePartOfActionRequest(actionRequest); + + Node childDevice = childAction.getFirstChild(); + checkNode(childDevice, "device", "device", qname.getNamespace().toString()); + + checkAction(actionStartQName, childDevice.getFirstChild(), "start-at", "start-at", "now"); + } + + @Test + public void toActionRequestContainerInContainerTest() { + QName boxOutQName = QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "box-out"); + QName boxInQName = QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "box-in"); + QName actionOpenQName = QName.create(boxOutQName, "open"); + + Set nodeIdentifiers = new HashSet<>(); + nodeIdentifiers.add(NodeIdentifier.create(boxOutQName)); + nodeIdentifiers.add(NodeIdentifier.create(boxInQName)); + + DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers); + + NormalizedNode payload = initInputAction(QName.create(boxOutQName, "start-at"), "now"); + NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest( + SchemaPath.create(true, actionOpenQName), domDataTreeIdentifier, payload); + + Node childAction = checkBasePartOfActionRequest(actionRequest); + + Node childBoxOut = childAction.getFirstChild(); + checkNode(childBoxOut, "box-out", "box-out", boxOutQName.getNamespace().toString()); + + Node childBoxIn = childBoxOut.getFirstChild(); + checkNode(childBoxIn, "box-in", "box-in", boxOutQName.getNamespace().toString()); + + Node action = childBoxIn.getFirstChild(); + checkNode(action, null, actionOpenQName.getLocalName(), null); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void toActionResultTest() throws Exception { + QName qname = QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "reset"); + + NetconfMessage message = new NetconfMessage(XmlUtil.readXmlToDocument( + "" + + "" + + "now" + + "" + + "")); + DOMActionResult actionResult = actionNetconfMessageTransformer.toActionResult( + SchemaPath.create(true, qname), message); + assertNotNull(actionResult); + ContainerNode containerNode = actionResult.getOutput().get(); + assertNotNull(containerNode); + LeafNode leaf = (LeafNode) containerNode.getValue().iterator().next(); + assertEquals("now", leaf.getValue()); + } + + private void checkAction(QName actionQname, Node action , String inputLocalName, String inputNodeName, + String inputValue) { + checkNode(action, null, actionQname.getLocalName(), null); + + Node childResetAt = action.getFirstChild(); + checkNode(childResetAt, inputLocalName, inputNodeName, actionQname.getNamespace().toString()); + + TextImpl firstChild = (TextImpl) childResetAt.getFirstChild(); + assertEquals(firstChild.getData(), inputValue); + } + + private Node checkBasePartOfActionRequest(NetconfMessage actionRequest) { + Node baseRpc = actionRequest.getDocument().getFirstChild(); + checkNode(baseRpc, "rpc", "rpc", NetconfMessageTransformUtil.NETCONF_QNAME.getNamespace().toString()); + assertTrue(baseRpc.getLocalName().equals("rpc")); + assertTrue(baseRpc.getNodeName().equals("rpc")); + + Node messageId = baseRpc.getAttributes().getNamedItem("message-id"); + assertNotNull(messageId); + assertTrue(messageId.getNodeValue().contains("m-")); + + Node childAction = baseRpc.getFirstChild(); + checkNode(childAction, "action", "action", NetconfMessageTransformUtil.NETCONF_ACTION_NAMESPACE.toString()); + return childAction; + } + + private DOMDataTreeIdentifier prepareDataTreeId(Set nodeIdentifiers) { + YangInstanceIdentifier yangInstanceIdentifier = + YangInstanceIdentifier.builder().append(nodeIdentifiers).build(); + DOMDataTreeIdentifier domDataTreeIdentifier = + new DOMDataTreeIdentifier(org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION, + yangInstanceIdentifier); + return domDataTreeIdentifier; + } + + private ContainerNode initInputAction(QName qname, String value) { + ImmutableLeafNodeBuilder immutableLeafNodeBuilder = new ImmutableLeafNodeBuilder<>(); + DataContainerChild build = immutableLeafNodeBuilder.withNodeIdentifier( + NodeIdentifier.create(qname)).withValue(value).build(); + ContainerNode data = ImmutableContainerNodeBuilder.create().withNodeIdentifier(NodeIdentifier.create( + QName.create(qname, "input"))).withChild(build).build(); + return data; + } + + private void checkNode(Node childServer, String expectedLocalName, String expectedNodeName, + String expectedNamespace) { + assertNotNull(childServer); + assertEquals(childServer.getLocalName(), expectedLocalName); + assertEquals(childServer.getNodeName(), expectedNodeName); + assertEquals(childServer.getNamespaceURI(), expectedNamespace); + } + + private SchemaContext getActionSchema() { + return YangParserTestUtils.parseYangResource("/schemas/example-server-farm.yang"); + } + + private NetconfMessageTransformer getActionMessageTransformer() { + return new NetconfMessageTransformer(getActionSchema(), true); + } } diff --git a/netconf/sal-netconf-connector/src/test/resources/schemas/example-server-farm.yang b/netconf/sal-netconf-connector/src/test/resources/schemas/example-server-farm.yang new file mode 100644 index 0000000000..0cc0fef7be --- /dev/null +++ b/netconf/sal-netconf-connector/src/test/resources/schemas/example-server-farm.yang @@ -0,0 +1,51 @@ +module example-server-farm { + yang-version 1.1; + namespace "urn:example:server-farm"; + prefix "sfarm"; + revision 2018-08-07; + + list server { + key name; + leaf name { + type string; + } + action reset { + input { + leaf reset-at { + type string; + mandatory true; + } + } + output { + leaf reset-finished-at { + type string; + mandatory true; + } + } + } + } + + container device { + action start { + input { + leaf start-at { + type string; + mandatory true; + } + } + output { + leaf start-finished-at { + type string; + mandatory true; + } + } + } + } + + container box-out { + container box-in { + action open { + } + } + } +} \ No newline at end of file -- 2.36.6