Thomas Vachuska
Committed by Gerrit Code Review

ONOS-2744 Adding a small app to manage cluster IP alias; requires arping tool.

Change-Id: Ib7032586206542c1db67d248196ad8a883d67b51
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2014 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-cip</artifactId>
<packaging>bundle</packaging>
<description>Cluster IP alias</description>
<properties>
<onos.app.name>org.onosproject.cip</onos.app.name>
</properties>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
</dependencies>
</project>
/*
* Copyright 2015 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.cip;
import com.google.common.io.ByteStreams;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.LeadershipEvent;
import org.onosproject.cluster.LeadershipEventListener;
import org.onosproject.cluster.LeadershipService;
import org.onosproject.cluster.NodeId;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Objects;
import java.util.Properties;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onlab.util.Tools.get;
/**
* Manages cluster IP address alias.
*
* To use the application, simply install it on ONOS and then configure it
* with the desired alias IP/mask/adapter configuration.
*
* If you are running it using upstart, you can also add the following
* command to the /opt/onos/options file:
*
* sudo ifconfig eth0:0 down # use the desired alias adapter
*
* This will make sure that if the process is killed abruptly, the IP alias
* will be dropped upon respawn.
*/
@Component(immediate = true)
public class ClusterIpManager {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String CLUSTER_IP = "cluster/ip";
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ClusterService clusterService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LeadershipService leadershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService cfgService;
private final LeadershipEventListener listener = new InternalLeadershipListener();
private NodeId localId;
private boolean wasLeader = false;
// By default there is no IP; this has to be configured
@Property(name = "aliasIp", value = "", label = "Alias IP address")
private String aliasIp = "";
public static final String DEFAULT_MASK = "255.255.0.0";
@Property(name = "aliasMask", value = DEFAULT_MASK, label = "Alias IP mask")
private String aliasMask = DEFAULT_MASK;
public static final String ETH_0 = "eth0:0";
@Property(name = "aliasAdapter", value = ETH_0, label = "Alias IP adapter")
private String aliasAdapter = ETH_0;
@Activate
protected void activate(ComponentContext context) {
cfgService.registerProperties(getClass());
localId = clusterService.getLocalNode().id();
processLeadershipChange(leadershipService.getLeader(CLUSTER_IP));
leadershipService.addListener(listener);
leadershipService.runForLeadership(CLUSTER_IP);
log.info("Started");
}
@Deactivate
protected void deactivate(ComponentContext context) {
cfgService.unregisterProperties(getClass(), false);
removeIpAlias(aliasIp, aliasMask, aliasAdapter);
leadershipService.removeListener(listener);
leadershipService.withdraw(CLUSTER_IP);
log.info("Stopped");
}
@Modified
protected void modified(ComponentContext context) {
log.info("Received configuration change...");
Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
String newIp = get(properties, "aliasIp");
String newMask = get(properties, "aliasMask");
String newAdapter = get(properties, "aliasAdapter");
// Process any changes in the parameters...
if (!Objects.equals(newIp, aliasIp) ||
!Objects.equals(newMask, aliasMask) ||
!Objects.equals(newAdapter, aliasAdapter)) {
synchronized (this) {
log.info("Reconfiguring with aliasIp={}, aliasMask={}, aliasAdapter={}, wasLeader={}",
newIp, newMask, newAdapter, wasLeader);
if (wasLeader) {
removeIpAlias(aliasIp, aliasMask, aliasAdapter);
addIpAlias(newIp, newMask, newAdapter);
}
aliasIp = newIp;
aliasMask = newMask;
aliasAdapter = newAdapter;
}
}
}
private synchronized void processLeadershipChange(NodeId newLeader) {
if (newLeader == null) {
return;
}
boolean isLeader = Objects.equals(newLeader, localId);
log.info("Processing leadership change; wasLeader={}, isLeader={}", wasLeader, isLeader);
if (!wasLeader && isLeader) {
// Gaining leadership, so setup the IP alias
addIpAlias(aliasIp, aliasMask, aliasAdapter);
wasLeader = true;
} else if (wasLeader && !isLeader) {
// Loosing leadership, so drop the IP alias
removeIpAlias(aliasIp, aliasMask, aliasAdapter);
wasLeader = false;
}
}
private synchronized void addIpAlias(String ip, String mask, String adapter) {
if (!isNullOrEmpty(ip) && !isNullOrEmpty(mask) && !isNullOrEmpty(adapter)) {
log.info("Adding IP alias {}/{} to {}", ip, mask, adapter);
execute("sudo ifconfig " + adapter + " " + ip + " netmask " + mask + " up", false);
execute("sudo /usr/sbin/arping -c 1 -I " + adapter + " " + ip, true);
}
}
private synchronized void removeIpAlias(String ip, String mask, String adapter) {
if (!isNullOrEmpty(ip) && !isNullOrEmpty(mask) && !isNullOrEmpty(adapter)) {
log.info("Removing IP alias from {}", adapter, false);
execute("sudo ifconfig " + adapter + " down", true);
}
}
private void execute(String command, boolean ignoreCode) {
try {
log.info("Executing [{}]", command);
Process process = Runtime.getRuntime().exec(command);
byte[] output = ByteStreams.toByteArray(process.getInputStream());
byte[] error = ByteStreams.toByteArray(process.getErrorStream());
int code = process.waitFor();
if (code != 0 && !ignoreCode) {
log.info("Command failed: status={}, output={}, error={}",
code, new String(output), new String(error));
}
} catch (IOException e) {
log.error("Unable to execute command {}", command, e);
} catch (InterruptedException e) {
log.error("Interrupted executing command {}", command, e);
}
}
// Listens for leadership changes.
private class InternalLeadershipListener implements LeadershipEventListener {
@Override
public void event(LeadershipEvent event) {
if (event.subject().topic().equals(CLUSTER_IP)) {
processLeadershipChange(event.subject().leader());
}
}
}
}
......@@ -52,6 +52,7 @@
<module>xos-integration</module>
<module>pcep-api</module>
<module>olt</module>
<module>cip</module>
<module>flowanalyzer</module>
<module>vtnrsc</module>
<module>vtn</module>
......