blob: a6f3d1c512f2c91035cb6c035b291986a98f73e8 [file] [log] [blame]
Thomas Vachuskac03df252015-08-26 23:37:13 -07001/*
2 * Copyright 2015 Open Networking Laboratory
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 */
16package org.onosproject.cip;
17
18import com.google.common.io.ByteStreams;
Madan Jampani620f70d2016-01-30 22:22:47 -080019
Thomas Vachuskac03df252015-08-26 23:37:13 -070020import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Modified;
24import org.apache.felix.scr.annotations.Property;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
27import org.onosproject.cfg.ComponentConfigService;
28import org.onosproject.cluster.ClusterService;
29import org.onosproject.cluster.LeadershipEvent;
30import org.onosproject.cluster.LeadershipEventListener;
31import org.onosproject.cluster.LeadershipService;
32import org.onosproject.cluster.NodeId;
33import org.osgi.service.component.ComponentContext;
34import org.slf4j.Logger;
35import org.slf4j.LoggerFactory;
36
37import java.io.IOException;
38import java.util.Dictionary;
39import java.util.Objects;
40import java.util.Properties;
41
42import static com.google.common.base.Strings.isNullOrEmpty;
43import 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)
60public 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 Jampani620f70d2016-01-30 22:22:47 -080097 processLeaderChange(leadershipService.getLeader(CLUSTER_IP));
Thomas Vachuskac03df252015-08-26 23:37:13 -070098
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 Jampani620f70d2016-01-30 22:22:47 -0800141 private synchronized void processLeaderChange(NodeId newLeader) {
Thomas Vachuskac03df252015-08-26 23:37:13 -0700142 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 Jampani620f70d2016-01-30 22:22:47 -0800190
191 @Override
192 public boolean isRelevant(LeadershipEvent event) {
193 return CLUSTER_IP.equals(event.subject().topic());
194 }
195
Thomas Vachuskac03df252015-08-26 23:37:13 -0700196 @Override
197 public void event(LeadershipEvent event) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800198 processLeaderChange(event.subject().leaderNodeId());
Thomas Vachuskac03df252015-08-26 23:37:13 -0700199 }
200 }
201
202}