Prepare netconf to support YANG 1.1 actions 45/74745/17
authorJakub Tóth <jakub.toth@pantheon.tech>
Mon, 6 Aug 2018 14:11:29 +0000 (16:11 +0200)
committerJakubToth <jakub.toth@pantheon.tech>
Wed, 15 Aug 2018 17:08:55 +0000 (17:08 +0000)
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 <jakub.toth@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
18 files changed:
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/BaseCallHomeTopology.java
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcher.java
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/DeviceActionFactory.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/MessageTransformer.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/util/NetconfMessageTransformUtil.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceTest.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java
netconf/sal-netconf-connector/src/test/resources/schemas/example-server-farm.yang [new file with mode: 0644]

index d4f9e132b8ed0429de94999b077db6dde98fab7c..2d174ea0ba3c115c58acb9137af3babc6203dd64 100644 (file)
@@ -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);
     }
 }
index d661a9e5a3f379d744fa09964a0014f82dbac75e..95d4b7be7bbdaa9792b28303c8f356a45334bedd 100644 (file)
@@ -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
index 41d121eac324074f11cc1159e43e5802021cdbe5..d918def01b13e2d4bc00a263cdde2c842593c797 100644 (file)
@@ -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
index 342d3e9cdaaa2a5661463dee7071aadc025e203a..818a3895dc0fe2944d3c35a1cfaf47f7393c6922 100644 (file)
@@ -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<UserPreferences> userCapabilities = getUserCapabilities(node);
index 98c45544e2d2f070bf570ba4c89b42186acae548..ebfd283e762da1bc3de2ef3aaf83a0e4b3e3a07b 100644 (file)
@@ -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<NetconfTopologyImpl> 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 (file)
index 0000000..e8df4d4
--- /dev/null
@@ -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<NetconfMessage> messageTransformer,
+            RemoteDeviceCommunicator<NetconfMessage> listener, SchemaContext schemaContext) {
+        return null;
+    }
+}
+
index 1d1acaa0ff7764fc34cf176bb011c03864312f40..0cbc2fd5569b35258948fedd15fccda8ee3af6b5 100644 (file)
@@ -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<M> {
 
     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();
+    }
 }
index 699c39945bd2788eb3151af022b3d940d736bba2..5db885e77e6665b7af713cdac53ac9383bbe6b0c 100644 (file)
@@ -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<PREF> 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();
 
index fc100b3db80a2ec232db0ad3e15ceb8fdb0f7e42..993b1230bda2f51e48598ec8de68cc7dcb2a2fcc 100644 (file)
@@ -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<NetconfSessionPreferences> salFacade;
-    private final ListeningExecutorService processingExecutor;
     protected final SchemaSourceRegistry schemaRegistry;
     protected final SchemaRepository schemaRepository;
-    private final NetconfDeviceSchemasResolver stateSchemasResolver;
-    private final NotificationHandler notificationHandler;
+
     protected final List<SchemaSourceRegistration<? extends SchemaSourceRepresentation>> sourceRegistrations =
             new ArrayList<>();
+
+    private final RemoteDeviceHandler<NetconfSessionPreferences> 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<NetconfSessionPreferences> salFacade,
                          final ExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange) {
+        this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null);
+    }
+
+    public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id,
+            final RemoteDeviceHandler<NetconfSessionPreferences> 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<NetconfMessage> 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<SourceIdentifier> 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
index f5521e61f4acca2f876d1fa5aba457de6ebb6212..199f0c09f768ec5b952ef2d6d987e02ae7adb450 100644 (file)
@@ -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<NetconfSessionPreferences> 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");
     }
 }
index 5d42d3de4df17b8830ea89820285312720a72147..41e282ff88e39c8768b820df49c9b7783902d3df 100644 (file)
@@ -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<AutoCloseable> 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());
     }
index b24eb89c9927bcc3da6d7dcd1b8bcc4405de9e75..b215f882d23bf212e811f0fd56495df679c21458 100644 (file)
@@ -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<DOMMountPoint> 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")
index 8b68b22b2d4f3f117592d5ff52622753a5572f26..2bd1ce4b4afb594d18b0158d92bd997596b890f3 100644 (file)
@@ -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<NetconfMess
     private final MessageCounter counter;
     private final Map<QName, RpcDefinition> mappedRpcs;
     private final Multimap<QName, NotificationDefinition> mappedNotifications;
-
     private final boolean strictParsing;
