Thomas Vachuska | c03df25 | 2015-08-26 23:37:13 -0700 | [diff] [blame] | 1 | /* |
Brian O'Connor | 5ab426f | 2016-04-09 01:19:45 -0700 | [diff] [blame] | 2 | * Copyright 2015-present Open Networking Laboratory |
Thomas Vachuska | c03df25 | 2015-08-26 23:37:13 -0700 | [diff] [blame] | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | package org.onosproject.cip; |
| 17 | |
| 18 | import com.google.common.io.ByteStreams; |
Madan Jampani | 620f70d | 2016-01-30 22:22:47 -0800 | [diff] [blame] | 19 | |
Thomas Vachuska | c03df25 | 2015-08-26 23:37:13 -0700 | [diff] [blame] | 20 | import org.apache.felix.scr.annotations.Activate; |
| 21 | import org.apache.felix.scr.annotations.Component; |
| 22 | import org.apache.felix.scr.annotations.Deactivate; |
| 23 | import org.apache.felix.scr.annotations.Modified; |
| 24 | import org.apache.felix.scr.annotations.Property; |
| 25 | import org.apache.felix.scr.annotations.Reference; |
| 26 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
| 27 | import org.onosproject.cfg.ComponentConfigService; |
| 28 | import org.onosproject.cluster.ClusterService; |
| 29 | import org.onosproject.cluster.LeadershipEvent; |
| 30 | import org.onosproject.cluster.LeadershipEventListener; |
| 31 | import org.onosproject.cluster.LeadershipService; |
| 32 | import org.onosproject.cluster.NodeId; |
| 33 | import org.osgi.service.component.ComponentContext; |
| 34 | import org.slf4j.Logger; |
| 35 | import org.slf4j.LoggerFactory; |
| 36 | |
| 37 | import java.io.IOException; |
| 38 | import java.util.Dictionary; |
| 39 | import java.util.Objects; |
| 40 | import java.util.Properties; |
| 41 | |
| 42 | import static com.google.common.base.Strings.isNullOrEmpty; |
| 43 | import static org.onlab.util.Tools.get; |
| 44 | |
| 45 | /** |
| 46 | * Manages cluster IP address alias. |
| 47 | * |
| 48 | * To use the application, simply install it on ONOS and then configure it |
| 49 | * with the desired alias IP/mask/adapter configuration. |
| 50 | * |
| 51 | * If you are running it using upstart, you can also add the following |
| 52 | * command to the /opt/onos/options file: |
| 53 | * |
| 54 | * sudo ifconfig eth0:0 down # use the desired alias adapter |
| 55 | * |
| 56 | * This will make sure that if the process is killed abruptly, the IP alias |
| 57 | * will be dropped upon respawn. |
| 58 | */ |
| 59 | @Component(immediate = true) |
| 60 | public class ClusterIpManager { |
| 61 | |
| 62 | private final Logger log = LoggerFactory.getLogger(getClass()); |
| 63 | |
| 64 | private static final String CLUSTER_IP = "cluster/ip"; |
| 65 | |
| 66 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 67 | protected ClusterService clusterService; |
| 68 | |
| 69 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 70 | protected LeadershipService leadershipService; |
| 71 | |
| 72 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 73 | protected ComponentConfigService cfgService; |
| 74 | |
| 75 | private final LeadershipEventListener listener = new InternalLeadershipListener(); |
| 76 | |
| 77 | private NodeId localId; |
| 78 | private boolean wasLeader = false; |
| 79 | |
| 80 | // By default there is no IP; this has to be configured |
| 81 | @Property(name = "aliasIp", value = "", label = "Alias IP address") |
| 82 | private String aliasIp = ""; |
| 83 | |
| 84 | public static final String DEFAULT_MASK = "255.255.0.0"; |
| 85 | @Property(name = "aliasMask", value = DEFAULT_MASK, label = "Alias IP mask") |
| 86 | private String aliasMask = DEFAULT_MASK; |
| 87 | |
| 88 | public static final String ETH_0 = "eth0:0"; |
| 89 | @Property(name = "aliasAdapter", value = ETH_0, label = "Alias IP adapter") |
| 90 | private String aliasAdapter = ETH_0; |
| 91 | |
| 92 | @Activate |
| 93 | protected void activate(ComponentContext context) { |
| 94 | cfgService.registerProperties(getClass()); |
| 95 | |
| 96 | localId = clusterService.getLocalNode().id(); |
Madan Jampani | 620f70d | 2016-01-30 22:22:47 -0800 | [diff] [blame] | 97 | processLeaderChange(leadershipService.getLeader(CLUSTER_IP)); |
Thomas Vachuska | c03df25 | 2015-08-26 23:37:13 -0700 | [diff] [blame] | 98 | |
| 99 | leadershipService.addListener(listener); |
| 100 | leadershipService.runForLeadership(CLUSTER_IP); |
| 101 | log.info("Started"); |
| 102 | } |
| 103 | |
| 104 | @Deactivate |
| 105 | protected void deactivate(ComponentContext context) { |
| 106 | cfgService.unregisterProperties(getClass(), false); |
| 107 | |
| 108 | removeIpAlias(aliasIp, aliasMask, aliasAdapter); |
| 109 | |
| 110 | leadershipService.removeListener(listener); |
| 111 | leadershipService.withdraw(CLUSTER_IP); |
| 112 | log.info("Stopped"); |
| 113 | } |
| 114 | |
| 115 | @Modified |
| 116 | protected void modified(ComponentContext context) { |
| 117 | log.info("Received configuration change..."); |
| 118 | Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties(); |
| 119 | String newIp = get(properties, "aliasIp"); |
| 120 | String newMask = get(properties, "aliasMask"); |
| 121 | String newAdapter = get(properties, "aliasAdapter"); |
| 122 | |
| 123 | // Process any changes in the parameters... |
| 124 | if (!Objects.equals(newIp, aliasIp) || |
| 125 | !Objects.equals(newMask, aliasMask) || |
| 126 | !Objects.equals(newAdapter, aliasAdapter)) { |
| 127 | synchronized (this) { |
| 128 | log.info("Reconfiguring with aliasIp={}, aliasMask={}, aliasAdapter={}, wasLeader={}", |
| 129 | newIp, newMask, newAdapter, wasLeader); |
| 130 | if (wasLeader) { |
| 131 | removeIpAlias(aliasIp, aliasMask, aliasAdapter); |
| 132 | addIpAlias(newIp, newMask, newAdapter); |
| 133 | } |
| 134 | aliasIp = newIp; |
| 135 | aliasMask = newMask; |
| 136 | aliasAdapter = newAdapter; |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | |
Madan Jampani | 620f70d | 2016-01-30 22:22:47 -0800 | [diff] [blame] | 141 | private synchronized void processLeaderChange(NodeId newLeader) { |
Thomas Vachuska | c03df25 | 2015-08-26 23:37:13 -0700 | [diff] [blame] | 142 | boolean isLeader = Objects.equals(newLeader, localId); |
| 143 | log.info("Processing leadership change; wasLeader={}, isLeader={}", wasLeader, isLeader); |
| 144 | if (!wasLeader && isLeader) { |
| 145 | // Gaining leadership, so setup the IP alias |
| 146 | addIpAlias(aliasIp, aliasMask, aliasAdapter); |
| 147 | wasLeader = true; |
| 148 | } else if (wasLeader && !isLeader) { |
| 149 | // Loosing leadership, so drop the IP alias |
| 150 | removeIpAlias(aliasIp, aliasMask, aliasAdapter); |
| 151 | wasLeader = false; |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | private synchronized void addIpAlias(String ip, String mask, String adapter) { |
| 156 | if (!isNullOrEmpty(ip) && !isNullOrEmpty(mask) && !isNullOrEmpty(adapter)) { |
| 157 | log.info("Adding IP alias {}/{} to {}", ip, mask, adapter); |
| 158 | execute("sudo ifconfig " + adapter + " " + ip + " netmask " + mask + " up", false); |
| 159 | execute("sudo /usr/sbin/arping -c 1 -I " + adapter + " " + ip, true); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | private synchronized void removeIpAlias(String ip, String mask, String adapter) { |
| 164 | if (!isNullOrEmpty(ip) && !isNullOrEmpty(mask) && !isNullOrEmpty(adapter)) { |
| 165 | log.info("Removing IP alias from {}", adapter, false); |
| 166 | execute("sudo ifconfig " + adapter + " down", true); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | private void execute(String command, boolean ignoreCode) { |
| 171 | try { |
| 172 | log.info("Executing [{}]", command); |
| 173 | Process process = Runtime.getRuntime().exec(command); |
| 174 | byte[] output = ByteStreams.toByteArray(process.getInputStream()); |
| 175 | byte[] error = ByteStreams.toByteArray(process.getErrorStream()); |
| 176 | int code = process.waitFor(); |
| 177 | if (code != 0 && !ignoreCode) { |
| 178 | log.info("Command failed: status={}, output={}, error={}", |
| 179 | code, new String(output), new String(error)); |
| 180 | } |
| 181 | } catch (IOException e) { |
| 182 | log.error("Unable to execute command {}", command, e); |
| 183 | } catch (InterruptedException e) { |
| 184 | log.error("Interrupted executing command {}", command, e); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | // Listens for leadership changes. |
| 189 | private class InternalLeadershipListener implements LeadershipEventListener { |
Madan Jampani | 620f70d | 2016-01-30 22:22:47 -0800 | [diff] [blame] | 190 | |
| 191 | @Override |
| 192 | public boolean isRelevant(LeadershipEvent event) { |
| 193 | return CLUSTER_IP.equals(event.subject().topic()); |
| 194 | } |
| 195 | |
Thomas Vachuska | c03df25 | 2015-08-26 23:37:13 -0700 | [diff] [blame] | 196 | @Override |
| 197 | public void event(LeadershipEvent event) { |
Madan Jampani | 620f70d | 2016-01-30 22:22:47 -0800 | [diff] [blame] | 198 | processLeaderChange(event.subject().leaderNodeId()); |
Thomas Vachuska | c03df25 | 2015-08-26 23:37:13 -0700 | [diff] [blame] | 199 | } |
| 200 | } |
| 201 | |
| 202 | } |