blob: 406d09c1ca7be85dd9077957d3f0fbde6f18a18e [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.isis.controller.impl;
import com.fasterxml.jackson.databind.JsonNode;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.AdaptiveReceiveBufferSizePredictor;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.FixedReceiveBufferSizePredictorFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onosproject.isis.controller.IsisInterface;
import org.onosproject.isis.controller.IsisNetworkType;
import org.onosproject.isis.controller.IsisProcess;
import org.onosproject.isis.controller.IsisRouterType;
import org.onosproject.isis.controller.topology.IsisAgent;
import org.onosproject.isis.controller.topology.IsisLink;
import org.onosproject.isis.controller.topology.IsisRouter;
import org.onosproject.isis.io.util.IsisConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static org.onlab.util.Tools.groupedThreads;
/**
* Representation of an ISIS controller.
*/
public class Controller {
protected static final int BUFFER_SIZE = 4 * 1024 * 1024;
private static final Logger log = LoggerFactory.getLogger(Controller.class);
private static final int RETRY_INTERVAL = 4;
private final int peerWorkerThreads = 16;
byte[] configPacket = null;
private List<IsisProcess> processes = null;
private IsisChannelHandler isisChannelHandler;
private NioClientSocketChannelFactory peerExecFactory;
private ClientBootstrap peerBootstrap = null;
private TpPort isisPort = TpPort.tpPort(IsisConstants.SPORT);
private ScheduledExecutorService connectExecutor = null;
private int connectRetryCounter = 0;
private int connectRetryTime;
private ScheduledFuture future = null;
private IsisAgent agent;
/**
* Deactivates ISIS controller.
*/
public void isisDeactivate() {
disconnectExecutor();
processes = null;
peerExecFactory.shutdown();
}
/**
* Sets ISIS agent.
*
* @param agent ISIS agent instance
*/
public void setAgent(IsisAgent agent) {
this.agent = agent;
}
/**
* Updates the processes configuration.
*
* @param jsonNode json node instance
* @throws Exception might throws parse exception
*/
public void updateConfig(JsonNode jsonNode) throws Exception {
log.debug("Controller::UpdateConfig called");
configPacket = new byte[IsisConstants.CONFIG_LENGTH];
byte numberOfInterface = 0; // number of interfaces to configure
configPacket[0] = (byte) 0xFF; // its a conf packet - identifier
List<IsisProcess> isisProcesses = getConfig(jsonNode);
for (IsisProcess isisProcess : isisProcesses) {
log.debug("IsisProcessDetails : " + isisProcess);
for (IsisInterface isisInterface : isisProcess.isisInterfaceList()) {
DefaultIsisInterface isisInterfaceImpl = (DefaultIsisInterface) isisInterface;
log.debug("IsisInterfaceDetails : " + isisInterface);
numberOfInterface++;
configPacket[2 * numberOfInterface] = (byte) isisInterfaceImpl.interfaceIndex();
if (isisInterface.networkType() == IsisNetworkType.BROADCAST &&
isisInterfaceImpl.reservedPacketCircuitType() == IsisRouterType.L1.value()) {
configPacket[(2 * numberOfInterface) + 1] = (byte) 0;
} else if (isisInterface.networkType() == IsisNetworkType.BROADCAST &&
isisInterfaceImpl.reservedPacketCircuitType() == IsisRouterType.L2.value()) {
configPacket[(2 * numberOfInterface) + 1] = (byte) 1;
} else if (isisInterface.networkType() == IsisNetworkType.P2P) {
configPacket[(2 * numberOfInterface) + 1] = (byte) 2;
} else if (isisInterface.networkType() == IsisNetworkType.BROADCAST &&
isisInterfaceImpl.reservedPacketCircuitType() == IsisRouterType.L1L2.value()) {
configPacket[(2 * numberOfInterface) + 1] = (byte) 3;
}
}
}
configPacket[1] = numberOfInterface;
//First time configuration
if (processes == null) {
if (isisProcesses.size() > 0) {
processes = isisProcesses;
connectPeer();
}
} else {
isisChannelHandler.updateInterfaceMap(isisProcesses);
//Send the config packet
isisChannelHandler.sentConfigPacket(configPacket);
}
}
/**
* Initializes the netty client channel connection.
*/
private void initConnection() {
if (peerBootstrap != null) {
return;
}
peerBootstrap = createPeerBootStrap();
peerBootstrap.setOption("reuseAddress", true);
peerBootstrap.setOption("tcpNoDelay", true);
peerBootstrap.setOption("keepAlive", true);
peerBootstrap.setOption("receiveBufferSize", Controller.BUFFER_SIZE);
peerBootstrap.setOption("receiveBufferSizePredictorFactory",
new FixedReceiveBufferSizePredictorFactory(
Controller.BUFFER_SIZE));
peerBootstrap.setOption("receiveBufferSizePredictor",
new AdaptiveReceiveBufferSizePredictor(64, 1024, 65536));
peerBootstrap.setOption("child.keepAlive", true);
peerBootstrap.setOption("child.tcpNoDelay", true);
peerBootstrap.setOption("child.sendBufferSize", Controller.BUFFER_SIZE);
peerBootstrap.setOption("child.receiveBufferSize", Controller.BUFFER_SIZE);
peerBootstrap.setOption("child.receiveBufferSizePredictorFactory",
new FixedReceiveBufferSizePredictorFactory(
Controller.BUFFER_SIZE));
peerBootstrap.setOption("child.reuseAddress", true);
isisChannelHandler = new IsisChannelHandler(this, processes);
ChannelPipelineFactory pfact = new IsisPipelineFactory(isisChannelHandler);
peerBootstrap.setPipelineFactory(pfact);
}
/**
* Creates peer boot strap.
*
* @return client bootstrap instance
*/
private ClientBootstrap createPeerBootStrap() {
if (peerWorkerThreads == 0) {
peerExecFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(groupedThreads("onos/isis", "boss-%d")),
Executors.newCachedThreadPool(groupedThreads("onos/isis", "worker-%d")));
return new ClientBootstrap(peerExecFactory);
} else {
peerExecFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(groupedThreads("onos/isis", "boss-%d")),
Executors.newCachedThreadPool(groupedThreads("onos/isis", "worker-%d")),
peerWorkerThreads);
return new ClientBootstrap(peerExecFactory);
}
}
/**
* Gets all configured processes.
*
* @return all configured processes
*/
public List<IsisProcess> getAllConfiguredProcesses() {
return processes;
}
/**
* Gets the list of processes configured.
*
* @param json posted json
* @return list of processes configured
*/
private List<IsisProcess> getConfig(JsonNode json) throws Exception {
List<IsisProcess> isisProcessesList = new ArrayList<>();
JsonNode jsonNodes = json;
if (jsonNodes == null) {
return isisProcessesList;
}
jsonNodes.forEach(jsonNode -> {
List<IsisInterface> interfaceList = new ArrayList<>();
for (JsonNode jsonNode1 : jsonNode.path(IsisConstants.INTERFACE)) {
IsisInterface isisInterface = new DefaultIsisInterface();
String index = jsonNode1.path(IsisConstants.INTERFACEINDEX).asText();
if (isPrimitive(index)) {
int input = Integer.parseInt(index);
if (input < 1 || input > 255) {
log.debug("Wrong interface index: {}", index);
continue;
}
isisInterface.setInterfaceIndex(Integer.parseInt(index));
} else {
log.debug("Wrong interface index {}", index);
continue;
}
Ip4Address ipAddress = getInterfaceIp(isisInterface.interfaceIndex());
if (ipAddress != null && !ipAddress.equals(IsisConstants.DEFAULTIP)) {
isisInterface.setInterfaceIpAddress(ipAddress);
} else {
log.debug("Wrong interface index {}. No matching interface in system.", index);
continue;
}
MacAddress macAddress = getInterfaceMac(isisInterface.interfaceIndex());
if (macAddress != null) {
isisInterface.setInterfaceMacAddress(macAddress);
} else {
log.debug("Wrong interface index {}. No matching interface in system.", index);
continue;
}
String mask = getInterfaceMask(isisInterface.interfaceIndex());
if (mask != null) {
try {
isisInterface.setNetworkMask(InetAddress.getByName(mask).getAddress());
} catch (UnknownHostException e) {
log.debug("Wrong interface index {}. Error while getting network mask.", index);
}
} else {
log.debug("Wrong interface index {}. Error while getting network mask.", index);
continue;
}
isisInterface.setIntermediateSystemName(jsonNode1
.path(IsisConstants.INTERMEDIATESYSTEMNAME)
.asText());
String systemId = jsonNode1.path(IsisConstants.SYSTEMID).asText();
if (isValidSystemId(systemId)) {
isisInterface.setSystemId(systemId);
} else {
log.debug("Wrong systemId: {} for interface index {}.", systemId, index);
continue;
}
String circuitType = jsonNode1.path(IsisConstants.RESERVEDPACKETCIRCUITTYPE).asText();
if (isPrimitive(circuitType)) {
int input = Integer.parseInt(circuitType);
if (input < 1 || input > 3) {
log.debug("Wrong ReservedPacketCircuitType: {} for interface index {}.", circuitType, index);
continue;
}
isisInterface.setReservedPacketCircuitType(input);
} else {
log.debug("Wrong ReservedPacketCircuitType: {} for interface index {}.", circuitType, index);
continue;
}
String networkType = jsonNode1.path(IsisConstants.NETWORKTYPE).asText();
if (isPrimitive(networkType)) {
int input = Integer.parseInt(networkType);
if (input < 1 || input > 2) {
log.debug("Wrong networkType: {} for interface index {}.", networkType, index);
continue;
}
isisInterface.setNetworkType(IsisNetworkType.get(input));
} else {
log.debug("Wrong networkType: {} for interface index {}.", networkType, index);
continue;
}
String areaAddress = jsonNode1.path(IsisConstants.AREAADDRESS).asText();
if (isPrimitive(areaAddress)) {
if (areaAddress.length() > 7) {
log.debug("Wrong areaAddress: {} for interface index {}.", areaAddress, index);
continue;
}
isisInterface.setAreaAddress(areaAddress);
} else {
log.debug("Wrong areaAddress: {} for interface index {}.", areaAddress, index);
continue;
}
String circuitId = jsonNode1.path(IsisConstants.CIRCUITID).asText();
if (isPrimitive(circuitId)) {
int input = Integer.parseInt(circuitId);
if (input < 1) {
log.debug("Wrong circuitId: {} for interface index {}.", circuitId, index);
continue;
}
isisInterface.setCircuitId(circuitId);
} else {
log.debug("Wrong circuitId: {} for interface index {}.", circuitId, index);
continue;
}
String holdingTime = jsonNode1.path(IsisConstants.HOLDINGTIME).asText();
if (isPrimitive(holdingTime)) {
int input = Integer.parseInt(holdingTime);
if (input < 1 || input > 255) {
log.debug("Wrong holdingTime: {} for interface index {}.", holdingTime, index);
continue;
}
isisInterface.setHoldingTime(input);
} else {
log.debug("Wrong holdingTime: {} for interface index {}.", holdingTime, index);
continue;
}
String helloInterval = jsonNode1.path(IsisConstants.HELLOINTERVAL).asText();
if (isPrimitive(helloInterval)) {
int interval = Integer.parseInt(helloInterval);
if (interval > 0 && interval <= 255) {
isisInterface.setHelloInterval(interval);
} else {
log.debug("Wrong hello interval: {} for interface index {}.", helloInterval, index);
continue;
}
} else {
log.debug("Wrong hello interval: {} for interface index {}.", helloInterval, index);
continue;
}
interfaceList.add(isisInterface);
}
if (interfaceList.size() > 0) {
IsisProcess process = new DefaultIsisProcess();
process.setProcessId(jsonNode.path(IsisConstants.PROCESSESID).asText());
process.setIsisInterfaceList(interfaceList);
isisProcessesList.add(process);
}
});
return isisProcessesList;
}
/**
* Returns interface MAC by index.
*
* @param interfaceIndex interface index
* @return interface IP by index
*/
private MacAddress getInterfaceMac(int interfaceIndex) {
MacAddress macAddress = null;
try {
NetworkInterface networkInterface = NetworkInterface.getByIndex(interfaceIndex);
macAddress = MacAddress.valueOf(networkInterface.getHardwareAddress());
} catch (Exception e) {
log.debug("Error while getting Interface IP by index");
return macAddress;
}
return macAddress;
}
/**
* Returns interface IP by index.
*
* @param interfaceIndex interface index
* @return interface IP by index
*/
private Ip4Address getInterfaceIp(int interfaceIndex) {
Ip4Address ipAddress = null;
try {
NetworkInterface networkInterface = NetworkInterface.getByIndex(interfaceIndex);
Enumeration ipAddresses = networkInterface.getInetAddresses();
while (ipAddresses.hasMoreElements()) {
InetAddress address = (InetAddress) ipAddresses.nextElement();
if (!address.isLinkLocalAddress()) {
ipAddress = Ip4Address.valueOf(address.getAddress());
break;
}
}
} catch (Exception e) {
log.debug("Error while getting Interface IP by index");
return IsisConstants.DEFAULTIP;
}
return ipAddress;
}
/**
* Returns interface MAC by index.
*
* @param interfaceIndex interface index
* @return interface IP by index
*/
private String getInterfaceMask(int interfaceIndex) {
String subnetMask = null;
try {
Ip4Address ipAddress = getInterfaceIp(interfaceIndex);
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(
InetAddress.getByName(ipAddress.toString()));
Enumeration ipAddresses = networkInterface.getInetAddresses();
int index = 0;
while (ipAddresses.hasMoreElements()) {
InetAddress address = (InetAddress) ipAddresses.nextElement();
if (!address.isLinkLocalAddress()) {
break;
}
index++;
}
int prfLen = networkInterface.getInterfaceAddresses().get(index).getNetworkPrefixLength();
int shft = 0xffffffff << (32 - prfLen);
int oct1 = ((byte) ((shft & 0xff000000) >> 24)) & 0xff;
int oct2 = ((byte) ((shft & 0x00ff0000) >> 16)) & 0xff;
int oct3 = ((byte) ((shft & 0x0000ff00) >> 8)) & 0xff;
int oct4 = ((byte) (shft & 0x000000ff)) & 0xff;
subnetMask = oct1 + "." + oct2 + "." + oct3 + "." + oct4;
} catch (Exception e) {
log.debug("Error while getting Interface network mask by index");
return subnetMask;
}
return subnetMask;
}
/**
* Checks if primitive or not.
*
* @param value input value
* @return true if number else false
*/
private boolean isPrimitive(String value) {
boolean status = true;
value = value.trim();
if (value.length() < 1) {
return false;
}
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (!Character.isDigit(c)) {
status = false;
break;
}
}
return status;
}
/**
* Checks if system id is valid or not.
*
* @param value input value
* @return true if valid else false
*/
private boolean isValidSystemId(String value) {
value = value.trim();
boolean status = true;
if (value.length() != 14) {
return false;
}
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (!Character.isDigit(c)) {
if (!((i == 4 || i == 9) && c == '.')) {
status = false;
break;
}
}
}
return status;
}
/**
* Disconnects the executor.
*/
public void disconnectExecutor() {
if (connectExecutor != null) {
future.cancel(true);
connectExecutor.shutdownNow();
connectExecutor = null;
}
}
/**
* Connects to peer.
*/
public void connectPeer() {
scheduleConnectionRetry(this.connectRetryTime);
}
/**
* Retry connection with exponential back-off mechanism.
*
* @param retryDelay retry delay
*/
private void scheduleConnectionRetry(long retryDelay) {
if (connectExecutor == null) {
connectExecutor = Executors.newSingleThreadScheduledExecutor();
}
future = connectExecutor.schedule(new ConnectionRetry(), retryDelay, TimeUnit.MINUTES);
}
/**
* Adds device details.
*
* @param isisRouter ISIS router instance
*/
public void addDeviceDetails(IsisRouter isisRouter) {
agent.addConnectedRouter(isisRouter);
}
/**
* Removes device details.
*
* @param isisRouter Isis router instance
*/
public void removeDeviceDetails(IsisRouter isisRouter) {
agent.removeConnectedRouter(isisRouter);
}
/**
* Adds link details.
*
* @param isisLink ISIS link instance
*/
public void addLinkDetails(IsisLink isisLink) {
agent.addLink(isisLink);
}
/**
* Removes link details.
*
* @param isisLink ISIS link instance
*/
public void removeLinkDetails(IsisLink isisLink) {
agent.deleteLink(isisLink);
}
/**
* Returns the isisAgent instance.
*
* @return agent
*/
public IsisAgent agent() {
return this.agent;
}
/**
* Implements ISIS connection and manages connection to peer with back-off mechanism in case of failure.
*/
class ConnectionRetry implements Runnable {
@Override
public void run() {
log.debug("Connect to peer {}", IsisConstants.SHOST);
initConnection();
isisChannelHandler.sentConfigPacket(configPacket);
InetSocketAddress connectToSocket = new InetSocketAddress(IsisConstants.SHOST, isisPort.toInt());
try {
peerBootstrap.connect(connectToSocket).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
connectRetryCounter++;
log.error("Connection failed, ConnectRetryCounter {} remote host {}", connectRetryCounter,
IsisConstants.SHOST);
/*
* Reconnect to peer on failure is exponential till 4 mins, later on retry after every 4
* mins.
*/
if (connectRetryTime < RETRY_INTERVAL) {
connectRetryTime = (connectRetryTime != 0) ? connectRetryTime * 2 : 1;
}
scheduleConnectionRetry(connectRetryTime);
} else {
//Send the config packet
isisChannelHandler.sentConfigPacket(configPacket);
connectRetryCounter++;
log.info("Connected to remote host {}, Connect Counter {}", IsisConstants.SHOST,
connectRetryCounter);
disconnectExecutor();
return;
}
}
});
} catch (Exception e) {
log.info("Connect peer exception : " + e.toString());
disconnectExecutor();
}
}
}
}