Port the Router functionality from SDN-IP.
As part of this we added an onlab-thirdparty artifact which allows us to bring in dependencies that aren't bundles.
Showing
12 changed files
with
1240 additions
and
1 deletions
... | @@ -36,6 +36,12 @@ | ... | @@ -36,6 +36,12 @@ |
36 | <groupId>com.google.guava</groupId> | 36 | <groupId>com.google.guava</groupId> |
37 | <artifactId>guava</artifactId> | 37 | <artifactId>guava</artifactId> |
38 | </dependency> | 38 | </dependency> |
39 | + | ||
40 | + <dependency> | ||
41 | + <groupId>org.onlab.onos</groupId> | ||
42 | + <artifactId>onlab-thirdparty</artifactId> | ||
43 | + </dependency> | ||
44 | + | ||
39 | </dependencies> | 45 | </dependencies> |
40 | 46 | ||
41 | </project> | 47 | </project> | ... | ... |
1 | +package org.onlab.onos.sdnip; | ||
2 | + | ||
3 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
4 | + | ||
5 | +import java.util.Objects; | ||
6 | + | ||
7 | +import org.onlab.packet.IpAddress; | ||
8 | +import org.onlab.packet.IpPrefix; | ||
9 | + | ||
10 | +import com.google.common.base.MoreObjects; | ||
11 | + | ||
12 | +/** | ||
13 | + * Represents a route entry for an IP prefix. | ||
14 | + */ | ||
15 | +public class RouteEntry { | ||
16 | + private final IpPrefix prefix; // The IP prefix | ||
17 | + private final IpAddress nextHop; // Next-hop IP address | ||
18 | + | ||
19 | + /** | ||
20 | + * Class constructor. | ||
21 | + * | ||
22 | + * @param prefix the IP prefix of the route | ||
23 | + * @param nextHop the next hop IP address for the route | ||
24 | + */ | ||
25 | + public RouteEntry(IpPrefix prefix, IpAddress nextHop) { | ||
26 | + this.prefix = checkNotNull(prefix); | ||
27 | + this.nextHop = checkNotNull(nextHop); | ||
28 | + } | ||
29 | + | ||
30 | + /** | ||
31 | + * Returns the IP prefix of the route. | ||
32 | + * | ||
33 | + * @return the IP prefix of the route | ||
34 | + */ | ||
35 | + public IpPrefix prefix() { | ||
36 | + return prefix; | ||
37 | + } | ||
38 | + | ||
39 | + /** | ||
40 | + * Returns the next hop IP address for the route. | ||
41 | + * | ||
42 | + * @return the next hop IP address for the route | ||
43 | + */ | ||
44 | + public IpAddress nextHop() { | ||
45 | + return nextHop; | ||
46 | + } | ||
47 | + | ||
48 | + /** | ||
49 | + * Creates the binary string representation of an IPv4 prefix. | ||
50 | + * The string length is equal to the prefix length. | ||
51 | + * | ||
52 | + * @param ip4Prefix the IPv4 prefix to use | ||
53 | + * @return the binary string representation | ||
54 | + */ | ||
55 | + static String createBinaryString(IpPrefix ip4Prefix) { | ||
56 | + if (ip4Prefix.prefixLength() == 0) { | ||
57 | + return ""; | ||
58 | + } | ||
59 | + | ||
60 | + StringBuilder result = new StringBuilder(ip4Prefix.prefixLength()); | ||
61 | + long value = ip4Prefix.toRealInt(); | ||
62 | + for (int i = 0; i < ip4Prefix.prefixLength(); i++) { | ||
63 | + long mask = 1 << (IpAddress.MAX_INET_MASK - 1 - i); | ||
64 | + result.append(((value & mask) == 0) ? "0" : "1"); | ||
65 | + } | ||
66 | + return result.toString(); | ||
67 | + } | ||
68 | + | ||
69 | + @Override | ||
70 | + public boolean equals(Object other) { | ||
71 | + if (this == other) { | ||
72 | + return true; | ||
73 | + } | ||
74 | + | ||
75 | + // | ||
76 | + // NOTE: Subclasses are considered as change of identity, hence | ||
77 | + // equals() will return false if the class type doesn't match. | ||
78 | + // | ||
79 | + if (other == null || getClass() != other.getClass()) { | ||
80 | + return false; | ||
81 | + } | ||
82 | + | ||
83 | + RouteEntry otherRoute = (RouteEntry) other; | ||
84 | + return Objects.equals(this.prefix, otherRoute.prefix) && | ||
85 | + Objects.equals(this.nextHop, otherRoute.nextHop); | ||
86 | + } | ||
87 | + | ||
88 | + @Override | ||
89 | + public int hashCode() { | ||
90 | + return Objects.hash(prefix, nextHop); | ||
91 | + } | ||
92 | + | ||
93 | + @Override | ||
94 | + public String toString() { | ||
95 | + return MoreObjects.toStringHelper(getClass()) | ||
96 | + .add("prefix", prefix) | ||
97 | + .add("nextHop", nextHop) | ||
98 | + .toString(); | ||
99 | + } | ||
100 | +} |
1 | +package org.onlab.onos.sdnip; | ||
2 | + | ||
3 | +/** | ||
4 | + * An interface to receive route updates from route providers. | ||
5 | + */ | ||
6 | +public interface RouteListener { | ||
7 | + /** | ||
8 | + * Receives a route update from a route provider. | ||
9 | + * | ||
10 | + * @param routeUpdate the updated route information | ||
11 | + */ | ||
12 | + public void update(RouteUpdate routeUpdate); | ||
13 | +} |
1 | +package org.onlab.onos.sdnip; | ||
2 | + | ||
3 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
4 | + | ||
5 | +import java.util.Objects; | ||
6 | + | ||
7 | +import com.google.common.base.MoreObjects; | ||
8 | + | ||
9 | +/** | ||
10 | + * Represents a change in routing information. | ||
11 | + */ | ||
12 | +public class RouteUpdate { | ||
13 | + private final Type type; // The route update type | ||
14 | + private final RouteEntry routeEntry; // The updated route entry | ||
15 | + | ||
16 | + /** | ||
17 | + * Specifies the type of a route update. | ||
18 | + * <p/> | ||
19 | + * Route updates can either provide updated information for a route, or | ||
20 | + * withdraw a previously updated route. | ||
21 | + */ | ||
22 | + public enum Type { | ||
23 | + /** | ||
24 | + * The update contains updated route information for a route. | ||
25 | + */ | ||
26 | + UPDATE, | ||
27 | + /** | ||
28 | + * The update withdraws the route, meaning any previous information is | ||
29 | + * no longer valid. | ||
30 | + */ | ||
31 | + DELETE | ||
32 | + } | ||
33 | + | ||
34 | + /** | ||
35 | + * Class constructor. | ||
36 | + * | ||
37 | + * @param type the type of the route update | ||
38 | + * @param routeEntry the route entry with the update | ||
39 | + */ | ||
40 | + public RouteUpdate(Type type, RouteEntry routeEntry) { | ||
41 | + this.type = type; | ||
42 | + this.routeEntry = checkNotNull(routeEntry); | ||
43 | + } | ||
44 | + | ||
45 | + /** | ||
46 | + * Returns the type of the route update. | ||
47 | + * | ||
48 | + * @return the type of the update | ||
49 | + */ | ||
50 | + public Type type() { | ||
51 | + return type; | ||
52 | + } | ||
53 | + | ||
54 | + /** | ||
55 | + * Returns the route entry the route update is for. | ||
56 | + * | ||
57 | + * @return the route entry the route update is for | ||
58 | + */ | ||
59 | + public RouteEntry routeEntry() { | ||
60 | + return routeEntry; | ||
61 | + } | ||
62 | + | ||
63 | + @Override | ||
64 | + public boolean equals(Object other) { | ||
65 | + if (other == this) { | ||
66 | + return true; | ||
67 | + } | ||
68 | + | ||
69 | + if (!(other instanceof RouteUpdate)) { | ||
70 | + return false; | ||
71 | + } | ||
72 | + | ||
73 | + RouteUpdate otherUpdate = (RouteUpdate) other; | ||
74 | + | ||
75 | + return Objects.equals(this.type, otherUpdate.type) && | ||
76 | + Objects.equals(this.routeEntry, otherUpdate.routeEntry); | ||
77 | + } | ||
78 | + | ||
79 | + @Override | ||
80 | + public int hashCode() { | ||
81 | + return Objects.hash(type, routeEntry); | ||
82 | + } | ||
83 | + | ||
84 | + @Override | ||
85 | + public String toString() { | ||
86 | + return MoreObjects.toStringHelper(getClass()) | ||
87 | + .add("type", type) | ||
88 | + .add("routeEntry", routeEntry) | ||
89 | + .toString(); | ||
90 | + } | ||
91 | +} |
1 | +package org.onlab.onos.sdnip; | ||
2 | + | ||
3 | +import java.util.Collection; | ||
4 | +import java.util.HashMap; | ||
5 | +import java.util.HashSet; | ||
6 | +import java.util.Iterator; | ||
7 | +import java.util.LinkedList; | ||
8 | +import java.util.List; | ||
9 | +import java.util.Map; | ||
10 | +import java.util.Set; | ||
11 | +import java.util.concurrent.BlockingQueue; | ||
12 | +import java.util.concurrent.ConcurrentHashMap; | ||
13 | +import java.util.concurrent.ExecutorService; | ||
14 | +import java.util.concurrent.Executors; | ||
15 | +import java.util.concurrent.LinkedBlockingQueue; | ||
16 | +import java.util.concurrent.Semaphore; | ||
17 | + | ||
18 | +import org.apache.commons.lang3.tuple.Pair; | ||
19 | +import org.onlab.onos.net.ConnectPoint; | ||
20 | +import org.onlab.onos.net.Host; | ||
21 | +import org.onlab.onos.net.flow.DefaultTrafficSelector; | ||
22 | +import org.onlab.onos.net.flow.DefaultTrafficTreatment; | ||
23 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
24 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
25 | +import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion; | ||
26 | +import org.onlab.onos.net.flow.criteria.Criterion; | ||
27 | +import org.onlab.onos.net.flow.criteria.Criterion.Type; | ||
28 | +import org.onlab.onos.net.host.HostEvent; | ||
29 | +import org.onlab.onos.net.host.HostListener; | ||
30 | +import org.onlab.onos.net.host.HostService; | ||
31 | +import org.onlab.onos.net.intent.Intent; | ||
32 | +import org.onlab.onos.net.intent.IntentId; | ||
33 | +import org.onlab.onos.net.intent.IntentService; | ||
34 | +import org.onlab.onos.net.intent.MultiPointToSinglePointIntent; | ||
35 | +import org.onlab.onos.sdnip.config.BgpPeer; | ||
36 | +import org.onlab.onos.sdnip.config.Interface; | ||
37 | +import org.onlab.onos.sdnip.config.SdnIpConfigService; | ||
38 | +import org.onlab.packet.Ethernet; | ||
39 | +import org.onlab.packet.IpAddress; | ||
40 | +import org.onlab.packet.IpPrefix; | ||
41 | +import org.onlab.packet.MacAddress; | ||
42 | +import org.slf4j.Logger; | ||
43 | +import org.slf4j.LoggerFactory; | ||
44 | + | ||
45 | +import com.google.common.base.Objects; | ||
46 | +import com.google.common.collect.HashMultimap; | ||
47 | +import com.google.common.collect.Multimaps; | ||
48 | +import com.google.common.collect.SetMultimap; | ||
49 | +import com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
50 | +import com.googlecode.concurrenttrees.common.KeyValuePair; | ||
51 | +import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory; | ||
52 | +import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree; | ||
53 | +import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree; | ||
54 | + | ||
55 | +/** | ||
56 | + * This class processes BGP route update, translates each update into a intent | ||
57 | + * and submits the intent. | ||
58 | + * | ||
59 | + * TODO: Make it thread-safe. | ||
60 | + */ | ||
61 | +public class Router implements RouteListener { | ||
62 | + | ||
63 | + private static final Logger log = LoggerFactory.getLogger(Router.class); | ||
64 | + | ||
65 | + // Store all route updates in a InvertedRadixTree. | ||
66 | + // The key in this Tree is the binary sting of prefix of route. | ||
67 | + // The Ip4Address is the next hop address of route, and is also the value | ||
68 | + // of each entry. | ||
69 | + private InvertedRadixTree<RouteEntry> bgpRoutes; | ||
70 | + | ||
71 | + // Stores all incoming route updates in a queue. | ||
72 | + private BlockingQueue<RouteUpdate> routeUpdates; | ||
73 | + | ||
74 | + // The Ip4Address is the next hop address of each route update. | ||
75 | + private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp; | ||
76 | + private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents; | ||
77 | + | ||
78 | + private IntentService intentService; | ||
79 | + //private IProxyArpService proxyArp; | ||
80 | + private HostService hostService; | ||
81 | + private SdnIpConfigService configInfoService; | ||
82 | + private InterfaceService interfaceService; | ||
83 | + | ||
84 | + private ExecutorService bgpUpdatesExecutor; | ||
85 | + private ExecutorService bgpIntentsSynchronizerExecutor; | ||
86 | + | ||
87 | + // TODO temporary | ||
88 | + private int intentId = Integer.MAX_VALUE / 2; | ||
89 | + | ||
90 | + // | ||
91 | + // State to deal with SDN-IP Leader election and pushing Intents | ||
92 | + // | ||
93 | + private Semaphore intentsSynchronizerSemaphore = new Semaphore(0); | ||
94 | + private volatile boolean isElectedLeader = false; | ||
95 | + private volatile boolean isActivatedLeader = false; | ||
96 | + | ||
97 | + // For routes announced by local BGP deamon in SDN network, | ||
98 | + // the next hop will be 0.0.0.0. | ||
99 | + public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0"); | ||
100 | + | ||
101 | + /** | ||
102 | + * Class constructor. | ||
103 | + * | ||
104 | + * @param intentService the intent service | ||
105 | + * @param proxyArp the proxy ARP service | ||
106 | + * @param configInfoService the configuration service | ||
107 | + * @param interfaceService the interface service | ||
108 | + */ | ||
109 | + public Router(IntentService intentService, HostService hostService, | ||
110 | + SdnIpConfigService configInfoService, InterfaceService interfaceService) { | ||
111 | + | ||
112 | + this.intentService = intentService; | ||
113 | + this.hostService = hostService; | ||
114 | + this.configInfoService = configInfoService; | ||
115 | + this.interfaceService = interfaceService; | ||
116 | + | ||
117 | + bgpRoutes = new ConcurrentInvertedRadixTree<>( | ||
118 | + new DefaultByteArrayNodeFactory()); | ||
119 | + routeUpdates = new LinkedBlockingQueue<>(); | ||
120 | + routesWaitingOnArp = Multimaps.synchronizedSetMultimap( | ||
121 | + HashMultimap.<IpAddress, RouteEntry>create()); | ||
122 | + pushedRouteIntents = new ConcurrentHashMap<>(); | ||
123 | + | ||
124 | + bgpUpdatesExecutor = Executors.newSingleThreadExecutor( | ||
125 | + new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build()); | ||
126 | + bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor( | ||
127 | + new ThreadFactoryBuilder() | ||
128 | + .setNameFormat("bgp-intents-synchronizer-%d").build()); | ||
129 | + } | ||
130 | + | ||
131 | + /** | ||
132 | + * Starts the Router. | ||
133 | + */ | ||
134 | + public void start() { | ||
135 | + | ||
136 | + bgpUpdatesExecutor.execute(new Runnable() { | ||
137 | + @Override | ||
138 | + public void run() { | ||
139 | + doUpdatesThread(); | ||
140 | + } | ||
141 | + }); | ||
142 | + | ||
143 | + bgpIntentsSynchronizerExecutor.execute(new Runnable() { | ||
144 | + @Override | ||
145 | + public void run() { | ||
146 | + doIntentSynchronizationThread(); | ||
147 | + } | ||
148 | + }); | ||
149 | + } | ||
150 | + | ||
151 | + //@Override TODO hook this up to something | ||
152 | + public void leaderChanged(boolean isLeader) { | ||
153 | + log.debug("Leader changed: {}", isLeader); | ||
154 | + | ||
155 | + if (!isLeader) { | ||
156 | + this.isElectedLeader = false; | ||
157 | + this.isActivatedLeader = false; | ||
158 | + return; // Nothing to do | ||
159 | + } | ||
160 | + this.isActivatedLeader = false; | ||
161 | + this.isElectedLeader = true; | ||
162 | + | ||
163 | + // | ||
164 | + // Tell the Intents Synchronizer thread to start the synchronization | ||
165 | + // | ||
166 | + intentsSynchronizerSemaphore.release(); | ||
167 | + } | ||
168 | + | ||
169 | + @Override | ||
170 | + public void update(RouteUpdate routeUpdate) { | ||
171 | + log.debug("Received new route Update: {}", routeUpdate); | ||
172 | + | ||
173 | + try { | ||
174 | + routeUpdates.put(routeUpdate); | ||
175 | + } catch (InterruptedException e) { | ||
176 | + log.debug("Interrupted while putting on routeUpdates queue", e); | ||
177 | + Thread.currentThread().interrupt(); | ||
178 | + } | ||
179 | + } | ||
180 | + | ||
181 | + /** | ||
182 | + * Thread for Intent Synchronization. | ||
183 | + */ | ||
184 | + private void doIntentSynchronizationThread() { | ||
185 | + boolean interrupted = false; | ||
186 | + try { | ||
187 | + while (!interrupted) { | ||
188 | + try { | ||
189 | + intentsSynchronizerSemaphore.acquire(); | ||
190 | + // | ||
191 | + // Drain all permits, because a single synchronization is | ||
192 | + // sufficient. | ||
193 | + // | ||
194 | + intentsSynchronizerSemaphore.drainPermits(); | ||
195 | + } catch (InterruptedException e) { | ||
196 | + log.debug("Interrupted while waiting to become " + | ||
197 | + "Intent Synchronization leader"); | ||
198 | + interrupted = true; | ||
199 | + break; | ||
200 | + } | ||
201 | + syncIntents(); | ||
202 | + } | ||
203 | + } finally { | ||
204 | + if (interrupted) { | ||
205 | + Thread.currentThread().interrupt(); | ||
206 | + } | ||
207 | + } | ||
208 | + } | ||
209 | + | ||
210 | + /** | ||
211 | + * Thread for handling route updates. | ||
212 | + */ | ||
213 | + private void doUpdatesThread() { | ||
214 | + boolean interrupted = false; | ||
215 | + try { | ||
216 | + while (!interrupted) { | ||
217 | + try { | ||
218 | + RouteUpdate update = routeUpdates.take(); | ||
219 | + switch (update.type()) { | ||
220 | + case UPDATE: | ||
221 | + processRouteAdd(update.routeEntry()); | ||
222 | + break; | ||
223 | + case DELETE: | ||
224 | + processRouteDelete(update.routeEntry()); | ||
225 | + break; | ||
226 | + default: | ||
227 | + log.error("Unknown update Type: {}", update.type()); | ||
228 | + break; | ||
229 | + } | ||
230 | + } catch (InterruptedException e) { | ||
231 | + log.debug("Interrupted while taking from updates queue", e); | ||
232 | + interrupted = true; | ||
233 | + } catch (Exception e) { | ||
234 | + log.debug("exception", e); | ||
235 | + } | ||
236 | + } | ||
237 | + } finally { | ||
238 | + if (interrupted) { | ||
239 | + Thread.currentThread().interrupt(); | ||
240 | + } | ||
241 | + } | ||
242 | + } | ||
243 | + | ||
244 | + /** | ||
245 | + * Performs Intents Synchronization between the internally stored Route | ||
246 | + * Intents and the installed Route Intents. | ||
247 | + */ | ||
248 | + private void syncIntents() { | ||
249 | + synchronized (this) { | ||
250 | + if (!isElectedLeader) { | ||
251 | + return; // Nothing to do: not the leader anymore | ||
252 | + } | ||
253 | + log.debug("Syncing SDN-IP Route Intents..."); | ||
254 | + | ||
255 | + Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents = | ||
256 | + new HashMap<>(); | ||
257 | + | ||
258 | + // | ||
259 | + // Fetch all intents, and classify the Multi-Point-to-Point Intents | ||
260 | + // based on the matching prefix. | ||
261 | + // | ||
262 | + for (Intent intent : intentService.getIntents()) { | ||
263 | + // | ||
264 | + // TODO: Ignore all intents that are not installed by | ||
265 | + // the SDN-IP application. | ||
266 | + // | ||
267 | + if (!(intent instanceof MultiPointToSinglePointIntent)) { | ||
268 | + continue; | ||
269 | + } | ||
270 | + MultiPointToSinglePointIntent mp2pIntent = | ||
271 | + (MultiPointToSinglePointIntent) intent; | ||
272 | + /*Match match = mp2pIntent.getMatch(); | ||
273 | + if (!(match instanceof PacketMatch)) { | ||
274 | + continue; | ||
275 | + } | ||
276 | + PacketMatch packetMatch = (PacketMatch) match; | ||
277 | + Ip4Prefix prefix = packetMatch.getDstIpAddress(); | ||
278 | + if (prefix == null) { | ||
279 | + continue; | ||
280 | + } | ||
281 | + fetchedIntents.put(prefix, mp2pIntent);*/ | ||
282 | + for (Criterion criterion : mp2pIntent.selector().criteria()) { | ||
283 | + if (criterion.type() == Type.IPV4_DST) { | ||
284 | + IPCriterion ipCriterion = (IPCriterion) criterion; | ||
285 | + fetchedIntents.put(ipCriterion.ip(), mp2pIntent); | ||
286 | + } | ||
287 | + } | ||
288 | + | ||
289 | + } | ||
290 | + | ||
291 | + // | ||
292 | + // Compare for each prefix the local IN-MEMORY Intents with the | ||
293 | + // FETCHED Intents: | ||
294 | + // - If the IN-MEMORY Intent is same as the FETCHED Intent, store | ||
295 | + // the FETCHED Intent in the local memory (i.e., override the | ||
296 | + // IN-MEMORY Intent) to preserve the original Intent ID | ||
297 | + // - if the IN-MEMORY Intent is not same as the FETCHED Intent, | ||
298 | + // delete the FETCHED Intent, and push/install the IN-MEMORY | ||
299 | + // Intent. | ||
300 | + // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED | ||
301 | + // Intent for same prefix, then push/install the IN-MEMORY | ||
302 | + // Intent. | ||
303 | + // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY | ||
304 | + // Intent for same prefix, then delete/withdraw the FETCHED | ||
305 | + // Intent. | ||
306 | + // | ||
307 | + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>> | ||
308 | + storeInMemoryIntents = new LinkedList<>(); | ||
309 | + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>> | ||
310 | + addIntents = new LinkedList<>(); | ||
311 | + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>> | ||
312 | + deleteIntents = new LinkedList<>(); | ||
313 | + for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry : | ||
314 | + pushedRouteIntents.entrySet()) { | ||
315 | + IpPrefix prefix = entry.getKey(); | ||
316 | + MultiPointToSinglePointIntent inMemoryIntent = | ||
317 | + entry.getValue(); | ||
318 | + MultiPointToSinglePointIntent fetchedIntent = | ||
319 | + fetchedIntents.get(prefix); | ||
320 | + | ||
321 | + if (fetchedIntent == null) { | ||
322 | + // | ||
323 | + // No FETCHED Intent for same prefix: push the IN-MEMORY | ||
324 | + // Intent. | ||
325 | + // | ||
326 | + addIntents.add(Pair.of(prefix, inMemoryIntent)); | ||
327 | + continue; | ||
328 | + } | ||
329 | + | ||
330 | + // | ||
331 | + // If IN-MEMORY Intent is same as the FETCHED Intent, | ||
332 | + // store the FETCHED Intent in the local memory. | ||
333 | + // | ||
334 | + if (compareMultiPointToSinglePointIntents(inMemoryIntent, | ||
335 | + fetchedIntent)) { | ||
336 | + storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent)); | ||
337 | + } else { | ||
338 | + // | ||
339 | + // The IN-MEMORY Intent is not same as the FETCHED Intent, | ||
340 | + // hence delete the FETCHED Intent, and install the | ||
341 | + // IN-MEMORY Intent. | ||
342 | + // | ||
343 | + deleteIntents.add(Pair.of(prefix, fetchedIntent)); | ||
344 | + addIntents.add(Pair.of(prefix, inMemoryIntent)); | ||
345 | + } | ||
346 | + fetchedIntents.remove(prefix); | ||
347 | + } | ||
348 | + | ||
349 | + // | ||
350 | + // Any remaining FETCHED Intents have to be deleted/withdrawn | ||
351 | + // | ||
352 | + for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry : | ||
353 | + fetchedIntents.entrySet()) { | ||
354 | + IpPrefix prefix = entry.getKey(); | ||
355 | + MultiPointToSinglePointIntent fetchedIntent = entry.getValue(); | ||
356 | + deleteIntents.add(Pair.of(prefix, fetchedIntent)); | ||
357 | + } | ||
358 | + | ||
359 | + // | ||
360 | + // Perform the actions: | ||
361 | + // 1. Store in memory fetched intents that are same. Can be done | ||
362 | + // even if we are not the leader anymore | ||
363 | + // 2. Delete intents: check if the leader before each operation | ||
364 | + // 3. Add intents: check if the leader before each operation | ||
365 | + // | ||
366 | + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair : | ||
367 | + storeInMemoryIntents) { | ||
368 | + IpPrefix prefix = pair.getLeft(); | ||
369 | + MultiPointToSinglePointIntent intent = pair.getRight(); | ||
370 | + log.debug("Intent synchronization: updating in-memory " + | ||
371 | + "Intent for prefix: {}", prefix); | ||
372 | + pushedRouteIntents.put(prefix, intent); | ||
373 | + } | ||
374 | + // | ||
375 | + isActivatedLeader = true; // Allow push of Intents | ||
376 | + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair : | ||
377 | + deleteIntents) { | ||
378 | + IpPrefix prefix = pair.getLeft(); | ||
379 | + MultiPointToSinglePointIntent intent = pair.getRight(); | ||
380 | + if (!isElectedLeader) { | ||
381 | + isActivatedLeader = false; | ||
382 | + return; | ||
383 | + } | ||
384 | + log.debug("Intent synchronization: deleting Intent for " + | ||
385 | + "prefix: {}", prefix); | ||
386 | + intentService.withdraw(intent); | ||
387 | + } | ||
388 | + // | ||
389 | + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair : | ||
390 | + addIntents) { | ||
391 | + IpPrefix prefix = pair.getLeft(); | ||
392 | + MultiPointToSinglePointIntent intent = pair.getRight(); | ||
393 | + if (!isElectedLeader) { | ||
394 | + isActivatedLeader = false; | ||
395 | + return; | ||
396 | + } | ||
397 | + log.debug("Intent synchronization: adding Intent for " + | ||
398 | + "prefix: {}", prefix); | ||
399 | + intentService.submit(intent); | ||
400 | + } | ||
401 | + if (!isElectedLeader) { | ||
402 | + isActivatedLeader = false; | ||
403 | + } | ||
404 | + log.debug("Syncing SDN-IP routes completed."); | ||
405 | + } | ||
406 | + } | ||
407 | + | ||
408 | + /** | ||
409 | + * Compares two Multi-point to Single Point Intents whether they represent | ||
410 | + * same logical intention. | ||
411 | + * | ||
412 | + * @param intent1 the first Intent to compare | ||
413 | + * @param intent2 the second Intent to compare | ||
414 | + * @return true if both Intents represent same logical intention, otherwise | ||
415 | + * false | ||
416 | + */ | ||
417 | + private boolean compareMultiPointToSinglePointIntents( | ||
418 | + MultiPointToSinglePointIntent intent1, | ||
419 | + MultiPointToSinglePointIntent intent2) { | ||
420 | + /*Match match1 = intent1.getMatch(); | ||
421 | + Match match2 = intent2.getMatch(); | ||
422 | + Action action1 = intent1.getAction(); | ||
423 | + Action action2 = intent2.getAction(); | ||
424 | + Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts(); | ||
425 | + Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts(); | ||
426 | + SwitchPort egressPort1 = intent1.getEgressPort(); | ||
427 | + SwitchPort egressPort2 = intent2.getEgressPort(); | ||
428 | + | ||
429 | + return Objects.equal(match1, match2) && | ||
430 | + Objects.equal(action1, action2) && | ||
431 | + Objects.equal(egressPort1, egressPort2) && | ||
432 | + Objects.equal(ingressPorts1, ingressPorts2);*/ | ||
433 | + return Objects.equal(intent1.selector(), intent2.selector()) && | ||
434 | + Objects.equal(intent1.treatment(), intent2.treatment()) && | ||
435 | + Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) && | ||
436 | + Objects.equal(intent1.egressPoint(), intent2.egressPoint()); | ||
437 | + } | ||
438 | + | ||
439 | + /** | ||
440 | + * Processes adding a route entry. | ||
441 | + * <p/> | ||
442 | + * Put new route entry into InvertedRadixTree. If there was an existing | ||
443 | + * nexthop for this prefix, but the next hop was different, then execute | ||
444 | + * deleting old route entry. If the next hop is the SDN domain, we do not | ||
445 | + * handle it at the moment. Otherwise, execute adding a route. | ||
446 | + * | ||
447 | + * @param routeEntry the route entry to add | ||
448 | + */ | ||
449 | + protected void processRouteAdd(RouteEntry routeEntry) { | ||
450 | + synchronized (this) { | ||
451 | + log.debug("Processing route add: {}", routeEntry); | ||
452 | + | ||
453 | + IpPrefix prefix = routeEntry.prefix(); | ||
454 | + IpAddress nextHop = null; | ||
455 | + RouteEntry foundRouteEntry = | ||
456 | + bgpRoutes.put(RouteEntry.createBinaryString(prefix), | ||
457 | + routeEntry); | ||
458 | + if (foundRouteEntry != null) { | ||
459 | + nextHop = foundRouteEntry.nextHop(); | ||
460 | + } | ||
461 | + | ||
462 | + if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) { | ||
463 | + // There was an existing nexthop for this prefix. This update | ||
464 | + // supersedes that, so we need to remove the old flows for this | ||
465 | + // prefix from the switches | ||
466 | + executeRouteDelete(routeEntry); | ||
467 | + } | ||
468 | + if (nextHop != null && nextHop.equals(routeEntry.nextHop())) { | ||
469 | + return; | ||
470 | + } | ||
471 | + | ||
472 | + if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) { | ||
473 | + // Route originated by SDN domain | ||
474 | + // We don't handle these at the moment | ||
475 | + log.debug("Own route {} to {}", | ||
476 | + routeEntry.prefix(), routeEntry.nextHop()); | ||
477 | + return; | ||
478 | + } | ||
479 | + | ||
480 | + executeRouteAdd(routeEntry); | ||
481 | + } | ||
482 | + } | ||
483 | + | ||
484 | + /** | ||
485 | + * Executes adding a route entry. | ||
486 | + * <p/> | ||
487 | + * Find out the egress Interface and MAC address of next hop router for | ||
488 | + * this route entry. If the MAC address can not be found in ARP cache, | ||
489 | + * then this prefix will be put in routesWaitingOnArp queue. Otherwise, | ||
490 | + * new route intent will be created and installed. | ||
491 | + * | ||
492 | + * @param routeEntry the route entry to add | ||
493 | + */ | ||
494 | + private void executeRouteAdd(RouteEntry routeEntry) { | ||
495 | + log.debug("Executing route add: {}", routeEntry); | ||
496 | + | ||
497 | + // See if we know the MAC address of the next hop | ||
498 | + //MacAddress nextHopMacAddress = | ||
499 | + //proxyArp.getMacAddress(routeEntry.getNextHop()); | ||
500 | + MacAddress nextHopMacAddress = null; | ||
501 | + Set<Host> hosts = hostService.getHostsByIp( | ||
502 | + routeEntry.nextHop().toPrefix()); | ||
503 | + if (!hosts.isEmpty()) { | ||
504 | + // TODO how to handle if multiple hosts are returned? | ||
505 | + nextHopMacAddress = hosts.iterator().next().mac(); | ||
506 | + } | ||
507 | + | ||
508 | + if (nextHopMacAddress == null) { | ||
509 | + routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry); | ||
510 | + //proxyArp.sendArpRequest(routeEntry.getNextHop(), this, true); | ||
511 | + // TODO maybe just do this for every prefix anyway | ||
512 | + hostService.startMonitoringIp(routeEntry.nextHop()); | ||
513 | + return; | ||
514 | + } | ||
515 | + | ||
516 | + addRouteIntentToNextHop(routeEntry.prefix(), | ||
517 | + routeEntry.nextHop(), | ||
518 | + nextHopMacAddress); | ||
519 | + } | ||
520 | + | ||
521 | + /** | ||
522 | + * Adds a route intent given a prefix and a next hop IP address. This | ||
523 | + * method will find the egress interface for the intent. | ||
524 | + * | ||
525 | + * @param prefix IP prefix of the route to add | ||
526 | + * @param nextHopIpAddress IP address of the next hop | ||
527 | + * @param nextHopMacAddress MAC address of the next hop | ||
528 | + */ | ||
529 | + private void addRouteIntentToNextHop(IpPrefix prefix, | ||
530 | + IpAddress nextHopIpAddress, | ||
531 | + MacAddress nextHopMacAddress) { | ||
532 | + | ||
533 | + // Find the attachment point (egress interface) of the next hop | ||
534 | + Interface egressInterface; | ||
535 | + if (configInfoService.getBgpPeers().containsKey(nextHopIpAddress)) { | ||
536 | + // Route to a peer | ||
537 | + log.debug("Route to peer {}", nextHopIpAddress); | ||
538 | + BgpPeer peer = | ||
539 | + configInfoService.getBgpPeers().get(nextHopIpAddress); | ||
540 | + egressInterface = | ||
541 | + interfaceService.getInterface(peer.connectPoint()); | ||
542 | + } else { | ||
543 | + // Route to non-peer | ||
544 | + log.debug("Route to non-peer {}", nextHopIpAddress); | ||
545 | + egressInterface = | ||
546 | + interfaceService.getMatchingInterface(nextHopIpAddress); | ||
547 | + if (egressInterface == null) { | ||
548 | + log.warn("No outgoing interface found for {}", | ||
549 | + nextHopIpAddress); | ||
550 | + return; | ||
551 | + } | ||
552 | + } | ||
553 | + | ||
554 | + doAddRouteIntent(prefix, egressInterface, nextHopMacAddress); | ||
555 | + } | ||
556 | + | ||
557 | + /** | ||
558 | + * Installs a route intent for a prefix. | ||
559 | + * <p/> | ||
560 | + * Intent will match dst IP prefix and rewrite dst MAC address at all other | ||
561 | + * border switches, then forward packets according to dst MAC address. | ||
562 | + * | ||
563 | + * @param prefix IP prefix from route | ||
564 | + * @param egressInterface egress Interface connected to next hop router | ||
565 | + * @param nextHopMacAddress MAC address of next hop router | ||
566 | + */ | ||
567 | + private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface, | ||
568 | + MacAddress nextHopMacAddress) { | ||
569 | + log.debug("Adding intent for prefix {}, next hop mac {}", | ||
570 | + prefix, nextHopMacAddress); | ||
571 | + | ||
572 | + MultiPointToSinglePointIntent pushedIntent = | ||
573 | + pushedRouteIntents.get(prefix); | ||
574 | + | ||
575 | + // Just for testing. | ||
576 | + if (pushedIntent != null) { | ||
577 | + log.error("There should not be a pushed intent: {}", pushedIntent); | ||
578 | + } | ||
579 | + | ||
580 | + ConnectPoint egressPort = egressInterface.connectPoint(); | ||
581 | + | ||
582 | + Set<ConnectPoint> ingressPorts = new HashSet<>(); | ||
583 | + | ||
584 | + for (Interface intf : interfaceService.getInterfaces()) { | ||
585 | + if (!intf.equals(egressInterface)) { | ||
586 | + ConnectPoint srcPort = intf.connectPoint(); | ||
587 | + ingressPorts.add(srcPort); | ||
588 | + } | ||
589 | + } | ||
590 | + | ||
591 | + // Match the destination IP prefix at the first hop | ||
592 | + //PacketMatchBuilder builder = new PacketMatchBuilder(); | ||
593 | + //builder.setEtherType(Ethernet.TYPE_IPV4).setDstIpNet(prefix); | ||
594 | + //PacketMatch packetMatch = builder.build(); | ||
595 | + TrafficSelector selector = DefaultTrafficSelector.builder() | ||
596 | + .matchEthType(Ethernet.TYPE_IPV4) | ||
597 | + .matchIPDst(prefix) | ||
598 | + .build(); | ||
599 | + | ||
600 | + // Rewrite the destination MAC address | ||
601 | + //ModifyDstMacAction modifyDstMacAction = | ||
602 | + //new ModifyDstMacAction(nextHopMacAddress); | ||
603 | + TrafficTreatment treatment = DefaultTrafficTreatment.builder() | ||
604 | + .setEthDst(nextHopMacAddress) | ||
605 | + .build(); | ||
606 | + | ||
607 | + MultiPointToSinglePointIntent intent = | ||
608 | + new MultiPointToSinglePointIntent(nextIntentId(), | ||
609 | + selector, treatment, ingressPorts, egressPort); | ||
610 | + | ||
611 | + if (isElectedLeader && isActivatedLeader) { | ||
612 | + log.debug("Intent installation: adding Intent for prefix: {}", | ||
613 | + prefix); | ||
614 | + intentService.submit(intent); | ||
615 | + } | ||
616 | + | ||
617 | + // Maintain the Intent | ||
618 | + pushedRouteIntents.put(prefix, intent); | ||
619 | + } | ||
620 | + | ||
621 | + /** | ||
622 | + * Executes deleting a route entry. | ||
623 | + * <p/> | ||
624 | + * Removes prefix from InvertedRadixTree, if success, then try to delete | ||
625 | + * the relative intent. | ||
626 | + * | ||
627 | + * @param routeEntry the route entry to delete | ||
628 | + */ | ||
629 | + protected void processRouteDelete(RouteEntry routeEntry) { | ||
630 | + synchronized (this) { | ||
631 | + log.debug("Processing route delete: {}", routeEntry); | ||
632 | + IpPrefix prefix = routeEntry.prefix(); | ||
633 | + | ||
634 | + // TODO check the change of logic here - remove doesn't check that | ||
635 | + // the route entry was what we expected (and we can't do this | ||
636 | + // concurrently) | ||
637 | + | ||
638 | + if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) { | ||
639 | + // | ||
640 | + // Only delete flows if an entry was actually removed from the | ||
641 | + // tree. If no entry was removed, the <prefix, nexthop> wasn't | ||
642 | + // there so it's probably already been removed and we don't | ||
643 | + // need to do anything. | ||
644 | + // | ||
645 | + executeRouteDelete(routeEntry); | ||
646 | + } | ||
647 | + | ||
648 | + routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry); | ||
649 | + // TODO cancel the request in the ARP manager as well | ||
650 | + } | ||
651 | + } | ||
652 | + | ||
653 | + /** | ||
654 | + * Executed deleting a route entry. | ||
655 | + * | ||
656 | + * @param routeEntry the route entry to delete | ||
657 | + */ | ||
658 | + private void executeRouteDelete(RouteEntry routeEntry) { | ||
659 | + log.debug("Executing route delete: {}", routeEntry); | ||
660 | + | ||
661 | + IpPrefix prefix = routeEntry.prefix(); | ||
662 | + | ||
663 | + MultiPointToSinglePointIntent intent = | ||
664 | + pushedRouteIntents.remove(prefix); | ||
665 | + | ||
666 | + if (intent == null) { | ||
667 | + log.debug("There is no intent in pushedRouteIntents to delete " + | ||
668 | + "for prefix: {}", prefix); | ||
669 | + } else { | ||
670 | + if (isElectedLeader && isActivatedLeader) { | ||
671 | + log.debug("Intent installation: deleting Intent for prefix: {}", | ||
672 | + prefix); | ||
673 | + intentService.withdraw(intent); | ||
674 | + } | ||
675 | + } | ||
676 | + } | ||
677 | + | ||
678 | + /** | ||
679 | + * This method handles the prefixes which are waiting for ARP replies for | ||
680 | + * MAC addresses of next hops. | ||
681 | + * | ||
682 | + * @param ipAddress next hop router IP address, for which we sent ARP | ||
683 | + * request out | ||
684 | + * @param macAddress MAC address which is relative to the ipAddress | ||
685 | + */ | ||
686 | + //@Override | ||
687 | + // TODO change name | ||
688 | + public void arpResponse(IpAddress ipAddress, MacAddress macAddress) { | ||
689 | + log.debug("Received ARP response: {} => {}", ipAddress, macAddress); | ||
690 | + | ||
691 | + // We synchronize on this to prevent changes to the InvertedRadixTree | ||
692 | + // while we're pushing intent. If the InvertedRadixTree changes, the | ||
693 | + // InvertedRadixTree and intent could get out of sync. | ||
694 | + synchronized (this) { | ||
695 | + | ||
696 | + Set<RouteEntry> routesToPush = | ||
697 | + routesWaitingOnArp.removeAll(ipAddress); | ||
698 | + | ||
699 | + for (RouteEntry routeEntry : routesToPush) { | ||
700 | + // These will always be adds | ||
701 | + IpPrefix prefix = routeEntry.prefix(); | ||
702 | + String binaryString = RouteEntry.createBinaryString(prefix); | ||
703 | + RouteEntry foundRouteEntry = | ||
704 | + bgpRoutes.getValueForExactKey(binaryString); | ||
705 | + if (foundRouteEntry != null && | ||
706 | + foundRouteEntry.nextHop().equals(routeEntry.nextHop())) { | ||
707 | + log.debug("Pushing prefix {} next hop {}", | ||
708 | + routeEntry.prefix(), routeEntry.nextHop()); | ||
709 | + // We only push prefix flows if the prefix is still in the | ||
710 | + // InvertedRadixTree and the next hop is the same as our | ||
711 | + // update. | ||
712 | + // The prefix could have been removed while we were waiting | ||
713 | + // for the ARP, or the next hop could have changed. | ||
714 | + addRouteIntentToNextHop(prefix, ipAddress, macAddress); | ||
715 | + } else { | ||
716 | + log.debug("Received ARP response, but {}/{} is no longer in" | ||
717 | + + " InvertedRadixTree", routeEntry.prefix(), | ||
718 | + routeEntry.nextHop()); | ||
719 | + } | ||
720 | + } | ||
721 | + } | ||
722 | + } | ||
723 | + | ||
724 | + /** | ||
725 | + * Gets the SDN-IP routes. | ||
726 | + * | ||
727 | + * @return the SDN-IP routes | ||
728 | + */ | ||
729 | + public Collection<RouteEntry> getRoutes() { | ||
730 | + Iterator<KeyValuePair<RouteEntry>> it = | ||
731 | + bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator(); | ||
732 | + | ||
733 | + List<RouteEntry> routes = new LinkedList<>(); | ||
734 | + | ||
735 | + while (it.hasNext()) { | ||
736 | + KeyValuePair<RouteEntry> entry = it.next(); | ||
737 | + routes.add(entry.getValue()); | ||
738 | + } | ||
739 | + | ||
740 | + return routes; | ||
741 | + } | ||
742 | + | ||
743 | + /** | ||
744 | + * Generates a new unique intent ID. | ||
745 | + * | ||
746 | + * @return the new intent ID. | ||
747 | + */ | ||
748 | + private IntentId nextIntentId() { | ||
749 | + return new IntentId(intentId++); | ||
750 | + } | ||
751 | + | ||
752 | + /** | ||
753 | + * Listener for host events. | ||
754 | + */ | ||
755 | + class InternalHostListener implements HostListener { | ||
756 | + @Override | ||
757 | + public void event(HostEvent event) { | ||
758 | + if (event.type() == HostEvent.Type.HOST_ADDED || | ||
759 | + event.type() == HostEvent.Type.HOST_UPDATED) { | ||
760 | + Host host = event.subject(); | ||
761 | + for (IpPrefix ip : host.ipAddresses()) { | ||
762 | + arpResponse(ip.toIpAddress(), host.mac()); | ||
763 | + } | ||
764 | + } | ||
765 | + } | ||
766 | + } | ||
767 | +} |
... | @@ -9,7 +9,10 @@ import org.apache.felix.scr.annotations.Reference; | ... | @@ -9,7 +9,10 @@ import org.apache.felix.scr.annotations.Reference; |
9 | import org.apache.felix.scr.annotations.ReferenceCardinality; | 9 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
10 | import org.onlab.onos.net.host.HostService; | 10 | import org.onlab.onos.net.host.HostService; |
11 | import org.onlab.onos.net.intent.IntentService; | 11 | import org.onlab.onos.net.intent.IntentService; |
12 | +import org.onlab.onos.sdnip.RouteUpdate.Type; | ||
12 | import org.onlab.onos.sdnip.config.SdnIpConfigReader; | 13 | import org.onlab.onos.sdnip.config.SdnIpConfigReader; |
14 | +import org.onlab.packet.IpAddress; | ||
15 | +import org.onlab.packet.IpPrefix; | ||
13 | import org.slf4j.Logger; | 16 | import org.slf4j.Logger; |
14 | 17 | ||
15 | /** | 18 | /** |
... | @@ -28,6 +31,7 @@ public class SdnIp { | ... | @@ -28,6 +31,7 @@ public class SdnIp { |
28 | 31 | ||
29 | private SdnIpConfigReader config; | 32 | private SdnIpConfigReader config; |
30 | private PeerConnectivity peerConnectivity; | 33 | private PeerConnectivity peerConnectivity; |
34 | + private Router router; | ||
31 | 35 | ||
32 | @Activate | 36 | @Activate |
33 | protected void activate() { | 37 | protected void activate() { |
... | @@ -41,6 +45,14 @@ public class SdnIp { | ... | @@ -41,6 +45,14 @@ public class SdnIp { |
41 | peerConnectivity = new PeerConnectivity(config, interfaceService, intentService); | 45 | peerConnectivity = new PeerConnectivity(config, interfaceService, intentService); |
42 | peerConnectivity.start(); | 46 | peerConnectivity.start(); |
43 | 47 | ||
48 | + router = new Router(intentService, hostService, config, interfaceService); | ||
49 | + router.start(); | ||
50 | + | ||
51 | + // TODO need to disable link discovery on external ports | ||
52 | + | ||
53 | + router.update(new RouteUpdate(Type.UPDATE, new RouteEntry( | ||
54 | + IpPrefix.valueOf("172.16.20.0/24"), | ||
55 | + IpAddress.valueOf("192.168.10.1")))); | ||
44 | } | 56 | } |
45 | 57 | ||
46 | @Deactivate | 58 | @Deactivate | ... | ... |
1 | +package org.onlab.onos.sdnip; | ||
2 | + | ||
3 | +import static org.hamcrest.Matchers.is; | ||
4 | +import static org.hamcrest.Matchers.not; | ||
5 | +import static org.junit.Assert.assertThat; | ||
6 | + | ||
7 | +import org.junit.Test; | ||
8 | +import org.onlab.packet.IpAddress; | ||
9 | +import org.onlab.packet.IpPrefix; | ||
10 | + | ||
11 | +/** | ||
12 | + * Unit tests for the RouteEntry class. | ||
13 | + */ | ||
14 | +public class RouteEntryTest { | ||
15 | + /** | ||
16 | + * Tests valid class constructor. | ||
17 | + */ | ||
18 | + @Test | ||
19 | + public void testConstructor() { | ||
20 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
21 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
22 | + | ||
23 | + RouteEntry routeEntry = new RouteEntry(prefix, nextHop); | ||
24 | + assertThat(routeEntry.toString(), | ||
25 | + is("RouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8}")); | ||
26 | + } | ||
27 | + | ||
28 | + /** | ||
29 | + * Tests invalid class constructor for null IPv4 prefix. | ||
30 | + */ | ||
31 | + @Test(expected = NullPointerException.class) | ||
32 | + public void testInvalidConstructorNullPrefix() { | ||
33 | + IpPrefix prefix = null; | ||
34 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
35 | + | ||
36 | + new RouteEntry(prefix, nextHop); | ||
37 | + } | ||
38 | + | ||
39 | + /** | ||
40 | + * Tests invalid class constructor for null IPv4 next-hop. | ||
41 | + */ | ||
42 | + @Test(expected = NullPointerException.class) | ||
43 | + public void testInvalidConstructorNullNextHop() { | ||
44 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
45 | + IpAddress nextHop = null; | ||
46 | + | ||
47 | + new RouteEntry(prefix, nextHop); | ||
48 | + } | ||
49 | + | ||
50 | + /** | ||
51 | + * Tests getting the fields of a route entry. | ||
52 | + */ | ||
53 | + @Test | ||
54 | + public void testGetFields() { | ||
55 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
56 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
57 | + | ||
58 | + RouteEntry routeEntry = new RouteEntry(prefix, nextHop); | ||
59 | + assertThat(routeEntry.prefix(), is(prefix)); | ||
60 | + assertThat(routeEntry.nextHop(), is(nextHop)); | ||
61 | + } | ||
62 | + | ||
63 | + /** | ||
64 | + * Tests creating a binary string from IPv4 prefix. | ||
65 | + */ | ||
66 | + @Test | ||
67 | + public void testCreateBinaryString() { | ||
68 | + IpPrefix prefix; | ||
69 | + | ||
70 | + prefix = IpPrefix.valueOf("0.0.0.0/0"); | ||
71 | + assertThat(RouteEntry.createBinaryString(prefix), is("")); | ||
72 | + | ||
73 | + prefix = IpPrefix.valueOf("192.168.166.0/22"); | ||
74 | + assertThat(RouteEntry.createBinaryString(prefix), | ||
75 | + is("1100000010101000101001")); | ||
76 | + | ||
77 | + prefix = IpPrefix.valueOf("192.168.166.0/23"); | ||
78 | + assertThat(RouteEntry.createBinaryString(prefix), | ||
79 | + is("11000000101010001010011")); | ||
80 | + | ||
81 | + prefix = IpPrefix.valueOf("192.168.166.0/24"); | ||
82 | + assertThat(RouteEntry.createBinaryString(prefix), | ||
83 | + is("110000001010100010100110")); | ||
84 | + | ||
85 | + prefix = IpPrefix.valueOf("130.162.10.1/25"); | ||
86 | + assertThat(RouteEntry.createBinaryString(prefix), | ||
87 | + is("1000001010100010000010100")); | ||
88 | + | ||
89 | + prefix = IpPrefix.valueOf("255.255.255.255/32"); | ||
90 | + assertThat(RouteEntry.createBinaryString(prefix), | ||
91 | + is("11111111111111111111111111111111")); | ||
92 | + } | ||
93 | + | ||
94 | + /** | ||
95 | + * Tests equality of {@link RouteEntry}. | ||
96 | + */ | ||
97 | + @Test | ||
98 | + public void testEquality() { | ||
99 | + IpPrefix prefix1 = IpPrefix.valueOf("1.2.3.0/24"); | ||
100 | + IpAddress nextHop1 = IpAddress.valueOf("5.6.7.8"); | ||
101 | + RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1); | ||
102 | + | ||
103 | + IpPrefix prefix2 = IpPrefix.valueOf("1.2.3.0/24"); | ||
104 | + IpAddress nextHop2 = IpAddress.valueOf("5.6.7.8"); | ||
105 | + RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2); | ||
106 | + | ||
107 | + assertThat(routeEntry1, is(routeEntry2)); | ||
108 | + } | ||
109 | + | ||
110 | + /** | ||
111 | + * Tests non-equality of {@link RouteEntry}. | ||
112 | + */ | ||
113 | + @Test | ||
114 | + public void testNonEquality() { | ||
115 | + IpPrefix prefix1 = IpPrefix.valueOf("1.2.3.0/24"); | ||
116 | + IpAddress nextHop1 = IpAddress.valueOf("5.6.7.8"); | ||
117 | + RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1); | ||
118 | + | ||
119 | + IpPrefix prefix2 = IpPrefix.valueOf("1.2.3.0/25"); // Different | ||
120 | + IpAddress nextHop2 = IpAddress.valueOf("5.6.7.8"); | ||
121 | + RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2); | ||
122 | + | ||
123 | + IpPrefix prefix3 = IpPrefix.valueOf("1.2.3.0/24"); | ||
124 | + IpAddress nextHop3 = IpAddress.valueOf("5.6.7.9"); // Different | ||
125 | + RouteEntry routeEntry3 = new RouteEntry(prefix3, nextHop3); | ||
126 | + | ||
127 | + assertThat(routeEntry1, is(not(routeEntry2))); | ||
128 | + assertThat(routeEntry1, is(not(routeEntry3))); | ||
129 | + } | ||
130 | + | ||
131 | + /** | ||
132 | + * Tests object string representation. | ||
133 | + */ | ||
134 | + @Test | ||
135 | + public void testToString() { | ||
136 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
137 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
138 | + RouteEntry routeEntry = new RouteEntry(prefix, nextHop); | ||
139 | + | ||
140 | + assertThat(routeEntry.toString(), | ||
141 | + is("RouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8}")); | ||
142 | + } | ||
143 | +} |
... | @@ -30,6 +30,7 @@ | ... | @@ -30,6 +30,7 @@ |
30 | 30 | ||
31 | <bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.13</bundle> | 31 | <bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.13</bundle> |
32 | <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle> | 32 | <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle> |
33 | + <bundle>mvn:org.onlab.onos/onlab-thirdparty/1.0.0-SNAPSHOT</bundle> | ||
33 | </feature> | 34 | </feature> |
34 | 35 | ||
35 | <feature name="onos-thirdparty-web" version="1.0.0" | 36 | <feature name="onos-thirdparty-web" version="1.0.0" | ... | ... |
... | @@ -107,7 +107,13 @@ | ... | @@ -107,7 +107,13 @@ |
107 | </dependency> | 107 | </dependency> |
108 | 108 | ||
109 | <dependency> | 109 | <dependency> |
110 | - <groupId>commons-lang</groupId> | 110 | + <groupId>com.googlecode.concurrent-trees</groupId> |
111 | + <artifactId>concurrent-trees</artifactId> | ||
112 | + <version>2.4.0</version> | ||
113 | + </dependency> | ||
114 | + | ||
115 | + <dependency> | ||
116 | + <groupId>commons-lang</groupId> | ||
111 | <artifactId>commons-lang</artifactId> | 117 | <artifactId>commons-lang</artifactId> |
112 | <version>2.6</version> | 118 | <version>2.6</version> |
113 | </dependency> | 119 | </dependency> |
... | @@ -266,6 +272,13 @@ | ... | @@ -266,6 +272,13 @@ |
266 | <artifactId>onos-of-api</artifactId> | 272 | <artifactId>onos-of-api</artifactId> |
267 | <version>${project.version}</version> | 273 | <version>${project.version}</version> |
268 | </dependency> | 274 | </dependency> |
275 | + | ||
276 | + <dependency> | ||
277 | + <groupId>org.onlab.onos</groupId> | ||
278 | + <artifactId>onlab-thirdparty</artifactId> | ||
279 | + <version>${project.version}</version> | ||
280 | + </dependency> | ||
281 | + | ||
269 | <dependency> | 282 | <dependency> |
270 | <groupId>org.onlab.onos</groupId> | 283 | <groupId>org.onlab.onos</groupId> |
271 | <artifactId>onos-of-api</artifactId> | 284 | <artifactId>onos-of-api</artifactId> | ... | ... |
... | @@ -191,6 +191,15 @@ public final class IpAddress { | ... | @@ -191,6 +191,15 @@ public final class IpAddress { |
191 | } | 191 | } |
192 | 192 | ||
193 | /** | 193 | /** |
194 | + * Converts the IP address to a /32 IP prefix. | ||
195 | + * | ||
196 | + * @return the new IP prefix | ||
197 | + */ | ||
198 | + public IpPrefix toPrefix() { | ||
199 | + return IpPrefix.valueOf(octets, MAX_INET_MASK); | ||
200 | + } | ||
201 | + | ||
202 | + /** | ||
194 | * Helper for computing the mask value from CIDR. | 203 | * Helper for computing the mask value from CIDR. |
195 | * | 204 | * |
196 | * @return an integer bitmask | 205 | * @return an integer bitmask | ... | ... |
utils/thirdparty/pom.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
5 | + <modelVersion>4.0.0</modelVersion> | ||
6 | + | ||
7 | + <parent> | ||
8 | + <groupId>org.onlab.onos</groupId> | ||
9 | + <artifactId>onlab-utils</artifactId> | ||
10 | + <version>1.0.0-SNAPSHOT</version> | ||
11 | + <relativePath>../pom.xml</relativePath> | ||
12 | + </parent> | ||
13 | + | ||
14 | + <artifactId>onlab-thirdparty</artifactId> | ||
15 | + <packaging>bundle</packaging> | ||
16 | + | ||
17 | + <description>ONLab third-party dependencies</description> | ||
18 | + | ||
19 | + <dependencies> | ||
20 | + <dependency> | ||
21 | + <groupId>com.googlecode.concurrent-trees</groupId> | ||
22 | + <artifactId>concurrent-trees</artifactId> | ||
23 | + <version>2.4.0</version> | ||
24 | + </dependency> | ||
25 | + </dependencies> | ||
26 | + | ||
27 | + <build> | ||
28 | + <plugins> | ||
29 | + <plugin> | ||
30 | + <groupId>org.apache.maven.plugins</groupId> | ||
31 | + <artifactId>maven-shade-plugin</artifactId> | ||
32 | + <version>2.3</version> | ||
33 | + <configuration> | ||
34 | + <filters> | ||
35 | + <filter> | ||
36 | + <artifact>com.googlecode.concurrent-trees:concurrent-trees</artifact> | ||
37 | + <includes> | ||
38 | + <include>com/googlecode/**</include> | ||
39 | + </includes> | ||
40 | + | ||
41 | + </filter> | ||
42 | + <filter> | ||
43 | + <artifact>com.google.guava:guava</artifact> | ||
44 | + <excludes> | ||
45 | + <exclude>**</exclude> | ||
46 | + </excludes> | ||
47 | + </filter> | ||
48 | + </filters> | ||
49 | + </configuration> | ||
50 | + <executions> | ||
51 | + <execution> | ||
52 | + <phase>package</phase> | ||
53 | + <goals> | ||
54 | + <goal>shade</goal> | ||
55 | + </goals> | ||
56 | + </execution> | ||
57 | + </executions> | ||
58 | + </plugin> | ||
59 | + <plugin> | ||
60 | + <groupId>org.apache.felix</groupId> | ||
61 | + <artifactId>maven-bundle-plugin</artifactId> | ||
62 | + <configuration> | ||
63 | + <instructions> | ||
64 | + <Export-Package> | ||
65 | + com.googlecode.concurrenttrees.* | ||
66 | + </Export-Package> | ||
67 | + </instructions> | ||
68 | + </configuration> | ||
69 | + </plugin> | ||
70 | + </plugins> | ||
71 | + </build> | ||
72 | + | ||
73 | +</project> |
-
Please register or login to post a comment