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