/*
* Copyright (c) 2015 Cisco Systems, Inc. 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.protocol.bmp.impl;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import org.opendaylight.protocol.bmp.api.BmpDispatcher;
import org.opendaylight.protocol.bmp.api.BmpSessionFactory;
import org.opendaylight.protocol.bmp.api.BmpSessionListenerFactory;
import org.opendaylight.protocol.bmp.spi.registry.BmpMessageRegistry;
import org.opendaylight.protocol.concepts.KeyMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BmpDispatcherImpl implements BmpDispatcher {
private static final Logger LOG = LoggerFactory.getLogger(BmpDispatcherImpl.class);
private static final int MAX_CONNECTIONS_COUNT = 128;
private static final int CONNECT_TIMEOUT = 5000;
private static final int INITIAL_BACKOFF = 30_000;
private static final int MAXIMUM_BACKOFF = 720_000;
private static final long TIMEOUT = 10;
private final BmpHandlerFactory hf;
private final EventLoopGroup bossGroup;
private final EventLoopGroup workerGroup;
private final BmpSessionFactory sessionFactory;
public BmpDispatcherImpl(final EventLoopGroup bossGroup, final EventLoopGroup workerGroup,
final BmpMessageRegistry registry, final BmpSessionFactory sessionFactory) {
if (Epoll.isAvailable()) {
this.bossGroup = new EpollEventLoopGroup();
this.workerGroup = new EpollEventLoopGroup();
} else {
this.bossGroup = Preconditions.checkNotNull(bossGroup);
this.workerGroup = Preconditions.checkNotNull(workerGroup);
}
this.hf = new BmpHandlerFactory(Preconditions.checkNotNull(registry));
this.sessionFactory = Preconditions.checkNotNull(sessionFactory);
}
@Override
public ChannelFuture createClient(final InetSocketAddress address, final BmpSessionListenerFactory slf, final Optional<KeyMapping> keys) {
final Bootstrap b = new Bootstrap();
Preconditions.checkNotNull(address);
if (Epoll.isAvailable()) {
b.channel(EpollSocketChannel.class);
} else {
b.channel(NioSocketChannel.class);
}
if (keys.isPresent()) {
if (Epoll.isAvailable()) {
b.option(EpollChannelOption.TCP_MD5SIG, keys.get());
} else {
throw new UnsupportedOperationException (Epoll.unavailabilityCause().getCause());
}
}
b.option(ChannelOption.SO_KEEPALIVE, true);
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT);
b.group(this.workerGroup);
b.handler(new ChannelInitializer<AbstractChannel>() {
@Override
protected void initChannel(final AbstractChannel ch) throws Exception {
ch.pipeline().addLast(BmpDispatcherImpl.this.hf.getDecoders());
ch.pipeline().addLast(BmpDispatcherImpl.this.sessionFactory.getSession(ch, slf));
}
});
b.remoteAddress(address);
final ChannelFuture channelPromise = b.connect();
channelPromise.addListener(new BmpDispatcherImpl.BootstrapListener(b, address));
return channelPromise;
}
@Override
public ChannelFuture createServer(final InetSocketAddress address, final BmpSessionListenerFactory slf, final Optional<KeyMapping> keys) {
Preconditions.checkNotNull(address);
Preconditions.checkNotNull(slf);
final ServerBootstrap b = new ServerBootstrap();
b.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(final Channel ch) throws Exception {
ch.pipeline().addLast(BmpDispatcherImpl.this.hf.getDecoders());
ch.pipeline().addLast(BmpDispatcherImpl.this.sessionFactory.getSession(ch, slf));
}
});
b.option(ChannelOption.SO_BACKLOG, MAX_CONNECTIONS_COUNT);
b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
if (Epoll.isAvailable()) {
b.channel(EpollServerSocketChannel.class);
} else {
b.channel(NioServerSocketChannel.class);
}
if (keys.isPresent()) {
if (Epoll.isAvailable()) {
b.option(EpollChannelOption.TCP_MD5SIG, keys.get());
} else {
throw new UnsupportedOperationException (Epoll.unavailabilityCause().getCause());
}
}
b.group(this.bossGroup, this.workerGroup);
final ChannelFuture f = b.bind(address);
LOG.debug("Initiated BMP server {} at {}.", f, address);
return f;
}
@Override
public void close() {
if (Epoll.isAvailable()) {
this.workerGroup.shutdownGracefully(0, TIMEOUT, TimeUnit.SECONDS);
this.bossGroup.shutdownGracefully(0, TIMEOUT, TimeUnit.SECONDS);
}
}
private class BootstrapListener implements ChannelFutureListener {
private final Bootstrap bootstrap;
private long delay;
private final InetSocketAddress address;
public BootstrapListener(final Bootstrap bootstrap, final InetSocketAddress address) {
this.bootstrap = bootstrap;
this.address = address;
this.delay = INITIAL_BACKOFF;
}
@Override
public void operationComplete(final ChannelFuture cf) throws Exception {
if (cf.isCancelled()) {
LOG.debug("Connection {} cancelled!", cf);
} else if (cf.isSuccess()) {
LOG.debug("Connection {} succeeded!", cf);
} else {
if (this.delay > MAXIMUM_BACKOFF) {
LOG.warn("The time of maximum backoff has been exceeded. No further connection attempts with BMP router {}.", this.address);
cf.cancel(false);
return;
}
final EventLoop loop = cf.channel().eventLoop();
loop.schedule(() -> this.bootstrap.connect().addListener(this), this.delay, TimeUnit.MILLISECONDS);
LOG.info("The connection try to BMP router {} failed. Next reconnection attempt in {} milliseconds.", this.address, this.delay);
this.delay *= 2;
}
}
}
}