Jonathan Hart
Committed by Gerrit Code Review

Implementation of the route service

Change-Id: I4e905cf868ad69c426e4f4155dfd83e1f8b00335
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.cli.net;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.incubator.net.routing.Route;
import org.onosproject.incubator.net.routing.RouteAdminService;
import java.util.Collections;
/**
* Command to add a route to the routing table.
*/
@Command(scope = "onos", name = "route-add",
description = "Adds a route to the route table")
public class RouteAddCommand extends AbstractShellCommand {
@Argument(index = 0, name = "prefix", description = "IP prefix of the route",
required = true)
String prefixString = null;
@Argument(index = 1, name = "nextHop", description = "IP address of the next hop",
required = true)
String nextHopString = null;
@Override
protected void execute() {
RouteAdminService service = AbstractShellCommand.get(RouteAdminService.class);
IpPrefix prefix = IpPrefix.valueOf(prefixString);
IpAddress nextHop = IpAddress.valueOf(nextHopString);
service.update(Collections.singleton(new Route(Route.Source.STATIC, prefix, nextHop)));
}
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.cli.net;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onlab.packet.IpPrefix;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.incubator.net.routing.Route;
import org.onosproject.incubator.net.routing.RouteAdminService;
import java.util.Collections;
/**
* Command to remove a route from the routing table.
*/
@Command(scope = "onos", name = "route-remove",
description = "Removes a route from the route table")
public class RouteRemoveCommand extends AbstractShellCommand {
@Argument(index = 0, name = "prefix", description = "IP prefix of the route",
required = true)
String prefixString = null;
@Override
protected void execute() {
RouteAdminService service = AbstractShellCommand.get(RouteAdminService.class);
IpPrefix prefix = IpPrefix.valueOf(prefixString);
service.withdraw(Collections.singleton(new Route(Route.Source.STATIC, prefix, null)));
}
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.cli.net;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.incubator.net.routing.Route;
import org.onosproject.incubator.net.routing.RouteService;
import org.onosproject.incubator.net.routing.RouteTableId;
import java.util.Collection;
import java.util.Map;
/**
* Command to show the routes in the routing tables.
*/
// TODO update command name when we switch over to new rib
@Command(scope = "onos", name = "routes2",
description = "Lists all routes in the route store")
public class RoutesListCommand extends AbstractShellCommand {
private static final String FORMAT_HEADER =
" Network Next Hop";
private static final String FORMAT_ROUTE =
" %-18s %-15s";
private static final String FORMAT_TABLE = "Table: %s";
private static final String FORMAT_TOTAL = " Total: %d";
@Override
protected void execute() {
RouteService service = AbstractShellCommand.get(RouteService.class);
Map<RouteTableId, Collection<Route>> allRoutes = service.getAllRoutes();
allRoutes.forEach((id, routes) -> {
print(FORMAT_TABLE, id);
print(FORMAT_HEADER);
routes.forEach(r -> print(FORMAT_ROUTE, r.prefix(), r.nextHop()));
print(FORMAT_TOTAL, routes.size());
print("");
});
}
}
......@@ -497,6 +497,16 @@
</command>
<command>
<action class="org.onosproject.cli.net.RoutesListCommand"/>
</command>
<command>
<action class="org.onosproject.cli.net.RouteAddCommand"/>
</command>
<command>
<action class="org.onosproject.cli.net.RouteRemoveCommand"/>
</command>
<command>
<action class="org.onosproject.cli.net.GlobalLabelCommand"/>
</command>
<command>
......
......@@ -18,7 +18,7 @@ package org.onosproject.net.host;
import org.onosproject.store.StoreDelegate;
/**
* Infrastructure link store delegate abstraction.
* Host store delegate abstraction.
*/
public interface HostStoreDelegate extends StoreDelegate<HostEvent> {
}
......
......@@ -70,8 +70,7 @@ public class Route {
*/
public Route(Source source, IpPrefix prefix, IpAddress nextHop) {
checkNotNull(prefix);
checkNotNull(nextHop);
checkArgument(prefix.version().equals(nextHop.version()), VERSION_MISMATCH);
checkArgument(nextHop == null || prefix.version().equals(nextHop.version()), VERSION_MISMATCH);
this.source = checkNotNull(source);
this.prefix = prefix;
......
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.incubator.net.routing;
import java.util.Collection;
/**
* Service allowing mutation of unicast routing state.
*/
public interface RouteAdminService extends RouteService {
/**
* Updates the given routes in the route service.
*
* @param routes collection of routes to update
*/
void update(Collection<Route> routes);
/**
* Withdraws the given routes from the route service.
*
* @param routes collection of routes to withdraw
*/
void withdraw(Collection<Route> routes);
}
......@@ -20,47 +20,28 @@ import org.onlab.packet.IpAddress;
import org.onosproject.event.ListenerService;
import java.util.Collection;
import java.util.Map;
/**
* IP unicast routing service.
* Unicast IP route service.
*/
public interface RouteService extends ListenerService<RouteEvent, RouteListener> {
/**
* Gets all routes.
* Returns all routes for all route tables in the system.
*
* @return collection of all routes
* @return map of route table name to routes in that table
*/
Collection<Route> getRoutes();
Map<RouteTableId, Collection<Route>> getAllRoutes();
/**
* Gets routes learnt from a particular source.
* Performs a longest prefix match on the given IP address. The call will
* return the route with the most specific prefix that contains the given
* IP address.
*
* @param source route source
* @return collection of routes
*/
Collection<Route> getRoutesFromSource(Route.Source source);
/**
* Gets the longest prefix route that matches the given IP address.
*
* @param ip IP addres
* @param ip IP address
* @return longest prefix matched route
*/
Route longestPrefixMatch(IpAddress ip);
//TODO should mutation methods be pushed to a different interface?
/**
* Updates the given routes in the route service.
*
* @param routes collection of routes to update
*/
void update(Collection<Route> routes);
/**
* Withdraws the given routes from the route service.
*
* @param routes collection of routes to withdraw
*/
void withdraw(Collection<Route> routes);
}
......
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.incubator.net.routing;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.store.Store;
import java.util.Collection;
import java.util.Set;
/**
* Unicast route store.
*/
public interface RouteStore extends Store<RouteEvent, RouteStoreDelegate> {
/**
* Adds or updates the given route in the store.
*
* @param route route to add or update
*/
void updateRoute(Route route);
/**
* Removes the given route from the store.
*
* @param route route to remove
*/
void removeRoute(Route route);
/**
* Returns the IDs for all route tables in the store.
*
* @return route table IDs
*/
Set<RouteTableId> getRouteTables();
/**
* Returns the routes for a particular route table.
*
* @param table route table
* @return collection of route in the table
*/
Collection<Route> getRoutes(RouteTableId table);
/**
* Performs a longest prefix match with the given IP address.
*
* @param ip IP to look up
* @return longest prefix match route
*/
Route longestPrefixMatch(IpAddress ip);
/**
* Updates a next hop IP and MAC in the store.
*
* @param ip IP address
* @param mac MAC address
*/
void updateNextHop(IpAddress ip, MacAddress mac);
/**
* Removes a next hop IP and MAC from the store.
*
* @param ip IP address
* @param mac MAC address
*/
void removeNextHop(IpAddress ip, MacAddress mac);
/**
* Returns the MAC address of the given next hop.
*
* @param ip next hop IP
* @return MAC address
*/
MacAddress getNextHop(IpAddress ip);
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.incubator.net.routing;
import org.onosproject.store.StoreDelegate;
/**
* Route store delegate abstraction.
*/
public interface RouteStoreDelegate extends StoreDelegate<RouteEvent> {
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.incubator.net.routing;
import java.util.Objects;
/**
* Identifier for a routing table.
*/
public class RouteTableId {
private final String name;
/**
* Creates a new route table ID.
*
* @param name unique name for the route table
*/
public RouteTableId(String name) {
this.name = name;
}
/**
* Returns the name of the route table.
*
* @return table name
*/
public String name() {
return name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof RouteTableId) {
RouteTableId that = (RouteTableId) obj;
return Objects.equals(this.name, that.name);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return name;
}
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.incubator.net.routing.impl;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.event.ListenerService;
import org.onosproject.incubator.net.routing.ResolvedRoute;
import org.onosproject.incubator.net.routing.Route;
import org.onosproject.incubator.net.routing.RouteAdminService;
import org.onosproject.incubator.net.routing.RouteEvent;
import org.onosproject.incubator.net.routing.RouteListener;
import org.onosproject.incubator.net.routing.RouteService;
import org.onosproject.incubator.net.routing.RouteStore;
import org.onosproject.incubator.net.routing.RouteStoreDelegate;
import org.onosproject.incubator.net.routing.RouteTableId;
import org.onosproject.net.Host;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.host.HostService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.concurrent.GuardedBy;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
/**
* Implementation of the unicast route service.
*/
@Service
@Component
public class RouteManager implements ListenerService<RouteEvent, RouteListener>,
RouteService, RouteAdminService {
private final Logger log = LoggerFactory.getLogger(getClass());
private RouteStoreDelegate delegate = new InternalRouteStoreDelegate();
private InternalHostListener hostListener = new InternalHostListener();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RouteStore routeStore;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@GuardedBy(value = "this")
private Map<RouteListener, ListenerQueue> listeners = new HashMap<>();
private ThreadFactory threadFactory;
@Activate
protected void activate() {
threadFactory = groupedThreads("onos/route", "listener-%d");
routeStore.setDelegate(delegate);
hostService.addListener(hostListener);
}
@Deactivate
protected void deactivate() {
listeners.values().forEach(l -> l.stop());
routeStore.unsetDelegate(delegate);
hostService.removeListener(hostListener);
}
/**
* {@inheritDoc}
*
* In a departure from other services in ONOS, calling addListener will
* cause all current routes to be pushed to the listener before any new
* events are sent. This allows a listener to easily get the exact set of
* routes without worrying about missing any.
*
* @param listener listener to be added
*/
@Override
public void addListener(RouteListener listener) {
synchronized (this) {
log.debug("Synchronizing current routes to new listener");
ListenerQueue l = new ListenerQueue(listener);
routeStore.getRouteTables().forEach(table -> {
Collection<Route> routes = routeStore.getRoutes(table);
if (routes != null) {
routes.forEach(route ->
l.post(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
new ResolvedRoute(route, routeStore.getNextHop(route.nextHop())))));
}
});
listeners.put(listener, l);
l.start();
log.debug("Route synchronization complete");
}
}
@Override
public void removeListener(RouteListener listener) {
synchronized (this) {
ListenerQueue l = listeners.remove(listener);
if (l != null) {
l.stop();
}
}
}
/**
* Posts an event to all listeners.
*
* @param event event
*/
private void post(RouteEvent event) {
synchronized (this) {
listeners.values().forEach(l -> l.post(event));
}
}
@Override
public Map<RouteTableId, Collection<Route>> getAllRoutes() {
return routeStore.getRouteTables().stream()
.collect(Collectors.toMap(Function.identity(),
table -> (table == null) ?
Collections.emptySet() : routeStore.getRoutes(table)));
}
@Override
public Route longestPrefixMatch(IpAddress ip) {
return routeStore.longestPrefixMatch(ip);
}
@Override
public void update(Collection<Route> routes) {
synchronized (this) {
routes.forEach(route -> {
routeStore.updateRoute(route);
resolve(route);
});
}
}
@Override
public void withdraw(Collection<Route> routes) {
synchronized (this) {
routes.forEach(route -> routeStore.removeRoute(route));
}
}
private void resolve(Route route) {
// Monitor the IP address for updates of the MAC address
hostService.startMonitoringIp(route.nextHop());
MacAddress nextHopMac = routeStore.getNextHop(route.nextHop());
if (nextHopMac == null) {
Set<Host> hosts = hostService.getHostsByIp(route.nextHop());
Optional<Host> host = hosts.stream().findFirst();
if (host.isPresent()) {
nextHopMac = host.get().mac();
}
}
if (nextHopMac != null) {
routeStore.updateNextHop(route.nextHop(), nextHopMac);
}
}
private void hostUpdated(Host host) {
synchronized (this) {
for (IpAddress ip : host.ipAddresses()) {
routeStore.updateNextHop(ip, host.mac());
}
}
}
private void hostRemoved(Host host) {
synchronized (this) {
for (IpAddress ip : host.ipAddresses()) {
routeStore.removeNextHop(ip, host.mac());
}
}
}
/**
* Queues updates for a route listener to ensure they are received in the
* correct order.
*/
private class ListenerQueue {
private final ExecutorService executorService;
private final BlockingQueue<RouteEvent> queue;
private final RouteListener listener;
/**
* Creates a new listener queue.
*
* @param listener route listener to queue updates for
*/
public ListenerQueue(RouteListener listener) {
this.listener = listener;
queue = new LinkedBlockingQueue<>();
executorService = newSingleThreadExecutor(threadFactory);
}
/**
* Posts and event to the listener.
*
* @param event event
*/
public void post(RouteEvent event) {
queue.add(event);
}
/**
* Initiates event delivery to the listener.
*/
public void start() {
executorService.execute(this::poll);
}
/**
* Halts event delivery to the listener.
*/
public void stop() {
executorService.shutdown();
}
private void poll() {
try {
while (true) {
listener.event(queue.take());
}
} catch (InterruptedException e) {
log.info("Route listener event thread shutting down: {}", e.getMessage());
}
}
}
/**
* Delegate to receive events from the route store.
*/
private class InternalRouteStoreDelegate implements RouteStoreDelegate {
@Override
public void notify(RouteEvent event) {
post(event);
}
}
/**
* Internal listener for host events.
*/
private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
switch (event.type()) {
case HOST_ADDED:
case HOST_UPDATED:
hostUpdated(event.subject());
break;
case HOST_REMOVED:
hostRemoved(event.subject());
break;
case HOST_MOVED:
break;
default:
break;
}
}
}
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Implementation of route service.
*/
package org.onosproject.incubator.net.routing.impl;
......@@ -53,6 +53,13 @@
<version>${project.version}</version>
</dependency>
<!-- FIXME: not OSGi-ready -->
<dependency>
<groupId>com.googlecode.concurrent-trees</groupId>
<artifactId>concurrent-trees</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
......
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.incubator.store.routing.impl;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onosproject.incubator.net.routing.ResolvedRoute;
import org.onosproject.incubator.net.routing.Route;
import org.onosproject.incubator.net.routing.RouteEvent;
import org.onosproject.incubator.net.routing.RouteStore;
import org.onosproject.incubator.net.routing.RouteStoreDelegate;
import org.onosproject.incubator.net.routing.RouteTableId;
import org.onosproject.store.AbstractStore;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Route store based on in-memory storage.
*/
@Service
@Component
public class LocalRouteStore extends AbstractStore<RouteEvent, RouteStoreDelegate>
implements RouteStore {
private Map<RouteTableId, RouteTable> routeTables;
private static final RouteTableId IPV4 = new RouteTableId("ipv4");
private static final RouteTableId IPV6 = new RouteTableId("ipv6");
private Map<IpAddress, MacAddress> nextHops = new ConcurrentHashMap<>();
@Activate
protected void activate() {
routeTables = new ConcurrentHashMap<>();
routeTables.put(IPV4, new RouteTable());
routeTables.put(IPV6, new RouteTable());
}
@Override
public void updateRoute(Route route) {
getDefaultRouteTable(route).update(route);
}
@Override
public void removeRoute(Route route) {
RouteTable table = getDefaultRouteTable(route);
table.remove(route);
if (table.getRoutesForNextHop(route.nextHop()).isEmpty()) {
nextHops.remove(route.nextHop());
}
}
@Override
public Set<RouteTableId> getRouteTables() {
return routeTables.keySet();
}
@Override
public Collection<Route> getRoutes(RouteTableId table) {
RouteTable routeTable = routeTables.get(table);
if (routeTable == null) {
return Collections.emptySet();
}
return routeTable.getRoutes();
}
@Override
public Route longestPrefixMatch(IpAddress ip) {
return getDefaultRouteTable(ip).longestPrefixMatch(ip);
}
@Override
public void updateNextHop(IpAddress ip, MacAddress mac) {
Collection<Route> routes = getDefaultRouteTable(ip).getRoutesForNextHop(ip);
if (!routes.isEmpty() && !mac.equals(nextHops.get(ip))) {
nextHops.put(ip, mac);
for (Route route : routes) {
notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED, new ResolvedRoute(route, mac)));
}
}
}
@Override
public void removeNextHop(IpAddress ip, MacAddress mac) {
if (nextHops.remove(ip, mac)) {
Collection<Route> routes = getDefaultRouteTable(ip).getRoutesForNextHop(ip);
for (Route route : routes) {
notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED, new ResolvedRoute(route, null)));
}
}
}
@Override
public MacAddress getNextHop(IpAddress ip) {
return nextHops.get(ip);
}
private RouteTable getDefaultRouteTable(Route route) {
return getDefaultRouteTable(route.prefix().address());
}
private RouteTable getDefaultRouteTable(IpAddress ip) {
RouteTableId routeTableId = (ip.isIp4()) ? IPV4 : IPV6;
return routeTables.get(routeTableId);
}
private static String createBinaryString(IpPrefix ipPrefix) {
byte[] octets = ipPrefix.address().toOctets();
StringBuilder result = new StringBuilder(ipPrefix.prefixLength());
result.append("0");
for (int i = 0; i < ipPrefix.prefixLength(); i++) {
int byteOffset = i / Byte.SIZE;
int bitOffset = i % Byte.SIZE;
int mask = 1 << (Byte.SIZE - 1 - bitOffset);
byte value = octets[byteOffset];
boolean isSet = ((value & mask) != 0);
result.append(isSet ? "1" : "0");
}
return result.toString();
}
/**
* Route table into which routes can be placed.
*/
private class RouteTable {
private final InvertedRadixTree<Route> routeTable;
private final Map<IpPrefix, Route> routes = new ConcurrentHashMap<>();
private final Multimap<IpAddress, Route> reverseIndex =
Multimaps.synchronizedMultimap(HashMultimap.create());
/**
* Creates a new route table.
*/
public RouteTable() {
routeTable = new ConcurrentInvertedRadixTree<>(
new DefaultByteArrayNodeFactory());
}
/**
* Adds or updates the route in the route table.
*
* @param route route to update
*/
public void update(Route route) {
synchronized (this) {
Route oldRoute = routes.put(route.prefix(), route);
routeTable.put(createBinaryString(route.prefix()), route);
// TODO manage routes from multiple providers
reverseIndex.remove(route.nextHop(), oldRoute);
reverseIndex.put(route.nextHop(), route);
if (oldRoute != null && !oldRoute.nextHop().equals(route.nextHop())) {
// Remove old route because new one is different
// TODO ROUTE_UPDATED?
notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED, new ResolvedRoute(oldRoute, null)));
}
}
}
/**
* Removes the route from the route table.
*
* @param route route to remove
*/
public void remove(Route route) {
synchronized (this) {
Route removed = routes.remove(route.prefix());
routeTable.remove(createBinaryString(route.prefix()));
reverseIndex.remove(route.nextHop(), route);
if (removed != null) {
notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED, new ResolvedRoute(route, null)));
}
}
}
/**
* Returns the routes pointing to a particular next hop.
*
* @param ip next hop IP address
* @return routes for the next hop
*/
public Collection<Route> getRoutesForNextHop(IpAddress ip) {
return reverseIndex.get(ip);
}
/**
* Returns all routes in the route table.
*
* @return all routes
*/
public Collection<Route> getRoutes() {
return routes.values();
}
/**
* Performs a longest prefix match with the given IP in the route table.
*
* @param ip IP address to look up
* @return most specific prefix containing the given
*/
public Route longestPrefixMatch(IpAddress ip) {
Iterable<Route> prefixes =
routeTable.getValuesForKeysPrefixing(createBinaryString(ip.toIpPrefix()));
Iterator<Route> it = prefixes.iterator();
Route route = null;
while (it.hasNext()) {
route = it.next();
}
return route;
}
}
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Implementation of the unicast routing service.
*/
package org.onosproject.incubator.store.routing.impl;