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