blob: 1dcd9d3c12b5aeec1bb1a1b012d03b11a3654118 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07002 * Copyright 2014 Open Networking Laboratory
Thomas Vachuska781d18b2014-10-27 10:31:25 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * 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
Thomas Vachuska781d18b2014-10-27 10:31:25 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * 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.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
Jonathan Hart335ef462014-10-16 08:20:46 -070016package org.onlab.onos.sdnip;
17
Pingping3855f312014-10-22 12:50:37 -070018import java.util.Collection;
19import java.util.HashMap;
20import java.util.HashSet;
21import java.util.Iterator;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Map;
25import java.util.Set;
26import java.util.concurrent.BlockingQueue;
27import java.util.concurrent.ConcurrentHashMap;
28import java.util.concurrent.ExecutorService;
29import java.util.concurrent.Executors;
30import java.util.concurrent.LinkedBlockingQueue;
31import java.util.concurrent.Semaphore;
32
Thomas Vachuskab97cf282014-10-20 23:31:12 -070033import org.apache.commons.lang3.tuple.Pair;
Thomas Vachuskae0f804a2014-10-27 23:40:48 -070034import org.onlab.onos.core.ApplicationId;
Thomas Vachuskab97cf282014-10-20 23:31:12 -070035import org.onlab.onos.net.ConnectPoint;
36import org.onlab.onos.net.Host;
37import org.onlab.onos.net.flow.DefaultTrafficSelector;
38import org.onlab.onos.net.flow.DefaultTrafficTreatment;
39import org.onlab.onos.net.flow.TrafficSelector;
40import org.onlab.onos.net.flow.TrafficTreatment;
41import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
42import org.onlab.onos.net.flow.criteria.Criterion;
43import org.onlab.onos.net.flow.criteria.Criterion.Type;
44import org.onlab.onos.net.host.HostEvent;
45import org.onlab.onos.net.host.HostListener;
46import org.onlab.onos.net.host.HostService;
47import org.onlab.onos.net.intent.Intent;
48import org.onlab.onos.net.intent.IntentService;
49import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
50import org.onlab.onos.sdnip.config.BgpPeer;
51import org.onlab.onos.sdnip.config.Interface;
52import org.onlab.onos.sdnip.config.SdnIpConfigService;
53import org.onlab.packet.Ethernet;
54import org.onlab.packet.IpAddress;
55import org.onlab.packet.IpPrefix;
56import org.onlab.packet.MacAddress;
57import org.slf4j.Logger;
58import org.slf4j.LoggerFactory;
59
Pingping3855f312014-10-22 12:50:37 -070060import com.google.common.base.Objects;
61import com.google.common.collect.HashMultimap;
62import com.google.common.collect.Multimaps;
63import com.google.common.collect.SetMultimap;
64import com.google.common.util.concurrent.ThreadFactoryBuilder;
65import com.googlecode.concurrenttrees.common.KeyValuePair;
66import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
67import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
68import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
Jonathan Hart335ef462014-10-16 08:20:46 -070069
Jonathan Hart335ef462014-10-16 08:20:46 -070070/**
71 * This class processes BGP route update, translates each update into a intent
72 * and submits the intent.
Jonathan Hart335ef462014-10-16 08:20:46 -070073 */
74public class Router implements RouteListener {
75
76 private static final Logger log = LoggerFactory.getLogger(Router.class);
77
Jonathan Hart0b04bed2014-10-16 16:39:19 -070078 // Store all route updates in a radix tree.
79 // The key in this tree is the binary string of prefix of the route.
Jonathan Hart335ef462014-10-16 08:20:46 -070080 private InvertedRadixTree<RouteEntry> bgpRoutes;
81
82 // Stores all incoming route updates in a queue.
83 private BlockingQueue<RouteUpdate> routeUpdates;
84
Jonathan Hart31582d12014-10-22 13:52:41 -070085 // The IpAddress is the next hop address of each route update.
Jonathan Hart335ef462014-10-16 08:20:46 -070086 private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
87 private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents;
88
89 private IntentService intentService;
Jonathan Hart335ef462014-10-16 08:20:46 -070090 private HostService hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -070091 private SdnIpConfigService configService;
Jonathan Hart335ef462014-10-16 08:20:46 -070092 private InterfaceService interfaceService;
93
94 private ExecutorService bgpUpdatesExecutor;
95 private ExecutorService bgpIntentsSynchronizerExecutor;
96
Thomas Vachuskab97cf282014-10-20 23:31:12 -070097 private final ApplicationId appId;
Jonathan Hart335ef462014-10-16 08:20:46 -070098
99 //
100 // State to deal with SDN-IP Leader election and pushing Intents
101 //
102 private Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
103 private volatile boolean isElectedLeader = false;
104 private volatile boolean isActivatedLeader = false;
105
Jonathan Hartbcae7bd2014-10-16 10:24:41 -0700106 // For routes announced by local BGP daemon in SDN network,
Jonathan Hart335ef462014-10-16 08:20:46 -0700107 // the next hop will be 0.0.0.0.
108 public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0");
109
110 /**
111 * Class constructor.
112 *
Jonathan Hart31582d12014-10-22 13:52:41 -0700113 * @param appId the application ID
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700114 * @param intentService the intent service
115 * @param hostService the host service
Jonathan Hart31582d12014-10-22 13:52:41 -0700116 * @param configService the configuration service
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700117 * @param interfaceService the interface service
Jonathan Hart335ef462014-10-16 08:20:46 -0700118 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700119 public Router(ApplicationId appId, IntentService intentService,
Jonathan Hart31582d12014-10-22 13:52:41 -0700120 HostService hostService, SdnIpConfigService configService,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700121 InterfaceService interfaceService) {
122 this.appId = appId;
Jonathan Hart335ef462014-10-16 08:20:46 -0700123 this.intentService = intentService;
124 this.hostService = hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -0700125 this.configService = configService;
Jonathan Hart335ef462014-10-16 08:20:46 -0700126 this.interfaceService = interfaceService;
127
128 bgpRoutes = new ConcurrentInvertedRadixTree<>(
129 new DefaultByteArrayNodeFactory());
130 routeUpdates = new LinkedBlockingQueue<>();
131 routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
132 HashMultimap.<IpAddress, RouteEntry>create());
133 pushedRouteIntents = new ConcurrentHashMap<>();
134
135 bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
136 new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build());
137 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
138 new ThreadFactoryBuilder()
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700139 .setNameFormat("bgp-intents-synchronizer-%d").build());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700140
141 this.hostService.addListener(new InternalHostListener());
Jonathan Hart335ef462014-10-16 08:20:46 -0700142 }
143
144 /**
145 * Starts the Router.
146 */
147 public void start() {
148
Jonathan Hart0e12fad2014-10-17 14:54:58 -0700149 // TODO hack to enable SDN-IP now for testing
150 isElectedLeader = true;
151 isActivatedLeader = true;
152
Jonathan Hart335ef462014-10-16 08:20:46 -0700153 bgpUpdatesExecutor.execute(new Runnable() {
154 @Override
155 public void run() {
156 doUpdatesThread();
157 }
158 });
159
160 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
161 @Override
162 public void run() {
163 doIntentSynchronizationThread();
164 }
165 });
166 }
167
168 //@Override TODO hook this up to something
169 public void leaderChanged(boolean isLeader) {
170 log.debug("Leader changed: {}", isLeader);
171
172 if (!isLeader) {
173 this.isElectedLeader = false;
174 this.isActivatedLeader = false;
175 return; // Nothing to do
176 }
177 this.isActivatedLeader = false;
178 this.isElectedLeader = true;
179
180 //
181 // Tell the Intents Synchronizer thread to start the synchronization
182 //
183 intentsSynchronizerSemaphore.release();
184 }
185
186 @Override
187 public void update(RouteUpdate routeUpdate) {
Jonathan Hart31582d12014-10-22 13:52:41 -0700188 log.debug("Received new route update: {}", routeUpdate);
Jonathan Hart335ef462014-10-16 08:20:46 -0700189
190 try {
191 routeUpdates.put(routeUpdate);
192 } catch (InterruptedException e) {
193 log.debug("Interrupted while putting on routeUpdates queue", e);
194 Thread.currentThread().interrupt();
195 }
196 }
197
198 /**
199 * Thread for Intent Synchronization.
200 */
201 private void doIntentSynchronizationThread() {
202 boolean interrupted = false;
203 try {
204 while (!interrupted) {
205 try {
206 intentsSynchronizerSemaphore.acquire();
207 //
208 // Drain all permits, because a single synchronization is
209 // sufficient.
210 //
211 intentsSynchronizerSemaphore.drainPermits();
212 } catch (InterruptedException e) {
213 log.debug("Interrupted while waiting to become " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700214 "Intent Synchronization leader");
Jonathan Hart335ef462014-10-16 08:20:46 -0700215 interrupted = true;
216 break;
217 }
218 syncIntents();
219 }
220 } finally {
221 if (interrupted) {
222 Thread.currentThread().interrupt();
223 }
224 }
225 }
226
227 /**
228 * Thread for handling route updates.
229 */
230 private void doUpdatesThread() {
231 boolean interrupted = false;
232 try {
233 while (!interrupted) {
234 try {
235 RouteUpdate update = routeUpdates.take();
236 switch (update.type()) {
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700237 case UPDATE:
238 processRouteAdd(update.routeEntry());
239 break;
240 case DELETE:
241 processRouteDelete(update.routeEntry());
242 break;
243 default:
244 log.error("Unknown update Type: {}", update.type());
245 break;
Jonathan Hart335ef462014-10-16 08:20:46 -0700246 }
247 } catch (InterruptedException e) {
248 log.debug("Interrupted while taking from updates queue", e);
249 interrupted = true;
250 } catch (Exception e) {
251 log.debug("exception", e);
252 }
253 }
254 } finally {
255 if (interrupted) {
256 Thread.currentThread().interrupt();
257 }
258 }
259 }
260
261 /**
262 * Performs Intents Synchronization between the internally stored Route
263 * Intents and the installed Route Intents.
264 */
265 private void syncIntents() {
266 synchronized (this) {
267 if (!isElectedLeader) {
268 return; // Nothing to do: not the leader anymore
269 }
270 log.debug("Syncing SDN-IP Route Intents...");
271
272 Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700273 new HashMap<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700274
275 //
276 // Fetch all intents, and classify the Multi-Point-to-Point Intents
277 // based on the matching prefix.
278 //
279 for (Intent intent : intentService.getIntents()) {
280 //
281 // TODO: Ignore all intents that are not installed by
282 // the SDN-IP application.
283 //
284 if (!(intent instanceof MultiPointToSinglePointIntent)) {
285 continue;
286 }
287 MultiPointToSinglePointIntent mp2pIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700288 (MultiPointToSinglePointIntent) intent;
Jonathan Hart335ef462014-10-16 08:20:46 -0700289 /*Match match = mp2pIntent.getMatch();
290 if (!(match instanceof PacketMatch)) {
291 continue;
292 }
293 PacketMatch packetMatch = (PacketMatch) match;
294 Ip4Prefix prefix = packetMatch.getDstIpAddress();
295 if (prefix == null) {
296 continue;
297 }
298 fetchedIntents.put(prefix, mp2pIntent);*/
299 for (Criterion criterion : mp2pIntent.selector().criteria()) {
300 if (criterion.type() == Type.IPV4_DST) {
301 IPCriterion ipCriterion = (IPCriterion) criterion;
302 fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
303 }
304 }
305
306 }
307
308 //
309 // Compare for each prefix the local IN-MEMORY Intents with the
310 // FETCHED Intents:
311 // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
312 // the FETCHED Intent in the local memory (i.e., override the
313 // IN-MEMORY Intent) to preserve the original Intent ID
314 // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
315 // delete the FETCHED Intent, and push/install the IN-MEMORY
316 // Intent.
317 // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
318 // Intent for same prefix, then push/install the IN-MEMORY
319 // Intent.
320 // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
321 // Intent for same prefix, then delete/withdraw the FETCHED
322 // Intent.
323 //
324 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700325 storeInMemoryIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700326 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700327 addIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700328 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700329 deleteIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700330 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700331 pushedRouteIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700332 IpPrefix prefix = entry.getKey();
333 MultiPointToSinglePointIntent inMemoryIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700334 entry.getValue();
Jonathan Hart335ef462014-10-16 08:20:46 -0700335 MultiPointToSinglePointIntent fetchedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700336 fetchedIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700337
338 if (fetchedIntent == null) {
339 //
340 // No FETCHED Intent for same prefix: push the IN-MEMORY
341 // Intent.
342 //
343 addIntents.add(Pair.of(prefix, inMemoryIntent));
344 continue;
345 }
346
347 //
348 // If IN-MEMORY Intent is same as the FETCHED Intent,
349 // store the FETCHED Intent in the local memory.
350 //
351 if (compareMultiPointToSinglePointIntents(inMemoryIntent,
352 fetchedIntent)) {
353 storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
354 } else {
355 //
356 // The IN-MEMORY Intent is not same as the FETCHED Intent,
357 // hence delete the FETCHED Intent, and install the
358 // IN-MEMORY Intent.
359 //
360 deleteIntents.add(Pair.of(prefix, fetchedIntent));
361 addIntents.add(Pair.of(prefix, inMemoryIntent));
362 }
363 fetchedIntents.remove(prefix);
364 }
365
366 //
367 // Any remaining FETCHED Intents have to be deleted/withdrawn
368 //
369 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700370 fetchedIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700371 IpPrefix prefix = entry.getKey();
372 MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
373 deleteIntents.add(Pair.of(prefix, fetchedIntent));
374 }
375
376 //
377 // Perform the actions:
378 // 1. Store in memory fetched intents that are same. Can be done
379 // even if we are not the leader anymore
380 // 2. Delete intents: check if the leader before each operation
381 // 3. Add intents: check if the leader before each operation
382 //
383 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700384 storeInMemoryIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700385 IpPrefix prefix = pair.getLeft();
386 MultiPointToSinglePointIntent intent = pair.getRight();
387 log.debug("Intent synchronization: updating in-memory " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700388 "Intent for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700389 pushedRouteIntents.put(prefix, intent);
390 }
391 //
392 isActivatedLeader = true; // Allow push of Intents
393 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700394 deleteIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700395 IpPrefix prefix = pair.getLeft();
396 MultiPointToSinglePointIntent intent = pair.getRight();
397 if (!isElectedLeader) {
398 isActivatedLeader = false;
399 return;
400 }
401 log.debug("Intent synchronization: deleting Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700402 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700403 intentService.withdraw(intent);
404 }
405 //
406 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700407 addIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700408 IpPrefix prefix = pair.getLeft();
409 MultiPointToSinglePointIntent intent = pair.getRight();
410 if (!isElectedLeader) {
411 isActivatedLeader = false;
412 return;
413 }
414 log.debug("Intent synchronization: adding Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700415 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700416 intentService.submit(intent);
417 }
418 if (!isElectedLeader) {
419 isActivatedLeader = false;
420 }
421 log.debug("Syncing SDN-IP routes completed.");
422 }
423 }
424
425 /**
426 * Compares two Multi-point to Single Point Intents whether they represent
427 * same logical intention.
428 *
429 * @param intent1 the first Intent to compare
430 * @param intent2 the second Intent to compare
431 * @return true if both Intents represent same logical intention, otherwise
432 * false
433 */
434 private boolean compareMultiPointToSinglePointIntents(
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700435 MultiPointToSinglePointIntent intent1,
436 MultiPointToSinglePointIntent intent2) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700437 /*Match match1 = intent1.getMatch();
438 Match match2 = intent2.getMatch();
439 Action action1 = intent1.getAction();
440 Action action2 = intent2.getAction();
441 Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts();
442 Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts();
443 SwitchPort egressPort1 = intent1.getEgressPort();
444 SwitchPort egressPort2 = intent2.getEgressPort();
445
446 return Objects.equal(match1, match2) &&
447 Objects.equal(action1, action2) &&
448 Objects.equal(egressPort1, egressPort2) &&
449 Objects.equal(ingressPorts1, ingressPorts2);*/
Pingpingf5d90932014-10-27 10:50:04 -0700450 return Objects.equal(intent1.appId(), intent2.appId()) &&
451 Objects.equal(intent1.selector(), intent2.selector()) &&
Jonathan Hart335ef462014-10-16 08:20:46 -0700452 Objects.equal(intent1.treatment(), intent2.treatment()) &&
453 Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
454 Objects.equal(intent1.egressPoint(), intent2.egressPoint());
455 }
456
457 /**
458 * Processes adding a route entry.
459 * <p/>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700460 * Put new route entry into the radix tree. If there was an existing
461 * next hop for this prefix, but the next hop was different, then execute
Jonathan Hart335ef462014-10-16 08:20:46 -0700462 * deleting old route entry. If the next hop is the SDN domain, we do not
463 * handle it at the moment. Otherwise, execute adding a route.
464 *
465 * @param routeEntry the route entry to add
466 */
467 protected void processRouteAdd(RouteEntry routeEntry) {
468 synchronized (this) {
469 log.debug("Processing route add: {}", routeEntry);
470
471 IpPrefix prefix = routeEntry.prefix();
472 IpAddress nextHop = null;
473 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700474 bgpRoutes.put(RouteEntry.createBinaryString(prefix),
475 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700476 if (foundRouteEntry != null) {
477 nextHop = foundRouteEntry.nextHop();
478 }
479
480 if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
481 // There was an existing nexthop for this prefix. This update
482 // supersedes that, so we need to remove the old flows for this
483 // prefix from the switches
484 executeRouteDelete(routeEntry);
485 }
486 if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
487 return;
488 }
489
490 if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
491 // Route originated by SDN domain
492 // We don't handle these at the moment
493 log.debug("Own route {} to {}",
494 routeEntry.prefix(), routeEntry.nextHop());
495 return;
496 }
497
498 executeRouteAdd(routeEntry);
499 }
500 }
501
502 /**
503 * Executes adding a route entry.
504 * <p/>
505 * Find out the egress Interface and MAC address of next hop router for
506 * this route entry. If the MAC address can not be found in ARP cache,
507 * then this prefix will be put in routesWaitingOnArp queue. Otherwise,
508 * new route intent will be created and installed.
509 *
510 * @param routeEntry the route entry to add
511 */
512 private void executeRouteAdd(RouteEntry routeEntry) {
513 log.debug("Executing route add: {}", routeEntry);
514
Jonathan Hart31582d12014-10-22 13:52:41 -0700515 // Monitor the IP address so we'll get notified of updates to the MAC
516 // address.
517 hostService.startMonitoringIp(routeEntry.nextHop());
518
Jonathan Hart335ef462014-10-16 08:20:46 -0700519 // See if we know the MAC address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700520 MacAddress nextHopMacAddress = null;
Pavlin Radoslavov33f228a2014-10-27 19:33:16 -0700521 Set<Host> hosts = hostService.getHostsByIp(routeEntry.nextHop());
Jonathan Hart335ef462014-10-16 08:20:46 -0700522 if (!hosts.isEmpty()) {
523 // TODO how to handle if multiple hosts are returned?
524 nextHopMacAddress = hosts.iterator().next().mac();
525 }
526
527 if (nextHopMacAddress == null) {
528 routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700529 return;
530 }
531
532 addRouteIntentToNextHop(routeEntry.prefix(),
533 routeEntry.nextHop(),
534 nextHopMacAddress);
535 }
536
537 /**
538 * Adds a route intent given a prefix and a next hop IP address. This
539 * method will find the egress interface for the intent.
540 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700541 * @param prefix IP prefix of the route to add
542 * @param nextHopIpAddress IP address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700543 * @param nextHopMacAddress MAC address of the next hop
544 */
545 private void addRouteIntentToNextHop(IpPrefix prefix,
546 IpAddress nextHopIpAddress,
547 MacAddress nextHopMacAddress) {
548
549 // Find the attachment point (egress interface) of the next hop
550 Interface egressInterface;
Jonathan Hart31582d12014-10-22 13:52:41 -0700551 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700552 // Route to a peer
553 log.debug("Route to peer {}", nextHopIpAddress);
554 BgpPeer peer =
Jonathan Hart31582d12014-10-22 13:52:41 -0700555 configService.getBgpPeers().get(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700556 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700557 interfaceService.getInterface(peer.connectPoint());
Jonathan Hart335ef462014-10-16 08:20:46 -0700558 } else {
559 // Route to non-peer
560 log.debug("Route to non-peer {}", nextHopIpAddress);
561 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700562 interfaceService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700563 if (egressInterface == null) {
564 log.warn("No outgoing interface found for {}",
565 nextHopIpAddress);
566 return;
567 }
568 }
569
570 doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
571 }
572
573 /**
574 * Installs a route intent for a prefix.
575 * <p/>
576 * Intent will match dst IP prefix and rewrite dst MAC address at all other
577 * border switches, then forward packets according to dst MAC address.
578 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700579 * @param prefix IP prefix from route
580 * @param egressInterface egress Interface connected to next hop router
Jonathan Hart335ef462014-10-16 08:20:46 -0700581 * @param nextHopMacAddress MAC address of next hop router
582 */
583 private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700584 MacAddress nextHopMacAddress) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700585 log.debug("Adding intent for prefix {}, next hop mac {}",
586 prefix, nextHopMacAddress);
587
588 MultiPointToSinglePointIntent pushedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700589 pushedRouteIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700590
591 // Just for testing.
592 if (pushedIntent != null) {
593 log.error("There should not be a pushed intent: {}", pushedIntent);
594 }
595
596 ConnectPoint egressPort = egressInterface.connectPoint();
597
598 Set<ConnectPoint> ingressPorts = new HashSet<>();
599
600 for (Interface intf : interfaceService.getInterfaces()) {
601 if (!intf.equals(egressInterface)) {
602 ConnectPoint srcPort = intf.connectPoint();
603 ingressPorts.add(srcPort);
604 }
605 }
606
607 // Match the destination IP prefix at the first hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700608 TrafficSelector selector = DefaultTrafficSelector.builder()
609 .matchEthType(Ethernet.TYPE_IPV4)
610 .matchIPDst(prefix)
611 .build();
612
613 // Rewrite the destination MAC address
Jonathan Hart335ef462014-10-16 08:20:46 -0700614 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
615 .setEthDst(nextHopMacAddress)
616 .build();
617
618 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700619 new MultiPointToSinglePointIntent(appId, selector, treatment,
620 ingressPorts, egressPort);
Jonathan Hart335ef462014-10-16 08:20:46 -0700621
622 if (isElectedLeader && isActivatedLeader) {
623 log.debug("Intent installation: adding Intent for prefix: {}",
624 prefix);
625 intentService.submit(intent);
626 }
627
628 // Maintain the Intent
629 pushedRouteIntents.put(prefix, intent);
630 }
631
632 /**
633 * Executes deleting a route entry.
634 * <p/>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700635 * Removes prefix from radix tree, and if successful, then try to delete
636 * the related intent.
Jonathan Hart335ef462014-10-16 08:20:46 -0700637 *
638 * @param routeEntry the route entry to delete
639 */
640 protected void processRouteDelete(RouteEntry routeEntry) {
641 synchronized (this) {
642 log.debug("Processing route delete: {}", routeEntry);
643 IpPrefix prefix = routeEntry.prefix();
644
Jonathan Hart335ef462014-10-16 08:20:46 -0700645 if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
646 //
647 // Only delete flows if an entry was actually removed from the
648 // tree. If no entry was removed, the <prefix, nexthop> wasn't
649 // there so it's probably already been removed and we don't
650 // need to do anything.
651 //
652 executeRouteDelete(routeEntry);
653 }
654
655 routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
656 // TODO cancel the request in the ARP manager as well
657 }
658 }
659
660 /**
661 * Executed deleting a route entry.
662 *
663 * @param routeEntry the route entry to delete
664 */
665 private void executeRouteDelete(RouteEntry routeEntry) {
666 log.debug("Executing route delete: {}", routeEntry);
667
668 IpPrefix prefix = routeEntry.prefix();
669
670 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700671 pushedRouteIntents.remove(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700672
673 if (intent == null) {
674 log.debug("There is no intent in pushedRouteIntents to delete " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700675 "for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700676 } else {
677 if (isElectedLeader && isActivatedLeader) {
678 log.debug("Intent installation: deleting Intent for prefix: {}",
679 prefix);
680 intentService.withdraw(intent);
681 }
682 }
683 }
684
685 /**
Jonathan Hart31582d12014-10-22 13:52:41 -0700686 * Signals the Router that the MAC to IP mapping has potentially been
687 * updated. This has the effect of updating the MAC address for any
688 * installed prefixes if it has changed, as well as installing any pending
689 * prefixes that were waiting for MAC resolution.
Jonathan Hart335ef462014-10-16 08:20:46 -0700690 *
Jonathan Hart31582d12014-10-22 13:52:41 -0700691 * @param ipAddress the IP address that an event was received for
692 * @param macAddress the most recently known MAC address for the IP address
Jonathan Hart335ef462014-10-16 08:20:46 -0700693 */
Jonathan Hart31582d12014-10-22 13:52:41 -0700694 private void updateMac(IpAddress ipAddress, MacAddress macAddress) {
695 log.debug("Received updated MAC info: {} => {}", ipAddress, macAddress);
696
697 // TODO here we should check whether the next hop for any of our
698 // installed prefixes has changed, not just prefixes pending installation.
Jonathan Hart335ef462014-10-16 08:20:46 -0700699
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700700 // We synchronize on this to prevent changes to the radix tree
701 // while we're pushing intents. If the tree changes, the
702 // tree and intents could get out of sync.
Jonathan Hart335ef462014-10-16 08:20:46 -0700703 synchronized (this) {
704
705 Set<RouteEntry> routesToPush =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700706 routesWaitingOnArp.removeAll(ipAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700707
708 for (RouteEntry routeEntry : routesToPush) {
709 // These will always be adds
710 IpPrefix prefix = routeEntry.prefix();
711 String binaryString = RouteEntry.createBinaryString(prefix);
712 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700713 bgpRoutes.getValueForExactKey(binaryString);
Jonathan Hart335ef462014-10-16 08:20:46 -0700714 if (foundRouteEntry != null &&
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700715 foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700716 // We only push prefix flows if the prefix is still in the
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700717 // radix tree and the next hop is the same as our
Jonathan Hart335ef462014-10-16 08:20:46 -0700718 // update.
719 // The prefix could have been removed while we were waiting
720 // for the ARP, or the next hop could have changed.
721 addRouteIntentToNextHop(prefix, ipAddress, macAddress);
722 } else {
Jonathan Hart31582d12014-10-22 13:52:41 -0700723 log.debug("{} has been revoked before the MAC was resolved",
724 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700725 }
726 }
727 }
728 }
729
730 /**
731 * Gets the SDN-IP routes.
732 *
733 * @return the SDN-IP routes
734 */
735 public Collection<RouteEntry> getRoutes() {
736 Iterator<KeyValuePair<RouteEntry>> it =
737 bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
738
739 List<RouteEntry> routes = new LinkedList<>();
740
741 while (it.hasNext()) {
742 KeyValuePair<RouteEntry> entry = it.next();
743 routes.add(entry.getValue());
744 }
745
746 return routes;
747 }
748
749 /**
Pingping3855f312014-10-22 12:50:37 -0700750 * Gets the pushed route intents.
751 *
752 * @return the pushed route intents
753 */
754 public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
755 List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
756
757 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
758 pushedRouteIntents.entrySet()) {
759 pushedIntents.add(entry.getValue());
760 }
761 return pushedIntents;
762 }
763
764 /**
Jonathan Hart335ef462014-10-16 08:20:46 -0700765 * Listener for host events.
766 */
767 class InternalHostListener implements HostListener {
768 @Override
769 public void event(HostEvent event) {
770 if (event.type() == HostEvent.Type.HOST_ADDED ||
771 event.type() == HostEvent.Type.HOST_UPDATED) {
772 Host host = event.subject();
Pavlin Radoslavov33f228a2014-10-27 19:33:16 -0700773 for (IpAddress ip : host.ipAddresses()) {
774 updateMac(ip, host.mac());
Jonathan Hart335ef462014-10-16 08:20:46 -0700775 }
776 }
777 }
778 }
779}