blob: 17259fd07aabe5748d09ff99be61da0ba79db60a [file] [log] [blame]
Thomas Vachuskac03df252015-08-26 23:37:13 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Thomas Vachuskac03df252015-08-26 23:37:13 -07003 *
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;
Thomas Vachuskac03df252015-08-26 23:37:13 -070019import org.onosproject.cfg.ComponentConfigService;
20import org.onosproject.cluster.ClusterService;
21import org.onosproject.cluster.LeadershipEvent;
22import org.onosproject.cluster.LeadershipEventListener;
23import org.onosproject.cluster.LeadershipService;
24import org.onosproject.cluster.NodeId;
25import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070026import org.osgi.service.component.annotations.Activate;
27import org.osgi.service.component.annotations.Component;
28import org.osgi.service.component.annotations.Deactivate;
29import org.osgi.service.component.annotations.Modified;
30import org.osgi.service.component.annotations.Reference;
31import org.osgi.service.component.annotations.ReferenceCardinality;
Thomas Vachuskac03df252015-08-26 23:37:13 -070032import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
34
35import java.io.IOException;
36import java.util.Dictionary;
37import java.util.Objects;
38import java.util.Properties;
39
40import static com.google.common.base.Strings.isNullOrEmpty;
41import static org.onlab.util.Tools.get;
42
43/**
44 * Manages cluster IP address alias.
45 *
46 * To use the application, simply install it on ONOS and then configure it
47 * with the desired alias IP/mask/adapter configuration.
48 *
49 * If you are running it using upstart, you can also add the following
50 * command to the /opt/onos/options file:
51 *
52 * sudo ifconfig eth0:0 down # use the desired alias adapter
53 *
54 * This will make sure that if the process is killed abruptly, the IP alias
55 * will be dropped upon respawn.
56 */
57@Component(immediate = true)
58public class ClusterIpManager {
59
60 private final Logger log = LoggerFactory.getLogger(getClass());
61
62 private static final String CLUSTER_IP = "cluster/ip";
63
Ray Milkeyd84f89b2018-08-17 14:54:17 -070064 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuskac03df252015-08-26 23:37:13 -070065 protected ClusterService clusterService;
66
Ray Milkeyd84f89b2018-08-17 14:54:17 -070067 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuskac03df252015-08-26 23:37:13 -070068 protected LeadershipService leadershipService;
69
Ray Milkeyd84f89b2018-08-17 14:54:17 -070070 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuskac03df252015-08-26 23:37:13 -070071 protected ComponentConfigService cfgService;
72
73 private final LeadershipEventListener listener = new InternalLeadershipListener();
74
75 private NodeId localId;
76 private boolean wasLeader = false;
77
78 // By default there is no IP; this has to be configured
Ray Milkeyd84f89b2018-08-17 14:54:17 -070079 //@Property(name = "aliasIp", value = "", label = "Alias IP address")
Thomas Vachuskac03df252015-08-26 23:37:13 -070080 private String aliasIp = "";
81
82 public static final String DEFAULT_MASK = "255.255.0.0";
Ray Milkeyd84f89b2018-08-17 14:54:17 -070083 //@Property(name = "aliasMask", value = DEFAULT_MASK, label = "Alias IP mask")
Thomas Vachuskac03df252015-08-26 23:37:13 -070084 private String aliasMask = DEFAULT_MASK;
85
86 public static final String ETH_0 = "eth0:0";
Ray Milkeyd84f89b2018-08-17 14:54:17 -070087 //@Property(name = "aliasAdapter", value = ETH_0, label = "Alias IP adapter")
Thomas Vachuskac03df252015-08-26 23:37:13 -070088 private String aliasAdapter = ETH_0;
89
90 @Activate
91 protected void activate(ComponentContext context) {
92 cfgService.registerProperties(getClass());
93
94 localId = clusterService.getLocalNode().id();
Madan Jampani620f70d2016-01-30 22:22:47 -080095 processLeaderChange(leadershipService.getLeader(CLUSTER_IP));
Thomas Vachuskac03df252015-08-26 23:37:13 -070096
97 leadershipService.addListener(listener);
98 leadershipService.runForLeadership(CLUSTER_IP);
99 log.info("Started");
100 }
101
102 @Deactivate
103 protected void deactivate(ComponentContext context) {
104 cfgService.unregisterProperties(getClass(), false);
105
106 removeIpAlias(aliasIp, aliasMask, aliasAdapter);
107
108 leadershipService.removeListener(listener);
109 leadershipService.withdraw(CLUSTER_IP);
110 log.info("Stopped");
111 }
112
113 @Modified
114 protected void modified(ComponentContext context) {
115 log.info("Received configuration change...");
116 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
117 String newIp = get(properties, "aliasIp");
118 String newMask = get(properties, "aliasMask");
119 String newAdapter = get(properties, "aliasAdapter");
120
121 // Process any changes in the parameters...
122 if (!Objects.equals(newIp, aliasIp) ||
123 !Objects.equals(newMask, aliasMask) ||
124 !Objects.equals(newAdapter, aliasAdapter)) {
125 synchronized (this) {
126 log.info("Reconfiguring with aliasIp={}, aliasMask={}, aliasAdapter={}, wasLeader={}",
127 newIp, newMask, newAdapter, wasLeader);
128 if (wasLeader) {
129 removeIpAlias(aliasIp, aliasMask, aliasAdapter);
130 addIpAlias(newIp, newMask, newAdapter);
131 }
132 aliasIp = newIp;
133 aliasMask = newMask;
134 aliasAdapter = newAdapter;
135 }
136 }
137 }
138
Madan Jampani620f70d2016-01-30 22:22:47 -0800139 private synchronized void processLeaderChange(NodeId newLeader) {
Thomas Vachuskac03df252015-08-26 23:37:13 -0700140 boolean isLeader = Objects.equals(newLeader, localId);
141 log.info("Processing leadership change; wasLeader={}, isLeader={}", wasLeader, isLeader);
142 if (!wasLeader && isLeader) {
143 // Gaining leadership, so setup the IP alias
144 addIpAlias(aliasIp, aliasMask, aliasAdapter);
145 wasLeader = true;
146 } else if (wasLeader && !isLeader) {
Ray Milkeyc108a6b2017-08-23 15:23:50 -0700147 // Losing leadership, so drop the IP alias
Thomas Vachuskac03df252015-08-26 23:37:13 -0700148 removeIpAlias(aliasIp, aliasMask, aliasAdapter);
149 wasLeader = false;
150 }
151 }
152
153 private synchronized void addIpAlias(String ip, String mask, String adapter) {
154 if (!isNullOrEmpty(ip) && !isNullOrEmpty(mask) && !isNullOrEmpty(adapter)) {
155 log.info("Adding IP alias {}/{} to {}", ip, mask, adapter);
156 execute("sudo ifconfig " + adapter + " " + ip + " netmask " + mask + " up", false);
157 execute("sudo /usr/sbin/arping -c 1 -I " + adapter + " " + ip, true);
158 }
159 }
160
161 private synchronized void removeIpAlias(String ip, String mask, String adapter) {
162 if (!isNullOrEmpty(ip) && !isNullOrEmpty(mask) && !isNullOrEmpty(adapter)) {
163 log.info("Removing IP alias from {}", adapter, false);
164 execute("sudo ifconfig " + adapter + " down", true);
165 }
166 }
167
168 private void execute(String command, boolean ignoreCode) {
169 try {
170 log.info("Executing [{}]", command);
171 Process process = Runtime.getRuntime().exec(command);
172 byte[] output = ByteStreams.toByteArray(process.getInputStream());
173 byte[] error = ByteStreams.toByteArray(process.getErrorStream());
174 int code = process.waitFor();
175 if (code != 0 && !ignoreCode) {
176 log.info("Command failed: status={}, output={}, error={}",
177 code, new String(output), new String(error));
178 }
179 } catch (IOException e) {
180 log.error("Unable to execute command {}", command, e);
181 } catch (InterruptedException e) {
182 log.error("Interrupted executing command {}", command, e);
Ray Milkey5c7d4882018-02-05 14:50:39 -0800183 Thread.currentThread().interrupt();
Thomas Vachuskac03df252015-08-26 23:37:13 -0700184 }
185 }
186
187 // Listens for leadership changes.
188 private class InternalLeadershipListener implements LeadershipEventListener {
Madan Jampani620f70d2016-01-30 22:22:47 -0800189
190 @Override
191 public boolean isRelevant(LeadershipEvent event) {
192 return CLUSTER_IP.equals(event.subject().topic());
193 }
194
Thomas Vachuskac03df252015-08-26 23:37:13 -0700195 @Override
196 public void event(LeadershipEvent event) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800197 processLeaderChange(event.subject().leaderNodeId());
Thomas Vachuskac03df252015-08-26 23:37:13 -0700198 }
199 }
200
201}