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