blob: fd8bb40f3a08a4609bb0c731361259fb80c55e2f [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;
Ray Milkey20695652018-10-31 10:32:34 -070042import static org.onosproject.cip.OsgiPropertyConstants.ALIAS_ADAPTER;
43import static org.onosproject.cip.OsgiPropertyConstants.ALIAS_ADAPTER_DEFAULT;
44import static org.onosproject.cip.OsgiPropertyConstants.ALIAS_IP;
45import static org.onosproject.cip.OsgiPropertyConstants.ALIAS_IP_DEFAULT;
46import static org.onosproject.cip.OsgiPropertyConstants.ALIAS_MASK;
47import static org.onosproject.cip.OsgiPropertyConstants.ALIAS_MASK_DEFAULT;
Thomas Vachuskac03df252015-08-26 23:37:13 -070048
49/**
50 * Manages cluster IP address alias.
51 *
52 * To use the application, simply install it on ONOS and then configure it
53 * with the desired alias IP/mask/adapter configuration.
54 *
55 * If you are running it using upstart, you can also add the following
56 * command to the /opt/onos/options file:
57 *
58 * sudo ifconfig eth0:0 down # use the desired alias adapter
59 *
60 * This will make sure that if the process is killed abruptly, the IP alias
61 * will be dropped upon respawn.
62 */
Ray Milkey20695652018-10-31 10:32:34 -070063@Component(
64 immediate = true,
65 property = {
66 ALIAS_IP + "=" + ALIAS_IP_DEFAULT,
67 ALIAS_MASK + "=" + ALIAS_MASK_DEFAULT,
68 ALIAS_ADAPTER + "=" + ALIAS_ADAPTER_DEFAULT
69 }
70)
Thomas Vachuskac03df252015-08-26 23:37:13 -070071public class ClusterIpManager {
72
73 private final Logger log = LoggerFactory.getLogger(getClass());
74
75 private static final String CLUSTER_IP = "cluster/ip";
76
Ray Milkeyd84f89b2018-08-17 14:54:17 -070077 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuskac03df252015-08-26 23:37:13 -070078 protected ClusterService clusterService;
79
Ray Milkeyd84f89b2018-08-17 14:54:17 -070080 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuskac03df252015-08-26 23:37:13 -070081 protected LeadershipService leadershipService;
82
Ray Milkeyd84f89b2018-08-17 14:54:17 -070083 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuskac03df252015-08-26 23:37:13 -070084 protected ComponentConfigService cfgService;
85
86 private final LeadershipEventListener listener = new InternalLeadershipListener();
87
88 private NodeId localId;
89 private boolean wasLeader = false;
90
Ray Milkey20695652018-10-31 10:32:34 -070091 /** Alias IP address. */
92 private String aliasIp = ALIAS_IP_DEFAULT;
Thomas Vachuskac03df252015-08-26 23:37:13 -070093
Ray Milkey20695652018-10-31 10:32:34 -070094 /** Alias IP mask. */
95 private String aliasMask = ALIAS_MASK_DEFAULT;
Thomas Vachuskac03df252015-08-26 23:37:13 -070096
Ray Milkey20695652018-10-31 10:32:34 -070097 /** Alias IP adapter. */
98 private String aliasAdapter = ALIAS_ADAPTER_DEFAULT;
Thomas Vachuskac03df252015-08-26 23:37:13 -070099
100 @Activate
101 protected void activate(ComponentContext context) {
102 cfgService.registerProperties(getClass());
103
104 localId = clusterService.getLocalNode().id();
Madan Jampani620f70d2016-01-30 22:22:47 -0800105 processLeaderChange(leadershipService.getLeader(CLUSTER_IP));
Thomas Vachuskac03df252015-08-26 23:37:13 -0700106
107 leadershipService.addListener(listener);
108 leadershipService.runForLeadership(CLUSTER_IP);
109 log.info("Started");
110 }
111
112 @Deactivate
113 protected void deactivate(ComponentContext context) {
114 cfgService.unregisterProperties(getClass(), false);
115
116 removeIpAlias(aliasIp, aliasMask, aliasAdapter);
117
118 leadershipService.removeListener(listener);
119 leadershipService.withdraw(CLUSTER_IP);
120 log.info("Stopped");
121 }
122
123 @Modified
124 protected void modified(ComponentContext context) {
125 log.info("Received configuration change...");
126 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
Ray Milkey20695652018-10-31 10:32:34 -0700127 String newIp = get(properties, ALIAS_IP);
128 String newMask = get(properties, ALIAS_MASK);
129 String newAdapter = get(properties, ALIAS_ADAPTER);
Thomas Vachuskac03df252015-08-26 23:37:13 -0700130
131 // Process any changes in the parameters...
132 if (!Objects.equals(newIp, aliasIp) ||
133 !Objects.equals(newMask, aliasMask) ||
134 !Objects.equals(newAdapter, aliasAdapter)) {
135 synchronized (this) {
136 log.info("Reconfiguring with aliasIp={}, aliasMask={}, aliasAdapter={}, wasLeader={}",
137 newIp, newMask, newAdapter, wasLeader);
138 if (wasLeader) {
139 removeIpAlias(aliasIp, aliasMask, aliasAdapter);
140 addIpAlias(newIp, newMask, newAdapter);
141 }
142 aliasIp = newIp;
143 aliasMask = newMask;
144 aliasAdapter = newAdapter;
145 }
146 }
147 }
148
Madan Jampani620f70d2016-01-30 22:22:47 -0800149 private synchronized void processLeaderChange(NodeId newLeader) {
Thomas Vachuskac03df252015-08-26 23:37:13 -0700150 boolean isLeader = Objects.equals(newLeader, localId);
151 log.info("Processing leadership change; wasLeader={}, isLeader={}", wasLeader, isLeader);
152 if (!wasLeader && isLeader) {
153 // Gaining leadership, so setup the IP alias
154 addIpAlias(aliasIp, aliasMask, aliasAdapter);
155 wasLeader = true;
156 } else if (wasLeader && !isLeader) {
Ray Milkeyc108a6b2017-08-23 15:23:50 -0700157 // Losing leadership, so drop the IP alias
Thomas Vachuskac03df252015-08-26 23:37:13 -0700158 removeIpAlias(aliasIp, aliasMask, aliasAdapter);
159 wasLeader = false;
160 }
161 }
162
163 private synchronized void addIpAlias(String ip, String mask, String adapter) {
164 if (!isNullOrEmpty(ip) && !isNullOrEmpty(mask) && !isNullOrEmpty(adapter)) {
165 log.info("Adding IP alias {}/{} to {}", ip, mask, adapter);
166 execute("sudo ifconfig " + adapter + " " + ip + " netmask " + mask + " up", false);
167 execute("sudo /usr/sbin/arping -c 1 -I " + adapter + " " + ip, true);
168 }
169 }
170
171 private synchronized void removeIpAlias(String ip, String mask, String adapter) {
172 if (!isNullOrEmpty(ip) && !isNullOrEmpty(mask) && !isNullOrEmpty(adapter)) {
173 log.info("Removing IP alias from {}", adapter, false);
174 execute("sudo ifconfig " + adapter + " down", true);
175 }
176 }
177
178 private void execute(String command, boolean ignoreCode) {
179 try {
180 log.info("Executing [{}]", command);
181 Process process = Runtime.getRuntime().exec(command);
182 byte[] output = ByteStreams.toByteArray(process.getInputStream());
183 byte[] error = ByteStreams.toByteArray(process.getErrorStream());
184 int code = process.waitFor();
185 if (code != 0 && !ignoreCode) {
186 log.info("Command failed: status={}, output={}, error={}",
187 code, new String(output), new String(error));
188 }
189 } catch (IOException e) {
190 log.error("Unable to execute command {}", command, e);
191 } catch (InterruptedException e) {
192 log.error("Interrupted executing command {}", command, e);
Ray Milkey5c7d4882018-02-05 14:50:39 -0800193 Thread.currentThread().interrupt();
Thomas Vachuskac03df252015-08-26 23:37:13 -0700194 }
195 }
196
197 // Listens for leadership changes.
198 private class InternalLeadershipListener implements LeadershipEventListener {
Madan Jampani620f70d2016-01-30 22:22:47 -0800199
200 @Override
201 public boolean isRelevant(LeadershipEvent event) {
202 return CLUSTER_IP.equals(event.subject().topic());
203 }
204
Thomas Vachuskac03df252015-08-26 23:37:13 -0700205 @Override
206 public void event(LeadershipEvent event) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800207 processLeaderChange(event.subject().leaderNodeId());
Thomas Vachuskac03df252015-08-26 23:37:13 -0700208 }
209 }
210
211}