Move netconf-console to apps/
[netconf.git] / plugins / 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.DISCARD_CHANGES_RPC_CONTENT;
14 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.EDIT_CONTENT_NODEID;
15 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.GET_RPC_CONTENT;
16 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_NODEID;
17 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME;
18 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_NODEID;
19 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_QNAME;
20 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_NODEID;
21 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME;
22 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_NODEID;
23 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME;
24 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_NODEID;
25 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_NODEID;
26 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME;
27 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_NODEID;
28 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME;
29 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_LOCK_NODEID;
30 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_LOCK_QNAME;
31 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_NODEID;
32 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_SOURCE_NODEID;
33 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_NODEID;
34 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_UNLOCK_NODEID;
35 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME;
36 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_VALIDATE_NODEID;
37 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME;
38 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION;
39 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure;
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.netconf.api.EffectiveOperation;
55 import org.opendaylight.netconf.sal.connect.api.NetconfRpcService;
56 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceServices.Rpcs;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.copy.config.input.target.ConfigTarget;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.get.config.input.source.ConfigSource;
59 import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
60 import org.opendaylight.yangtools.yang.common.Empty;
61 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
62 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
63 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
64 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
65 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
66 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
67 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
68 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
69 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
70
71 /**
72  * Provides base operations for NETCONF e.g. {@code get}, {@code get-config}, {@code edit-config}, {@code commit} etc.
73  * as per <a href="https://www.rfc-editor.org/rfc/rfc6241#section-7">RFC6241 Protocol Operations</a>.
74  */
75 // FIXME: turn Optional arguments to @Nullable
76 public final class NetconfBaseOps {
77     private static final NodeIdentifier CONFIG_SOURCE_NODEID = NodeIdentifier.create(ConfigSource.QNAME);
78     private static final NodeIdentifier CONFIG_TARGET_NODEID = NodeIdentifier.create(ConfigTarget.QNAME);
79     private static final LeafNode<String> NETCONF_ERROR_OPTION_ROLLBACK =
80         ImmutableNodes.leafNode(NETCONF_ERROR_OPTION_NODEID, ROLLBACK_ON_ERROR_OPTION);
81
82     private final NetconfRpcService rpc;
83     private final MountPointContext mountContext;
84     private final RpcStructureTransformer transformer;
85
86     public NetconfBaseOps(final Rpcs rpc, final MountPointContext mountContext) {
87         this.rpc = requireNonNull(rpc);
88         this.mountContext = requireNonNull(mountContext);
89
90         if (rpc instanceof Rpcs.Schemaless) {
91             transformer = new SchemalessRpcStructureTransformer();
92         } else if (rpc instanceof Rpcs.Normalized) {
93             transformer = new NetconfRpcStructureTransformer(mountContext);
94         } else {
95             throw new IllegalStateException("Unhandled rpcs " + rpc);
96         }
97     }
98
99     public ListenableFuture<? extends DOMRpcResult> lock(final FutureCallback<DOMRpcResult> callback,
100             final NodeIdentifier datastore) {
101         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_LOCK_QNAME, getLockContent(datastore)));
102     }
103
104     private static <T> ListenableFuture<T> addCallback(final FutureCallback<? super T> callback,
105             final ListenableFuture<T> future) {
106         Futures.addCallback(future, callback, MoreExecutors.directExecutor());
107         return future;
108     }
109
110     public ListenableFuture<? extends DOMRpcResult> lockCandidate(final FutureCallback<DOMRpcResult> callback) {
111         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_LOCK_QNAME,
112             getLockContent(NETCONF_CANDIDATE_NODEID)));
113     }
114
115     public ListenableFuture<? extends DOMRpcResult> lockRunning(final FutureCallback<DOMRpcResult> callback) {
116         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_LOCK_QNAME,
117             getLockContent(NETCONF_RUNNING_NODEID)));
118     }
119
120     public ListenableFuture<? extends DOMRpcResult> unlock(final FutureCallback<DOMRpcResult> callback,
121             final NodeIdentifier datastore) {
122         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_UNLOCK_QNAME,
123             getUnLockContent(datastore)));
124     }
125
126     public ListenableFuture<? extends DOMRpcResult> unlockRunning(final FutureCallback<DOMRpcResult> callback) {
127         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_UNLOCK_QNAME,
128             getUnLockContent(NETCONF_RUNNING_NODEID)));
129     }
130
131     public ListenableFuture<? extends DOMRpcResult> unlockCandidate(final FutureCallback<DOMRpcResult> callback) {
132         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_UNLOCK_QNAME,
133             getUnLockContent(NETCONF_CANDIDATE_NODEID)));
134     }
135
136     public ListenableFuture<? extends DOMRpcResult> discardChanges(final FutureCallback<DOMRpcResult> callback) {
137         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_DISCARD_CHANGES_QNAME,
138             DISCARD_CHANGES_RPC_CONTENT));
139     }
140
141     public ListenableFuture<? extends DOMRpcResult> commit(final FutureCallback<DOMRpcResult> callback) {
142         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_COMMIT_QNAME, COMMIT_RPC_CONTENT));
143     }
144
145     public ListenableFuture<? extends DOMRpcResult> validate(final FutureCallback<DOMRpcResult> callback,
146             final NodeIdentifier datastore) {
147         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_VALIDATE_QNAME,
148             getValidateContent(requireNonNull(datastore))));
149     }
150
151     public ListenableFuture<? extends DOMRpcResult> validateCandidate(final FutureCallback<DOMRpcResult> callback) {
152         return validate(callback, NETCONF_CANDIDATE_NODEID);
153     }
154
155     public ListenableFuture<? extends DOMRpcResult> validateRunning(final FutureCallback<DOMRpcResult> callback) {
156         return validate(callback, NETCONF_RUNNING_NODEID);
157     }
158
159     public ListenableFuture<? extends DOMRpcResult> copyConfig(final FutureCallback<DOMRpcResult> callback,
160             final NodeIdentifier sourceDatastore, final NodeIdentifier targetDatastore) {
161         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_COPY_CONFIG_QNAME,
162             getCopyConfigContent(sourceDatastore, targetDatastore)));
163     }
164
165     public ListenableFuture<? extends DOMRpcResult> copyRunningToCandidate(
166             final FutureCallback<DOMRpcResult> callback) {
167         return copyConfig(callback, NETCONF_RUNNING_NODEID, NETCONF_CANDIDATE_NODEID);
168     }
169
170     public ListenableFuture<? extends DOMRpcResult> getConfig(final FutureCallback<DOMRpcResult> callback,
171             final NodeIdentifier datastore, final Optional<YangInstanceIdentifier> filterPath) {
172         final var source = getSourceNode(datastore);
173         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_GET_CONFIG_QNAME,
174             nonEmptyFilter(filterPath)
175                 .map(path -> NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, source,
176                     transformer.toFilterStructure(path)))
177                 .orElseGet(() -> NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, source))));
178     }
179
180     private ListenableFuture<? extends DOMRpcResult> getConfig(final FutureCallback<DOMRpcResult> callback,
181             final NodeIdentifier 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.invokeNetconf(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.errors();
263             checkArgument(errors.isEmpty(), "Unable to read data: %s, errors: %s", path, errors);
264             return transformer.selectFromDataStructure(result.value()
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_NODEID, 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_NODEID, 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_NODEID, filterPath);
282     }
283
284     public ListenableFuture<? extends DOMRpcResult> get(final FutureCallback<DOMRpcResult> callback,
285             final Optional<YangInstanceIdentifier> filterPath) {
286         return addCallback(requireNonNull(callback), rpc.invokeNetconf(NETCONF_GET_QNAME,
287             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.invokeNetconf(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 EffectiveOperation modifyAction, final boolean rollback) {
330         return editConfig(callback, NETCONF_CANDIDATE_NODEID, 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_NODEID, editStructure, Optional.empty(), rollback);
337     }
338
339     public ListenableFuture<? extends DOMRpcResult> editConfigRunning(
340             final FutureCallback<? super DOMRpcResult> callback, final DataContainerChild editStructure,
341             final EffectiveOperation modifyAction, final boolean rollback) {
342         return editConfig(callback, NETCONF_RUNNING_NODEID, 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_NODEID, editStructure, Optional.empty(), rollback);
349     }
350
351     public ListenableFuture<? extends DOMRpcResult> editConfig(
352             final FutureCallback<? super DOMRpcResult> callback, final NodeIdentifier datastore,
353             final DataContainerChild editStructure, final Optional<EffectiveOperation> modifyAction,
354             final boolean rollback) {
355         return addCallback(requireNonNull(callback), rpc.invokeNetconf(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<EffectiveOperation> 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 NodeIdentifier datastore,
368             final DataContainerChild editStructure, final Optional<EffectiveOperation> defaultOperation,
369             final boolean rollback) {
370         final var editBuilder = Builders.containerBuilder()
371             .withNodeIdentifier(NETCONF_EDIT_CONFIG_NODEID)
372             // Target
373             .withChild(getTargetNode(datastore));
374
375         // Default operation
376         defaultOperation.ifPresent(op -> {
377             editBuilder.withChild(ImmutableNodes.leafNode(NETCONF_DEFAULT_OPERATION_NODEID, op.xmlValue()));
378         });
379
380         // Error option
381         if (rollback) {
382             editBuilder.withChild(NETCONF_ERROR_OPTION_ROLLBACK);
383         }
384
385         // Edit content
386         return editBuilder.withChild(editStructure).build();
387     }
388
389     public static @NonNull ContainerNode getSourceNode(final NodeIdentifier datastore) {
390         return Builders.containerBuilder()
391             .withNodeIdentifier(NETCONF_SOURCE_NODEID)
392             .withChild(Builders.choiceBuilder()
393                 .withNodeIdentifier(CONFIG_SOURCE_NODEID)
394                 .withChild(ImmutableNodes.leafNode(datastore, Empty.value()))
395                 .build())
396             .build();
397     }
398
399     public static @NonNull ContainerNode getLockContent(final NodeIdentifier datastore) {
400         return Builders.containerBuilder()
401             .withNodeIdentifier(NETCONF_LOCK_NODEID)
402             .withChild(getTargetNode(datastore))
403             .build();
404     }
405
406     public static @NonNull ContainerNode getTargetNode(final NodeIdentifier datastore) {
407         return Builders.containerBuilder()
408             .withNodeIdentifier(NETCONF_TARGET_NODEID)
409             .withChild(Builders.choiceBuilder()
410                 .withNodeIdentifier(CONFIG_TARGET_NODEID)
411                 .withChild(ImmutableNodes.leafNode(datastore, Empty.value()))
412                 .build())
413             .build();
414     }
415
416     public static @NonNull ContainerNode getCopyConfigContent(final NodeIdentifier sourceDatastore,
417             final NodeIdentifier targetDatastore) {
418         return Builders.containerBuilder()
419             .withNodeIdentifier(NETCONF_COPY_CONFIG_NODEID)
420             .withChild(getTargetNode(targetDatastore))
421             .withChild(getSourceNode(sourceDatastore))
422             .build();
423     }
424
425     public static @NonNull ContainerNode getValidateContent(final NodeIdentifier sourceDatastore) {
426         return Builders.containerBuilder()
427             .withNodeIdentifier(NETCONF_VALIDATE_NODEID)
428             .withChild(getSourceNode(sourceDatastore))
429             .build();
430     }
431
432     public static @NonNull ContainerNode getUnLockContent(final NodeIdentifier datastore) {
433         return Builders.containerBuilder()
434             .withNodeIdentifier(NETCONF_UNLOCK_NODEID)
435             .withChild(getTargetNode(datastore))
436             .build();
437     }
438 }