blob: 5dff893c675b2bf6b3932547d6d31f5b3dad6133 [file] [log] [blame]
/*
* Copyright 2015-present Open Networking Foundation
*
* 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();
processLeaderChange(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 processLeaderChange(NodeId newLeader) {
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) {
// Losing 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);
Thread.currentThread().interrupt();
}
}
// Listens for leadership changes.
private class InternalLeadershipListener implements LeadershipEventListener {
@Override
public boolean isRelevant(LeadershipEvent event) {
return CLUSTER_IP.equals(event.subject().topic());
}
@Override
public void event(LeadershipEvent event) {
processLeaderChange(event.subject().leaderNodeId());
}
}
}