Thomas Vachuska
Committed by Gerrit Code Review

ONOS-2744 Adding a small app to manage cluster IP alias; requires arping tool.

Change-Id: Ib7032586206542c1db67d248196ad8a883d67b51
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2014 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<project xmlns="http://maven.apache.org/POM/4.0.0"
18 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
20 + <modelVersion>4.0.0</modelVersion>
21 +
22 + <parent>
23 + <groupId>org.onosproject</groupId>
24 + <artifactId>onos-apps</artifactId>
25 + <version>1.3.0-SNAPSHOT</version>
26 + <relativePath>../pom.xml</relativePath>
27 + </parent>
28 +
29 + <artifactId>onos-app-cip</artifactId>
30 + <packaging>bundle</packaging>
31 +
32 + <description>Cluster IP alias</description>
33 +
34 + <properties>
35 + <onos.app.name>org.onosproject.cip</onos.app.name>
36 + </properties>
37 +
38 + <dependencies>
39 + <dependency>
40 + <groupId>org.osgi</groupId>
41 + <artifactId>org.osgi.compendium</artifactId>
42 + </dependency>
43 + </dependencies>
44 +
45 +</project>
1 +/*
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 + */
16 +package org.onosproject.cip;
17 +
18 +import com.google.common.io.ByteStreams;
19 +import org.apache.felix.scr.annotations.Activate;
20 +import org.apache.felix.scr.annotations.Component;
21 +import org.apache.felix.scr.annotations.Deactivate;
22 +import org.apache.felix.scr.annotations.Modified;
23 +import org.apache.felix.scr.annotations.Property;
24 +import org.apache.felix.scr.annotations.Reference;
25 +import org.apache.felix.scr.annotations.ReferenceCardinality;
26 +import org.onosproject.cfg.ComponentConfigService;
27 +import org.onosproject.cluster.ClusterService;
28 +import org.onosproject.cluster.LeadershipEvent;
29 +import org.onosproject.cluster.LeadershipEventListener;
30 +import org.onosproject.cluster.LeadershipService;
31 +import org.onosproject.cluster.NodeId;
32 +import org.osgi.service.component.ComponentContext;
33 +import org.slf4j.Logger;
34 +import org.slf4j.LoggerFactory;
35 +
36 +import java.io.IOException;
37 +import java.util.Dictionary;
38 +import java.util.Objects;
39 +import java.util.Properties;
40 +
41 +import static com.google.common.base.Strings.isNullOrEmpty;
42 +import 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)
59 +public 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 +}
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
52 <module>xos-integration</module> 52 <module>xos-integration</module>
53 <module>pcep-api</module> 53 <module>pcep-api</module>
54 <module>olt</module> 54 <module>olt</module>
55 + <module>cip</module>
55 <module>flowanalyzer</module> 56 <module>flowanalyzer</module>
56 <module>vtnrsc</module> 57 <module>vtnrsc</module>
57 <module>vtn</module> 58 <module>vtn</module>
......