b39da28c067f0f8514dca3d0ff4c6c6f41ae9a65
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / util / NetconfBaseOps.java
1 /*
2  * Copyright (c) 2014 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.sal.connect.netconf.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.COMMIT_RPC_CONTENT;
13 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.EDIT_CONTENT_NODEID;
14 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.GET_RPC_CONTENT;
15 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
16 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME;
17 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_NODEID;
18 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_QNAME;
19 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_NODEID;
20 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME;
21 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_NODEID;
22 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME;
23 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_NODEID;
24 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_NODEID;
25 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME;
26 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_NODEID;
27 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME;
28 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_LOCK_NODEID;
29 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_LOCK_QNAME;
30 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME;
31 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_SOURCE_NODEID;
32 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_NODEID;
33 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_UNLOCK_NODEID;
34 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME;
35 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_VALIDATE_NODEID;
36 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME;
37 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION;
38 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure;
39 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toId;
40
41 import com.google.common.collect.Iterables;
42 import com.google.common.util.concurrent.FutureCallback;
43 import com.google.common.util.concurrent.Futures;
44 import com.google.common.util.concurrent.ListenableFuture;
45 import com.google.common.util.concurrent.MoreExecutors;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Map.Entry;
50 import java.util.Optional;
51 import java.util.stream.Collectors;
52 import org.eclipse.jdt.annotation.NonNull;
53 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
54 import org.opendaylight.mdsal.dom.api.DOMRpcService;
55 import org.opendaylight.netconf.api.ModifyAction;
56 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade.KeepaliveDOMRpcService;
57 import org.opendaylight.netconf.sal.connect.netconf.sal.SchemalessNetconfDeviceRpc;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.copy.config.input.target.ConfigTarget;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.get.config.input.source.ConfigSource;
60 import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
61 import org.opendaylight.yangtools.yang.common.Empty;
62 import org.opendaylight.yangtools.yang.common.QName;
63 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
64 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
65 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
66 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
67 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
68 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
69 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
70 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
71 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
72
73 /**
74  * Provides base operations for NETCONF e.g. {@code get}, {@code get-config}, {@code edit-config}, {@code commit} etc.
75  * as per <a href="https://www.rfc-editor.org/rfc/rfc6241#section-7">RFC6241 Protocol Operations</a>.
76  */
77 // FIXME: datastore etc. should require NodeIdentifier instead of QName to reduce GC pressure
78 // FIXME: turn Optional arguments to @Nullable
79 public final class NetconfBaseOps {
80     private static final NodeIdentifier CONFIG_SOURCE_NODEID = NodeIdentifier.create(ConfigSource.QNAME);
81     private static final NodeIdentifier CONFIG_TARGET_NODEID = NodeIdentifier.create(ConfigTarget.QNAME);
82     private static final LeafNode<String> NETCONF_ERROR_OPTION_ROLLBACK =
83         ImmutableNodes.leafNode(NETCONF_ERROR_OPTION_NODEID, ROLLBACK_ON_ERROR_OPTION);
84
85     private final DOMRpcService rpc;
86     private final MountPointContext mountContext;
87     private final RpcStructureTransformer transformer;
88
89     public NetconfBaseOps(final DOMRpcService rpc, final MountPointContext mountContext) {
90         this.rpc = requireNonNull(rpc);
91         this.mountContext = requireNonNull(mountContext);
92
93         if (rpc instanceof KeepaliveDOMRpcService keepAlive
94             && keepAlive.getDeviceRpc() instanceof SchemalessNetconfDeviceRpc) {
95             transformer = new SchemalessRpcStructureTransformer();
96         } else {
97             transformer = new NetconfRpcStructureTransformer(mountContext);
98         }
99     }
100
101     public ListenableFuture<? extends DOMRpcResult> lock(final FutureCallback<DOMRpcResult> callback,
102             final QName datastore) {
103         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(datastore)));
104     }
105
106     private static <T> ListenableFuture<T> addCallback(final FutureCallback<? super T> callback,
107             final ListenableFuture<T> future) {
108         Futures.addCallback(future, callback, MoreExecutors.directExecutor());
109         return future;
110     }
111
112     public ListenableFuture<? extends DOMRpcResult> lockCandidate(final FutureCallback<DOMRpcResult> callback) {
113         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_LOCK_QNAME,
114             getLockContent(NETCONF_CANDIDATE_QNAME)));
115     }
116
117     public ListenableFuture<? extends DOMRpcResult> lockRunning(final FutureCallback<DOMRpcResult> callback) {
118         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_LOCK_QNAME,
119             getLockContent(NETCONF_RUNNING_QNAME)));
120     }
121
122     public ListenableFuture<? extends DOMRpcResult> unlock(final FutureCallback<DOMRpcResult> callback,
123             final QName datastore) {
124         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_UNLOCK_QNAME,
125             getUnLockContent(datastore)));
126     }
127
128     public ListenableFuture<? extends DOMRpcResult> unlockRunning(final FutureCallback<DOMRpcResult> callback) {
129         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_UNLOCK_QNAME,
130             getUnLockContent(NETCONF_RUNNING_QNAME)));
131     }
132
133     public ListenableFuture<? extends DOMRpcResult> unlockCandidate(final FutureCallback<DOMRpcResult> callback) {
134         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_UNLOCK_QNAME,
135             getUnLockContent(NETCONF_CANDIDATE_QNAME)));
136     }
137
138     public ListenableFuture<? extends DOMRpcResult> discardChanges(final FutureCallback<DOMRpcResult> callback) {
139         // FIXME: eliminate null
140         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, null));
141     }
142
143     public ListenableFuture<? extends DOMRpcResult> commit(final FutureCallback<DOMRpcResult> callback) {
144         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_COMMIT_QNAME, COMMIT_RPC_CONTENT));
145     }
146
147     public ListenableFuture<? extends DOMRpcResult> validate(final FutureCallback<DOMRpcResult> callback,
148             final QName datastore) {
149         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_VALIDATE_QNAME,
150             getValidateContent(requireNonNull(datastore))));
151     }
152
153     public ListenableFuture<? extends DOMRpcResult> validateCandidate(final FutureCallback<DOMRpcResult> callback) {
154         return validate(callback, NETCONF_CANDIDATE_QNAME);
155     }
156
157     public ListenableFuture<? extends DOMRpcResult> validateRunning(final FutureCallback<DOMRpcResult> callback) {
158         return validate(callback, NETCONF_RUNNING_QNAME);
159     }
160
161     public ListenableFuture<? extends DOMRpcResult> copyConfig(final FutureCallback<DOMRpcResult> callback,
162             final QName source, final QName target) {
163         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_COPY_CONFIG_QNAME,
164             getCopyConfigContent(source, target)));
165     }
166
167     public ListenableFuture<? extends DOMRpcResult> copyRunningToCandidate(
168             final FutureCallback<DOMRpcResult> callback) {
169         return copyConfig(callback, NETCONF_RUNNING_QNAME, NETCONF_CANDIDATE_QNAME);
170     }
171
172     public ListenableFuture<? extends DOMRpcResult> getConfig(final FutureCallback<DOMRpcResult> callback,
173             final QName datastore, final Optional<YangInstanceIdentifier> filterPath) {
174         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME, nonEmptyFilter(filterPath)
175             .map(path -> NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID,
176                 getSourceNode(datastore), transformer.toFilterStructure(path)))
177             .orElseGet(() -> NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, getSourceNode(datastore)))));
178     }
179
180     private ListenableFuture<? extends DOMRpcResult> getConfig(final FutureCallback<DOMRpcResult> callback,
181             final QName datastore, final Optional<YangInstanceIdentifier> filterPath,
182             final List<YangInstanceIdentifier> fields) {
183         final ContainerNode rpcInput;
184         if (nonEmptyFilter(filterPath).isPresent()) {
185             rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, getSourceNode(datastore),
186                 transformer.toFilterStructure(List.of(FieldsFilter.of(filterPath.orElseThrow(), fields))));
187         } else if (containsEmptyPath(fields)) {
188             rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, getSourceNode(datastore));
189         } else {
190             rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID,
191                     getSourceNode(datastore), getSubtreeFilterFromRootFields(fields));
192         }
193         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME, rpcInput));
194     }
195
196     /**
197      * Calling GET-CONFIG RPC with subtree filter that is specified by {@link YangInstanceIdentifier}.
198      *
199      * @param callback   RPC response callback
200      * @param filterPath path to requested data
201      * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
202      */
203     public ListenableFuture<Optional<NormalizedNode>> getConfigRunningData(final FutureCallback<DOMRpcResult> callback,
204             final Optional<YangInstanceIdentifier> filterPath) {
205         return extractData(filterPath, getConfigRunning(callback, filterPath));
206     }
207
208     /**
209      * Calling GET-CONFIG RPC with subtree filter tha tis specified by parent {@link YangInstanceIdentifier} and list
210      * of specific fields that caller would like to read. Field paths are relative to parent path.
211      *
212      * @param callback   RPC response callback
213      * @param filterPath parent path to requested data
214      * @param fields     paths to specific fields that are selected under parent path
215      * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
216      */
217     public ListenableFuture<Optional<NormalizedNode>> getConfigRunningData(final FutureCallback<DOMRpcResult> callback,
218             final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
219         if (fields.isEmpty()) {
220             // RFC doesn't allow to build subtree filter that would expect just empty element in response
221             return Futures.immediateFailedFuture(new IllegalArgumentException(
222                 "Failed to build NETCONF GET-CONFIG RPC: provided list of fields is empty; filter path: "
223                     + filterPath));
224         }
225         return extractData(filterPath, getConfigRunning(callback, filterPath, fields));
226     }
227
228     /**
229      * Calling GET RPC with subtree filter that is specified by {@link YangInstanceIdentifier}.
230      *
231      * @param callback   RPC response callback
232      * @param filterPath path to requested data
233      * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
234      */
235     public ListenableFuture<Optional<NormalizedNode>> getData(final FutureCallback<DOMRpcResult> callback,
236             final Optional<YangInstanceIdentifier> filterPath) {
237         return extractData(filterPath, get(callback, filterPath));
238     }
239
240     /**
241      * Calling GET RPC with subtree filter tha tis specified by parent {@link YangInstanceIdentifier} and list
242      * of specific fields that caller would like to read. Field paths are relative to parent path.
243      *
244      * @param callback   RPC response callback
245      * @param filterPath parent path to requested data
246      * @param fields     paths to specific fields that are selected under parent path
247      * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
248      */
249     public ListenableFuture<Optional<NormalizedNode>> getData(final FutureCallback<DOMRpcResult> callback,
250             final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
251         if (fields.isEmpty()) {
252             // RFC doesn't allow to build subtree filter that would expect just empty element in response
253             return Futures.immediateFailedFuture(new IllegalArgumentException(
254                     "Failed to build NETCONF GET RPC: provided list of fields is empty; filter path: " + filterPath));
255         }
256         return extractData(filterPath, get(callback, filterPath, fields));
257     }
258
259     private ListenableFuture<Optional<NormalizedNode>> extractData(final Optional<YangInstanceIdentifier> path,
260             final ListenableFuture<? extends DOMRpcResult> configRunning) {
261         return Futures.transform(configRunning, result -> {
262             final var errors = result.getErrors();
263             checkArgument(errors.isEmpty(), "Unable to read data: %s, errors: %s", path, errors);
264             return transformer.selectFromDataStructure(((ContainerNode) result.getResult())
265                 .getChildByArg(NetconfMessageTransformUtil.NETCONF_DATA_NODEID), path.orElseThrow());
266         }, MoreExecutors.directExecutor());
267     }
268
269     public ListenableFuture<? extends DOMRpcResult> getConfigRunning(final FutureCallback<DOMRpcResult> callback,
270             final Optional<YangInstanceIdentifier> filterPath) {
271         return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath);
272     }
273
274     private ListenableFuture<? extends DOMRpcResult> getConfigRunning(final FutureCallback<DOMRpcResult> callback,
275             final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
276         return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath, fields);
277     }
278
279     public ListenableFuture<? extends DOMRpcResult> getConfigCandidate(final FutureCallback<DOMRpcResult> callback,
280             final Optional<YangInstanceIdentifier> filterPath) {
281         return getConfig(callback, NETCONF_CANDIDATE_QNAME, filterPath);
282     }
283
284     public ListenableFuture<? extends DOMRpcResult> get(final FutureCallback<DOMRpcResult> callback,
285             final Optional<YangInstanceIdentifier> filterPath) {
286         return addCallback(requireNonNull(callback),
287             rpc.invokeRpc(NETCONF_GET_QNAME, nonEmptyFilter(filterPath)
288                 .map(path -> NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID,
289                     toFilterStructure(path, mountContext.getEffectiveModelContext())))
290                 .orElse(NetconfMessageTransformUtil.GET_RPC_CONTENT)));
291     }
292
293     private ListenableFuture<? extends DOMRpcResult> get(final FutureCallback<DOMRpcResult> callback,
294             final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
295         final ContainerNode rpcInput;
296         if (nonEmptyFilter(filterPath).isPresent()) {
297             rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, transformer.toFilterStructure(
298                     Collections.singletonList(FieldsFilter.of(filterPath.orElseThrow(), fields))));
299         } else if (containsEmptyPath(fields)) {
300             rpcInput = GET_RPC_CONTENT;
301         } else {
302             rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, getSubtreeFilterFromRootFields(fields));
303         }
304         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_GET_QNAME, rpcInput));
305     }
306
307     private static boolean containsEmptyPath(final List<YangInstanceIdentifier> fields) {
308         return fields.stream().anyMatch(YangInstanceIdentifier::isEmpty);
309     }
310
311     private DataContainerChild getSubtreeFilterFromRootFields(final List<YangInstanceIdentifier> fields) {
312         return transformer.toFilterStructure(fields.stream()
313             .map(fieldPath -> Map.entry(
314                 YangInstanceIdentifier.create(Iterables.limit(fieldPath.getPathArguments(), 1)),
315                 YangInstanceIdentifier.create(Iterables.skip(fieldPath.getPathArguments(), 1))))
316             .collect(Collectors.groupingBy(Entry::getKey,
317                 Collectors.mapping(Entry::getValue, Collectors.toUnmodifiableList())))
318             .entrySet().stream()
319             .map(entry -> FieldsFilter.of(entry.getKey(), entry.getValue()))
320             .collect(Collectors.toUnmodifiableList()));
321     }
322
323     private static Optional<YangInstanceIdentifier> nonEmptyFilter(final Optional<YangInstanceIdentifier> filterPath) {
324         return filterPath.filter(path -> !path.isEmpty());
325     }
326
327     public ListenableFuture<? extends DOMRpcResult> editConfigCandidate(
328             final FutureCallback<? super DOMRpcResult> callback, final DataContainerChild editStructure,
329             final ModifyAction modifyAction, final boolean rollback) {
330         return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.of(modifyAction), rollback);
331     }
332
333     public ListenableFuture<? extends DOMRpcResult> editConfigCandidate(
334             final FutureCallback<? super DOMRpcResult> callback, final DataContainerChild editStructure,
335             final boolean rollback) {
336         return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.empty(), rollback);
337     }
338
339     public ListenableFuture<? extends DOMRpcResult> editConfigRunning(
340             final FutureCallback<? super DOMRpcResult> callback, final DataContainerChild editStructure,
341             final ModifyAction modifyAction, final boolean rollback) {
342         return editConfig(callback, NETCONF_RUNNING_QNAME, editStructure, Optional.of(modifyAction), rollback);
343     }
344
345     public ListenableFuture<? extends DOMRpcResult> editConfigRunning(
346             final FutureCallback<? super DOMRpcResult> callback, final DataContainerChild editStructure,
347             final boolean rollback) {
348         return editConfig(callback, NETCONF_RUNNING_QNAME, editStructure, Optional.empty(), rollback);
349     }
350
351     public ListenableFuture<? extends DOMRpcResult> editConfig(
352             final FutureCallback<? super DOMRpcResult> callback, final QName datastore,
353             final DataContainerChild editStructure, final Optional<ModifyAction> modifyAction,
354             final boolean rollback) {
355         return addCallback(requireNonNull(callback), rpc.invokeRpc(NETCONF_EDIT_CONFIG_QNAME,
356             getEditConfigContent(requireNonNull(datastore), requireNonNull(editStructure), modifyAction, rollback)));
357     }
358
359     public ChoiceNode createEditConfigStructure(final Optional<NormalizedNode> lastChild,
360             final Optional<ModifyAction> operation, final YangInstanceIdentifier dataPath) {
361         return Builders.choiceBuilder()
362             .withNodeIdentifier(EDIT_CONTENT_NODEID)
363             .withChild(transformer.createEditConfigStructure(lastChild, dataPath, operation))
364             .build();
365     }
366
367     private static ContainerNode getEditConfigContent(final QName datastore, final DataContainerChild editStructure,
368             final Optional<ModifyAction> defaultOperation, final boolean rollback) {
369         final var editBuilder = Builders.containerBuilder()
370             .withNodeIdentifier(NETCONF_EDIT_CONFIG_NODEID)
371             // Target
372             .withChild(getTargetNode(datastore));
373
374         // Default operation
375         defaultOperation.ifPresent(op -> {
376             editBuilder.withChild(ImmutableNodes.leafNode(NETCONF_DEFAULT_OPERATION_NODEID, op.xmlValue()));
377         });
378
379         // Error option
380         if (rollback) {
381             editBuilder.withChild(NETCONF_ERROR_OPTION_ROLLBACK);
382         }
383
384         // Edit content
385         return editBuilder.withChild(editStructure).build();
386     }
387
388     public static @NonNull ContainerNode getSourceNode(final QName datastore) {
389         return Builders.containerBuilder()
390             .withNodeIdentifier(NETCONF_SOURCE_NODEID)
391             .withChild(Builders.choiceBuilder()
392                 .withNodeIdentifier(CONFIG_SOURCE_NODEID)
393                 .withChild(ImmutableNodes.leafNode(datastore, Empty.value()))
394                 .build())
395             .build();
396     }
397
398     public static @NonNull ContainerNode getLockContent(final QName datastore) {
399         return Builders.containerBuilder()
400             .withNodeIdentifier(NETCONF_LOCK_NODEID)
401             .withChild(getTargetNode(datastore))
402             .build();
403     }
404
405     public static @NonNull ContainerNode getTargetNode(final QName datastore) {
406         return Builders.containerBuilder()
407             .withNodeIdentifier(NETCONF_TARGET_NODEID)
408             .withChild(Builders.choiceBuilder()
409                 .withNodeIdentifier(CONFIG_TARGET_NODEID)
410                 .withChild(ImmutableNodes.leafNode(toId(datastore), Empty.value()))
411                 .build())
412             .build();
413     }
414
415     public static @NonNull ContainerNode getCopyConfigContent(final QName source, final QName target) {
416         return Builders.containerBuilder()
417             .withNodeIdentifier(NETCONF_COPY_CONFIG_NODEID)
418             .withChild(getTargetNode(target))
419             .withChild(getSourceNode(source))
420             .build();
421     }
422
423     public static @NonNull ContainerNode getValidateContent(final QName source) {
424         return Builders.containerBuilder()
425             .withNodeIdentifier(NETCONF_VALIDATE_NODEID)
426             .withChild(getSourceNode(source))
427             .build();
428     }
429
430     public static @NonNull ContainerNode getUnLockContent(final QName datastore) {
431         return Builders.containerBuilder()
432             .withNodeIdentifier(NETCONF_UNLOCK_NODEID)
433             .withChild(getTargetNode(datastore))
434             .build();
435     }
436 }