Improve monitoring access to revision-less schemas
[netconf.git] / plugins / netconf-client-mdsal / src / test / java / org / opendaylight / netconf / client / mdsal / impl / NetconfMessageTransformerTest.java
1 /*
2  * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.client.mdsal.impl;
9
10 import static org.hamcrest.CoreMatchers.instanceOf;
11 import static org.hamcrest.MatcherAssert.assertThat;
12 import static org.junit.Assert.assertEquals;
13 import static org.junit.Assert.assertNotNull;
14 import static org.junit.Assert.assertNull;
15 import static org.junit.Assert.assertTrue;
16 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_CANDIDATE_NODEID;
17 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_NODEID;
18 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_NODEID;
19 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_NODEID;
20 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_RUNNING_NODEID;
21 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.createEditConfigStructure;
22 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.toFilterStructure;
23
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.Set;
30 import org.custommonkey.xmlunit.Diff;
31 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier;
32 import org.custommonkey.xmlunit.XMLUnit;
33 import org.junit.AfterClass;
34 import org.junit.Before;
35 import org.junit.BeforeClass;
36 import org.junit.Test;
37 import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
38 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
39 import org.opendaylight.netconf.api.CapabilityURN;
40 import org.opendaylight.netconf.api.messages.NetconfMessage;
41 import org.opendaylight.netconf.api.xml.XmlUtil;
42 import org.opendaylight.netconf.client.mdsal.AbstractBaseSchemasTest;
43 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
44 import org.opendaylight.netconf.common.mdsal.NormalizedDataUtil;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.Commit;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.DiscardChanges;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.EditConfig;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.Get;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.GetConfig;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.IetfNetconfData;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.Lock;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.get.config.output.Data;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscription;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInput;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.GetSchema;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Datastores;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas;
60 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Sessions;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Statistics;
62 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.Datastore;
63 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.datastore.Locks;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.datastore.locks.lock.type.partial.lock.PartialLock;
65 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.Schema;
66 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.sessions.Session;
67 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfConfigChange;
68 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.netconf.config.change.Edit;
69 import org.opendaylight.yangtools.yang.common.QName;
70 import org.opendaylight.yangtools.yang.common.Revision;
71 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
72 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
73 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
74 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
75 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
76 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
77 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
78 import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
79 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
80 import org.opendaylight.yangtools.yang.data.api.schema.MountPointContext;
81 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
82 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
83 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
84 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
85 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
86 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
87 import org.w3c.dom.Element;
88 import org.w3c.dom.Node;
89 import org.xml.sax.SAXException;
90
91 public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest {
92
93     private static final String REVISION_EXAMPLE_SERVER_FARM = "2018-08-07";
94     private static final String URN_EXAMPLE_SERVER_FARM = "urn:example:server-farm";
95
96     private static final String REVISION_EXAMPLE_SERVER_FARM_2 = "2019-05-20";
97     private static final String URN_EXAMPLE_SERVER_FARM_2 = "urn:example:server-farm-2";
98
99     private static final String URN_EXAMPLE_CONFLICT = "urn:example:conflict";
100
101     private static final String URN_EXAMPLE_AUGMENTED_ACTION = "urn:example:augmented-action";
102
103     private static final String URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS = "urn:example:rpcs-actions-outputs";
104
105     private static final QName SERVER_QNAME =
106             QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "server");
107     private static final QName RESET_QNAME = QName.create(SERVER_QNAME, "reset");
108     private static final Absolute RESET_SERVER_PATH = Absolute.of(SERVER_QNAME, RESET_QNAME);
109     private static final QName APPLICATIONS_QNAME = QName.create(URN_EXAMPLE_SERVER_FARM_2,
110             REVISION_EXAMPLE_SERVER_FARM_2, "applications");
111     private static final QName APPLICATION_QNAME = QName.create(APPLICATIONS_QNAME, "application");
112     private static final QName KILL_QNAME = QName.create(APPLICATION_QNAME, "kill");
113     private static final Absolute KILL_SERVER_APP_PATH =
114             Absolute.of(SERVER_QNAME, APPLICATIONS_QNAME, APPLICATION_QNAME, KILL_QNAME);
115
116     private static final QName DEVICE_QNAME =
117             QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "device");
118     private static final QName START_QNAME = QName.create(DEVICE_QNAME, "start");
119     private static final Absolute START_DEVICE_PATH = Absolute.of(DEVICE_QNAME, START_QNAME);
120     private static final QName INTERFACE_QNAME = QName.create(DEVICE_QNAME, "interface");
121     private static final QName ENABLE_QNAME = QName.create(INTERFACE_QNAME, "enable");
122     private static final Absolute ENABLE_INTERFACE_PATH = Absolute.of(DEVICE_QNAME, INTERFACE_QNAME, ENABLE_QNAME);
123
124     private static final QName DISABLE_QNAME = QName.create(URN_EXAMPLE_AUGMENTED_ACTION, "disable");
125     private static final Absolute DISABLE_INTERFACE_PATH = Absolute.of(DEVICE_QNAME, INTERFACE_QNAME, DISABLE_QNAME);
126
127     private static final QName CHECK_WITH_OUTPUT_QNAME =
128             QName.create(URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS, "check-with-output");
129     private static final Absolute CHECK_WITH_OUTPUT_INTERFACE_PATH =
130             Absolute.of(DEVICE_QNAME, INTERFACE_QNAME, CHECK_WITH_OUTPUT_QNAME);
131     private static final QName CHECK_WITHOUT_OUTPUT_QNAME =
132             QName.create(URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS, "check-without-output");
133     private static final Absolute CHECK_WITHOUT_OUTPUT_INTERFACE_PATH =
134             Absolute.of(DEVICE_QNAME, INTERFACE_QNAME, CHECK_WITHOUT_OUTPUT_QNAME);
135     private static final QName RPC_WITH_OUTPUT_QNAME =
136             QName.create(URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS, "rpc-with-output");
137     private static final QName RPC_WITHOUT_OUTPUT_QNAME =
138             QName.create(URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS, "rpc-without-output");
139
140     private static final QName BOX_OUT_QNAME =
141             QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "box-out");
142     private static final QName BOX_IN_QNAME = QName.create(BOX_OUT_QNAME, "box-in");
143     private static final QName OPEN_QNAME = QName.create(BOX_IN_QNAME, "open");
144     private static final Absolute OPEN_BOXES_PATH = Absolute.of(BOX_OUT_QNAME, BOX_IN_QNAME, OPEN_QNAME);
145
146     private static final QName FOO_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "foo");
147     private static final QName BAR_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "bar");
148     private static final QName XYZZY_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "xyzzy");
149     private static final Absolute XYZZY_FOO_PATH = Absolute.of(FOO_QNAME, XYZZY_QNAME);
150     private static final Absolute XYZZY_BAR_PATH = Absolute.of(BAR_QNAME, XYZZY_QNAME);
151
152     private static final QName CONFLICT_CHOICE_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "conflict-choice");
153     private static final QName CHOICE_CONT_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "choice-cont");
154     private static final QName CHOICE_ACTION_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "choice-action");
155     private static final Absolute CHOICE_ACTION_PATH =
156             Absolute.of(CONFLICT_CHOICE_QNAME, CHOICE_CONT_QNAME, CHOICE_CONT_QNAME, CHOICE_ACTION_QNAME);
157
158     private static EffectiveModelContext PARTIAL_SCHEMA;
159     private static EffectiveModelContext SCHEMA;
160     private static EffectiveModelContext ACTION_SCHEMA;
161
162     private NetconfMessageTransformer actionNetconfMessageTransformer;
163     private NetconfMessageTransformer netconfMessageTransformer;
164
165     @BeforeClass
166     public static void beforeClass() {
167         PARTIAL_SCHEMA = BindingRuntimeHelpers.createEffectiveModel(NetconfState.class);
168         SCHEMA = BindingRuntimeHelpers.createEffectiveModel(IetfNetconfData.class, NetconfState.class,
169             NetconfConfigChange.class);
170         ACTION_SCHEMA = YangParserTestUtils.parseYangResources(NetconfMessageTransformerTest.class,
171             "/schemas/example-server-farm.yang","/schemas/example-server-farm-2.yang",
172             "/schemas/conflicting-actions.yang", "/schemas/augmented-action.yang",
173             "/schemas/rpcs-actions-outputs.yang");
174     }
175
176     @AfterClass
177     public static void afterClass() {
178         PARTIAL_SCHEMA = null;
179         SCHEMA = null;
180         ACTION_SCHEMA = null;
181     }
182
183     @Before
184     public void setUp() throws Exception {
185         XMLUnit.setIgnoreWhitespace(true);
186         XMLUnit.setIgnoreAttributeOrder(true);
187         XMLUnit.setIgnoreComments(true);
188
189         netconfMessageTransformer = getTransformer(SCHEMA);
190         actionNetconfMessageTransformer = new NetconfMessageTransformer(MountPointContext.of(ACTION_SCHEMA), true,
191             BASE_SCHEMAS.baseSchemaForCapabilities(NetconfSessionPreferences.fromStrings(Set.of())));
192     }
193
194     @Test
195     public void testLockRequestBaseSchemaNotPresent() throws Exception {
196         final var transformer = getTransformer(PARTIAL_SCHEMA);
197         final var netconfMessage = transformer.toRpcRequest(Lock.QNAME,
198             NetconfBaseOps.getLockContent(NETCONF_CANDIDATE_NODEID));
199         assertEquals("""
200             <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="m-0">
201                 <lock>
202                     <target>
203                         <candidate/>
204                     </target>
205                 </lock>
206             </rpc>
207             """, XmlUtil.toString(netconfMessage.getDocument()));
208     }
209
210     @Test
211     public void testCreateSubscriberNotificationSchemaNotPresent() throws Exception {
212         final var transformer = new NetconfMessageTransformer(MountPointContext.of(SCHEMA), true,
213             BASE_SCHEMAS.baseSchemaForCapabilities(NetconfSessionPreferences.fromStrings(
214                 Set.of(CapabilityURN.NOTIFICATION))));
215         var netconfMessage = transformer.toRpcRequest(CreateSubscription.QNAME, ImmutableNodes.newContainerBuilder()
216             .withNodeIdentifier(new NodeIdentifier(CreateSubscriptionInput.QNAME))
217             .build());
218         assertEquals("""
219             <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="m-0">
220                 <create-subscription xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"/>
221             </rpc>
222             """, XmlUtil.toString(netconfMessage.getDocument()));
223     }
224
225     @Test
226     public void testLockSchemaRequest() throws Exception {
227         final var transformer = getTransformer(PARTIAL_SCHEMA);
228         final String result = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><ok/></rpc-reply>";
229
230         transformer.toRpcResult(
231             RpcResultBuilder.success(new NetconfMessage(XmlUtil.readXmlToDocument(result))).build(),
232             Lock.QNAME);
233     }
234
235     @Test
236     public void testRpcEmptyBodyWithOutputDefinedSchemaResult() throws Exception {
237         final String result = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><ok/></rpc-reply>";
238
239         final var domRpcResult = actionNetconfMessageTransformer.toRpcResult(
240             RpcResultBuilder.success(new NetconfMessage(XmlUtil.readXmlToDocument(result))).build(),
241             RPC_WITH_OUTPUT_QNAME);
242         assertNotNull(domRpcResult);
243     }
244
245     @Test
246     public void testRpcEmptyBodyWithoutOutputDefinedSchemaResult() throws Exception {
247         final String result = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><ok/></rpc-reply>";
248
249         final var domRpcResult = actionNetconfMessageTransformer.toRpcResult(
250             RpcResultBuilder.success(new NetconfMessage(XmlUtil.readXmlToDocument(result))).build(),
251             RPC_WITHOUT_OUTPUT_QNAME);
252         assertNotNull(domRpcResult);
253     }
254
255     @Test
256     public void testDiscardChangesRequest() throws Exception {
257         final var netconfMessage = netconfMessageTransformer.toRpcRequest(DiscardChanges.QNAME, null);
258         assertEquals("""
259             <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="m-0">
260                 <discard-changes/>
261             </rpc>
262             """, XmlUtil.toString(netconfMessage.getDocument()));
263     }
264
265     @Test
266     public void testGetSchemaRequest() throws Exception {
267         final var netconfMessage = netconfMessageTransformer.toRpcRequest(GetSchema.QNAME,
268                 MonitoringSchemaSourceProvider.createGetSchemaRequest("module", Revision.of("2012-12-12")));
269         assertSimilarXml(netconfMessage, """
270             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
271               <get-schema xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
272                 <format>yang</format>
273                 <identifier>module</identifier>
274                 <version>2012-12-12</version>
275               </get-schema>
276             </rpc>""");
277     }
278
279     @Test
280     public void testGetSchemaResponse() throws Exception {
281         final var transformer = getTransformer(SCHEMA);
282         final var response = new NetconfMessage(XmlUtil.readXmlToDocument("""
283             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
284               <data xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
285                 <schema xmlns="http://www.w3.org/2001/XMLSchema">Random YANG SCHEMA</schema>
286               </data>
287             </rpc-reply>"""));
288         final var compositeNodeRpcResult = transformer.toRpcResult(RpcResultBuilder.success(response).build(),
289             GetSchema.QNAME);
290         assertTrue(compositeNodeRpcResult.errors().isEmpty());
291         assertNotNull(compositeNodeRpcResult.value());
292         final var schemaContent = ((DOMSourceAnyxmlNode) compositeNodeRpcResult.value()
293                 .body().iterator().next()).body();
294         assertEquals("""
295             <data xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
296                 <schema xmlns="http://www.w3.org/2001/XMLSchema">Random YANG SCHEMA</schema>
297             </data>
298             """, XmlUtil.toString((Element) schemaContent.getNode()));
299     }
300
301     @Test
302     public void testGetConfigResponse() throws Exception {
303         final var response = new NetconfMessage(XmlUtil.readXmlToDocument("""
304             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
305               <data>
306                  <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
307                    <schemas>
308                      <schema>
309                        <identifier>module</identifier>
310                        <version>2012-12-12</version>
311                        <format xmlns:x="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">x:yang</format>
312                      </schema>
313                    </schemas>
314                  </netconf-state>
315                </data>
316              </rpc-reply>"""));
317
318         final var transformer = getTransformer(SCHEMA);
319         final var compositeNodeRpcResult = transformer.toRpcResult(RpcResultBuilder.success(response).build(),
320             GetConfig.QNAME);
321         assertTrue(compositeNodeRpcResult.errors().isEmpty());
322         assertNotNull(compositeNodeRpcResult.value());
323
324         final var values = MonitoringSchemaSourceProvider.createGetSchemaRequest("module", Revision.of("2012-12-12"))
325             .body();
326
327         final var keys = new HashMap<QName, Object>();
328         for (var value : values) {
329             keys.put(value.name().getNodeType(), value.body());
330         }
331
332         final var schemaNode = ImmutableNodes.newMapEntryBuilder()
333             .withNodeIdentifier(NodeIdentifierWithPredicates.of(Schema.QNAME, keys))
334             .withValue(values)
335             .build();
336
337         final var data = (DOMSourceAnyxmlNode) compositeNodeRpcResult.value()
338             .getChildByArg(new NodeIdentifier(Data.QNAME));
339
340         final var nodeResult = NormalizedDataUtil.transformDOMSourceToNormalizedNode(SCHEMA, data.body());
341         final var result = (ContainerNode) nodeResult.getResult().data();
342         final var state = (ContainerNode) result.getChildByArg(new NodeIdentifier(NetconfState.QNAME));
343         final var schemas = (ContainerNode) state.getChildByArg(new NodeIdentifier(Schemas.QNAME));
344         final var schemaParent = (MapNode) schemas.getChildByArg(new NodeIdentifier(Schema.QNAME));
345         assertEquals(1, schemaParent.body().size());
346
347         assertEquals(schemaNode, schemaParent.body().iterator().next());
348     }
349
350     @Test
351     public void testGetConfigLeafRequest() throws Exception {
352         final var filter = toFilterStructure(YangInstanceIdentifier.of(
353             new NodeIdentifier(NetconfState.QNAME),
354             new NodeIdentifier(Schemas.QNAME),
355             new NodeIdentifier(Schema.QNAME),
356             NodeIdentifierWithPredicates.of(Schema.QNAME),
357             new NodeIdentifier(QName.create(Schemas.QNAME, "version"))), SCHEMA);
358
359         final var source = NetconfBaseOps.getSourceNode(NETCONF_RUNNING_NODEID);
360
361         final var netconfMessage = netconfMessageTransformer.toRpcRequest(GetConfig.QNAME,
362                 NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, source, filter));
363
364         assertSimilarXml(netconfMessage, """
365             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
366               <get-config>
367                 <filter type="subtree">
368                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
369                     <schemas>
370                       <schema>
371                         <version/>
372                       </schema>
373                     </schemas>
374                   </netconf-state>
375                 </filter>
376                 <source>
377                   <running/>
378                 </source>
379               </get-config>
380             </rpc>""");
381     }
382
383     @Test
384     public void testGetConfigRequest() throws Exception {
385         final var filter = toFilterStructure(YangInstanceIdentifier.of(NetconfState.QNAME, Schemas.QNAME), SCHEMA);
386
387         final var source = NetconfBaseOps.getSourceNode(NETCONF_RUNNING_NODEID);
388
389         final var netconfMessage = netconfMessageTransformer.toRpcRequest(GetConfig.QNAME,
390                 NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, source, filter));
391
392         assertSimilarXml(netconfMessage, """
393             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
394               <get-config>
395                 <filter type="subtree">
396                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
397                     <schemas/>
398                   </netconf-state>
399                 </filter>
400                 <source>
401                  <running/>
402                 </source>
403               </get-config>
404             </rpc>""");
405     }
406
407     @Test
408     public void testEditConfigRequest() throws Exception {
409         final var values = MonitoringSchemaSourceProvider.createGetSchemaRequest("module", Revision.of("2012-12-12"))
410             .body();
411
412         final var keys = new HashMap<QName, Object>();
413         for (var value : values) {
414             keys.put(value.name().getNodeType(), value.body());
415         }
416
417         final var schemaNode = ImmutableNodes.newMapEntryBuilder()
418             .withNodeIdentifier(NodeIdentifierWithPredicates.of(Schema.QNAME, keys))
419             .withValue(values)
420             .build();
421
422         final var id = YangInstanceIdentifier.builder()
423                 .node(NetconfState.QNAME).node(Schemas.QNAME).node(Schema.QNAME)
424                 .nodeWithKey(Schema.QNAME, keys).build();
425         final var editConfigStructure = createEditConfigStructure(BASE_SCHEMAS.baseSchemaForCapabilities(
426             NetconfSessionPreferences.fromStrings(Set.of(
427                 CapabilityURN.CANDIDATE,
428                 "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring"
429                     + "&revision=2010-10-04"))).modelContext(), id, Optional.empty(), Optional.ofNullable(schemaNode));
430
431         final var target = NetconfBaseOps.getTargetNode(NETCONF_CANDIDATE_NODEID);
432
433         final var wrap = NetconfMessageTransformUtil.wrap(NETCONF_EDIT_CONFIG_NODEID, editConfigStructure, target);
434         final var netconfMessage = netconfMessageTransformer.toRpcRequest(EditConfig.QNAME, wrap);
435
436         assertSimilarXml(netconfMessage, """
437             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
438               <edit-config>
439                 <target>
440                   <candidate/>
441                 </target>
442                 <config>
443                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
444                     <schemas>
445                       <schema>
446                         <identifier>module</identifier>
447                         <version>2012-12-12</version>
448                         <format>yang</format>
449                       </schema>
450                     </schemas>
451                   </netconf-state>
452                 </config>
453               </edit-config>
454             </rpc>""");
455     }
456
457     private static void assertSimilarXml(final NetconfMessage netconfMessage, final String xmlContent)
458             throws SAXException, IOException {
459         final Diff diff = XMLUnit.compareXML(netconfMessage.getDocument(), XmlUtil.readXmlToDocument(xmlContent));
460         diff.overrideElementQualifier(new ElementNameAndAttributeQualifier());
461         assertTrue(diff.toString(), diff.similar());
462     }
463
464     @Test
465     public void testGetRequest() throws Exception {
466         final var capability = QName.create(Capabilities.QNAME, "capability");
467         final var filter = toFilterStructure(YangInstanceIdentifier.of(
468             new NodeIdentifier(NetconfState.QNAME),
469             new NodeIdentifier(Capabilities.QNAME),
470             new NodeIdentifier(capability),
471             new NodeWithValue<>(capability, "a:b:c")), SCHEMA);
472
473         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
474                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filter));
475
476         assertSimilarXml(netconfMessage, """
477             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
478               <get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
479                 <filter type="subtree">
480                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
481                     <capabilities>
482                       <capability>a:b:c</capability>
483                     </capabilities>
484                   </netconf-state>
485                 </filter>
486               </get>
487             </rpc>""");
488     }
489
490     @Test
491     public void testGetLeafList() throws IOException, SAXException {
492         final var filter = toFilterStructure(YangInstanceIdentifier.of(
493             NetconfState.QNAME,
494             Capabilities.QNAME,
495             QName.create(Capabilities.QNAME, "capability")), SCHEMA);
496         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
497                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filter));
498
499         assertSimilarXml(netconfMessage, """
500             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
501               <get>
502                 <filter type="subtree">
503                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
504                     <capabilities>
505                       <capability/>
506                     </capabilities>
507                   </netconf-state>
508                 </filter>
509               </get>
510             </rpc>""");
511     }
512
513     @Test
514     public void testGetList() throws IOException, SAXException {
515         final var filter = toFilterStructure(YangInstanceIdentifier.of(
516             NetconfState.QNAME,
517             Datastores.QNAME,
518             QName.create(Datastores.QNAME, "datastore")), SCHEMA);
519         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
520                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filter));
521
522         assertSimilarXml(netconfMessage, """
523             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
524               <get>
525                 <filter type="subtree">
526                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
527                     <datastores>
528                       <datastore/>
529                     </datastores>
530                   </netconf-state>
531                 </filter>
532               </get>
533             </rpc>""");
534     }
535
536     private static NetconfMessageTransformer getTransformer(final EffectiveModelContext schema) {
537         return new NetconfMessageTransformer(MountPointContext.of(schema), true,
538             BASE_SCHEMAS.baseSchemaForCapabilities(NetconfSessionPreferences.fromStrings(Set.of())));
539     }
540
541     @Test
542     public void testCommitResponse() throws Exception {
543         final var compositeNodeRpcResult = netconfMessageTransformer.toRpcResult(
544             RpcResultBuilder.success(new NetconfMessage(XmlUtil.readXmlToDocument(
545                 "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><ok/></rpc-reply>"))).build(),
546             Commit.QNAME);
547         assertTrue(compositeNodeRpcResult.errors().isEmpty());
548         assertNull(compositeNodeRpcResult.value());
549     }
550
551     @Test
552     public void getActionsTest() {
553         final var schemaPaths = Set.of(RESET_SERVER_PATH, START_DEVICE_PATH, ENABLE_INTERFACE_PATH, OPEN_BOXES_PATH,
554             KILL_SERVER_APP_PATH, XYZZY_FOO_PATH, XYZZY_BAR_PATH, CHOICE_ACTION_PATH, DISABLE_INTERFACE_PATH,
555             CHECK_WITH_OUTPUT_INTERFACE_PATH, CHECK_WITHOUT_OUTPUT_INTERFACE_PATH);
556
557         var actions = NetconfMessageTransformer.getActions(ACTION_SCHEMA);
558         assertEquals(schemaPaths.size(), actions.size());
559
560         for (var path : schemaPaths) {
561             assertNotNull("Action for " + path + " not found", actions.get(path));
562         }
563     }
564
565     @Test
566     public void toActionRequestListTopLevelTest() {
567         QName nameQname = QName.create(SERVER_QNAME, "name");
568         List<PathArgument> nodeIdentifiers = new ArrayList<>();
569         nodeIdentifiers.add(new NodeIdentifier(SERVER_QNAME));
570         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(SERVER_QNAME, nameQname, "test"));
571         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
572
573         ContainerNode data = initInputAction(QName.create(SERVER_QNAME, "reset-at"), "now");
574
575         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
576                 RESET_SERVER_PATH, domDataTreeIdentifier, data);
577
578         Node childAction = checkBasePartOfActionRequest(actionRequest);
579
580         Node childServer = childAction.getFirstChild();
581         checkNode(childServer, "server", "server", URN_EXAMPLE_SERVER_FARM);
582
583         Node childName = childServer.getFirstChild();
584         checkNode(childName, "name", "name", URN_EXAMPLE_SERVER_FARM);
585
586         Node childTest = childName.getFirstChild();
587         assertEquals(childTest.getNodeValue(), "test");
588
589         checkAction(RESET_QNAME, childName.getNextSibling(), "reset-at", "reset-at", "now");
590     }
591
592     @Test
593     public void toActionRequestContainerTopLevelTest() {
594         List<PathArgument> nodeIdentifiers = List.of(NodeIdentifier.create(DEVICE_QNAME));
595         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
596
597         ContainerNode payload = initInputAction(QName.create(DEVICE_QNAME, "start-at"), "now");
598         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
599                 START_DEVICE_PATH, domDataTreeIdentifier, payload);
600
601         Node childAction = checkBasePartOfActionRequest(actionRequest);
602
603         Node childDevice = childAction.getFirstChild();
604         checkNode(childDevice, "device", "device", URN_EXAMPLE_SERVER_FARM);
605
606         checkAction(START_QNAME, childDevice.getFirstChild(), "start-at", "start-at", "now");
607     }
608
609     @Test
610     public void toActionRequestContainerInContainerTest() {
611         List<PathArgument> nodeIdentifiers = new ArrayList<>();
612         nodeIdentifiers.add(NodeIdentifier.create(BOX_OUT_QNAME));
613         nodeIdentifiers.add(NodeIdentifier.create(BOX_IN_QNAME));
614
615         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
616
617         ContainerNode payload = initInputAction(QName.create(BOX_OUT_QNAME, "start-at"), "now");
618         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
619                 OPEN_BOXES_PATH, domDataTreeIdentifier, payload);
620
621         Node childAction = checkBasePartOfActionRequest(actionRequest);
622
623         Node childBoxOut = childAction.getFirstChild();
624         checkNode(childBoxOut, "box-out", "box-out", URN_EXAMPLE_SERVER_FARM);
625
626         Node childBoxIn = childBoxOut.getFirstChild();
627         checkNode(childBoxIn, "box-in", "box-in", URN_EXAMPLE_SERVER_FARM);
628
629         Node action = childBoxIn.getFirstChild();
630         checkNode(action, OPEN_QNAME.getLocalName(), OPEN_QNAME.getLocalName(), OPEN_QNAME.getNamespace().toString());
631     }
632
633     @Test
634     public void toActionRequestListInContainerTest() {
635         QName nameQname = QName.create(INTERFACE_QNAME, "name");
636
637         List<PathArgument> nodeIdentifiers = new ArrayList<>();
638         nodeIdentifiers.add(NodeIdentifier.create(DEVICE_QNAME));
639         nodeIdentifiers.add(NodeIdentifier.create(INTERFACE_QNAME));
640         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(INTERFACE_QNAME, nameQname, "test"));
641
642         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
643
644         ContainerNode payload = initEmptyInputAction(INTERFACE_QNAME);
645         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
646                 ENABLE_INTERFACE_PATH, domDataTreeIdentifier, payload);
647
648         Node childAction = checkBasePartOfActionRequest(actionRequest);
649
650         Node childDevice = childAction.getFirstChild();
651         checkNode(childDevice, "device", "device", URN_EXAMPLE_SERVER_FARM);
652
653         Node childInterface = childDevice.getFirstChild();
654         checkNode(childInterface, "interface", "interface", URN_EXAMPLE_SERVER_FARM);
655
656         Node childName = childInterface.getFirstChild();
657         checkNode(childName, "name", "name", nameQname.getNamespace().toString());
658
659         Node childTest = childName.getFirstChild();
660         assertEquals(childTest.getNodeValue(), "test");
661
662         Node action = childInterface.getLastChild();
663         checkNode(action, ENABLE_QNAME.getLocalName(), ENABLE_QNAME.getLocalName(),
664                 ENABLE_QNAME.getNamespace().toString());
665     }
666
667     @Test
668     public void toActionRequestListInContainerAugmentedIntoListTest() {
669         QName serverNameQname = QName.create(SERVER_QNAME, "name");
670         QName applicationNameQname = QName.create(APPLICATION_QNAME, "name");
671
672         List<PathArgument> nodeIdentifiers = new ArrayList<>();
673         nodeIdentifiers.add(NodeIdentifier.create(SERVER_QNAME));
674         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(SERVER_QNAME, serverNameQname, "testServer"));
675         nodeIdentifiers.add(NodeIdentifier.create(APPLICATIONS_QNAME));
676         nodeIdentifiers.add(NodeIdentifier.create(APPLICATION_QNAME));
677         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(APPLICATION_QNAME,
678                 applicationNameQname, "testApplication"));
679
680         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
681
682         ContainerNode payload = initEmptyInputAction(APPLICATION_QNAME);
683         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
684                 KILL_SERVER_APP_PATH, domDataTreeIdentifier, payload);
685
686         Node childAction = checkBasePartOfActionRequest(actionRequest);
687
688         Node childServer = childAction.getFirstChild();
689         checkNode(childServer, "server", "server", URN_EXAMPLE_SERVER_FARM);
690
691         Node childServerName = childServer.getFirstChild();
692         checkNode(childServerName, "name", "name", URN_EXAMPLE_SERVER_FARM);
693
694         Node childServerNameTest = childServerName.getFirstChild();
695         assertEquals(childServerNameTest.getNodeValue(), "testServer");
696
697         Node childApplications = childServer.getLastChild();
698         checkNode(childApplications, "applications", "applications", URN_EXAMPLE_SERVER_FARM_2);
699
700         Node childApplication = childApplications.getFirstChild();
701         checkNode(childApplication, "application", "application", URN_EXAMPLE_SERVER_FARM_2);
702
703         Node childApplicationName = childApplication.getFirstChild();
704         checkNode(childApplicationName, "name", "name", URN_EXAMPLE_SERVER_FARM_2);
705
706         Node childApplicationNameTest = childApplicationName.getFirstChild();
707         assertEquals(childApplicationNameTest.getNodeValue(), "testApplication");
708
709         Node childKillAction = childApplication.getLastChild();
710         checkNode(childApplication, "application", "application", URN_EXAMPLE_SERVER_FARM_2);
711         checkNode(childKillAction, KILL_QNAME.getLocalName(), KILL_QNAME.getLocalName(),
712                 KILL_QNAME.getNamespace().toString());
713     }
714
715     @Test
716     public void toActionRequestConflictingInListTest() {
717         QName barInputQname = QName.create(BAR_QNAME, "bar");
718         QName barIdQname = QName.create(BAR_QNAME, "bar-id");
719         Byte barInput = 1;
720
721         List<PathArgument> nodeIdentifiers = new ArrayList<>();
722         nodeIdentifiers.add(NodeIdentifier.create(BAR_QNAME));
723         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(BAR_QNAME, barIdQname, "test"));
724
725         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
726
727         ContainerNode payload = ImmutableNodes.newContainerBuilder()
728             .withNodeIdentifier(NodeIdentifier.create(QName.create(barInputQname, "input")))
729             .withChild(ImmutableNodes.leafNode(barInputQname, barInput))
730             .build();
731
732         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
733                 XYZZY_BAR_PATH, domDataTreeIdentifier, payload);
734
735         Node childAction = checkBasePartOfActionRequest(actionRequest);
736
737         Node childBar = childAction.getFirstChild();
738         checkNode(childBar, "bar", "bar", URN_EXAMPLE_CONFLICT);
739
740         Node childBarId = childBar.getFirstChild();
741         checkNode(childBarId, "bar-id", "bar-id", URN_EXAMPLE_CONFLICT);
742
743         Node childTest = childBarId.getFirstChild();
744         assertEquals(childTest.getNodeValue(), "test");
745
746         Node action = childBar.getLastChild();
747         checkNode(action, XYZZY_QNAME.getLocalName(), XYZZY_QNAME.getLocalName(),
748                 XYZZY_QNAME.getNamespace().toString());
749     }
750
751     @Test
752     public void toActionRequestConflictingInContainerTest() {
753         QName fooInputQname = QName.create(FOO_QNAME, "foo");
754
755         List<PathArgument> nodeIdentifiers = new ArrayList<>();
756         nodeIdentifiers.add(NodeIdentifier.create(FOO_QNAME));
757         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
758         ContainerNode payload = initInputAction(fooInputQname, "test");
759
760         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
761                 XYZZY_FOO_PATH, domDataTreeIdentifier, payload);
762
763         Node childAction = checkBasePartOfActionRequest(actionRequest);
764
765         Node childBar = childAction.getFirstChild();
766         checkNode(childBar, "foo", "foo", URN_EXAMPLE_CONFLICT);
767
768         Node action = childBar.getLastChild();
769         checkNode(action, XYZZY_QNAME.getLocalName(), XYZZY_QNAME.getLocalName(),
770                 XYZZY_QNAME.getNamespace().toString());
771     }
772
773     @Test
774     public void toActionRequestChoiceTest() {
775         List<PathArgument> nodeIdentifiers = new ArrayList<>();
776         nodeIdentifiers.add(NodeIdentifier.create(CONFLICT_CHOICE_QNAME));
777         nodeIdentifiers.add(NodeIdentifier.create(CHOICE_CONT_QNAME));
778         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
779         NormalizedNode payload = initEmptyInputAction(CHOICE_ACTION_QNAME);
780
781         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
782                 CHOICE_ACTION_PATH, domDataTreeIdentifier, payload);
783
784         Node childAction = checkBasePartOfActionRequest(actionRequest);
785
786         Node childChoiceCont = childAction.getFirstChild();
787         checkNode(childChoiceCont, "choice-cont", "choice-cont", URN_EXAMPLE_CONFLICT);
788
789         Node action = childChoiceCont.getLastChild();
790         checkNode(action, CHOICE_ACTION_QNAME.getLocalName(), CHOICE_ACTION_QNAME.getLocalName(),
791                 CHOICE_ACTION_QNAME.getNamespace().toString());
792     }
793
794     @Test
795     public void toAugmentedActionRequestListInContainerTest() {
796         QName nameQname = QName.create(INTERFACE_QNAME, "name");
797
798         List<PathArgument> nodeIdentifiers = new ArrayList<>();
799         nodeIdentifiers.add(NodeIdentifier.create(DEVICE_QNAME));
800         nodeIdentifiers.add(NodeIdentifier.create(INTERFACE_QNAME));
801         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(INTERFACE_QNAME, nameQname, "test"));
802
803         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
804
805         NormalizedNode payload = initEmptyInputAction(INTERFACE_QNAME);
806         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
807                 DISABLE_INTERFACE_PATH, domDataTreeIdentifier, payload);
808
809         Node childAction = checkBasePartOfActionRequest(actionRequest);
810
811         Node childDevice = childAction.getFirstChild();
812         checkNode(childDevice, "device", "device", URN_EXAMPLE_SERVER_FARM);
813
814         Node childInterface = childDevice.getFirstChild();
815         checkNode(childInterface, "interface", "interface", URN_EXAMPLE_SERVER_FARM);
816
817         Node childName = childInterface.getFirstChild();
818         checkNode(childName, "name", "name", nameQname.getNamespace().toString());
819
820         Node childTest = childName.getFirstChild();
821         assertEquals(childTest.getNodeValue(), "test");
822
823         Node action = childInterface.getLastChild();
824         checkNode(action, DISABLE_QNAME.getLocalName(), DISABLE_QNAME.getLocalName(),
825                 DISABLE_QNAME.getNamespace().toString());
826     }
827
828     @Test
829     public void toActionResultTest() throws Exception {
830         var message = new NetconfMessage(XmlUtil.readXmlToDocument("""
831             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
832               <reset-finished-at xmlns="urn:example:server-farm">now</reset-finished-at>
833             </rpc-reply>"""));
834         final var actionResult = actionNetconfMessageTransformer.toActionResult(RESET_SERVER_PATH, message);
835         assertNotNull(actionResult);
836         final var containerNode = actionResult.getOutput().orElseThrow();
837         assertNotNull(containerNode);
838         assertEquals("now", containerNode.body().iterator().next().body());
839     }
840
841     @Test
842     public void toActionEmptyBodyWithOutputDefinedResultTest() throws Exception {
843         final var message = new NetconfMessage(XmlUtil.readXmlToDocument("""
844             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
845               <ok/>
846             </rpc-reply>"""));
847         final var actionResult =
848             actionNetconfMessageTransformer.toActionResult(CHECK_WITH_OUTPUT_INTERFACE_PATH, message);
849         assertNotNull(actionResult);
850         assertTrue(actionResult.getOutput().isEmpty());
851     }
852
853     @Test
854     public void toActionEmptyBodyWithoutOutputDefinedResultTest() throws Exception {
855         final var message = new NetconfMessage(XmlUtil.readXmlToDocument("""
856             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
857               <ok/>
858             </rpc-reply>"""));
859         final var actionResult =
860             actionNetconfMessageTransformer.toActionResult(CHECK_WITHOUT_OUTPUT_INTERFACE_PATH, message);
861         assertNotNull(actionResult);
862         assertTrue(actionResult.getOutput().isEmpty());
863     }
864
865     @Test
866     public void getTwoNonOverlappingFieldsTest() throws IOException, SAXException {
867         // preparation of the fields
868         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME);
869         final var netconfStartTimeField = YangInstanceIdentifier.of(Statistics.QNAME,
870                 QName.create(Statistics.QNAME, "netconf-start-time"));
871         final var datastoresField = YangInstanceIdentifier.of(Datastores.QNAME);
872
873         // building filter structure and NETCONF message
874         final var filterStructure = toFilterStructure(
875             List.of(FieldsFilter.of(parentYiid, List.of(netconfStartTimeField, datastoresField))), SCHEMA);
876         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
877                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
878
879         // testing
880         assertSimilarXml(netconfMessage, """
881             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
882               <get>
883                 <filter type="subtree">
884                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
885                     <statistics>
886                       <netconf-start-time/>
887                     </statistics>
888                     <datastores/>
889                   </netconf-state>
890                 </filter>
891               </get>
892             </rpc>""");
893     }
894
895     @Test
896     public void getOverlappingFieldsTest() throws IOException, SAXException {
897         // preparation of the fields
898         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME);
899         final var capabilitiesField = YangInstanceIdentifier.of(Capabilities.QNAME);
900         final var capabilityField = YangInstanceIdentifier.of(Capabilities.QNAME,
901                 QName.create(Capabilities.QNAME, "capability"));
902         final var datastoreField = YangInstanceIdentifier.of(Datastores.QNAME);
903         final var locksFields = YangInstanceIdentifier.of(
904             new NodeIdentifier(Datastores.QNAME),
905             new NodeIdentifier(Datastore.QNAME),
906             // Note: acts as 'select all'
907             NodeIdentifierWithPredicates.of(Datastore.QNAME),
908             new NodeIdentifier(Locks.QNAME));
909
910         // building filter structure and NETCONF message
911         final var filterStructure = toFilterStructure(
912                 List.of(FieldsFilter.of(parentYiid,
913                     List.of(capabilitiesField, capabilityField, datastoreField, locksFields))),
914                 SCHEMA);
915         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
916                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
917
918         // testing
919         assertSimilarXml(netconfMessage, """
920             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
921               <get>
922                 <filter type="subtree">
923                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
924                     <capabilities/>
925                     <datastores/>
926                   </netconf-state>
927                 </filter>
928               </get>
929             </rpc>""");
930     }
931
932     @Test
933     public void getOverlappingFieldsWithEmptyFieldTest() throws IOException, SAXException {
934         // preparation of the fields
935         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME);
936         final var capabilitiesField = YangInstanceIdentifier.of(Capabilities.QNAME);
937
938         // building filter structure and NETCONF message
939         final var filterStructure = toFilterStructure(
940                 List.of(FieldsFilter.of(parentYiid, List.of(capabilitiesField, YangInstanceIdentifier.of()))),
941                 SCHEMA);
942         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
943                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
944
945         // testing
946         assertSimilarXml(netconfMessage, """
947             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
948             <get>
949             <filter type="subtree">
950             <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"/>
951             </filter>
952             </get>
953             </rpc>""");
954     }
955
956     @Test
957     public void getSpecificFieldsUnderListTest() throws IOException, SAXException {
958         // preparation of the fields
959         final var parentYiid = YangInstanceIdentifier.of(
960             new NodeIdentifier(NetconfState.QNAME),
961             new NodeIdentifier(Schemas.QNAME),
962             new NodeIdentifier(Schema.QNAME),
963             NodeIdentifierWithPredicates.of(Schema.QNAME));
964         final var versionField = YangInstanceIdentifier.of(
965                 QName.create(Schema.QNAME, "version"));
966         final var identifierField = YangInstanceIdentifier.of(
967                 QName.create(Schema.QNAME, "namespace"));
968
969         // building filter structure and NETCONF message
970         final var filterStructure = toFilterStructure(
971             List.of(FieldsFilter.of(parentYiid, List.of(versionField, identifierField))), SCHEMA);
972         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
973                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
974
975         // testing
976         assertSimilarXml(netconfMessage, """
977             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
978               <get>
979                 <filter type="subtree">
980                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
981                     <schemas>
982                       <schema>
983                         <version/>
984                         <namespace/>
985                         <identifier/>
986                         <format/>
987                       </schema>
988                     </schemas>
989                   </netconf-state>
990                 </filter>
991               </get>
992             </rpc>""");
993     }
994
995     @Test
996     public void getSpecificFieldsUnderMultipleLists() throws IOException, SAXException {
997         // preparation of the fields
998         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME, Datastores.QNAME);
999         final var partialLockYiid = YangInstanceIdentifier.of(
1000             new NodeIdentifier(Datastore.QNAME),
1001             NodeIdentifierWithPredicates.of(Datastore.QNAME),
1002             new NodeIdentifier(Locks.QNAME),
1003             new NodeIdentifier(QName.create(Locks.QNAME, "lock-type")),
1004             new NodeIdentifier(PartialLock.QNAME),
1005             NodeIdentifierWithPredicates.of(PartialLock.QNAME));
1006         final var lockedTimeField = partialLockYiid.node(QName.create(Locks.QNAME, "locked-time"));
1007         final var lockedBySessionField = partialLockYiid.node(QName.create(Locks.QNAME, "locked-by-session"));
1008
1009         // building filter structure and NETCONF message
1010         final var filterStructure = toFilterStructure(
1011             List.of(FieldsFilter.of(parentYiid, List.of(lockedTimeField, lockedBySessionField))),
1012             SCHEMA);
1013         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
1014                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
1015
1016         // testing
1017         assertSimilarXml(netconfMessage, """
1018             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
1019               <get>
1020                 <filter type="subtree">
1021                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
1022                     <datastores>
1023                       <datastore>
1024                         <locks>
1025                           <partial-lock>
1026                             <locked-time/>
1027                             <locked-by-session/>
1028                             <lock-id/>
1029                           </partial-lock>
1030                         </locks>
1031                         <name/>
1032                       </datastore>
1033                     </datastores>
1034                   </netconf-state>
1035                 </filter>
1036               </get>
1037             </rpc>""");
1038     }
1039
1040     @Test
1041     public void getWholeListsUsingFieldsTest() throws IOException, SAXException {
1042         // preparation of the fields
1043         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME);
1044         final var datastoreListField = YangInstanceIdentifier.of(
1045             new NodeIdentifier(Datastores.QNAME),
1046             new NodeIdentifier(Datastore.QNAME),
1047             NodeIdentifierWithPredicates.of(Datastore.QNAME));
1048         final var sessionListField = YangInstanceIdentifier.of(
1049             new NodeIdentifier(Sessions.QNAME),
1050             new NodeIdentifier(Session.QNAME),
1051             NodeIdentifierWithPredicates.of(Session.QNAME));
1052
1053         // building filter structure and NETCONF message
1054         final var filterStructure = toFilterStructure(
1055                 List.of(FieldsFilter.of(parentYiid, List.of(datastoreListField, sessionListField))), SCHEMA);
1056         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
1057                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
1058
1059         // testing
1060         assertSimilarXml(netconfMessage, """
1061             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
1062               <get>
1063                 <filter type="subtree">
1064                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
1065                     <datastores>
1066                       <datastore/>
1067                     </datastores>
1068                     <sessions>
1069                       <session/>
1070                     </sessions>
1071                   </netconf-state>
1072                 </filter>
1073               </get>
1074             </rpc>""");
1075     }
1076
1077     @Test
1078     public void getSpecificListEntriesWithSpecificFieldsTest() throws IOException, SAXException {
1079         // preparation of the fields
1080         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME, Sessions.QNAME);
1081         final var sessionId = QName.create(Session.QNAME, "session-id");
1082         final var session1Field = YangInstanceIdentifier.of(
1083             new NodeIdentifier(Session.QNAME),
1084             NodeIdentifierWithPredicates.of(Session.QNAME, sessionId, 1));
1085         final var session2TransportField = YangInstanceIdentifier.of(
1086             new NodeIdentifier(Session.QNAME),
1087             NodeIdentifierWithPredicates.of(Session.QNAME, sessionId, 2),
1088             new NodeIdentifier(QName.create(Session.QNAME, "transport")));
1089
1090         // building filter structure and NETCONF message
1091         final var filterStructure = toFilterStructure(
1092                 List.of(FieldsFilter.of(parentYiid, List.of(session1Field, session2TransportField))), SCHEMA);
1093         final var netconfMessage = netconfMessageTransformer.toRpcRequest(Get.QNAME,
1094                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
1095
1096         // testing
1097         assertSimilarXml(netconfMessage, """
1098             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
1099               <get>
1100                 <filter type="subtree">
1101                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
1102                     <sessions>
1103                       <session>
1104                         <session-id>1</session-id>
1105                       </session>
1106                       <session>
1107                         <session-id>2</session-id>
1108                         <transport/>
1109                       </session>
1110                     </sessions>
1111                   </netconf-state>
1112                 </filter>
1113               </get>
1114             </rpc>""");
1115     }
1116
1117     @Test
1118     // Proof that YANGTOOLS-1362 works on DOM level
1119     public void testConfigChangeToNotification() throws SAXException, IOException {
1120         final var message = new NetconfMessage(XmlUtil.readXmlToDocument(
1121             "<notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n"
1122             + " <eventTime>2021-11-11T11:26:16Z</eventTime> \n"
1123             + "  <netconf-config-change xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-notifications\">\n"
1124             + "     <changed-by> \n"
1125             + "       <username>root</username> \n"
1126             + "       <session-id>3</session-id> \n"
1127             + "     </changed-by> \n"
1128             + "     <datastore>running</datastore> \n"
1129             + "     <edit> \n"
1130             + "        <target xmlns:ncm=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">/ncm:netconf-state"
1131             + "/ncm:datastores/ncm:datastore[ncm:name='running']</target>\n"
1132             + "        <operation>replace</operation> \n"
1133             + "     </edit> \n"
1134             + "  </netconf-config-change> \n"
1135             + "</notification>"));
1136
1137         final var change = netconfMessageTransformer.toNotification(message).getBody();
1138         final var editList = change.getChildByArg(new NodeIdentifier(Edit.QNAME));
1139         assertThat(editList, instanceOf(UnkeyedListNode.class));
1140         final var edits = ((UnkeyedListNode) editList).body();
1141         assertEquals(1, edits.size());
1142         final var edit = edits.iterator().next();
1143         final var target = edit.getChildByArg(new NodeIdentifier(QName.create(Edit.QNAME, "target"))).body();
1144         assertThat(target, instanceOf(YangInstanceIdentifier.class));
1145
1146         final var args = ((YangInstanceIdentifier) target).getPathArguments();
1147         assertEquals(4, args.size());
1148     }
1149
1150     private static void checkAction(final QName actionQname, final Node action , final String inputLocalName,
1151             final String inputNodeName, final String inputValue) {
1152         checkNode(action, actionQname.getLocalName(), actionQname.getLocalName(),
1153                 actionQname.getNamespace().toString());
1154
1155         Node childResetAt = action.getFirstChild();
1156         checkNode(childResetAt, inputLocalName, inputNodeName, actionQname.getNamespace().toString());
1157
1158         Node firstChild = childResetAt.getFirstChild();
1159         assertEquals(firstChild.getNodeValue(), inputValue);
1160     }
1161
1162     private static Node checkBasePartOfActionRequest(final NetconfMessage actionRequest) {
1163         Node baseRpc = actionRequest.getDocument().getFirstChild();
1164         checkNode(baseRpc, "rpc", "rpc", "urn:ietf:params:xml:ns:netconf:base:1.0");
1165         assertTrue(baseRpc.getLocalName().equals("rpc"));
1166         assertTrue(baseRpc.getNodeName().equals("rpc"));
1167
1168         Node messageId = baseRpc.getAttributes().getNamedItem("message-id");
1169         assertNotNull(messageId);
1170         assertTrue(messageId.getNodeValue().contains("m-"));
1171         Node childAction = baseRpc.getFirstChild();
1172
1173         checkNode(childAction, "action", "action", "urn:ietf:params:xml:ns:yang:1");
1174         return childAction;
1175     }
1176
1177     private static DOMDataTreeIdentifier prepareDataTreeId(final List<PathArgument> nodeIdentifiers) {
1178         YangInstanceIdentifier yangInstanceIdentifier =
1179                 YangInstanceIdentifier.builder().append(nodeIdentifiers).build();
1180         DOMDataTreeIdentifier domDataTreeIdentifier =
1181                 DOMDataTreeIdentifier.of(org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION,
1182                         yangInstanceIdentifier);
1183         return domDataTreeIdentifier;
1184     }
1185
1186     private static ContainerNode initInputAction(final QName qname, final String value) {
1187         return ImmutableNodes.newContainerBuilder()
1188             .withNodeIdentifier(NodeIdentifier.create(QName.create(qname, "input")))
1189             .withChild(ImmutableNodes.leafNode(qname, value))
1190             .build();
1191     }
1192
1193     private static ContainerNode initEmptyInputAction(final QName qname) {
1194         return ImmutableNodes.newContainerBuilder()
1195             .withNodeIdentifier(NodeIdentifier.create(QName.create(qname, "input")))
1196             .build();
1197     }
1198
1199     private static void checkNode(final Node childServer, final String expectedLocalName, final String expectedNodeName,
1200             final String expectedNamespace) {
1201         assertNotNull(childServer);
1202         assertEquals(childServer.getLocalName(), expectedLocalName);
1203         assertEquals(childServer.getNodeName(), expectedNodeName);
1204         assertEquals(childServer.getNamespaceURI(), expectedNamespace);
1205     }
1206 }