+    private final Set<ActionDefinition> 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<NetconfMess
                                      final BaseSchema baseSchema) {
         this.counter = new MessageCounter();
         this.schemaContext = schemaContext;
-        mappedRpcs = Maps.uniqueIndex(schemaContext.getOperations(), SchemaNode::getQName);
-        mappedNotifications = Multimaps.index(schemaContext.getNotifications(),
+        this.mappedRpcs = Maps.uniqueIndex(schemaContext.getOperations(), SchemaNode::getQName);
+        this.actions = getActions();
+        this.mappedNotifications = Multimaps.index(schemaContext.getNotifications(),
             node -> node.getQName().withoutRevision());
         this.baseSchema = baseSchema;
         this.strictParsing = strictParsing;
     }
 
+    @VisibleForTesting
+    Set<ActionDefinition> getActions() {
+        Builder<ActionDefinition> builder = ImmutableSet.builder();
+        for (DataSchemaNode dataSchemaNode : schemaContext.getChildNodes()) {
+            if (dataSchemaNode instanceof ActionNodeContainer) {
+                findAction(dataSchemaNode, builder);
+            }
+        }
+        return builder.build();
+    }
+
+    private void findAction(DataSchemaNode dataSchemaNode, Builder<ActionDefinition> 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<Date, XmlElement> stripped = NetconfMessageTransformUtil.stripNotification(message);
@@ -150,9 +189,9 @@ public class NetconfMessageTransformer implements MessageTransformer<NetconfMess
         }
 
         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 node stream writer
         rpc = rpc.createChild(QName.create(rpcQName, "input").intern());
         final DOMResult result = NetconfMessageTransformUtil.prepareDomResultForRpcRequest(rpcQName, counter);
@@ -172,6 +211,47 @@ public class NetconfMessageTransformer implements MessageTransformer<NetconfMess
         return new NetconfMessage(node);
     }
 
+    @Override
+    public NetconfMessage toActionRequest(SchemaPath action, DOMDataTreeIdentifier domDataTreeIdentifier,
+            final NormalizedNode<?, ?> 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 MessageTransformer<NetconfMess
                     "Unable to parse response of %s, the rpc is unknown", rpcQName);
 
             // In case no input for rpc is defined, we can simply construct the payload here
-            if (rpcDefinition.getOutput().getChildNodes().isEmpty()) {
-                Preconditions.checkArgument(XmlElement.fromDomDocument(
-                    message.getDocument()).getOnlyChildElementWithSameNamespaceOptionally("ok").isPresent(),
-                    "Unexpected content in response of rpc: %s, %s", rpcDefinition.getQName(), message);
-                normalizedNode = 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,
-                            rpcDefinition.getOutput(), strictParsing);
-                    xmlParser.traverse(new DOMSource(element));
-                    normalizedNode = resultHolder.getResult();
-                } catch (XMLStreamException | URISyntaxException | IOException | ParserConfigurationException
-                        | SAXException e) {
-                    throw new IllegalArgumentException(String.format("Failed to parse RPC response %s", element), e);
-                }
-            }
+            normalizedNode = parseResult(message, rpcDefinition);
         }
         return new DefaultDOMRpcResult(normalizedNode);
     }
 
+    @Override
+    public DOMActionResult toActionResult(SchemaPath action, NetconfMessage message) {
+        ActionDefinition actionDefinition = null;
+        for (ActionDefinition actionDef : actions) {
+            if (actionDef.getPath().getLastComponent().equals(action.getLastComponent())) {
+                actionDefinition = actionDef;
+            }
+        }
+        Preconditions.checkNotNull(actionDefinition, "Action does not exist: %s", action);
+        ContainerNode normalizedNode = (ContainerNode) parseResult(message, actionDefinition);
+
+        return new SimpleDOMActionResult(normalizedNode, Collections.<RpcError>emptyList());
+    }
+
+    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;
index b7f9dfb96476643e1fce4f48d144d5235333a1c2..7f5e31d14000f191cc8288ba11e3fd5a6d9f7fbd 100644 (file)
@@ -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<PathArgument> 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<QName, Object> 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,
index 3618bf67d341a3f6d5519aaf840d689c1d0cf26e..0d3ca2dab49aeb8b7e6cfc0186d62e328749746f 100644 (file)
@@ -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<NetconfSessionPreferences> 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<NetconfSessionPreferences> 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;
index 05678f14ffa9ff3c3533b72998a6eaf0f7c54aea..c96a6741d16a079839597e90cbff0e88b780c7fd 100644 (file)
@@ -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());
     }
index a884a887f209f8f244dd8daad51bd39f691a6f90..96a59a18bfd1097783aa2f4378edbc41058ce77c 100644 (file)
@@ -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, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\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(
                 "<rpc-reply message-id=\"101\"\n"
@@ -343,4 +364,176 @@ public class NetconfMessageTransformerTest {
                         .netconf.monitoring.rev101004.$YangModuleInfoImpl.getInstance()));
         return moduleInfoBackedContext.tryToCreateSchemaContext().get();
     }
+
+    @Test
+    public void getActionsTest() {
+        QName reset = QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "reset");
+        QName start = QName.create(reset, "start");
+        QName open = QName.create(start, "open");
+        Set<QName> qnames = new HashSet<>(Arrays.asList(reset, start, open));
+        Set<ActionDefinition> 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<PathArgument> 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<PathArgument> 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<PathArgument> 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(
+                "<rpc-reply message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"
+                + "<reset-finished-at xmlns=\"urn:example:server-farm\">"
+                + "now"
+                + "</reset-finished-at>"
+                + "</rpc-reply>"));
+        DOMActionResult actionResult = actionNetconfMessageTransformer.toActionResult(
+                SchemaPath.create(true, qname), message);
+        assertNotNull(actionResult);
+        ContainerNode containerNode = actionResult.getOutput().get();
+        assertNotNull(containerNode);
+        LeafNode<String> 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<PathArgument> 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<String> immutableLeafNodeBuilder = new ImmutableLeafNodeBuilder<>();
+        DataContainerChild<NodeIdentifier, String> 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 (file)
index 0000000..0cc0fef
--- /dev/null
@@ -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