import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import io.netty.util.concurrent.EventExecutor;
-import io.netty.util.concurrent.FailedFuture;
-import io.netty.util.concurrent.Future;
import java.net.InetSocketAddress;
import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
import org.opendaylight.controller.config.threadpool.ThreadPool;
import org.opendaylight.netconf.callhome.protocol.CallHomeChannelActivator;
import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener;
import org.opendaylight.netconf.callhome.protocol.CallHomeProtocolSessionContext;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.NetconfClientSession;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@Component(service = { CallHomeMountDispatcher.class, CallHomeNetconfSubsystemListener.class }, immediate = true)
+@Component(service = { CallHomeMountFactory.class, CallHomeNetconfSubsystemListener.class }, immediate = true)
// Non-final for testing
-public class CallHomeMountDispatcher implements NetconfClientDispatcher, CallHomeNetconfSubsystemListener {
- private static final Logger LOG = LoggerFactory.getLogger(CallHomeMountDispatcher.class);
+public class CallHomeMountFactory implements NetconfClientFactory, CallHomeNetconfSubsystemListener {
+ private static final Logger LOG = LoggerFactory.getLogger(CallHomeMountFactory.class);
private final CallHomeMountSessionManager sessionManager = new CallHomeMountSessionManager();
private final String topologyId;
private final EventExecutor eventExecutor;
- private final ScheduledThreadPool keepaliveExecutor;
- private final ThreadPool processingExecutor;
+ private final ScheduledThreadPool scheduledThreadPool;
+ private final ThreadPool processingThreadPool;
private final SchemaResourceManager schemaRepositoryProvider;
private final DataBroker dataBroker;
private final DOMMountPointService mountService;
private final BaseNetconfSchemas baseSchemas;
- public CallHomeMountDispatcher(final String topologyId, final EventExecutor eventExecutor,
- final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor,
+ public CallHomeMountFactory(final String topologyId, final EventExecutor eventExecutor,
+ final ScheduledThreadPool scheduledThreadPool, final ThreadPool processingThreadPool,
final SchemaResourceManager schemaRepositoryProvider, final BaseNetconfSchemas baseSchemas,
final DataBroker dataBroker, final DOMMountPointService mountService,
final NetconfClientConfigurationBuilderFactory builderFactory) {
- this(topologyId, eventExecutor, keepaliveExecutor, processingExecutor, schemaRepositoryProvider, baseSchemas,
- dataBroker, mountService, builderFactory, null);
+ this(topologyId, eventExecutor, scheduledThreadPool, processingThreadPool, schemaRepositoryProvider,
+ baseSchemas, dataBroker, mountService, builderFactory, null);
}
@Activate
- public CallHomeMountDispatcher(
+ public CallHomeMountFactory(
@Reference(target = "(type=global-event-executor)") final EventExecutor eventExecutor,
@Reference(target = "(type=global-netconf-ssh-scheduled-executor)")
- final ScheduledThreadPool keepaliveExecutor,
- @Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingExecutor,
+ final ScheduledThreadPool scheduledThreadPool,
+ @Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingThreadPool,
@Reference final SchemaResourceManager schemaRepositoryProvider,
@Reference final BaseNetconfSchemas baseSchemas, @Reference final DataBroker dataBroker,
@Reference final DOMMountPointService mountService,
- @Reference final NetconfClientConfigurationBuilderFactory builderFactory,
+ @Reference(target = "(type=legacy)") final NetconfClientConfigurationBuilderFactory builderFactory,
@Reference final DeviceActionFactory deviceActionFactory) {
- this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, eventExecutor, keepaliveExecutor, processingExecutor,
+ this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, eventExecutor, scheduledThreadPool, processingThreadPool,
schemaRepositoryProvider, baseSchemas, dataBroker, mountService, builderFactory, deviceActionFactory);
}
- public CallHomeMountDispatcher(final String topologyId, final EventExecutor eventExecutor,
- final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor,
+ public CallHomeMountFactory(final String topologyId, final EventExecutor eventExecutor,
+ final ScheduledThreadPool scheduledThreadPool, final ThreadPool processingThreadPool,
final SchemaResourceManager schemaRepositoryProvider, final BaseNetconfSchemas baseSchemas,
final DataBroker dataBroker, final DOMMountPointService mountService,
final NetconfClientConfigurationBuilderFactory builderFactory,
final DeviceActionFactory deviceActionFactory) {
this.topologyId = topologyId;
this.eventExecutor = eventExecutor;
- this.keepaliveExecutor = keepaliveExecutor;
- this.processingExecutor = processingExecutor;
+ this.scheduledThreadPool = scheduledThreadPool;
+ this.processingThreadPool = processingThreadPool;
this.schemaRepositoryProvider = schemaRepositoryProvider;
this.deviceActionFactory = deviceActionFactory;
this.baseSchemas = requireNonNull(baseSchemas);
@Deprecated
@Override
- public Future<NetconfClientSession> createClient(final NetconfClientConfiguration clientConfiguration) {
+ public ListenableFuture<NetconfClientSession> createClient(final NetconfClientConfiguration clientConfiguration) {
return activateChannel(clientConfiguration);
}
- private Future<NetconfClientSession> activateChannel(final NetconfClientConfiguration conf) {
+ private ListenableFuture<NetconfClientSession> activateChannel(final NetconfClientConfiguration conf) {
final InetSocketAddress remoteAddr = conf.getAddress();
final CallHomeMountSessionContext context = sessionManager().getByAddress(remoteAddr);
LOG.info("Activating NETCONF channel for ip {} device context {}", remoteAddr, context);
- return context == null ? new FailedFuture<>(eventExecutor, new NullPointerException())
+ return context == null ? Futures.immediateFailedFuture(new NullPointerException("context is null"))
: context.activateNetconfChannel(conf.getSessionListener());
}
@VisibleForTesting
void createTopology() {
- topology = new CallHomeTopology(topologyId, this, eventExecutor, keepaliveExecutor, processingExecutor,
+ topology = new CallHomeTopology(topologyId, this, eventExecutor, scheduledThreadPool, processingThreadPool,
schemaRepositoryProvider, dataBroker, mountService, builderFactory, baseSchemas, deviceActionFactory);
}
CallHomeMountSessionManager sessionManager() {
return sessionManager;
}
+
+ @Override
+ public void close() throws Exception {
+ // no action
+ }
}
import static java.util.Objects.requireNonNull;
import com.google.common.base.MoreObjects;
-import io.netty.util.concurrent.Future;
+import com.google.common.util.concurrent.ListenableFuture;
import org.opendaylight.netconf.api.NetconfTerminationReason;
import org.opendaylight.netconf.api.messages.NetconfMessage;
import org.opendaylight.netconf.callhome.protocol.CallHomeChannelActivator;
.build();
}
- Future<NetconfClientSession> activateNetconfChannel(final NetconfClientSessionListener sessionListener) {
+ ListenableFuture<NetconfClientSession> activateNetconfChannel(final NetconfClientSessionListener sessionListener) {
return activator.activate(wrap(sessionListener));
}
import org.opendaylight.controller.config.threadpool.ThreadPool;
import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
// Non-final for mocking
public class CallHomeTopology extends AbstractNetconfTopology {
- public CallHomeTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
- final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
- final ThreadPool processingExecutor, final SchemaResourceManager schemaRepositoryProvider,
+ public CallHomeTopology(final String topologyId, final NetconfClientFactory clientFactory,
+ final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool,
+ final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider,
final DataBroker dataBroker, final DOMMountPointService mountPointService,
final NetconfClientConfigurationBuilderFactory builderFactory, final BaseNetconfSchemas baseSchemas,
final DeviceActionFactory deviceActionFactory) {
- super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor,
+ super(topologyId, clientFactory, eventExecutor, scheduledThreadPool, processingThreadPool,
schemaRepositoryProvider, dataBroker, mountPointService, builderFactory, deviceActionFactory,
baseSchemas);
}
private static final Logger LOG = LoggerFactory.getLogger(IetfZeroTouchCallHomeServerProvider.class);
private final DataBroker dataBroker;
- private final CallHomeMountDispatcher mountDispacher;
+ private final CallHomeMountFactory mountDispacher;
private final CallHomeAuthProviderImpl authProvider;
private final CallhomeStatusReporter statusReporter;
private final int port;
@Activate
public IetfZeroTouchCallHomeServerProvider(@Reference final DataBroker dataBroker,
- @Reference final CallHomeMountDispatcher mountDispacher) {
+ @Reference final CallHomeMountFactory mountDispacher) {
// FIXME: make this configurable
this(dataBroker, mountDispacher, Uint16.valueOf(4334));
}
public IetfZeroTouchCallHomeServerProvider(final DataBroker dataBroker,
- final CallHomeMountDispatcher mountDispacher, final Uint16 port) {
+ final CallHomeMountFactory mountFactory, final Uint16 port) {
this.dataBroker = requireNonNull(dataBroker);
- this.mountDispacher = requireNonNull(mountDispacher);
+ this.mountDispacher = requireNonNull(mountFactory);
LOG.info("Setting port for call home server to {}", port);
this.port = port.toJava();
*/
package org.opendaylight.netconf.callhome.mount;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import io.netty.util.concurrent.EventExecutor;
-import io.netty.util.concurrent.Future;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
+import java.util.concurrent.ExecutionException;
import org.junit.Before;
import org.junit.Test;
import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
import org.opendaylight.netconf.api.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.netconf.callhome.protocol.CallHomeChannelActivator;
import org.opendaylight.netconf.callhome.protocol.CallHomeProtocolSessionContext;
-import org.opendaylight.netconf.client.NetconfClientSession;
import org.opendaylight.netconf.client.NetconfClientSessionListener;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
-public class CallHomeMountDispatcherTest {
+public class CallHomeMountFactoryTest {
private String topologyId;
private EventExecutor mockExecutor;
private ScheduledThreadPool mockKeepAlive;
private ThreadPool mockProcessingExecutor;
private SchemaResourceManager mockSchemaRepoProvider;
- private CallHomeMountDispatcher instance;
+ private CallHomeMountFactory instance;
private DataBroker mockDataBroker;
private DOMMountPointService mockMount;
mockBuilderFactory = mock(NetconfClientConfigurationBuilderFactory .class);
mockBaseSchemas = mock(BaseNetconfSchemas.class);
- instance = new CallHomeMountDispatcher(topologyId, mockExecutor, mockKeepAlive,
+ instance = new CallHomeMountFactory(topologyId, mockExecutor, mockKeepAlive,
mockProcessingExecutor, mockSchemaRepoProvider, mockBaseSchemas, mockDataBroker, mockMount,
mockBuilderFactory) {
@Override
}
@Test
- public void noSessionIsCreatedWithoutAContextAvailableForAGivenAddress() {
+ public void noSessionIsCreatedWithoutAContextAvailableForAGivenAddress() throws Exception {
// given
final InetSocketAddress someAddress = InetSocketAddress.createUnresolved("1.2.3.4", 123);
final NetconfClientConfiguration someCfg = someConfiguration(someAddress);
// when
- final Future<NetconfClientSession> future = instance.createClient(someCfg);
+ final var future = instance.createClient(someCfg);
// then
- assertFalse(future.isSuccess());
+ assertNotNull(future);
+ assertThrows(ExecutionException.class, future::get);
}
@Test
import org.opendaylight.mdsal.binding.api.RpcProviderService;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
@Inject
@Activate
public NetconfTopologyImpl(
- @Reference(target = "(type=netconf-client-dispatcher)") final NetconfClientDispatcher clientDispatcher,
+ @Reference(target = "(type=netconf-client-factory)") final NetconfClientFactory clientFactory,
@Reference(target = "(type=global-event-executor)") final EventExecutor eventExecutor,
@Reference(target = "(type=global-netconf-ssh-scheduled-executor)")
- final ScheduledThreadPool keepaliveExecutor,
- @Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingExecutor,
+ final ScheduledThreadPool scheduledThreadPool,
+ @Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingThreadPool,
@Reference final SchemaResourceManager schemaRepositoryProvider, @Reference final DataBroker dataBroker,
@Reference final DOMMountPointService mountPointService,
@Reference final AAAEncryptionService encryptionService,
- @Reference final NetconfClientConfigurationBuilderFactory builderFactory,
+ @Reference(target = "(type=default)") final NetconfClientConfigurationBuilderFactory builderFactory,
@Reference final RpcProviderService rpcProviderService, @Reference final BaseNetconfSchemas baseSchemas,
@Reference final DeviceActionFactory deviceActionFactory) {
- this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, clientDispatcher, eventExecutor, keepaliveExecutor,
- processingExecutor, schemaRepositoryProvider, dataBroker, mountPointService, encryptionService,
+ this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, clientFactory, eventExecutor, scheduledThreadPool,
+ processingThreadPool, schemaRepositoryProvider, dataBroker, mountPointService, encryptionService,
builderFactory, rpcProviderService, baseSchemas, deviceActionFactory);
}
- public NetconfTopologyImpl(final String topologyId, final NetconfClientDispatcher clientDispatcher,
- final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
- final ThreadPool processingExecutor, final SchemaResourceManager schemaRepositoryProvider,
+ public NetconfTopologyImpl(final String topologyId, final NetconfClientFactory clientclientFactory,
+ final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool,
+ final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider,
final DataBroker dataBroker, final DOMMountPointService mountPointService,
final AAAEncryptionService encryptionService, final NetconfClientConfigurationBuilderFactory builderFactory,
final RpcProviderService rpcProviderService, final BaseNetconfSchemas baseSchemas) {
- this(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor,
+ this(topologyId, clientclientFactory, eventExecutor, scheduledThreadPool, processingThreadPool,
schemaRepositoryProvider, dataBroker, mountPointService, encryptionService, builderFactory,
rpcProviderService, baseSchemas, null);
}
@SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR",
justification = "DTCL registration of 'this'")
- public NetconfTopologyImpl(final String topologyId, final NetconfClientDispatcher clientDispatcher,
- final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
- final ThreadPool processingExecutor, final SchemaResourceManager schemaRepositoryProvider,
+ public NetconfTopologyImpl(final String topologyId, final NetconfClientFactory clientFactory,
+ final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool,
+ final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider,
final DataBroker dataBroker, final DOMMountPointService mountPointService,
final AAAEncryptionService encryptionService, final NetconfClientConfigurationBuilderFactory builderFactory,
final RpcProviderService rpcProviderService, final BaseNetconfSchemas baseSchemas,
final DeviceActionFactory deviceActionFactory) {
- super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor,
+ super(topologyId, clientFactory, eventExecutor, scheduledThreadPool, processingThreadPool,
schemaRepositoryProvider, dataBroker, mountPointService, builderFactory, deviceActionFactory, baseSchemas);
LOG.debug("Registering datastore listener");
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
import org.opendaylight.netconf.client.mdsal.impl.DefaultBaseNetconfSchemas;
InstanceIdentifier.builder(NetworkTopology.class).child(Topology.class, TOPOLOGY_KEY).build();
@Mock
- private NetconfClientDispatcher mockedClientDispatcher;
+ private NetconfClientFactory mockedClientFactory;
@Mock
private EventExecutor mockedEventExecutor;
@Mock
doReturn(wtx).when(dataBroker).newWriteOnlyTransaction();
doReturn(CommitInfo.emptyFluentFuture()).when(wtx).commit();
- topology = new TestingNetconfTopologyImpl(TOPOLOGY_KEY.getTopologyId().getValue(), mockedClientDispatcher,
+ topology = new TestingNetconfTopologyImpl(TOPOLOGY_KEY.getTopologyId().getValue(), mockedClientFactory,
mockedEventExecutor, mockedKeepaliveExecutor, mockedProcessingExecutor, mockedResourceManager, dataBroker,
mountPointService, encryptionService, builderFactory, rpcProviderService,
new DefaultBaseNetconfSchemas(new DefaultYangParserFactory()));
}
private static class TestingNetconfTopologyImpl extends NetconfTopologyImpl {
- TestingNetconfTopologyImpl(final String topologyId, final NetconfClientDispatcher clientDispatcher,
- final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
- final ThreadPool processingExecutor, final SchemaResourceManager schemaRepositoryProvider,
+ TestingNetconfTopologyImpl(final String topologyId, final NetconfClientFactory clientFactory,
+ final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool,
+ final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider,
final DataBroker dataBroker, final DOMMountPointService mountPointService,
final AAAEncryptionService encryptionService,
final NetconfClientConfigurationBuilderFactory builderFactory,
final RpcProviderService rpcProviderService, final BaseNetconfSchemas baseSchemas) {
- super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor,
+ super(topologyId, clientFactory, eventExecutor, scheduledThreadPool, processingThreadPool,
schemaRepositoryProvider, dataBroker, mountPointService, encryptionService, builderFactory,
rpcProviderService, baseSchemas);
}
// Instantiate the handler ...
masterSalFacade = createSalFacade(netconfNode.requireLockDatastore());
- nodeHandler = new NetconfNodeHandler(setup.getNetconfClientDispatcher(), setup.getEventExecutor(),
+ nodeHandler = new NetconfNodeHandler(setup.getNetconfClientFactory(),
setup.getKeepaliveExecutor(), setup.getBaseSchemas(), schemaManager, setup.getProcessingExecutor(),
builderFactory, deviceActionFactory, masterSalFacade, remoteDeviceId, configNode.getNodeId(), netconfNode,
nodeOptional);
import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
private final Executor processingExecutor;
private final ActorSystem actorSystem;
private final EventExecutor eventExecutor;
- private final NetconfClientDispatcher clientDispatcher;
+ private final NetconfClientFactory clientFactory;
private final String topologyId;
private final Duration writeTxIdleTimeout;
private final DOMMountPointService mountPointService;
@Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingExecutor,
@Reference final ActorSystemProvider actorSystemProvider,
@Reference(target = "(type=global-event-executor)") final EventExecutor eventExecutor,
- @Reference(target = "(type=netconf-client-dispatcher)") final NetconfClientDispatcher clientDispatcher,
+ @Reference(target = "(type=netconf-client-factory)") final NetconfClientFactory clientFactory,
@Reference final DOMMountPointService mountPointService,
@Reference final AAAEncryptionService encryptionService,
@Reference final RpcProviderService rpcProviderService,
@Reference final NetconfClientConfigurationBuilderFactory builderFactory,
final Configuration configuration) {
this(baseSchemas, dataBroker, clusterSingletonServiceProvider, keepaliveExecutor.getExecutor(),
- processingExecutor.getExecutor(), actorSystemProvider.getActorSystem(), eventExecutor, clientDispatcher,
+ processingExecutor.getExecutor(), actorSystemProvider.getActorSystem(), eventExecutor, clientFactory,
mountPointService, encryptionService, rpcProviderService, deviceActionFactory, resourceManager,
builderFactory, configuration.topology$_$id(),
Uint16.valueOf(configuration.write$_$transaction$_$idle$_$timeout()));
final ClusterSingletonServiceProvider clusterSingletonServiceProvider,
final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor,
final ActorSystemProvider actorSystemProvider, final EventExecutor eventExecutor,
- final NetconfClientDispatcher clientDispatcher, final DOMMountPointService mountPointService,
+ final NetconfClientFactory clientFactory, final DOMMountPointService mountPointService,
final AAAEncryptionService encryptionService, final RpcProviderService rpcProviderService,
final DeviceActionFactory deviceActionFactory, final SchemaResourceManager resourceManager,
final NetconfClientConfigurationBuilderFactory builderFactory) {
this(baseSchemas, dataBroker, clusterSingletonServiceProvider, keepaliveExecutor.getExecutor(),
- processingExecutor.getExecutor(), actorSystemProvider.getActorSystem(), eventExecutor, clientDispatcher,
+ processingExecutor.getExecutor(), actorSystemProvider.getActorSystem(), eventExecutor, clientFactory,
mountPointService, encryptionService, rpcProviderService, deviceActionFactory, resourceManager,
builderFactory, NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, Uint16.ZERO);
}
final ClusterSingletonServiceProvider clusterSingletonServiceProvider,
final ScheduledExecutorService keepaliveExecutor, final Executor processingExecutor,
final ActorSystem actorSystem, final EventExecutor eventExecutor,
- final NetconfClientDispatcher clientDispatcher, final DOMMountPointService mountPointService,
+ final NetconfClientFactory clientFactory, final DOMMountPointService mountPointService,
final AAAEncryptionService encryptionService, final RpcProviderService rpcProviderService,
final DeviceActionFactory deviceActionFactory, final SchemaResourceManager resourceManager,
final NetconfClientConfigurationBuilderFactory builderFactory, final String topologyId,
this.processingExecutor = requireNonNull(processingExecutor);
this.actorSystem = requireNonNull(actorSystem);
this.eventExecutor = requireNonNull(eventExecutor);
- this.clientDispatcher = requireNonNull(clientDispatcher);
+ this.clientFactory = requireNonNull(clientFactory);
this.topologyId = requireNonNull(topologyId);
writeTxIdleTimeout = Duration.ofSeconds(writeTransactionIdleTimeout.toJava());
this.mountPointService = mountPointService;
.setKeepaliveExecutor(keepaliveExecutor)
.setProcessingExecutor(processingExecutor)
.setTopologyId(topologyId)
- .setNetconfClientDispatcher(clientDispatcher)
+ .setNetconfClientFactory(clientFactory)
.setSchemaResourceDTO(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(),
deviceId))
.setIdleTimeout(writeTxIdleTimeout)
import java.util.concurrent.ScheduledExecutorService;
import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.mdsal.NetconfDevice;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
private final Executor processingExecutor;
private final ActorSystem actorSystem;
private final EventExecutor eventExecutor;
- private final NetconfClientDispatcher netconfClientDispatcher;
+ private final NetconfClientFactory netconfClientFactory;
private final String topologyId;
private final NetconfDevice.SchemaResourcesDTO schemaResourceDTO;
private final Duration idleTimeout;
processingExecutor = builder.getProcessingExecutor();
actorSystem = builder.getActorSystem();
eventExecutor = builder.getEventExecutor();
- netconfClientDispatcher = builder.getNetconfClientDispatcher();
+ netconfClientFactory = builder.getNetconfClientFactory();
topologyId = builder.getTopologyId();
schemaResourceDTO = builder.getSchemaResourceDTO();
idleTimeout = builder.getIdleTimeout();
return topologyId;
}
- public NetconfClientDispatcher getNetconfClientDispatcher() {
- return netconfClientDispatcher;
+ public NetconfClientFactory getNetconfClientFactory() {
+ return netconfClientFactory;
}
public NetconfDevice.SchemaResourcesDTO getSchemaResourcesDTO() {
private ActorSystem actorSystem;
private EventExecutor eventExecutor;
private String topologyId;
- private NetconfClientDispatcher netconfClientDispatcher;
+ private NetconfClientFactory netconfClientFactory;
private NetconfDevice.SchemaResourcesDTO schemaResourceDTO;
private Duration idleTimeout;
private BaseNetconfSchemas baseSchemas;
return this;
}
- NetconfClientDispatcher getNetconfClientDispatcher() {
- return netconfClientDispatcher;
+ NetconfClientFactory getNetconfClientFactory() {
+ return netconfClientFactory;
}
- public NetconfTopologySetupBuilder setNetconfClientDispatcher(final NetconfClientDispatcher clientDispatcher) {
- netconfClientDispatcher = clientDispatcher;
+ public NetconfTopologySetupBuilder setNetconfClientFactory(final NetconfClientFactory clientFactory) {
+ netconfClientFactory = clientFactory;
return this;
}
import com.google.common.util.concurrent.SettableFuture;
import com.typesafe.config.ConfigFactory;
import io.netty.util.concurrent.EventExecutor;
-import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.io.File;
import java.util.Iterator;
import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier;
import org.opendaylight.mdsal.singleton.dom.impl.DOMClusterSingletonServiceProviderImpl;
import org.opendaylight.netconf.api.CapabilityURN;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities;
import org.opendaylight.netconf.client.mdsal.NetconfDeviceSchema;
import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
@Mock private RpcProviderService mockRpcProviderService;
@Mock private Registration mockRpcReg;
- @Mock private NetconfClientDispatcher mockClientDispatcher;
+ @Mock private NetconfClientFactory mockClientFactory;
@Mock private AAAEncryptionService mockEncryptionService;
@Mock private ScheduledExecutorService mockKeepaliveExecutor;
@Mock private DeviceActionFactory deviceActionFactory;
setupSlave();
yangNodeInstanceId = bindingToNormalized.toYangInstanceIdentifier(NODE_INSTANCE_ID);
- doReturn(mock(Future.class)).when(mockClientDispatcher).createClient(any());
+ doReturn(mock(ListenableFuture.class)).when(mockClientFactory).createClient(any());
LOG.info("****** Setup complete");
}
masterNetconfTopologyManager = new NetconfTopologyManager(BASE_SCHEMAS, masterDataBroker,
masterClusterSingletonServiceProvider, mockKeepaliveExecutor, MoreExecutors.directExecutor(),
- masterSystem, eventExecutor, mockClientDispatcher, masterMountPointService,
+ masterSystem, eventExecutor, mockClientFactory, masterMountPointService,
mockEncryptionService, mockRpcProviderService, deviceActionFactory, resourceManager, builderFactory,
TOPOLOGY_ID, Uint16.ZERO) {
@Override
slaveNetconfTopologyManager = new NetconfTopologyManager(BASE_SCHEMAS, slaveDataBroker,
mockSlaveClusterSingletonServiceProvider, mockKeepaliveExecutor, MoreExecutors.directExecutor(),
- slaveSystem, eventExecutor, mockClientDispatcher, slaveMountPointService,
+ slaveSystem, eventExecutor, mockClientFactory, slaveMountPointService,
mockEncryptionService, mockRpcProviderService, deviceActionFactory, resourceManager, builderFactory,
TOPOLOGY_ID, Uint16.ZERO) {
@Override
import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
import org.opendaylight.netconf.client.mdsal.impl.DefaultSchemaResourceManager;
import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologySetup;
final ExecutorService processingService = mock(ExecutorService.class);
final ActorSystem actorSystem = mock(ActorSystem.class);
final EventExecutor eventExecutor = mock(EventExecutor.class);
- final NetconfClientDispatcher clientDispatcher = mock(NetconfClientDispatcher.class);
+ final NetconfClientFactory clientFactory = mock(NetconfClientFactory.class);
final DOMMountPointService mountPointService = mock(DOMMountPointService.class);
final AAAEncryptionService encryptionService = mock(AAAEncryptionService.class);
final DeviceActionFactory deviceActionFactory = mock(DeviceActionFactory.class);
doReturn(mockRpcReg).when(rpcProviderService).registerRpcImplementations(any());
netconfTopologyManager = new NetconfTopologyManager(BASE_SCHEMAS, dataBroker, clusterSingletonServiceProvider,
- keepaliveExecutor, processingService, actorSystem, eventExecutor, clientDispatcher,
+ keepaliveExecutor, processingService, actorSystem, eventExecutor, clientFactory,
mountPointService, encryptionService, rpcProviderService, deviceActionFactory,
new DefaultSchemaResourceManager(new DefaultYangParserFactory()), builderFactory,
TOPOLOGY_ID, Uint16.ZERO) {
import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
private final HashMap<NodeId, NetconfNodeHandler> activeConnectors = new HashMap<>();
- private final NetconfClientDispatcher clientDispatcher;
+ private final NetconfClientFactory clientFactory;
private final EventExecutor eventExecutor;
private final DeviceActionFactory deviceActionFactory;
private final SchemaResourceManager schemaManager;
private final BaseNetconfSchemas baseSchemas;
private final NetconfClientConfigurationBuilderFactory builderFactory;
- protected final ScheduledExecutorService keepaliveExecutor;
+ protected final ScheduledExecutorService scheduledExecutor;
protected final Executor processingExecutor;
protected final DataBroker dataBroker;
protected final DOMMountPointService mountPointService;
protected final String topologyId;
- protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
- final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
- final ThreadPool processingExecutor, final SchemaResourceManager schemaManager, final DataBroker dataBroker,
- final DOMMountPointService mountPointService, final NetconfClientConfigurationBuilderFactory builderFactory,
+ protected AbstractNetconfTopology(final String topologyId, final NetconfClientFactory clientDispatcher,
+ final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool,
+ final ThreadPool processingThreadPool, final SchemaResourceManager schemaManager,
+ final DataBroker dataBroker, final DOMMountPointService mountPointService,
+ final NetconfClientConfigurationBuilderFactory builderFactory,
final DeviceActionFactory deviceActionFactory, final BaseNetconfSchemas baseSchemas) {
this.topologyId = requireNonNull(topologyId);
- this.clientDispatcher = clientDispatcher;
+ this.clientFactory = clientDispatcher;
this.eventExecutor = eventExecutor;
- this.keepaliveExecutor = keepaliveExecutor.getExecutor();
- this.processingExecutor = processingExecutor.getExecutor();
+ this.scheduledExecutor = scheduledThreadPool.getExecutor();
+ this.processingExecutor = processingThreadPool.getExecutor();
this.schemaManager = requireNonNull(schemaManager);
this.deviceActionFactory = deviceActionFactory;
this.dataBroker = requireNonNull(dataBroker);
final NetconfNodeHandler nodeHandler;
try {
- nodeHandler = new NetconfNodeHandler(clientDispatcher, eventExecutor, keepaliveExecutor, baseSchemas,
- schemaManager, processingExecutor, builderFactory, deviceActionFactory, deviceSalFacade, deviceId,
- nodeId, netconfNode, nodeOptional);
+ nodeHandler = new NetconfNodeHandler(clientFactory, scheduledExecutor, baseSchemas,
+ schemaManager, processingExecutor, builderFactory, deviceActionFactory, deviceSalFacade,
+ deviceId, nodeId, netconfNode, nodeOptional);
} catch (IllegalArgumentException e) {
// This is a workaround for NETCONF-1114 where the encrypted password's lexical structure is not enforced
// in the datastore and it ends up surfacing when we decrypt the password.
import org.osgi.service.component.annotations.Reference;
/**
- * Default implementation of NetconfClientConfigurationBuildFactory.
+ * Legacy implementation of NetconfClientConfigurationBuildFactory.
+ *
+ * @deprecated as outdated. Should be replaced with {@link NetconfClientConfigurationBuilderFactoryImpl} once
+ * callhome-provider is migrated to transport-api.
*/
-@Component
+@Component(service = NetconfClientConfigurationBuilderFactory.class, property = "type=legacy")
@Singleton
+@Deprecated(forRemoval = true)
public final class DefaultNetconfClientConfigurationBuilderFactory implements NetconfClientConfigurationBuilderFactory {
private final SslHandlerFactoryProvider sslHandlerFactoryProvider;
private final AAAEncryptionService encryptionService;
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.topology.spi;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.io.StringReader;
+import java.security.KeyPair;
+import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.opendaylight.aaa.encrypt.AAAEncryptionService;
+import org.opendaylight.aaa.encrypt.PKIUtil;
+import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
+import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
+import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
+import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
+import org.opendaylight.netconf.shaded.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
+import org.opendaylight.netconf.shaded.sshd.common.keyprovider.KeyIdentityProvider;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.password.grouping.password.type.CleartextPasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.SshClientParametersBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.TcpClientParametersBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.ClientIdentity;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.ClientIdentityBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.client.identity.PasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.connection.parameters.Protocol.Name;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.Credentials;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.KeyAuth;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.LoginPw;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.LoginPwUnencrypted;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Default implementation of NetconfClientConfigurationBuildFactory.
+ */
+@Component(service = NetconfClientConfigurationBuilderFactory.class, property = "type=default")
+@Singleton
+public final class NetconfClientConfigurationBuilderFactoryImpl implements NetconfClientConfigurationBuilderFactory {
+ private final SslHandlerFactoryProvider sslHandlerFactoryProvider;
+ private final AAAEncryptionService encryptionService;
+ private final CredentialProvider credentialProvider;
+
+ @Inject
+ @Activate
+ public NetconfClientConfigurationBuilderFactoryImpl(
+ @Reference final AAAEncryptionService encryptionService,
+ @Reference final CredentialProvider credentialProvider,
+ @Reference final SslHandlerFactoryProvider sslHandlerFactoryProvider) {
+ this.encryptionService = requireNonNull(encryptionService);
+ this.credentialProvider = requireNonNull(credentialProvider);
+ this.sslHandlerFactoryProvider = requireNonNull(sslHandlerFactoryProvider);
+ }
+
+ @Override
+ public NetconfClientConfigurationBuilder createClientConfigurationBuilder(final NodeId nodeId,
+ final NetconfNode node) {
+ final var builder = NetconfClientConfigurationBuilder.create();
+ final var protocol = node.getProtocol();
+ if (node.requireTcpOnly()) {
+ builder.withProtocol(NetconfClientProtocol.TCP);
+ } else if (protocol == null || protocol.getName() == Name.SSH) {
+ builder.withProtocol(NetconfClientProtocol.SSH);
+ setSshParametersFromCredentials(builder, node.getCredentials());
+ } else if (protocol.getName() == Name.TLS) {
+ builder.withProtocol(NetconfClientProtocol.TLS).withTransportSslHandlerFactory(channel -> {
+ final var sslHandlerBuilder =
+ sslHandlerFactoryProvider.getSslHandlerFactory(protocol.getSpecification());
+ return sslHandlerBuilder.createSslHandler();
+ });
+ } else {
+ throw new IllegalArgumentException("Unsupported protocol type: " + protocol.getName());
+ }
+
+ final var helloCapabilities = node.getOdlHelloMessageCapabilities();
+ if (helloCapabilities != null) {
+ builder.withOdlHelloCapabilities(List.copyOf(helloCapabilities.requireCapability()));
+ }
+
+ return builder
+ .withName(nodeId.getValue())
+ .withTcpParameters(new TcpClientParametersBuilder()
+ .setRemoteAddress(node.requireHost())
+ .setRemotePort(node.requirePort()).build())
+ .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava());
+ }
+
+ private void setSshParametersFromCredentials(final NetconfClientConfigurationBuilder confBuilder,
+ final Credentials credentials) {
+ final var sshParamsBuilder = new SshClientParametersBuilder();
+ if (credentials instanceof LoginPwUnencrypted unencrypted) {
+ final var loginPassword = unencrypted.getLoginPasswordUnencrypted();
+ sshParamsBuilder.setClientIdentity(loginPasswordIdentity(
+ loginPassword.getUsername(), loginPassword.getPassword()));
+
+ } else if (credentials instanceof LoginPw loginPw) {
+ final var loginPassword = loginPw.getLoginPassword();
+ sshParamsBuilder.setClientIdentity(loginPasswordIdentity(
+ loginPassword.getUsername(), encryptionService.decrypt(loginPassword.getPassword())));
+
+ } else if (credentials instanceof KeyAuth keyAuth) {
+ final var keyBased = keyAuth.getKeyBased();
+ sshParamsBuilder.setClientIdentity(new ClientIdentityBuilder().setUsername(keyBased.getUsername()).build());
+ confBuilder.withSshConfigurator(factoryMgr -> {
+ final var keyPair = getKeyPair(keyBased.getKeyId(), credentialProvider, encryptionService);
+ factoryMgr.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keyPair));
+ final var factory = new UserAuthPublicKeyFactory();
+ factory.setSignatureFactories(factoryMgr.getSignatureFactories());
+ factoryMgr.setUserAuthFactories(List.of(factory));
+ });
+ } else {
+ throw new IllegalArgumentException("Unsupported credential type: " + credentials.getClass());
+ }
+ confBuilder.withSshParameters(sshParamsBuilder.build());
+ }
+
+ private static ClientIdentity loginPasswordIdentity(final String username, final String password) {
+ requireNonNull(username, "username is undefined");
+ requireNonNull(password, "password is undefined");
+ return new ClientIdentityBuilder()
+ .setUsername(username)
+ .setPassword(new PasswordBuilder()
+ .setPasswordType(new CleartextPasswordBuilder().setCleartextPassword(password).build())
+ .build())
+ .build();
+ }
+
+ private static KeyPair getKeyPair(final String keyId,
+ final CredentialProvider credentialProvider, final AAAEncryptionService encryptionService) {
+
+ // public key retrieval logic taken from DatastoreBackedPublicKeyAuth
+ final var dsKeypair = credentialProvider.credentialForId(keyId);
+ if (dsKeypair == null) {
+ throw new IllegalArgumentException("No keypair found with keyId=" + keyId);
+ }
+ final var passPhrase = Strings.isNullOrEmpty(dsKeypair.getPassphrase()) ? "" : dsKeypair.getPassphrase();
+ try {
+ return new PKIUtil().decodePrivateKey(
+ new StringReader(encryptionService.decrypt(dsKeypair.getPrivateKey()).replace("\\n", "\n")),
+ encryptionService.decrypt(passPhrase));
+ } catch (IOException e) {
+ throw new IllegalStateException("Could not decode private key with keyId=" + keyId, e);
+ }
+ }
+}
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
-import io.netty.util.concurrent.EventExecutor;
-import io.netty.util.concurrent.Future;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.checkerframework.checker.lock.qual.Holding;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.dom.api.DOMNotification;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas;
import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
import org.opendaylight.netconf.client.mdsal.spi.KeepaliveSalFacade;
+import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev221225.NetconfNodeAugmentedOptional;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode;
private static final Logger LOG = LoggerFactory.getLogger(NetconfNodeHandler.class);
private final @NonNull List<SchemaSourceRegistration<?>> yanglibRegistrations;
- private final @NonNull NetconfClientDispatcher clientDispatcher;
+ private final @NonNull NetconfClientFactory clientFactory;
private final @NonNull NetconfClientConfiguration clientConfig;
private final @NonNull NetconfDeviceCommunicator communicator;
private final @NonNull RemoteDeviceHandler delegate;
- private final @NonNull EventExecutor eventExecutor;
+ private final @NonNull ListeningScheduledExecutorService scheduledExecutor;
private final @NonNull RemoteDeviceId deviceId;
private final long maxAttempts;
@GuardedBy("this")
private long lastSleep;
@GuardedBy("this")
- private Future<?> currentTask;
+ private ListenableFuture<?> currentTask;
- public NetconfNodeHandler(final NetconfClientDispatcher clientDispatcher, final EventExecutor eventExecutor,
- final ScheduledExecutorService keepaliveExecutor, final BaseNetconfSchemas baseSchemas,
+ public NetconfNodeHandler(final NetconfClientFactory clientFactory,
+ final ScheduledExecutorService scheduledExecutor, final BaseNetconfSchemas baseSchemas,
final SchemaResourceManager schemaManager, final Executor processingExecutor,
final NetconfClientConfigurationBuilderFactory builderFactory,
final DeviceActionFactory deviceActionFactory, final RemoteDeviceHandler delegate,
final RemoteDeviceId deviceId, final NodeId nodeId, final NetconfNode node,
final NetconfNodeAugmentedOptional nodeOptional) {
- this.clientDispatcher = requireNonNull(clientDispatcher);
- this.eventExecutor = requireNonNull(eventExecutor);
+ this.clientFactory = requireNonNull(clientFactory);
+ // FIXME: do not wrap this executor
+ this.scheduledExecutor = MoreExecutors.listeningDecorator(scheduledExecutor);
this.delegate = requireNonNull(delegate);
this.deviceId = requireNonNull(deviceId);
final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
if (keepaliveDelay > 0) {
LOG.info("Adding keepalive facade, for device {}", nodeId);
- salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, this, keepaliveExecutor, keepaliveDelay,
+ salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, this, scheduledExecutor, keepaliveDelay,
node.requireDefaultRequestTimeoutMillis().toJava());
} else {
salFacade = this;
@Holding("this")
private void lockedConnect() {
- currentTask = clientDispatcher.createClient(clientConfig);
- currentTask.addListener(this::connectComplete);
+ try {
+ final var clientFuture = clientFactory.createClient(clientConfig);
+ clientFuture.addListener(() -> connectComplete(clientFuture), MoreExecutors.directExecutor());
+ currentTask = clientFuture;
+ } catch (UnsupportedConfigurationException e) {
+ onDeviceFailed(e);
+ }
}
- private void connectComplete(final Future<?> future) {
+ private void connectComplete(final ListenableFuture<?> future) {
// Locked manipulation of internal state
synchronized (this) {
// A quick sanity check
LOG.warn("Ignoring connection completion, expected {} actual {}", future, currentTask);
return;
}
-
currentTask = null;
- final var cause = future.cause();
- if (cause == null || cause instanceof CancellationException) {
- // Success or cancellation, nothing else to do.
- // In case of success the rest of the setup is driven by RemoteDeviceHandler callbacks
- return;
+ // ListenableFuture provide no detail on error unless you attempt to get() the result
+ // then only the original exception is rethrown wrapped with ExecutionException
+ try {
+ if (future.isCancelled() || future.isDone() && future.get() != null) {
+ // Success or cancellation, nothing else to do.
+ // In case of success the rest of the setup is driven by RemoteDeviceHandler callbacks
+ return;
+ }
+ } catch (InterruptedException | ExecutionException e) {
+ LOG.debug("Connection attempt {} to {} failed", attempts, deviceId, e);
}
-
- LOG.debug("Connection attempt {} to {} failed", attempts, deviceId, cause);
}
// We are invoking callbacks, do not hold locks
// Schedule a task for the right time. We always go through the executor to eliminate the special case of
// immediate reconnect. While we could check and got to lockedConnect(), it makes for a rare special case.
- // That special case makes for more code paths to test and introduces additional uncertainty as to whether
- // the attempt was executed on on this thread or not.
- currentTask = eventExecutor.schedule(this::reconnect, delayMillis, TimeUnit.MILLISECONDS);
+ // That special case makes for more code paths to test and introduces additional uncertainty whether
+ // the attempt was executed on this thread or not.
+ currentTask = scheduledExecutor.schedule(this::reconnect, delayMillis, TimeUnit.MILLISECONDS);
return null;
}
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.topology.spi;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.NoSuchElementException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.aaa.encrypt.AAAEncryptionService;
+import org.opendaylight.netconf.client.NetconfClientSessionListener;
+import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
+import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
+import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
+import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.connection.parameters.Protocol.Name;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.connection.parameters.ProtocolBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.LoginPwUnencryptedBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencryptedBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
+import org.opendaylight.yangtools.yang.common.Decimal64;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
+
+@ExtendWith(MockitoExtension.class)
+class NetconfClientConfigurationBuilderFactoryImplTest {
+ private static final NodeId NODE_ID = new NodeId("testing-node");
+ private static final Host HOST = new Host(new IpAddress(new Ipv4Address("127.0.0.1")));
+ private static final PortNumber PORT = new PortNumber(Uint16.valueOf(9999));
+
+ @Mock
+ private NetconfClientSessionListener sessionListener;
+ @Mock
+ private AAAEncryptionService encryptionService;
+ @Mock
+ private CredentialProvider credentialProvider;
+ @Mock
+ private SslHandlerFactoryProvider sslHandlerFactoryProvider;
+
+ private NetconfNodeBuilder nodeBuilder;
+ private NetconfClientConfigurationBuilderFactoryImpl factory;
+
+ @BeforeEach
+ void beforeEach() {
+ nodeBuilder = new NetconfNodeBuilder()
+ .setHost(HOST).setPort(PORT)
+ .setReconnectOnChangedSchema(true)
+ .setDefaultRequestTimeoutMillis(Uint32.valueOf(1000))
+ .setBetweenAttemptsTimeoutMillis(Uint16.valueOf(100))
+ .setKeepaliveDelay(Uint32.valueOf(1000))
+ .setCredentials(new LoginPwUnencryptedBuilder()
+ .setLoginPasswordUnencrypted(new LoginPasswordUnencryptedBuilder()
+ .setUsername("test-user")
+ .setPassword("test-password")
+ .build())
+ .build())
+ .setMaxConnectionAttempts(Uint32.ZERO)
+ .setSleepFactor(Decimal64.valueOf("1.5"))
+ .setConnectionTimeoutMillis(Uint32.valueOf(20000));
+ factory = new NetconfClientConfigurationBuilderFactoryImpl(encryptionService, credentialProvider,
+ sslHandlerFactoryProvider);
+ }
+
+ private void assertConfig(NetconfClientConfiguration config) {
+ assertNotNull(config);
+ assertNotNull(config.getTcpParameters());
+ assertEquals(HOST, config.getTcpParameters().getRemoteAddress());
+ assertEquals(PORT, config.getTcpParameters().getRemotePort());
+ assertSame(sessionListener, config.getSessionListener());
+ }
+
+ @Test
+ void testDefault() {
+ final var config = createConfig(nodeBuilder.setTcpOnly(false).build());
+ assertConfig(config);
+ assertEquals(NetconfClientProtocol.SSH, config.getProtocol());
+ assertNotNull(config.getSshParameters());
+ }
+
+ @Test
+ void testSsh() {
+ final var config = createConfig(
+ nodeBuilder.setTcpOnly(false).setProtocol(new ProtocolBuilder().setName(Name.SSH).build()).build());
+ assertConfig(config);
+ assertEquals(NetconfClientProtocol.SSH, config.getProtocol());
+ assertNotNull(config.getSshParameters());
+ }
+
+ @Test
+ void testTcp() {
+ final var config = createConfig(nodeBuilder.setTcpOnly(true).build());
+ assertConfig(config);
+ assertEquals(NetconfClientProtocol.TCP, config.getProtocol());
+ }
+
+ @Test
+ void testTls() {
+ final var config = createConfig(
+ nodeBuilder.setTcpOnly(false).setProtocol(new ProtocolBuilder().setName(Name.TLS).build()).build());
+ assertConfig(config);
+ assertEquals(NetconfClientProtocol.TLS, config.getProtocol());
+ assertNotNull(config.getTransportSslHandlerFactory());
+ }
+
+ @Test
+ void noPort() {
+ assertThrows(NoSuchElementException.class, () -> createConfig(nodeBuilder.setPort(null).build()));
+ }
+
+ @Test
+ void noHost() {
+ assertThrows(NoSuchElementException.class, () -> createConfig(nodeBuilder.setHost(null).build()));
+ }
+
+ private NetconfClientConfiguration createConfig(final NetconfNode netconfNode) {
+ return factory.createClientConfigurationBuilder(NODE_ID, netconfNode)
+ .withSessionListener(sessionListener)
+ .build();
+ }
+}
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verifyNoInteractions;
import com.google.common.net.InetAddresses;
-import io.netty.util.concurrent.DefaultPromise;
-import io.netty.util.concurrent.EventExecutor;
-import io.netty.util.concurrent.ImmediateEventExecutor;
-import io.netty.util.concurrent.ScheduledFuture;
-import io.netty.util.concurrent.SucceededFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.SettableFuture;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.BeforeClass;
import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.aaa.encrypt.AAAEncryptionService;
import org.opendaylight.netconf.api.CapabilityURN;
-import org.opendaylight.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.netconf.client.NetconfClientFactory;
import org.opendaylight.netconf.client.NetconfClientSession;
import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities;
import org.opendaylight.netconf.client.mdsal.NetconfDeviceSchema;
// Core setup
@Mock
- private ScheduledExecutorService keepaliveExecutor;
+ private ScheduledExecutorService scheduledExecutor;
@Mock
private SchemaResourceManager schemaManager;
@Mock
// Mock client dispatcher-related things
@Mock
- private NetconfClientDispatcher clientDispatcher;
+ private NetconfClientFactory clientFactory;
@Mock
private NetconfClientSession clientSession;
@Captor
// Mock eventExecutor-related things
@Mock
- private EventExecutor eventExecutor;
- @Mock
private ScheduledFuture<?> scheduleFuture;
@Captor
private ArgumentCaptor<Runnable> scheduleCaptor;
@Before
public void before() {
// Instantiate the handler
- handler = new NetconfNodeHandler(clientDispatcher, eventExecutor, keepaliveExecutor, BASE_SCHEMAS,
+ handler = new NetconfNodeHandler(clientFactory, scheduledExecutor, BASE_SCHEMAS,
schemaManager, processingExecutor,
new DefaultNetconfClientConfigurationBuilderFactory(encryptionService, credentialProvider,
sslHandlerFactoryProvider),
}
@Test
- public void successfullOnDeviceConnectedPropagates() {
+ public void successfulOnDeviceConnectedPropagates() throws Exception {
assertSuccessfulConnect();
assertEquals(1, handler.attempts());
}
@Test
- public void failedSchemaCausesReconnect() {
+ public void failedSchemaCausesReconnect() throws Exception {
assertSuccessfulConnect();
assertEquals(1, handler.attempts());
// Note: this will count as a second attempt
- doReturn(scheduleFuture).when(eventExecutor).schedule(scheduleCaptor.capture(), eq(150L),
- eq(TimeUnit.MILLISECONDS));
+ doReturn(scheduleFuture).when(scheduledExecutor)
+ .schedule(scheduleCaptor.capture(), anyLong(), any(TimeUnit.class));
+
handler.onDeviceFailed(new AssertionError("schema failure"));
assertEquals(2, handler.attempts());
// and when we run the task, we get a clientDispatcher invocation, but attempts are still the same
scheduleCaptor.getValue().run();
- verify(clientDispatcher, times(2)).createClient(any());
+ verify(clientFactory, times(2)).createClient(any());
assertEquals(2, handler.attempts());
}
@Test
- public void downAfterUpCausesReconnect() {
+ public void downAfterUpCausesReconnect() throws Exception {
// Let's borrow common bits
- successfullOnDeviceConnectedPropagates();
+ successfulOnDeviceConnectedPropagates();
// when the device is connected, we propagate the information and initiate reconnect
doNothing().when(delegate).onDeviceDisconnected();
- doReturn(scheduleFuture).when(eventExecutor).schedule(scheduleCaptor.capture(), eq(100L),
+ doReturn(scheduleFuture).when(scheduledExecutor).schedule(scheduleCaptor.capture(), eq(100L),
eq(TimeUnit.MILLISECONDS));
handler.onDeviceDisconnected();
// and when we run the task, we get a clientDispatcher invocation, but attempts are still the same
scheduleCaptor.getValue().run();
- verify(clientDispatcher, times(2)).createClient(any());
+ verify(clientFactory, times(2)).createClient(any());
assertEquals(1, handler.attempts());
}
@Test
- public void socketFailuresAreRetried() {
- final var firstPromise = new DefaultPromise<NetconfClientSession>(ImmediateEventExecutor.INSTANCE);
- final var secondPromise = new DefaultPromise<NetconfClientSession>(ImmediateEventExecutor.INSTANCE);
- doReturn(firstPromise, secondPromise).when(clientDispatcher).createClient(any());
+ public void socketFailuresAreRetried() throws Exception {
+ final var firstFuture = SettableFuture.create();
+ final var secondFuture = SettableFuture.create();
+ doReturn(firstFuture, secondFuture).when(clientFactory).createClient(any());
handler.connect();
assertEquals(1, handler.attempts());
- doReturn(scheduleFuture).when(eventExecutor).schedule(scheduleCaptor.capture(), eq(150L),
+ doReturn(scheduleFuture).when(scheduledExecutor).schedule(scheduleCaptor.capture(), eq(150L),
eq(TimeUnit.MILLISECONDS));
- firstPromise.setFailure(new AssertionError("first"));
+ firstFuture.setException(new AssertionError("first"));
assertEquals(2, handler.attempts());
// and when we run the task, we get a clientDispatcher invocation, but attempts are still the same
scheduleCaptor.getValue().run();
- verify(clientDispatcher, times(2)).createClient(any());
+ verify(clientFactory, times(2)).createClient(any());
assertEquals(2, handler.attempts());
// now report the second failure
final var throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
doNothing().when(delegate).onDeviceFailed(throwableCaptor.capture());
- secondPromise.setFailure(new AssertionError("second"));
+ secondFuture.setException(new AssertionError("second"));
assertThat(throwableCaptor.getValue(), instanceOf(ConnectGivenUpException.class));
// but nothing else happens
assertEquals(2, handler.attempts());
}
- // Initiate a connect() which results in immediate clientDispatcher report. No interactions with delegate may occur,
+ // Initiate connect() which results in immediate clientDispatcher report. No interactions with delegate may occur,
// as this is just a prelude to a follow-up callback
- private void assertSuccessfulConnect() {
- doReturn(new SucceededFuture<>(ImmediateEventExecutor.INSTANCE, clientSession))
- .when(clientDispatcher).createClient(any());
+ private void assertSuccessfulConnect() throws Exception {
+ doReturn(Futures.immediateFuture(clientSession)).when(clientFactory).createClient(any());
handler.connect();
- verify(clientDispatcher).createClient(any());
+ verify(clientFactory).createClient(any());
verifyNoInteractions(delegate);
}
}
*/
package org.opendaylight.netconf.callhome.protocol;
-import io.netty.util.concurrent.Future;
+import com.google.common.util.concurrent.ListenableFuture;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.netconf.client.NetconfClientSession;
import org.opendaylight.netconf.client.NetconfClientSessionListener;
* @param listener Client Session Listener to be attached to NETCONF session.
* @return Future with negotiated NETCONF session
*/
- @NonNull Future<NetconfClientSession> activate(@NonNull NetconfClientSessionListener listener);
+ @NonNull ListenableFuture<NetconfClientSession> activate(@NonNull NetconfClientSessionListener listener);
}
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
sshSession.close(false);
}
- private synchronized Promise<NetconfClientSession> doActivate(final ClientChannel netconfChannel,
+ private synchronized ListenableFuture<NetconfClientSession> doActivate(final ClientChannel netconfChannel,
final NetconfClientSessionListener listener, final MinaSshNettyChannel nettyChannel) {
if (activated) {
- return newSessionPromise().setFailure(new IllegalStateException("Session already activated."));
+ return Futures.immediateFailedFuture(new IllegalStateException("Session already activated."));
}
activated = true;
nettyChannel.pipeline().addFirst(new SshWriteAsyncHandlerAdapter(netconfChannel));
factory.getChannelInitializer(listener).initialize(nettyChannel, activationPromise);
((ChannelSubsystem) netconfChannel).onClose(nettyChannel::doNettyDisconnect);
factory.getNettyGroup().register(nettyChannel).awaitUninterruptibly(500);
- return activationPromise;
+ final SettableFuture<NetconfClientSession> future = SettableFuture.create();
+ activationPromise.addListener(ignored -> {
+ final var cause = activationPromise.cause();
+ if (cause != null) {
+ future.setException(cause);
+ } else {
+ future.set(activationPromise.getNow());
+ }
+ });
+ return future;
}
@Deprecated(since = "7.0.0", forRemoval = true)
- protected MinaSshNettyChannel newMinaSshNettyChannel() {
+ @VisibleForTesting
+ MinaSshNettyChannel newMinaSshNettyChannel() {
return new MinaSshNettyChannel(this, sshSession);
}
import static java.util.Objects.requireNonNull;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
import io.netty.channel.Channel;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.HashedWheelTimer;
channel.close();
}
- private Promise<NetconfClientSession> doActivate(final Channel ch, final NetconfClientSessionListener listener) {
+ private ListenableFuture<NetconfClientSession> doActivate(final Channel ch,
+ final NetconfClientSessionListener listener) {
final Promise<NetconfClientSession> activationPromise = newSessionPromise();
if (activated.compareAndExchange(false, true)) {
- return activationPromise.setFailure(new IllegalStateException("Session (channel) already activated."));
+ return Futures.immediateFailedFuture(new IllegalStateException("Session (channel) already activated."));
}
LOG.info("Activating Netconf channel for {} with {}", getRemoteAddress(), listener);
final TlsClientChannelInitializer tlsClientChannelInitializer = new TlsClientChannelInitializer(
sslHandlerFactory, negotiatorFactory, listener);
tlsClientChannelInitializer.initialize(ch, activationPromise);
- return activationPromise;
+ final SettableFuture<NetconfClientSession> future = SettableFuture.create();
+ activationPromise.addListener(ignored -> {
+ final var cause = activationPromise.cause();
+ if (cause != null) {
+ future.setException(cause);
+ } else {
+ future.set(activationPromise.getNow());
+ }
+ });
+ return future;
}
@Override