Improve failure message
[netconf.git] / netconf / netconf-topology / src / main / java / org / opendaylight / netconf / topology / spi / AbstractNetconfTopology.java
1 /*
2  * Copyright (c) 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.topology.spi;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.Lists;
15 import com.google.common.util.concurrent.FutureCallback;
16 import com.google.common.util.concurrent.Futures;
17 import com.google.common.util.concurrent.ListenableFuture;
18 import com.google.common.util.concurrent.ListeningExecutorService;
19 import com.google.common.util.concurrent.MoreExecutors;
20 import io.netty.util.concurrent.EventExecutor;
21 import java.net.URL;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.concurrent.ExecutionException;
28 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
29 import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
30 import org.opendaylight.controller.config.threadpool.ThreadPool;
31 import org.opendaylight.mdsal.binding.api.DataBroker;
32 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
33 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
34 import org.opendaylight.netconf.client.NetconfClientDispatcher;
35 import org.opendaylight.netconf.client.NetconfClientSessionListener;
36 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
37 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
38 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
39 import org.opendaylight.netconf.nettyutil.ReconnectStrategyFactory;
40 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategyFactory;
41 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
42 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
43 import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory;
44 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
45 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
46 import org.opendaylight.netconf.sal.connect.api.SchemaResourceManager;
47 import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
48 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice.SchemaResourcesDTO;
49 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
50 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
51 import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
52 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
53 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
54 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
55 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
56 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
57 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
58 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseNetconfSchemas;
59 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
60 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
61 import org.opendaylight.netconf.topology.api.NetconfTopology;
62 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
76 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
77 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
78 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
79 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
80 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
81 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
82 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
83 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
84 import org.opendaylight.yangtools.yang.common.Empty;
85 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
86 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
87 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
88 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
89 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
90 import org.slf4j.Logger;
91 import org.slf4j.LoggerFactory;
92
93 public abstract class AbstractNetconfTopology implements NetconfTopology {
94     private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
95
96     private final NetconfClientDispatcher clientDispatcher;
97     private final EventExecutor eventExecutor;
98     private final DeviceActionFactory deviceActionFactory;
99     private final NetconfKeystoreAdapter keystoreAdapter;
100     private final SchemaResourceManager schemaManager;
101     private final BaseNetconfSchemas baseSchemas;
102
103     protected final ScheduledThreadPool keepaliveExecutor;
104     protected final ListeningExecutorService processingExecutor;
105     protected final DataBroker dataBroker;
106     protected final DOMMountPointService mountPointService;
107     protected final String topologyId;
108     protected String privateKeyPath;
109     protected String privateKeyPassphrase;
110     protected final AAAEncryptionService encryptionService;
111     protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
112
113     protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
114                                       final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
115                                       final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
116                                       final DataBroker dataBroker, final DOMMountPointService mountPointService,
117                                       final AAAEncryptionService encryptionService,
118                                       final DeviceActionFactory deviceActionFactory,
119                                       final BaseNetconfSchemas baseSchemas) {
120         this.topologyId = requireNonNull(topologyId);
121         this.clientDispatcher = clientDispatcher;
122         this.eventExecutor = eventExecutor;
123         this.keepaliveExecutor = keepaliveExecutor;
124         this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
125         this.schemaManager = requireNonNull(schemaManager);
126         this.deviceActionFactory = deviceActionFactory;
127         this.dataBroker = requireNonNull(dataBroker);
128         this.mountPointService = mountPointService;
129         this.encryptionService = encryptionService;
130         this.baseSchemas = requireNonNull(baseSchemas);
131
132         keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
133
134         // FIXME: this should be a put(), as we are initializing and will be re-populating the datastore with all the
135         //        devices. Whatever has been there before should be nuked to properly re-align lifecycle.
136         final var wtx = dataBroker.newWriteOnlyTransaction();
137         wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
138             .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
139             .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
140         final var future = wtx.commit();
141         try {
142             future.get();
143         } catch (InterruptedException | ExecutionException e) {
144             LOG.error("Unable to initialize topology {}", topologyId, e);
145             throw new IllegalStateException(e);
146         }
147
148         LOG.debug("Topology {} initialized", topologyId);
149     }
150
151     @Override
152     public ListenableFuture<Empty> connectNode(final NodeId nodeId, final Node configNode) {
153         LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
154         return setupConnection(nodeId, configNode);
155     }
156
157     /**
158      * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
159      *
160      * @param nodeConfiguration Node configuration container.
161      * @return String representation of node configuration with credentials replaced by asterisks.
162      */
163     @VisibleForTesting
164     public static String hideCredentials(final Node nodeConfiguration) {
165         final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
166         final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
167         final String nodeConfigurationString = nodeConfiguration.toString();
168         return nodeConfigurationString.replace(nodeCredentials, "***");
169     }
170
171     @Override
172     public ListenableFuture<Empty> disconnectNode(final NodeId nodeId) {
173         final var nodeName = nodeId.getValue();
174         LOG.debug("Disconnecting RemoteDevice{{}}", nodeName);
175
176         final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
177         if (connectorDTO == null) {
178             return Futures.immediateFailedFuture(
179                 new IllegalStateException("Cannot disconnect " + nodeName + " as it is not connected"));
180         }
181
182         connectorDTO.close();
183         return Futures.immediateFuture(Empty.value());
184     }
185
186     protected ListenableFuture<Empty> setupConnection(final NodeId nodeId, final Node configNode) {
187         final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
188         final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
189
190         requireNonNull(netconfNode.getHost());
191         requireNonNull(netconfNode.getPort());
192
193         final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
194         final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
195         final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
196         final NetconfReconnectingClientConfiguration clientConfig =
197                 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
198         final ListenableFuture<Empty> future =
199                 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
200
201         activeConnectors.put(nodeId, deviceCommunicatorDTO);
202
203         Futures.addCallback(future, new FutureCallback<>() {
204             @Override
205             public void onSuccess(final Empty result) {
206                 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
207             }
208
209             @Override
210             public void onFailure(final Throwable throwable) {
211                 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
212                 // remove this node from active connectors?
213             }
214         }, MoreExecutors.directExecutor());
215
216         return future;
217     }
218
219     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
220         return createDeviceCommunicator(nodeId, node, null);
221     }
222
223     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
224             final NetconfNodeAugmentedOptional nodeOptional) {
225         final RemoteDeviceId remoteDeviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
226
227         final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
228         RemoteDeviceHandler salFacade = createSalFacade(remoteDeviceId);
229         if (keepaliveDelay > 0) {
230             LOG.info("Adding keepalive facade, for device {}", nodeId);
231             salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(),
232                     keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
233         }
234
235         final RemoteDevice<NetconfDeviceCommunicator> device;
236         final List<SchemaSourceRegistration<?>> yanglibRegistrations;
237         if (node.requireSchemaless()) {
238             device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade);
239             yanglibRegistrations = List.of();
240         } else {
241             final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
242             final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
243                 nodeId.getValue());
244             device = new NetconfDeviceBuilder()
245                 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
246                 .setSchemaResourcesDTO(resources)
247                 .setGlobalProcessingExecutor(processingExecutor)
248                 .setId(remoteDeviceId)
249                 .setSalFacade(salFacade)
250                 .setNode(node)
251                 .setEventExecutor(eventExecutor)
252                 .setNodeOptional(nodeOptional)
253                 .setDeviceActionFactory(deviceActionFactory)
254                 .setBaseSchemas(baseSchemas)
255                 .build();
256             yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
257         }
258
259         final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
260         final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
261         if (rpcMessageLimit < 1) {
262             LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
263         }
264
265         final NetconfDeviceCommunicator netconfDeviceCommunicator =
266              userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
267                      userCapabilities.get(), rpcMessageLimit)
268             : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
269
270         if (salFacade instanceof KeepaliveSalFacade) {
271             ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
272         }
273
274         return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
275     }
276
277     private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
278             final NetconfNode node, final SchemaResourcesDTO resources) {
279         final YangLibrary yangLibrary = node.getYangLibrary();
280         if (yangLibrary != null) {
281             final Uri uri = yangLibrary.getYangLibraryUrl();
282             if (uri != null) {
283                 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
284                 final String yangLibURL = uri.getValue();
285                 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
286
287                 // pre register yang library sources as fallback schemas to schema registry
288                 final LibraryModulesSchemas schemas;
289                 final String yangLibUsername = yangLibrary.getUsername();
290                 final String yangLigPassword = yangLibrary.getPassword();
291                 if (yangLibUsername != null && yangLigPassword != null) {
292                     schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
293                 } else {
294                     schemas = LibraryModulesSchemas.create(yangLibURL);
295                 }
296
297                 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
298                     registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
299                         remoteDeviceId, schemas.getAvailableModels()),
300                         PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
301                             PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
302                 }
303                 return List.copyOf(registrations);
304             }
305         }
306
307         return List.of();
308     }
309
310     /**
311      * Sets the private key path from location specified in configuration file using blueprint.
312      */
313     public void setPrivateKeyPath(final String privateKeyPath) {
314         this.privateKeyPath = privateKeyPath;
315     }
316
317     /**
318      * Sets the private key passphrase from location specified in configuration file using blueprint.
319      */
320     public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
321         this.privateKeyPassphrase = privateKeyPassphrase;
322     }
323
324     public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
325                                                                   final NetconfNode node, final NodeId nodeId) {
326         final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
327                 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
328                 node.requireSleepFactor().decimalValue());
329         final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
330         final Protocol protocol = node.getProtocol();
331         if (node.requireTcpOnly()) {
332             reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
333                     .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
334                     .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
335         } else if (protocol == null || protocol.getName() == Name.SSH) {
336             reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
337                     .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
338                     .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
339         } else if (protocol.getName() == Name.TLS) {
340             reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
341                 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
342                 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
343         } else {
344             throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
345         }
346
347         if (node.getOdlHelloMessageCapabilities() != null) {
348             reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
349                     Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
350         }
351
352         return reconnectingClientConfigurationBuilder
353                 .withName(nodeId.getValue())
354                 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
355                 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
356                 .withReconnectStrategy(sf.createReconnectStrategy())
357                 .withConnectStrategyFactory(sf)
358                 .withSessionListener(listener)
359                 .build();
360     }
361
362     private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
363         if (credentials
364                 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
365                     .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword) {
366             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
367         }
368         if (credentials instanceof LoginPwUnencrypted) {
369             final LoginPasswordUnencrypted loginPassword =
370                     ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
371             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
372         }
373         if (credentials instanceof LoginPw) {
374             final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
375             return new LoginPasswordHandler(loginPassword.getUsername(),
376                     encryptionService.decrypt(loginPassword.getPassword()));
377         }
378         if (credentials instanceof KeyAuth) {
379             final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
380             return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
381                     keystoreAdapter, encryptionService);
382         }
383         throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
384     }
385
386     protected abstract RemoteDeviceHandler createSalFacade(RemoteDeviceId id);
387
388     private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
389         // if none of yang-module-capabilities or non-module-capabilities is specified
390         // just return absent
391         if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
392             return Optional.empty();
393         }
394
395         final List<String> capabilities = new ArrayList<>();
396
397         boolean overrideYangModuleCaps = false;
398         if (node.getYangModuleCapabilities() != null) {
399             capabilities.addAll(node.getYangModuleCapabilities().getCapability());
400             overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
401         }
402
403         //non-module capabilities should not exist in yang module capabilities
404         final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
405         Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
406                 "List yang-module-capabilities/capability should contain only module based capabilities. "
407                         + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
408
409         boolean overrideNonModuleCaps = false;
410         if (node.getNonModuleCapabilities() != null) {
411             capabilities.addAll(node.getNonModuleCapabilities().getCapability());
412             overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
413         }
414
415         return Optional.of(new UserPreferences(NetconfSessionPreferences
416             .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));
417     }
418 }