Ayaka Koshibe

Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Conflicts:
	core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java

Change-Id: I6a8fc884f0c0b6578762d7b439177378e838735f
Showing 75 changed files with 5621 additions and 2812 deletions
...@@ -16,4 +16,11 @@ ...@@ -16,4 +16,11 @@
16 16
17 <description>ONOS simple reactive forwarding app</description> 17 <description>ONOS simple reactive forwarding app</description>
18 18
19 + <dependencies>
20 + <dependency>
21 + <groupId>org.osgi</groupId>
22 + <artifactId>org.osgi.compendium</artifactId>
23 + </dependency>
24 + </dependencies>
25 +
19 </project> 26 </project>
......
1 package org.onlab.onos.fwd; 1 package org.onlab.onos.fwd;
2 2
3 -import static org.slf4j.LoggerFactory.getLogger;
4 -
5 -import java.util.Set;
6 -
7 import org.apache.felix.scr.annotations.Activate; 3 import org.apache.felix.scr.annotations.Activate;
8 import org.apache.felix.scr.annotations.Component; 4 import org.apache.felix.scr.annotations.Component;
9 import org.apache.felix.scr.annotations.Deactivate; 5 import org.apache.felix.scr.annotations.Deactivate;
6 +import org.apache.felix.scr.annotations.Modified;
7 +import org.apache.felix.scr.annotations.Property;
10 import org.apache.felix.scr.annotations.Reference; 8 import org.apache.felix.scr.annotations.Reference;
11 import org.apache.felix.scr.annotations.ReferenceCardinality; 9 import org.apache.felix.scr.annotations.ReferenceCardinality;
12 import org.onlab.onos.ApplicationId; 10 import org.onlab.onos.ApplicationId;
...@@ -29,8 +27,14 @@ import org.onlab.onos.net.packet.PacketProcessor; ...@@ -29,8 +27,14 @@ import org.onlab.onos.net.packet.PacketProcessor;
29 import org.onlab.onos.net.packet.PacketService; 27 import org.onlab.onos.net.packet.PacketService;
30 import org.onlab.onos.net.topology.TopologyService; 28 import org.onlab.onos.net.topology.TopologyService;
31 import org.onlab.packet.Ethernet; 29 import org.onlab.packet.Ethernet;
30 +import org.osgi.service.component.ComponentContext;
32 import org.slf4j.Logger; 31 import org.slf4j.Logger;
33 32
33 +import java.util.Dictionary;
34 +import java.util.Set;
35 +
36 +import static org.slf4j.LoggerFactory.getLogger;
37 +
34 /** 38 /**
35 * Sample reactive forwarding application. 39 * Sample reactive forwarding application.
36 */ 40 */
...@@ -61,6 +65,10 @@ public class ReactiveForwarding { ...@@ -61,6 +65,10 @@ public class ReactiveForwarding {
61 65
62 private ApplicationId appId; 66 private ApplicationId appId;
63 67
68 + @Property(name = "enabled", boolValue = true,
69 + label = "Enable forwarding; default is true")
70 + private boolean isEnabled = true;
71 +
64 @Activate 72 @Activate
65 public void activate() { 73 public void activate() {
66 appId = coreService.registerApplication("org.onlab.onos.fwd"); 74 appId = coreService.registerApplication("org.onlab.onos.fwd");
...@@ -76,6 +84,22 @@ public class ReactiveForwarding { ...@@ -76,6 +84,22 @@ public class ReactiveForwarding {
76 log.info("Stopped"); 84 log.info("Stopped");
77 } 85 }
78 86
87 + @Modified
88 + public void modified(ComponentContext context) {
89 + Dictionary properties = context.getProperties();
90 + String flag = (String) properties.get("enabled");
91 + if (flag != null) {
92 + boolean enabled = flag.equals("true");
93 + if (isEnabled != enabled) {
94 + isEnabled = enabled;
95 + if (!isEnabled) {
96 + flowRuleService.removeFlowRulesById(appId);
97 + }
98 + log.info("Reconfigured. Forwarding is {}",
99 + isEnabled ? "enabled" : "disabled");
100 + }
101 + }
102 + }
79 103
80 /** 104 /**
81 * Packet processor responsible for forwarding packets along their paths. 105 * Packet processor responsible for forwarding packets along their paths.
...@@ -86,7 +110,7 @@ public class ReactiveForwarding { ...@@ -86,7 +110,7 @@ public class ReactiveForwarding {
86 public void process(PacketContext context) { 110 public void process(PacketContext context) {
87 // Stop processing if the packet has been handled, since we 111 // Stop processing if the packet has been handled, since we
88 // can't do any more to it. 112 // can't do any more to it.
89 - if (context.isHandled()) { 113 + if (!isEnabled || context.isHandled()) {
90 return; 114 return;
91 } 115 }
92 116
...@@ -114,8 +138,8 @@ public class ReactiveForwarding { ...@@ -114,8 +138,8 @@ public class ReactiveForwarding {
114 // Otherwise, get a set of paths that lead from here to the 138 // Otherwise, get a set of paths that lead from here to the
115 // destination edge switch. 139 // destination edge switch.
116 Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(), 140 Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(),
117 - pkt.receivedFrom().deviceId(), 141 + pkt.receivedFrom().deviceId(),
118 - dst.location().deviceId()); 142 + dst.location().deviceId());
119 if (paths.isEmpty()) { 143 if (paths.isEmpty()) {
120 // If there are no paths, flood and bail. 144 // If there are no paths, flood and bail.
121 flood(context); 145 flood(context);
...@@ -127,8 +151,8 @@ public class ReactiveForwarding { ...@@ -127,8 +151,8 @@ public class ReactiveForwarding {
127 Path path = pickForwardPath(paths, pkt.receivedFrom().port()); 151 Path path = pickForwardPath(paths, pkt.receivedFrom().port());
128 if (path == null) { 152 if (path == null) {
129 log.warn("Doh... don't know where to go... {} -> {} received on {}", 153 log.warn("Doh... don't know where to go... {} -> {} received on {}",
130 - ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(), 154 + ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
131 - pkt.receivedFrom()); 155 + pkt.receivedFrom());
132 flood(context); 156 flood(context);
133 return; 157 return;
134 } 158 }
...@@ -152,7 +176,7 @@ public class ReactiveForwarding { ...@@ -152,7 +176,7 @@ public class ReactiveForwarding {
152 // Floods the specified packet if permissible. 176 // Floods the specified packet if permissible.
153 private void flood(PacketContext context) { 177 private void flood(PacketContext context) {
154 if (topologyService.isBroadcastPoint(topologyService.currentTopology(), 178 if (topologyService.isBroadcastPoint(topologyService.currentTopology(),
155 - context.inPacket().receivedFrom())) { 179 + context.inPacket().receivedFrom())) {
156 packetOut(context, PortNumber.FLOOD); 180 packetOut(context, PortNumber.FLOOD);
157 } else { 181 } else {
158 context.block(); 182 context.block();
...@@ -174,18 +198,17 @@ public class ReactiveForwarding { ...@@ -174,18 +198,17 @@ public class ReactiveForwarding {
174 Ethernet inPkt = context.inPacket().parsed(); 198 Ethernet inPkt = context.inPacket().parsed();
175 TrafficSelector.Builder builder = DefaultTrafficSelector.builder(); 199 TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
176 builder.matchEthType(inPkt.getEtherType()) 200 builder.matchEthType(inPkt.getEtherType())
177 - .matchEthSrc(inPkt.getSourceMAC()) 201 + .matchEthSrc(inPkt.getSourceMAC())
178 - .matchEthDst(inPkt.getDestinationMAC()) 202 + .matchEthDst(inPkt.getDestinationMAC())
179 - .matchInport(context.inPacket().receivedFrom().port()); 203 + .matchInport(context.inPacket().receivedFrom().port());
180 204
181 TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder(); 205 TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder();
182 treat.setOutput(portNumber); 206 treat.setOutput(portNumber);
183 207
184 FlowRule f = new DefaultFlowRule(context.inPacket().receivedFrom().deviceId(), 208 FlowRule f = new DefaultFlowRule(context.inPacket().receivedFrom().deviceId(),
185 - builder.build(), treat.build(), PRIORITY, appId, TIMEOUT); 209 + builder.build(), treat.build(), PRIORITY, appId, TIMEOUT);
186 210
187 flowRuleService.applyFlowRules(f); 211 flowRuleService.applyFlowRules(f);
188 -
189 } 212 }
190 213
191 } 214 }
......
...@@ -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>
......
...@@ -126,8 +126,8 @@ public class PeerConnectivity { ...@@ -126,8 +126,8 @@ public class PeerConnectivity {
126 TrafficSelector selector = DefaultTrafficSelector.builder() 126 TrafficSelector selector = DefaultTrafficSelector.builder()
127 .matchEthType(Ethernet.TYPE_IPV4) 127 .matchEthType(Ethernet.TYPE_IPV4)
128 .matchIPProtocol(IPv4.PROTOCOL_TCP) 128 .matchIPProtocol(IPv4.PROTOCOL_TCP)
129 - .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH)) 129 + .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
130 - .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH)) 130 + .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
131 .matchTcpDst(BGP_PORT) 131 .matchTcpDst(BGP_PORT)
132 .build(); 132 .build();
133 133
...@@ -147,8 +147,8 @@ public class PeerConnectivity { ...@@ -147,8 +147,8 @@ public class PeerConnectivity {
147 selector = DefaultTrafficSelector.builder() 147 selector = DefaultTrafficSelector.builder()
148 .matchEthType(Ethernet.TYPE_IPV4) 148 .matchEthType(Ethernet.TYPE_IPV4)
149 .matchIPProtocol(IPv4.PROTOCOL_TCP) 149 .matchIPProtocol(IPv4.PROTOCOL_TCP)
150 - .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH)) 150 + .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
151 - .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH)) 151 + .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
152 .matchTcpSrc(BGP_PORT) 152 .matchTcpSrc(BGP_PORT)
153 .build(); 153 .build();
154 154
...@@ -165,8 +165,8 @@ public class PeerConnectivity { ...@@ -165,8 +165,8 @@ public class PeerConnectivity {
165 selector = DefaultTrafficSelector.builder() 165 selector = DefaultTrafficSelector.builder()
166 .matchEthType(Ethernet.TYPE_IPV4) 166 .matchEthType(Ethernet.TYPE_IPV4)
167 .matchIPProtocol(IPv4.PROTOCOL_TCP) 167 .matchIPProtocol(IPv4.PROTOCOL_TCP)
168 - .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH)) 168 + .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
169 - .matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH)) 169 + .matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
170 .matchTcpDst(BGP_PORT) 170 .matchTcpDst(BGP_PORT)
171 .build(); 171 .build();
172 172
...@@ -183,8 +183,8 @@ public class PeerConnectivity { ...@@ -183,8 +183,8 @@ public class PeerConnectivity {
183 selector = DefaultTrafficSelector.builder() 183 selector = DefaultTrafficSelector.builder()
184 .matchEthType(Ethernet.TYPE_IPV4) 184 .matchEthType(Ethernet.TYPE_IPV4)
185 .matchIPProtocol(IPv4.PROTOCOL_TCP) 185 .matchIPProtocol(IPv4.PROTOCOL_TCP)
186 - .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH)) 186 + .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
187 - .matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH)) 187 + .matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
188 .matchTcpSrc(BGP_PORT) 188 .matchTcpSrc(BGP_PORT)
189 .build(); 189 .build();
190 190
...@@ -251,8 +251,8 @@ public class PeerConnectivity { ...@@ -251,8 +251,8 @@ public class PeerConnectivity {
251 TrafficSelector selector = DefaultTrafficSelector.builder() 251 TrafficSelector selector = DefaultTrafficSelector.builder()
252 .matchEthType(Ethernet.TYPE_IPV4) 252 .matchEthType(Ethernet.TYPE_IPV4)
253 .matchIPProtocol(IPv4.PROTOCOL_ICMP) 253 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
254 - .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH)) 254 + .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
255 - .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH)) 255 + .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
256 .build(); 256 .build();
257 257
258 TrafficTreatment treatment = DefaultTrafficTreatment.builder() 258 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
...@@ -269,8 +269,8 @@ public class PeerConnectivity { ...@@ -269,8 +269,8 @@ public class PeerConnectivity {
269 selector = DefaultTrafficSelector.builder() 269 selector = DefaultTrafficSelector.builder()
270 .matchEthType(Ethernet.TYPE_IPV4) 270 .matchEthType(Ethernet.TYPE_IPV4)
271 .matchIPProtocol(IPv4.PROTOCOL_ICMP) 271 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
272 - .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH)) 272 + .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
273 - .matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH)) 273 + .matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
274 .build(); 274 .build();
275 275
276 PointToPointIntent reversedIntent = new PointToPointIntent( 276 PointToPointIntent reversedIntent = new PointToPointIntent(
......
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.toInt();
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 daemon 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 + this.hostService.addListener(new InternalHostListener());
131 + }
132 +
133 + /**
134 + * Starts the Router.
135 + */
136 + public void start() {
137 +
138 + bgpUpdatesExecutor.execute(new Runnable() {
139 + @Override
140 + public void run() {
141 + doUpdatesThread();
142 + }
143 + });
144 +
145 + bgpIntentsSynchronizerExecutor.execute(new Runnable() {
146 + @Override
147 + public void run() {
148 + doIntentSynchronizationThread();
149 + }
150 + });
151 + }
152 +
153 + //@Override TODO hook this up to something
154 + public void leaderChanged(boolean isLeader) {
155 + log.debug("Leader changed: {}", isLeader);
156 +
157 + if (!isLeader) {
158 + this.isElectedLeader = false;
159 + this.isActivatedLeader = false;
160 + return; // Nothing to do
161 + }
162 + this.isActivatedLeader = false;
163 + this.isElectedLeader = true;
164 +
165 + //
166 + // Tell the Intents Synchronizer thread to start the synchronization
167 + //
168 + intentsSynchronizerSemaphore.release();
169 + }
170 +
171 + @Override
172 + public void update(RouteUpdate routeUpdate) {
173 + log.debug("Received new route Update: {}", routeUpdate);
174 +
175 + try {
176 + routeUpdates.put(routeUpdate);
177 + } catch (InterruptedException e) {
178 + log.debug("Interrupted while putting on routeUpdates queue", e);
179 + Thread.currentThread().interrupt();
180 + }
181 + }
182 +
183 + /**
184 + * Thread for Intent Synchronization.
185 + */
186 + private void doIntentSynchronizationThread() {
187 + boolean interrupted = false;
188 + try {
189 + while (!interrupted) {
190 + try {
191 + intentsSynchronizerSemaphore.acquire();
192 + //
193 + // Drain all permits, because a single synchronization is
194 + // sufficient.
195 + //
196 + intentsSynchronizerSemaphore.drainPermits();
197 + } catch (InterruptedException e) {
198 + log.debug("Interrupted while waiting to become " +
199 + "Intent Synchronization leader");
200 + interrupted = true;
201 + break;
202 + }
203 + syncIntents();
204 + }
205 + } finally {
206 + if (interrupted) {
207 + Thread.currentThread().interrupt();
208 + }
209 + }
210 + }
211 +
212 + /**
213 + * Thread for handling route updates.
214 + */
215 + private void doUpdatesThread() {
216 + boolean interrupted = false;
217 + try {
218 + while (!interrupted) {
219 + try {
220 + RouteUpdate update = routeUpdates.take();
221 + switch (update.type()) {
222 + case UPDATE:
223 + processRouteAdd(update.routeEntry());
224 + break;
225 + case DELETE:
226 + processRouteDelete(update.routeEntry());
227 + break;
228 + default:
229 + log.error("Unknown update Type: {}", update.type());
230 + break;
231 + }
232 + } catch (InterruptedException e) {
233 + log.debug("Interrupted while taking from updates queue", e);
234 + interrupted = true;
235 + } catch (Exception e) {
236 + log.debug("exception", e);
237 + }
238 + }
239 + } finally {
240 + if (interrupted) {
241 + Thread.currentThread().interrupt();
242 + }
243 + }
244 + }
245 +
246 + /**
247 + * Performs Intents Synchronization between the internally stored Route
248 + * Intents and the installed Route Intents.
249 + */
250 + private void syncIntents() {
251 + synchronized (this) {
252 + if (!isElectedLeader) {
253 + return; // Nothing to do: not the leader anymore
254 + }
255 + log.debug("Syncing SDN-IP Route Intents...");
256 +
257 + Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
258 + new HashMap<>();
259 +
260 + //
261 + // Fetch all intents, and classify the Multi-Point-to-Point Intents
262 + // based on the matching prefix.
263 + //
264 + for (Intent intent : intentService.getIntents()) {
265 + //
266 + // TODO: Ignore all intents that are not installed by
267 + // the SDN-IP application.
268 + //
269 + if (!(intent instanceof MultiPointToSinglePointIntent)) {
270 + continue;
271 + }
272 + MultiPointToSinglePointIntent mp2pIntent =
273 + (MultiPointToSinglePointIntent) intent;
274 + /*Match match = mp2pIntent.getMatch();
275 + if (!(match instanceof PacketMatch)) {
276 + continue;
277 + }
278 + PacketMatch packetMatch = (PacketMatch) match;
279 + Ip4Prefix prefix = packetMatch.getDstIpAddress();
280 + if (prefix == null) {
281 + continue;
282 + }
283 + fetchedIntents.put(prefix, mp2pIntent);*/
284 + for (Criterion criterion : mp2pIntent.selector().criteria()) {
285 + if (criterion.type() == Type.IPV4_DST) {
286 + IPCriterion ipCriterion = (IPCriterion) criterion;
287 + fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
288 + }
289 + }
290 +
291 + }
292 +
293 + //
294 + // Compare for each prefix the local IN-MEMORY Intents with the
295 + // FETCHED Intents:
296 + // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
297 + // the FETCHED Intent in the local memory (i.e., override the
298 + // IN-MEMORY Intent) to preserve the original Intent ID
299 + // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
300 + // delete the FETCHED Intent, and push/install the IN-MEMORY
301 + // Intent.
302 + // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
303 + // Intent for same prefix, then push/install the IN-MEMORY
304 + // Intent.
305 + // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
306 + // Intent for same prefix, then delete/withdraw the FETCHED
307 + // Intent.
308 + //
309 + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
310 + storeInMemoryIntents = new LinkedList<>();
311 + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
312 + addIntents = new LinkedList<>();
313 + Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
314 + deleteIntents = new LinkedList<>();
315 + for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
316 + pushedRouteIntents.entrySet()) {
317 + IpPrefix prefix = entry.getKey();
318 + MultiPointToSinglePointIntent inMemoryIntent =
319 + entry.getValue();
320 + MultiPointToSinglePointIntent fetchedIntent =
321 + fetchedIntents.get(prefix);
322 +
323 + if (fetchedIntent == null) {
324 + //
325 + // No FETCHED Intent for same prefix: push the IN-MEMORY
326 + // Intent.
327 + //
328 + addIntents.add(Pair.of(prefix, inMemoryIntent));
329 + continue;
330 + }
331 +
332 + //
333 + // If IN-MEMORY Intent is same as the FETCHED Intent,
334 + // store the FETCHED Intent in the local memory.
335 + //
336 + if (compareMultiPointToSinglePointIntents(inMemoryIntent,
337 + fetchedIntent)) {
338 + storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
339 + } else {
340 + //
341 + // The IN-MEMORY Intent is not same as the FETCHED Intent,
342 + // hence delete the FETCHED Intent, and install the
343 + // IN-MEMORY Intent.
344 + //
345 + deleteIntents.add(Pair.of(prefix, fetchedIntent));
346 + addIntents.add(Pair.of(prefix, inMemoryIntent));
347 + }
348 + fetchedIntents.remove(prefix);
349 + }
350 +
351 + //
352 + // Any remaining FETCHED Intents have to be deleted/withdrawn
353 + //
354 + for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
355 + fetchedIntents.entrySet()) {
356 + IpPrefix prefix = entry.getKey();
357 + MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
358 + deleteIntents.add(Pair.of(prefix, fetchedIntent));
359 + }
360 +
361 + //
362 + // Perform the actions:
363 + // 1. Store in memory fetched intents that are same. Can be done
364 + // even if we are not the leader anymore
365 + // 2. Delete intents: check if the leader before each operation
366 + // 3. Add intents: check if the leader before each operation
367 + //
368 + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
369 + storeInMemoryIntents) {
370 + IpPrefix prefix = pair.getLeft();
371 + MultiPointToSinglePointIntent intent = pair.getRight();
372 + log.debug("Intent synchronization: updating in-memory " +
373 + "Intent for prefix: {}", prefix);
374 + pushedRouteIntents.put(prefix, intent);
375 + }
376 + //
377 + isActivatedLeader = true; // Allow push of Intents
378 + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
379 + deleteIntents) {
380 + IpPrefix prefix = pair.getLeft();
381 + MultiPointToSinglePointIntent intent = pair.getRight();
382 + if (!isElectedLeader) {
383 + isActivatedLeader = false;
384 + return;
385 + }
386 + log.debug("Intent synchronization: deleting Intent for " +
387 + "prefix: {}", prefix);
388 + intentService.withdraw(intent);
389 + }
390 + //
391 + for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
392 + addIntents) {
393 + IpPrefix prefix = pair.getLeft();
394 + MultiPointToSinglePointIntent intent = pair.getRight();
395 + if (!isElectedLeader) {
396 + isActivatedLeader = false;
397 + return;
398 + }
399 + log.debug("Intent synchronization: adding Intent for " +
400 + "prefix: {}", prefix);
401 + intentService.submit(intent);
402 + }
403 + if (!isElectedLeader) {
404 + isActivatedLeader = false;
405 + }
406 + log.debug("Syncing SDN-IP routes completed.");
407 + }
408 + }
409 +
410 + /**
411 + * Compares two Multi-point to Single Point Intents whether they represent
412 + * same logical intention.
413 + *
414 + * @param intent1 the first Intent to compare
415 + * @param intent2 the second Intent to compare
416 + * @return true if both Intents represent same logical intention, otherwise
417 + * false
418 + */
419 + private boolean compareMultiPointToSinglePointIntents(
420 + MultiPointToSinglePointIntent intent1,
421 + MultiPointToSinglePointIntent intent2) {
422 + /*Match match1 = intent1.getMatch();
423 + Match match2 = intent2.getMatch();
424 + Action action1 = intent1.getAction();
425 + Action action2 = intent2.getAction();
426 + Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts();
427 + Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts();
428 + SwitchPort egressPort1 = intent1.getEgressPort();
429 + SwitchPort egressPort2 = intent2.getEgressPort();
430 +
431 + return Objects.equal(match1, match2) &&
432 + Objects.equal(action1, action2) &&
433 + Objects.equal(egressPort1, egressPort2) &&
434 + Objects.equal(ingressPorts1, ingressPorts2);*/
435 + return Objects.equal(intent1.selector(), intent2.selector()) &&
436 + Objects.equal(intent1.treatment(), intent2.treatment()) &&
437 + Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
438 + Objects.equal(intent1.egressPoint(), intent2.egressPoint());
439 + }
440 +
441 + /**
442 + * Processes adding a route entry.
443 + * <p/>
444 + * Put new route entry into InvertedRadixTree. If there was an existing
445 + * nexthop for this prefix, but the next hop was different, then execute
446 + * deleting old route entry. If the next hop is the SDN domain, we do not
447 + * handle it at the moment. Otherwise, execute adding a route.
448 + *
449 + * @param routeEntry the route entry to add
450 + */
451 + protected void processRouteAdd(RouteEntry routeEntry) {
452 + synchronized (this) {
453 + log.debug("Processing route add: {}", routeEntry);
454 +
455 + IpPrefix prefix = routeEntry.prefix();
456 + IpAddress nextHop = null;
457 + RouteEntry foundRouteEntry =
458 + bgpRoutes.put(RouteEntry.createBinaryString(prefix),
459 + routeEntry);
460 + if (foundRouteEntry != null) {
461 + nextHop = foundRouteEntry.nextHop();
462 + }
463 +
464 + if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
465 + // There was an existing nexthop for this prefix. This update
466 + // supersedes that, so we need to remove the old flows for this
467 + // prefix from the switches
468 + executeRouteDelete(routeEntry);
469 + }
470 + if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
471 + return;
472 + }
473 +
474 + if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
475 + // Route originated by SDN domain
476 + // We don't handle these at the moment
477 + log.debug("Own route {} to {}",
478 + routeEntry.prefix(), routeEntry.nextHop());
479 + return;
480 + }
481 +
482 + executeRouteAdd(routeEntry);
483 + }
484 + }
485 +
486 + /**
487 + * Executes adding a route entry.
488 + * <p/>
489 + * Find out the egress Interface and MAC address of next hop router for
490 + * this route entry. If the MAC address can not be found in ARP cache,
491 + * then this prefix will be put in routesWaitingOnArp queue. Otherwise,
492 + * new route intent will be created and installed.
493 + *
494 + * @param routeEntry the route entry to add
495 + */
496 + private void executeRouteAdd(RouteEntry routeEntry) {
497 + log.debug("Executing route add: {}", routeEntry);
498 +
499 + // See if we know the MAC address of the next hop
500 + //MacAddress nextHopMacAddress =
501 + //proxyArp.getMacAddress(routeEntry.getNextHop());
502 + MacAddress nextHopMacAddress = null;
503 + Set<Host> hosts = hostService.getHostsByIp(
504 + routeEntry.nextHop().toPrefix());
505 + if (!hosts.isEmpty()) {
506 + // TODO how to handle if multiple hosts are returned?
507 + nextHopMacAddress = hosts.iterator().next().mac();
508 + }
509 +
510 + if (nextHopMacAddress == null) {
511 + routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
512 + //proxyArp.sendArpRequest(routeEntry.getNextHop(), this, true);
513 + // TODO maybe just do this for every prefix anyway
514 + hostService.startMonitoringIp(routeEntry.nextHop());
515 + return;
516 + }
517 +
518 + addRouteIntentToNextHop(routeEntry.prefix(),
519 + routeEntry.nextHop(),
520 + nextHopMacAddress);
521 + }
522 +
523 + /**
524 + * Adds a route intent given a prefix and a next hop IP address. This
525 + * method will find the egress interface for the intent.
526 + *
527 + * @param prefix IP prefix of the route to add
528 + * @param nextHopIpAddress IP address of the next hop
529 + * @param nextHopMacAddress MAC address of the next hop
530 + */
531 + private void addRouteIntentToNextHop(IpPrefix prefix,
532 + IpAddress nextHopIpAddress,
533 + MacAddress nextHopMacAddress) {
534 +
535 + // Find the attachment point (egress interface) of the next hop
536 + Interface egressInterface;
537 + if (configInfoService.getBgpPeers().containsKey(nextHopIpAddress)) {
538 + // Route to a peer
539 + log.debug("Route to peer {}", nextHopIpAddress);
540 + BgpPeer peer =
541 + configInfoService.getBgpPeers().get(nextHopIpAddress);
542 + egressInterface =
543 + interfaceService.getInterface(peer.connectPoint());
544 + } else {
545 + // Route to non-peer
546 + log.debug("Route to non-peer {}", nextHopIpAddress);
547 + egressInterface =
548 + interfaceService.getMatchingInterface(nextHopIpAddress);
549 + if (egressInterface == null) {
550 + log.warn("No outgoing interface found for {}",
551 + nextHopIpAddress);
552 + return;
553 + }
554 + }
555 +
556 + doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
557 + }
558 +
559 + /**
560 + * Installs a route intent for a prefix.
561 + * <p/>
562 + * Intent will match dst IP prefix and rewrite dst MAC address at all other
563 + * border switches, then forward packets according to dst MAC address.
564 + *
565 + * @param prefix IP prefix from route
566 + * @param egressInterface egress Interface connected to next hop router
567 + * @param nextHopMacAddress MAC address of next hop router
568 + */
569 + private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
570 + MacAddress nextHopMacAddress) {
571 + log.debug("Adding intent for prefix {}, next hop mac {}",
572 + prefix, nextHopMacAddress);
573 +
574 + MultiPointToSinglePointIntent pushedIntent =
575 + pushedRouteIntents.get(prefix);
576 +
577 + // Just for testing.
578 + if (pushedIntent != null) {
579 + log.error("There should not be a pushed intent: {}", pushedIntent);
580 + }
581 +
582 + ConnectPoint egressPort = egressInterface.connectPoint();
583 +
584 + Set<ConnectPoint> ingressPorts = new HashSet<>();
585 +
586 + for (Interface intf : interfaceService.getInterfaces()) {
587 + if (!intf.equals(egressInterface)) {
588 + ConnectPoint srcPort = intf.connectPoint();
589 + ingressPorts.add(srcPort);
590 + }
591 + }
592 +
593 + // Match the destination IP prefix at the first hop
594 + //PacketMatchBuilder builder = new PacketMatchBuilder();
595 + //builder.setEtherType(Ethernet.TYPE_IPV4).setDstIpNet(prefix);
596 + //PacketMatch packetMatch = builder.build();
597 + TrafficSelector selector = DefaultTrafficSelector.builder()
598 + .matchEthType(Ethernet.TYPE_IPV4)
599 + .matchIPDst(prefix)
600 + .build();
601 +
602 + // Rewrite the destination MAC address
603 + //ModifyDstMacAction modifyDstMacAction =
604 + //new ModifyDstMacAction(nextHopMacAddress);
605 + TrafficTreatment treatment = DefaultTrafficTreatment.builder()
606 + .setEthDst(nextHopMacAddress)
607 + .build();
608 +
609 + MultiPointToSinglePointIntent intent =
610 + new MultiPointToSinglePointIntent(nextIntentId(),
611 + selector, treatment, ingressPorts, egressPort);
612 +
613 + if (isElectedLeader && isActivatedLeader) {
614 + log.debug("Intent installation: adding Intent for prefix: {}",
615 + prefix);
616 + intentService.submit(intent);
617 + }
618 +
619 + // Maintain the Intent
620 + pushedRouteIntents.put(prefix, intent);
621 + }
622 +
623 + /**
624 + * Executes deleting a route entry.
625 + * <p/>
626 + * Removes prefix from InvertedRadixTree, if success, then try to delete
627 + * the relative intent.
628 + *
629 + * @param routeEntry the route entry to delete
630 + */
631 + protected void processRouteDelete(RouteEntry routeEntry) {
632 + synchronized (this) {
633 + log.debug("Processing route delete: {}", routeEntry);
634 + IpPrefix prefix = routeEntry.prefix();
635 +
636 + // TODO check the change of logic here - remove doesn't check that
637 + // the route entry was what we expected (and we can't do this
638 + // concurrently)
639 +
640 + if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
641 + //
642 + // Only delete flows if an entry was actually removed from the
643 + // tree. If no entry was removed, the <prefix, nexthop> wasn't
644 + // there so it's probably already been removed and we don't
645 + // need to do anything.
646 + //
647 + executeRouteDelete(routeEntry);
648 + }
649 +
650 + routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
651 + // TODO cancel the request in the ARP manager as well
652 + }
653 + }
654 +
655 + /**
656 + * Executed deleting a route entry.
657 + *
658 + * @param routeEntry the route entry to delete
659 + */
660 + private void executeRouteDelete(RouteEntry routeEntry) {
661 + log.debug("Executing route delete: {}", routeEntry);
662 +
663 + IpPrefix prefix = routeEntry.prefix();
664 +
665 + MultiPointToSinglePointIntent intent =
666 + pushedRouteIntents.remove(prefix);
667 +
668 + if (intent == null) {
669 + log.debug("There is no intent in pushedRouteIntents to delete " +
670 + "for prefix: {}", prefix);
671 + } else {
672 + if (isElectedLeader && isActivatedLeader) {
673 + log.debug("Intent installation: deleting Intent for prefix: {}",
674 + prefix);
675 + intentService.withdraw(intent);
676 + }
677 + }
678 + }
679 +
680 + /**
681 + * This method handles the prefixes which are waiting for ARP replies for
682 + * MAC addresses of next hops.
683 + *
684 + * @param ipAddress next hop router IP address, for which we sent ARP
685 + * request out
686 + * @param macAddress MAC address which is relative to the ipAddress
687 + */
688 + //@Override
689 + // TODO change name
690 + public void arpResponse(IpAddress ipAddress, MacAddress macAddress) {
691 + log.debug("Received ARP response: {} => {}", ipAddress, macAddress);
692 +
693 + // We synchronize on this to prevent changes to the InvertedRadixTree
694 + // while we're pushing intent. If the InvertedRadixTree changes, the
695 + // InvertedRadixTree and intent could get out of sync.
696 + synchronized (this) {
697 +
698 + Set<RouteEntry> routesToPush =
699 + routesWaitingOnArp.removeAll(ipAddress);
700 +
701 + for (RouteEntry routeEntry : routesToPush) {
702 + // These will always be adds
703 + IpPrefix prefix = routeEntry.prefix();
704 + String binaryString = RouteEntry.createBinaryString(prefix);
705 + RouteEntry foundRouteEntry =
706 + bgpRoutes.getValueForExactKey(binaryString);
707 + if (foundRouteEntry != null &&
708 + foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
709 + log.debug("Pushing prefix {} next hop {}",
710 + routeEntry.prefix(), routeEntry.nextHop());
711 + // We only push prefix flows if the prefix is still in the
712 + // InvertedRadixTree and the next hop is the same as our
713 + // update.
714 + // The prefix could have been removed while we were waiting
715 + // for the ARP, or the next hop could have changed.
716 + addRouteIntentToNextHop(prefix, ipAddress, macAddress);
717 + } else {
718 + log.debug("Received ARP response, but {}/{} is no longer in"
719 + + " InvertedRadixTree", routeEntry.prefix(),
720 + routeEntry.nextHop());
721 + }
722 + }
723 + }
724 + }
725 +
726 + /**
727 + * Gets the SDN-IP routes.
728 + *
729 + * @return the SDN-IP routes
730 + */
731 + public Collection<RouteEntry> getRoutes() {
732 + Iterator<KeyValuePair<RouteEntry>> it =
733 + bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
734 +
735 + List<RouteEntry> routes = new LinkedList<>();
736 +
737 + while (it.hasNext()) {
738 + KeyValuePair<RouteEntry> entry = it.next();
739 + routes.add(entry.getValue());
740 + }
741 +
742 + return routes;
743 + }
744 +
745 + /**
746 + * Generates a new unique intent ID.
747 + *
748 + * @return the new intent ID.
749 + */
750 + private IntentId nextIntentId() {
751 + return new IntentId(intentId++);
752 + }
753 +
754 + /**
755 + * Listener for host events.
756 + */
757 + class InternalHostListener implements HostListener {
758 + @Override
759 + public void event(HostEvent event) {
760 + if (event.type() == HostEvent.Type.HOST_ADDED ||
761 + event.type() == HostEvent.Type.HOST_UPDATED) {
762 + Host host = event.subject();
763 + for (IpPrefix ip : host.ipAddresses()) {
764 + arpResponse(ip.toIpAddress(), host.mac());
765 + }
766 + }
767 + }
768 + }
769 +}
...@@ -9,7 +9,11 @@ import org.apache.felix.scr.annotations.Reference; ...@@ -9,7 +9,11 @@ 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;
13 +import org.onlab.onos.sdnip.bgp.BgpSessionManager;
12 import org.onlab.onos.sdnip.config.SdnIpConfigReader; 14 import org.onlab.onos.sdnip.config.SdnIpConfigReader;
15 +import org.onlab.packet.IpAddress;
16 +import org.onlab.packet.IpPrefix;
13 import org.slf4j.Logger; 17 import org.slf4j.Logger;
14 18
15 /** 19 /**
...@@ -28,6 +32,8 @@ public class SdnIp { ...@@ -28,6 +32,8 @@ public class SdnIp {
28 32
29 private SdnIpConfigReader config; 33 private SdnIpConfigReader config;
30 private PeerConnectivity peerConnectivity; 34 private PeerConnectivity peerConnectivity;
35 + private Router router;
36 + private BgpSessionManager bgpSessionManager;
31 37
32 @Activate 38 @Activate
33 protected void activate() { 39 protected void activate() {
...@@ -41,6 +47,17 @@ public class SdnIp { ...@@ -41,6 +47,17 @@ public class SdnIp {
41 peerConnectivity = new PeerConnectivity(config, interfaceService, intentService); 47 peerConnectivity = new PeerConnectivity(config, interfaceService, intentService);
42 peerConnectivity.start(); 48 peerConnectivity.start();
43 49
50 + router = new Router(intentService, hostService, config, interfaceService);
51 + router.start();
52 +
53 + bgpSessionManager = new BgpSessionManager(router);
54 + bgpSessionManager.startUp(2000); // TODO
55 +
56 + // TODO need to disable link discovery on external ports
57 +
58 + router.update(new RouteUpdate(Type.UPDATE, new RouteEntry(
59 + IpPrefix.valueOf("172.16.20.0/24"),
60 + IpAddress.valueOf("192.168.10.1"))));
44 } 61 }
45 62
46 @Deactivate 63 @Deactivate
......
1 +package org.onlab.onos.sdnip.bgp;
2 +
3 +/**
4 + * BGP related constants.
5 + */
6 +public final class BgpConstants {
7 + /**
8 + * Default constructor.
9 + * <p>
10 + * The constructor is private to prevent creating an instance of
11 + * this utility class.
12 + */
13 + private BgpConstants() {
14 + }
15 +
16 + /** BGP port number (RFC 4271). */
17 + public static final int BGP_PORT = 179;
18 +
19 + /** BGP version. */
20 + public static final int BGP_VERSION = 4;
21 +
22 + /** BGP OPEN message type. */
23 + public static final int BGP_TYPE_OPEN = 1;
24 +
25 + /** BGP UPDATE message type. */
26 + public static final int BGP_TYPE_UPDATE = 2;
27 +
28 + /** BGP NOTIFICATION message type. */
29 + public static final int BGP_TYPE_NOTIFICATION = 3;
30 +
31 + /** BGP KEEPALIVE message type. */
32 + public static final int BGP_TYPE_KEEPALIVE = 4;
33 +
34 + /** BGP Header Marker field length. */
35 + public static final int BGP_HEADER_MARKER_LENGTH = 16;
36 +
37 + /** BGP Header length. */
38 + public static final int BGP_HEADER_LENGTH = 19;
39 +
40 + /** BGP message maximum length. */
41 + public static final int BGP_MESSAGE_MAX_LENGTH = 4096;
42 +
43 + /** BGP OPEN message minimum length (BGP Header included). */
44 + public static final int BGP_OPEN_MIN_LENGTH = 29;
45 +
46 + /** BGP UPDATE message minimum length (BGP Header included). */
47 + public static final int BGP_UPDATE_MIN_LENGTH = 23;
48 +
49 + /** BGP NOTIFICATION message minimum length (BGP Header included). */
50 + public static final int BGP_NOTIFICATION_MIN_LENGTH = 21;
51 +
52 + /** BGP KEEPALIVE message expected length (BGP Header included). */
53 + public static final int BGP_KEEPALIVE_EXPECTED_LENGTH = 19;
54 +
55 + /** BGP KEEPALIVE messages transmitted per Hold interval. */
56 + public static final int BGP_KEEPALIVE_PER_HOLD_INTERVAL = 3;
57 +
58 + /** BGP KEEPALIVE messages minimum Holdtime (in seconds). */
59 + public static final int BGP_KEEPALIVE_MIN_HOLDTIME = 3;
60 +
61 + /** BGP KEEPALIVE messages minimum transmission interval (in seconds). */
62 + public static final int BGP_KEEPALIVE_MIN_INTERVAL = 1;
63 +
64 + /** BGP AS 0 (zero) value. See draft-ietf-idr-as0-06.txt Internet Draft. */
65 + public static final long BGP_AS_0 = 0;
66 +
67 + /**
68 + * BGP UPDATE related constants.
69 + */
70 + public static final class Update {
71 + /**
72 + * Default constructor.
73 + * <p>
74 + * The constructor is private to prevent creating an instance of
75 + * this utility class.
76 + */
77 + private Update() {
78 + }
79 +
80 + /**
81 + * BGP UPDATE: ORIGIN related constants.
82 + */
83 + public static final class Origin {
84 + /**
85 + * Default constructor.
86 + * <p>
87 + * The constructor is private to prevent creating an instance of
88 + * this utility class.
89 + */
90 + private Origin() {
91 + }
92 +
93 + /** BGP UPDATE Attributes Type Code ORIGIN. */
94 + public static final int TYPE = 1;
95 +
96 + /** BGP UPDATE Attributes Type Code ORIGIN length. */
97 + public static final int LENGTH = 1;
98 +
99 + /** BGP UPDATE ORIGIN: IGP. */
100 + public static final int IGP = 0;
101 +
102 + /** BGP UPDATE ORIGIN: EGP. */
103 + public static final int EGP = 1;
104 +
105 + /** BGP UPDATE ORIGIN: INCOMPLETE. */
106 + public static final int INCOMPLETE = 2;
107 + }
108 +
109 + /**
110 + * BGP UPDATE: AS_PATH related constants.
111 + */
112 + public static final class AsPath {
113 + /**
114 + * Default constructor.
115 + * <p>
116 + * The constructor is private to prevent creating an instance of
117 + * this utility class.
118 + */
119 + private AsPath() {
120 + }
121 +
122 + /** BGP UPDATE Attributes Type Code AS_PATH. */
123 + public static final int TYPE = 2;
124 +
125 + /** BGP UPDATE AS_PATH Type: AS_SET. */
126 + public static final int AS_SET = 1;
127 +
128 + /** BGP UPDATE AS_PATH Type: AS_SEQUENCE. */
129 + public static final int AS_SEQUENCE = 2;
130 + }
131 +
132 + /**
133 + * BGP UPDATE: NEXT_HOP related constants.
134 + */
135 + public static final class NextHop {
136 + /**
137 + * Default constructor.
138 + * <p>
139 + * The constructor is private to prevent creating an instance of
140 + * this utility class.
141 + */
142 + private NextHop() {
143 + }
144 +
145 + /** BGP UPDATE Attributes Type Code NEXT_HOP. */
146 + public static final int TYPE = 3;
147 +
148 + /** BGP UPDATE Attributes Type Code NEXT_HOP length. */
149 + public static final int LENGTH = 4;
150 + }
151 +
152 + /**
153 + * BGP UPDATE: MULTI_EXIT_DISC related constants.
154 + */
155 + public static final class MultiExitDisc {
156 + /**
157 + * Default constructor.
158 + * <p>
159 + * The constructor is private to prevent creating an instance of
160 + * this utility class.
161 + */
162 + private MultiExitDisc() {
163 + }
164 +
165 + /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC. */
166 + public static final int TYPE = 4;
167 +
168 + /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC length. */
169 + public static final int LENGTH = 4;
170 +
171 + /** BGP UPDATE Attributes lowest MULTI_EXIT_DISC value. */
172 + public static final int LOWEST_MULTI_EXIT_DISC = 0;
173 + }
174 +
175 + /**
176 + * BGP UPDATE: LOCAL_PREF related constants.
177 + */
178 + public static final class LocalPref {
179 + /**
180 + * Default constructor.
181 + * <p>
182 + * The constructor is private to prevent creating an instance of
183 + * this utility class.
184 + */
185 + private LocalPref() {
186 + }
187 +
188 + /** BGP UPDATE Attributes Type Code LOCAL_PREF. */
189 + public static final int TYPE = 5;
190 +
191 + /** BGP UPDATE Attributes Type Code LOCAL_PREF length. */
192 + public static final int LENGTH = 4;
193 + }
194 +
195 + /**
196 + * BGP UPDATE: ATOMIC_AGGREGATE related constants.
197 + */
198 + public static final class AtomicAggregate {
199 + /**
200 + * Default constructor.
201 + * <p>
202 + * The constructor is private to prevent creating an instance of
203 + * this utility class.
204 + */
205 + private AtomicAggregate() {
206 + }
207 +
208 + /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE. */
209 + public static final int TYPE = 6;
210 +
211 + /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE length. */
212 + public static final int LENGTH = 0;
213 + }
214 +
215 + /**
216 + * BGP UPDATE: AGGREGATOR related constants.
217 + */
218 + public static final class Aggregator {
219 + /**
220 + * Default constructor.
221 + * <p>
222 + * The constructor is private to prevent creating an instance of
223 + * this utility class.
224 + */
225 + private Aggregator() {
226 + }
227 +
228 + /** BGP UPDATE Attributes Type Code AGGREGATOR. */
229 + public static final int TYPE = 7;
230 +
231 + /** BGP UPDATE Attributes Type Code AGGREGATOR length. */
232 + public static final int LENGTH = 6;
233 + }
234 + }
235 +
236 + /**
237 + * BGP NOTIFICATION related constants.
238 + */
239 + public static final class Notifications {
240 + /**
241 + * Default constructor.
242 + * <p>
243 + * The constructor is private to prevent creating an instance of
244 + * this utility class.
245 + */
246 + private Notifications() {
247 + }
248 +
249 + /**
250 + * BGP NOTIFICATION: Message Header Error constants.
251 + */
252 + public static final class MessageHeaderError {
253 + /**
254 + * Default constructor.
255 + * <p>
256 + * The constructor is private to prevent creating an instance of
257 + * this utility class.
258 + */
259 + private MessageHeaderError() {
260 + }
261 +
262 + /** Message Header Error code. */
263 + public static final int ERROR_CODE = 1;
264 +
265 + /** Message Header Error subcode: Connection Not Synchronized. */
266 + public static final int CONNECTION_NOT_SYNCHRONIZED = 1;
267 +
268 + /** Message Header Error subcode: Bad Message Length. */
269 + public static final int BAD_MESSAGE_LENGTH = 2;
270 +
271 + /** Message Header Error subcode: Bad Message Type. */
272 + public static final int BAD_MESSAGE_TYPE = 3;
273 + }
274 +
275 + /**
276 + * BGP NOTIFICATION: OPEN Message Error constants.
277 + */
278 + public static final class OpenMessageError {
279 + /**
280 + * Default constructor.
281 + * <p>
282 + * The constructor is private to prevent creating an instance of
283 + * this utility class.
284 + */
285 + private OpenMessageError() {
286 + }
287 +
288 + /** OPEN Message Error code. */
289 + public static final int ERROR_CODE = 2;
290 +
291 + /** OPEN Message Error subcode: Unsupported Version Number. */
292 + public static final int UNSUPPORTED_VERSION_NUMBER = 1;
293 +
294 + /** OPEN Message Error subcode: Bad PEER AS. */
295 + public static final int BAD_PEER_AS = 2;
296 +
297 + /** OPEN Message Error subcode: Unacceptable Hold Time. */
298 + public static final int UNACCEPTABLE_HOLD_TIME = 6;
299 + }
300 +
301 + /**
302 + * BGP NOTIFICATION: UPDATE Message Error constants.
303 + */
304 + public static final class UpdateMessageError {
305 + /**
306 + * Default constructor.
307 + * <p>
308 + * The constructor is private to prevent creating an instance of
309 + * this utility class.
310 + */
311 + private UpdateMessageError() {
312 + }
313 +
314 + /** UPDATE Message Error code. */
315 + public static final int ERROR_CODE = 3;
316 +
317 + /** UPDATE Message Error subcode: Malformed Attribute List. */
318 + public static final int MALFORMED_ATTRIBUTE_LIST = 1;
319 +
320 + /** UPDATE Message Error subcode: Unrecognized Well-known Attribute. */
321 + public static final int UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE = 2;
322 +
323 + /** UPDATE Message Error subcode: Missing Well-known Attribute. */
324 + public static final int MISSING_WELL_KNOWN_ATTRIBUTE = 3;
325 +
326 + /** UPDATE Message Error subcode: Attribute Flags Error. */
327 + public static final int ATTRIBUTE_FLAGS_ERROR = 4;
328 +
329 + /** UPDATE Message Error subcode: Attribute Length Error. */
330 + public static final int ATTRIBUTE_LENGTH_ERROR = 5;
331 +
332 + /** UPDATE Message Error subcode: Invalid ORIGIN Attribute. */
333 + public static final int INVALID_ORIGIN_ATTRIBUTE = 6;
334 +
335 + /** UPDATE Message Error subcode: Invalid NEXT_HOP Attribute. */
336 + public static final int INVALID_NEXT_HOP_ATTRIBUTE = 8;
337 +
338 + /** UPDATE Message Error subcode: Optional Attribute Error. Unused. */
339 + public static final int OPTIONAL_ATTRIBUTE_ERROR = 9;
340 +
341 + /** UPDATE Message Error subcode: Invalid Network Field. */
342 + public static final int INVALID_NETWORK_FIELD = 10;
343 +
344 + /** UPDATE Message Error subcode: Malformed AS_PATH. */
345 + public static final int MALFORMED_AS_PATH = 11;
346 + }
347 +
348 + /**
349 + * BGP NOTIFICATION: Hold Timer Expired constants.
350 + */
351 + public static final class HoldTimerExpired {
352 + /**
353 + * Default constructor.
354 + * <p>
355 + * The constructor is private to prevent creating an instance of
356 + * this utility class.
357 + */
358 + private HoldTimerExpired() {
359 + }
360 +
361 + /** Hold Timer Expired code. */
362 + public static final int ERROR_CODE = 4;
363 + }
364 +
365 + /** BGP NOTIFICATION message Error subcode: Unspecific. */
366 + public static final int ERROR_SUBCODE_UNSPECIFIC = 0;
367 + }
368 +}
1 +package org.onlab.onos.sdnip.bgp;
2 +
3 +import org.jboss.netty.buffer.ChannelBuffer;
4 +import org.jboss.netty.buffer.ChannelBuffers;
5 +import org.jboss.netty.channel.Channel;
6 +import org.jboss.netty.channel.ChannelHandlerContext;
7 +import org.jboss.netty.handler.codec.frame.FrameDecoder;
8 +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
9 +import org.slf4j.Logger;
10 +import org.slf4j.LoggerFactory;
11 +
12 +/**
13 + * Class for handling the decoding of the BGP messages.
14 + */
15 +class BgpFrameDecoder extends FrameDecoder {
16 + private static final Logger log =
17 + LoggerFactory.getLogger(BgpFrameDecoder.class);
18 +
19 + private final BgpSession bgpSession;
20 +
21 + /**
22 + * Constructor for a given BGP Session.
23 + *
24 + * @param bgpSession the BGP session state to use.
25 + */
26 + BgpFrameDecoder(BgpSession bgpSession) {
27 + this.bgpSession = bgpSession;
28 + }
29 +
30 + @Override
31 + protected Object decode(ChannelHandlerContext ctx,
32 + Channel channel,
33 + ChannelBuffer buf) throws Exception {
34 + //
35 + // NOTE: If we close the channel during the decoding, we might still
36 + // see some incoming messages while the channel closing is completed.
37 + //
38 + if (bgpSession.isClosed()) {
39 + return null;
40 + }
41 +
42 + log.trace("BGP Peer: decode(): remoteAddr = {} localAddr = {} " +
43 + "messageSize = {}",
44 + ctx.getChannel().getRemoteAddress(),
45 + ctx.getChannel().getLocalAddress(),
46 + buf.readableBytes());
47 +
48 + // Test for minimum length of the BGP message
49 + if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) {
50 + // No enough data received
51 + return null;
52 + }
53 +
54 + //
55 + // Mark the current buffer position in case we haven't received
56 + // the whole message.
57 + //
58 + buf.markReaderIndex();
59 +
60 + //
61 + // Read and check the BGP message Marker field: it must be all ones
62 + // (See RFC 4271, Section 4.1)
63 + //
64 + byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH];
65 + buf.readBytes(marker);
66 + for (int i = 0; i < marker.length; i++) {
67 + if (marker[i] != (byte) 0xff) {
68 + log.debug("BGP RX Error: invalid marker {} at position {}",
69 + marker[i], i);
70 + //
71 + // ERROR: Connection Not Synchronized
72 + //
73 + // Send NOTIFICATION and close the connection
74 + int errorCode = MessageHeaderError.ERROR_CODE;
75 + int errorSubcode =
76 + MessageHeaderError.CONNECTION_NOT_SYNCHRONIZED;
77 + ChannelBuffer txMessage =
78 + bgpSession.prepareBgpNotification(errorCode, errorSubcode,
79 + null);
80 + ctx.getChannel().write(txMessage);
81 + bgpSession.closeChannel(ctx);
82 + return null;
83 + }
84 + }
85 +
86 + //
87 + // Read and check the BGP message Length field
88 + //
89 + int length = buf.readUnsignedShort();
90 + if ((length < BgpConstants.BGP_HEADER_LENGTH) ||
91 + (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) {
92 + log.debug("BGP RX Error: invalid Length field {}. " +
93 + "Must be between {} and {}",
94 + length,
95 + BgpConstants.BGP_HEADER_LENGTH,
96 + BgpConstants.BGP_MESSAGE_MAX_LENGTH);
97 + //
98 + // ERROR: Bad Message Length
99 + //
100 + // Send NOTIFICATION and close the connection
101 + ChannelBuffer txMessage =
102 + bgpSession.prepareBgpNotificationBadMessageLength(length);
103 + ctx.getChannel().write(txMessage);
104 + bgpSession.closeChannel(ctx);
105 + return null;
106 + }
107 +
108 + //
109 + // Test whether the rest of the message is received:
110 + // So far we have read the Marker (16 octets) and the
111 + // Length (2 octets) fields.
112 + //
113 + int remainingMessageLen =
114 + length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2;
115 + if (buf.readableBytes() < remainingMessageLen) {
116 + // No enough data received
117 + buf.resetReaderIndex();
118 + return null;
119 + }
120 +
121 + //
122 + // Read the BGP message Type field, and process based on that type
123 + //
124 + int type = buf.readUnsignedByte();
125 + remainingMessageLen--; // Adjust after reading the type
126 + ChannelBuffer message = buf.readBytes(remainingMessageLen);
127 +
128 + //
129 + // Process the remaining of the message based on the message type
130 + //
131 + switch (type) {
132 + case BgpConstants.BGP_TYPE_OPEN:
133 + bgpSession.processBgpOpen(ctx, message);
134 + break;
135 + case BgpConstants.BGP_TYPE_UPDATE:
136 + bgpSession.processBgpUpdate(ctx, message);
137 + break;
138 + case BgpConstants.BGP_TYPE_NOTIFICATION:
139 + bgpSession.processBgpNotification(ctx, message);
140 + break;
141 + case BgpConstants.BGP_TYPE_KEEPALIVE:
142 + bgpSession.processBgpKeepalive(ctx, message);
143 + break;
144 + default:
145 + //
146 + // ERROR: Bad Message Type
147 + //
148 + // Send NOTIFICATION and close the connection
149 + int errorCode = MessageHeaderError.ERROR_CODE;
150 + int errorSubcode = MessageHeaderError.BAD_MESSAGE_TYPE;
151 + ChannelBuffer data = ChannelBuffers.buffer(1);
152 + data.writeByte(type);
153 + ChannelBuffer txMessage =
154 + bgpSession.prepareBgpNotification(errorCode, errorSubcode,
155 + data);
156 + ctx.getChannel().write(txMessage);
157 + bgpSession.closeChannel(ctx);
158 + return null;
159 + }
160 + return null;
161 + }
162 +}
1 +package org.onlab.onos.sdnip.bgp;
2 +
3 +import static com.google.common.base.Preconditions.checkNotNull;
4 +
5 +import java.util.ArrayList;
6 +import java.util.Objects;
7 +
8 +import org.onlab.onos.sdnip.RouteEntry;
9 +import org.onlab.packet.IpAddress;
10 +import org.onlab.packet.IpPrefix;
11 +
12 +import com.google.common.base.MoreObjects;
13 +
14 +/**
15 + * Represents a route in BGP.
16 + */
17 +public class BgpRouteEntry extends RouteEntry {
18 + private final BgpSession bgpSession; // The BGP Session the route was
19 + // received on
20 + private final byte origin; // Route ORIGIN: IGP, EGP, INCOMPLETE
21 + private final AsPath asPath; // The AS Path
22 + private final long localPref; // The local preference for the route
23 + private long multiExitDisc =
24 + BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
25 +
26 + /**
27 + * Class constructor.
28 + *
29 + * @param bgpSession the BGP Session the route was received on
30 + * @param prefix the prefix of the route
31 + * @param nextHop the next hop of the route
32 + * @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
33 + * @param asPath the AS path
34 + * @param localPref the route local preference
35 + */
36 + public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
37 + IpAddress nextHop, byte origin,
38 + BgpRouteEntry.AsPath asPath, long localPref) {
39 + super(prefix, nextHop);
40 + this.bgpSession = checkNotNull(bgpSession);
41 + this.origin = origin;
42 + this.asPath = checkNotNull(asPath);
43 + this.localPref = localPref;
44 + }
45 +
46 + /**
47 + * Gets the BGP Session the route was received on.
48 + *
49 + * @return the BGP Session the route was received on
50 + */
51 + public BgpSession getBgpSession() {
52 + return bgpSession;
53 + }
54 +
55 + /**
56 + * Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE.
57 + *
58 + * @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
59 + */
60 + public byte getOrigin() {
61 + return origin;
62 + }
63 +
64 + /**
65 + * Gets the route AS path.
66 + *
67 + * @return the route AS path
68 + */
69 + public BgpRouteEntry.AsPath getAsPath() {
70 + return asPath;
71 + }
72 +
73 + /**
74 + * Gets the route local preference.
75 + *
76 + * @return the route local preference
77 + */
78 + public long getLocalPref() {
79 + return localPref;
80 + }
81 +
82 + /**
83 + * Gets the route MED (Multi-Exit Discriminator).
84 + *
85 + * @return the route MED (Multi-Exit Discriminator)
86 + */
87 + public long getMultiExitDisc() {
88 + return multiExitDisc;
89 + }
90 +
91 + /**
92 + * Sets the route MED (Multi-Exit Discriminator).
93 + *
94 + * @param multiExitDisc the route MED (Multi-Exit Discriminator) to set
95 + */
96 + void setMultiExitDisc(long multiExitDisc) {
97 + this.multiExitDisc = multiExitDisc;
98 + }
99 +
100 + /**
101 + * Tests whether the route is originated from the local AS.
102 + * <p/>
103 + * The route is considered originated from the local AS if the AS Path
104 + * is empty or if it begins with an AS_SET.
105 + *
106 + * @return true if the route is originated from the local AS, otherwise
107 + * false
108 + */
109 + boolean isLocalRoute() {
110 + if (asPath.getPathSegments().isEmpty()) {
111 + return true;
112 + }
113 + PathSegment firstPathSegment = asPath.getPathSegments().get(0);
114 + if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) {
115 + return true;
116 + }
117 + return false;
118 + }
119 +
120 + /**
121 + * Gets the BGP Neighbor AS number the route was received from.
122 + * <p/>
123 + * If the router is originated from the local AS, the return value is
124 + * zero (BGP_AS_0).
125 + *
126 + * @return the BGP Neighbor AS number the route was received from.
127 + */
128 + long getNeighborAs() {
129 + if (isLocalRoute()) {
130 + return BgpConstants.BGP_AS_0;
131 + }
132 + PathSegment firstPathSegment = asPath.getPathSegments().get(0);
133 + if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
134 + // TODO: Shouldn't happen. Should check during the parsing.
135 + return BgpConstants.BGP_AS_0;
136 + }
137 + return firstPathSegment.getSegmentAsNumbers().get(0);
138 + }
139 +
140 + /**
141 + * Tests whether the AS Path contains a loop.
142 + * <p/>
143 + * The test is done by comparing whether the AS Path contains the
144 + * local AS number.
145 + *
146 + * @param localAsNumber the local AS number to compare against
147 + * @return true if the AS Path contains a loop, otherwise false
148 + */
149 + boolean hasAsPathLoop(long localAsNumber) {
150 + for (PathSegment pathSegment : asPath.getPathSegments()) {
151 + for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
152 + if (asNumber.equals(localAsNumber)) {
153 + return true;
154 + }
155 + }
156 + }
157 + return false;
158 + }
159 +
160 + /**
161 + * Compares this BGP route against another BGP route by using the
162 + * BGP Decision Process.
163 + * <p/>
164 + * NOTE: The comparison needs to be performed only on routes that have
165 + * same IP Prefix.
166 + *
167 + * @param other the BGP route to compare against
168 + * @return true if this BGP route is better than the other BGP route
169 + * or same, otherwise false
170 + */
171 + boolean isBetterThan(BgpRouteEntry other) {
172 + if (this == other) {
173 + return true; // Return true if same route
174 + }
175 +
176 + // Compare the LOCAL_PREF values: larger is better
177 + if (getLocalPref() != other.getLocalPref()) {
178 + return (getLocalPref() > other.getLocalPref());
179 + }
180 +
181 + // Compare the AS number in the path: smaller is better
182 + if (getAsPath().getAsPathLength() !=
183 + other.getAsPath().getAsPathLength()) {
184 + return getAsPath().getAsPathLength() <
185 + other.getAsPath().getAsPathLength();
186 + }
187 +
188 + // Compare the Origin number: lower is better
189 + if (getOrigin() != other.getOrigin()) {
190 + return (getOrigin() < other.getOrigin());
191 + }
192 +
193 + // Compare the MED if the neighbor AS is same: larger is better
194 + medLabel: {
195 + boolean thisIsLocalRoute = isLocalRoute();
196 + if (thisIsLocalRoute != other.isLocalRoute()) {
197 + break medLabel; // AS number is different
198 + }
199 + if (!thisIsLocalRoute) {
200 + long thisNeighborAs = getNeighborAs();
201 + if (thisNeighborAs != other.getNeighborAs()) {
202 + break medLabel; // AS number is different
203 + }
204 + if (thisNeighborAs == BgpConstants.BGP_AS_0) {
205 + break medLabel; // Invalid AS number
206 + }
207 + }
208 +
209 + // Compare the MED
210 + if (getMultiExitDisc() != other.getMultiExitDisc()) {
211 + return (getMultiExitDisc() > other.getMultiExitDisc());
212 + }
213 + }
214 +
215 + // Compare the peer BGP ID: lower is better
216 + IpAddress peerBgpId = getBgpSession().getRemoteBgpId();
217 + IpAddress otherPeerBgpId = other.getBgpSession().getRemoteBgpId();
218 + if (!peerBgpId.equals(otherPeerBgpId)) {
219 + return (peerBgpId.compareTo(otherPeerBgpId) < 0);
220 + }
221 +
222 + // Compare the peer BGP address: lower is better
223 + IpAddress peerAddress = getBgpSession().getRemoteIp4Address();
224 + IpAddress otherPeerAddress =
225 + other.getBgpSession().getRemoteIp4Address();
226 + if (!peerAddress.equals(otherPeerAddress)) {
227 + return (peerAddress.compareTo(otherPeerAddress) < 0);
228 + }
229 +
230 + return true; // Routes are same. Shouldn't happen?
231 + }
232 +
233 + /**
234 + * A class to represent AS Path Segment.
235 + */
236 + public static class PathSegment {
237 + private final byte type; // Segment type: AS_SET, AS_SEQUENCE
238 + private final ArrayList<Long> segmentAsNumbers; // Segment AS numbers
239 +
240 + /**
241 + * Constructor.
242 + *
243 + * @param type the Path Segment Type: 1=AS_SET, 2=AS_SEQUENCE
244 + * @param segmentAsNumbers the Segment AS numbers
245 + */
246 + PathSegment(byte type, ArrayList<Long> segmentAsNumbers) {
247 + this.type = type;
248 + this.segmentAsNumbers = checkNotNull(segmentAsNumbers);
249 + }
250 +
251 + /**
252 + * Gets the Path Segment Type: AS_SET, AS_SEQUENCE.
253 + *
254 + * @return the Path Segment Type: AS_SET, AS_SEQUENCE
255 + */
256 + public byte getType() {
257 + return type;
258 + }
259 +
260 + /**
261 + * Gets the Path Segment AS Numbers.
262 + *
263 + * @return the Path Segment AS Numbers
264 + */
265 + public ArrayList<Long> getSegmentAsNumbers() {
266 + return segmentAsNumbers;
267 + }
268 +
269 + @Override
270 + public boolean equals(Object other) {
271 + if (this == other) {
272 + return true;
273 + }
274 +
275 + if (!(other instanceof PathSegment)) {
276 + return false;
277 + }
278 +
279 + PathSegment otherPathSegment = (PathSegment) other;
280 + return Objects.equals(this.type, otherPathSegment.type) &&
281 + Objects.equals(this.segmentAsNumbers,
282 + otherPathSegment.segmentAsNumbers);
283 + }
284 +
285 + @Override
286 + public int hashCode() {
287 + return Objects.hash(type, segmentAsNumbers);
288 + }
289 +
290 + @Override
291 + public String toString() {
292 + return MoreObjects.toStringHelper(getClass())
293 + .add("type", this.type)
294 + .add("segmentAsNumbers", this.segmentAsNumbers)
295 + .toString();
296 + }
297 + }
298 +
299 + /**
300 + * A class to represent AS Path.
301 + */
302 + public static class AsPath {
303 + private final ArrayList<PathSegment> pathSegments;
304 + private final int asPathLength; // Precomputed AS Path Length
305 +
306 + /**
307 + * Constructor.
308 + *
309 + * @param pathSegments the Path Segments of the Path
310 + */
311 + AsPath(ArrayList<PathSegment> pathSegments) {
312 + this.pathSegments = checkNotNull(pathSegments);
313 +
314 + //
315 + // Precompute the AS Path Length:
316 + // - AS_SET counts as 1
317 + //
318 + int pl = 0;
319 + for (PathSegment pathSegment : pathSegments) {
320 + if (pathSegment.getType() ==
321 + BgpConstants.Update.AsPath.AS_SET) {
322 + pl++;
323 + continue;
324 + }
325 + pl += pathSegment.getSegmentAsNumbers().size();
326 + }
327 + asPathLength = pl;
328 + }
329 +
330 + /**
331 + * Gets the AS Path Segments.
332 + *
333 + * @return the AS Path Segments
334 + */
335 + public ArrayList<PathSegment> getPathSegments() {
336 + return pathSegments;
337 + }
338 +
339 + /**
340 + * Gets the AS Path Length as considered by the BGP Decision Process.
341 + *
342 + * @return the AS Path Length as considered by the BGP Decision Process
343 + */
344 + int getAsPathLength() {
345 + return asPathLength;
346 + }
347 +
348 + @Override
349 + public boolean equals(Object other) {
350 + if (this == other) {
351 + return true;
352 + }
353 +
354 + if (!(other instanceof AsPath)) {
355 + return false;
356 + }
357 +
358 + AsPath otherAsPath = (AsPath) other;
359 + return Objects.equals(this.pathSegments, otherAsPath.pathSegments);
360 + }
361 +
362 + @Override
363 + public int hashCode() {
364 + return Objects.hash(pathSegments);
365 + }
366 +
367 + @Override
368 + public String toString() {
369 + return MoreObjects.toStringHelper(getClass())
370 + .add("pathSegments", this.pathSegments)
371 + .toString();
372 + }
373 + }
374 +
375 + /**
376 + * Compares whether two objects are equal.
377 + * <p/>
378 + * NOTE: The bgpSession field is excluded from the comparison.
379 + *
380 + * @return true if the two objects are equal, otherwise false.
381 + */
382 + @Override
383 + public boolean equals(Object other) {
384 + if (this == other) {
385 + return true;
386 + }
387 +
388 + //
389 + // NOTE: Subclasses are considered as change of identity, hence
390 + // equals() will return false if the class type doesn't match.
391 + //
392 + if (other == null || getClass() != other.getClass()) {
393 + return false;
394 + }
395 +
396 + if (!super.equals(other)) {
397 + return false;
398 + }
399 +
400 + // NOTE: The bgpSession field is excluded from the comparison
401 + BgpRouteEntry otherRoute = (BgpRouteEntry) other;
402 + return (this.origin == otherRoute.origin) &&
403 + Objects.equals(this.asPath, otherRoute.asPath) &&
404 + (this.localPref == otherRoute.localPref) &&
405 + (this.multiExitDisc == otherRoute.multiExitDisc);
406 + }
407 +
408 + /**
409 + * Computes the hash code.
410 + * <p/>
411 + * NOTE: We return the base class hash code to avoid expensive computation
412 + *
413 + * @return the object hash code
414 + */
415 + @Override
416 + public int hashCode() {
417 + return super.hashCode();
418 + }
419 +
420 + @Override
421 + public String toString() {
422 + return MoreObjects.toStringHelper(getClass())
423 + .add("prefix", prefix())
424 + .add("nextHop", nextHop())
425 + .add("bgpId", bgpSession.getRemoteBgpId())
426 + .add("origin", origin)
427 + .add("asPath", asPath)
428 + .add("localPref", localPref)
429 + .add("multiExitDisc", multiExitDisc)
430 + .toString();
431 + }
432 +}
1 +package org.onlab.onos.sdnip.bgp;
2 +
3 +import java.net.InetAddress;
4 +import java.net.InetSocketAddress;
5 +import java.net.SocketAddress;
6 +import java.util.ArrayList;
7 +import java.util.Collection;
8 +import java.util.Collections;
9 +import java.util.HashMap;
10 +import java.util.Map;
11 +import java.util.concurrent.ConcurrentHashMap;
12 +import java.util.concurrent.ConcurrentMap;
13 +import java.util.concurrent.TimeUnit;
14 +
15 +import org.apache.commons.lang3.tuple.Pair;
16 +import org.jboss.netty.buffer.ChannelBuffer;
17 +import org.jboss.netty.buffer.ChannelBuffers;
18 +import org.jboss.netty.channel.ChannelHandlerContext;
19 +import org.jboss.netty.channel.ChannelStateEvent;
20 +import org.jboss.netty.channel.SimpleChannelHandler;
21 +import org.jboss.netty.util.HashedWheelTimer;
22 +import org.jboss.netty.util.Timeout;
23 +import org.jboss.netty.util.Timer;
24 +import org.jboss.netty.util.TimerTask;
25 +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications;
26 +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.HoldTimerExpired;
27 +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
28 +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.OpenMessageError;
29 +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.UpdateMessageError;
30 +import org.onlab.packet.IpAddress;
31 +import org.onlab.packet.IpPrefix;
32 +import org.slf4j.Logger;
33 +import org.slf4j.LoggerFactory;
34 +
35 +/**
36 + * Class for handling the BGP peer sessions.
37 + * There is one instance per each BGP peer session.
38 + */
39 +public class BgpSession extends SimpleChannelHandler {
40 + private static final Logger log =
41 + LoggerFactory.getLogger(BgpSession.class);
42 +
43 + private final BgpSessionManager bgpSessionManager;
44 +
45 + // Local flag to indicate the session is closed.
46 + // It is used to avoid the Netty's asynchronous closing of a channel.
47 + private boolean isClosed = false;
48 +
49 + private SocketAddress remoteAddress; // Peer IP addr/port
50 + private IpAddress remoteIp4Address; // Peer IPv4 address
51 + private int remoteBgpVersion; // 1 octet
52 + private long remoteAs; // 2 octets
53 + private long remoteHoldtime; // 2 octets
54 + private IpAddress remoteBgpId; // 4 octets -> IPv4 address
55 + //
56 + private SocketAddress localAddress; // Local IP addr/port
57 + private IpAddress localIp4Address; // Local IPv4 address
58 + private int localBgpVersion; // 1 octet
59 + private long localAs; // 2 octets
60 + private long localHoldtime; // 2 octets
61 + private IpAddress localBgpId; // 4 octets -> IPv4 address
62 + //
63 + private long localKeepaliveInterval; // Keepalive interval
64 +
65 + // Timers state
66 + private Timer timer = new HashedWheelTimer();
67 + private volatile Timeout keepaliveTimeout; // Periodic KEEPALIVE
68 + private volatile Timeout sessionTimeout; // Session timeout
69 +
70 + // BGP RIB-IN routing entries from this peer
71 + private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRibIn =
72 + new ConcurrentHashMap<>();
73 +
74 + /**
75 + * Constructor for a given BGP Session Manager.
76 + *
77 + * @param bgpSessionManager the BGP Session Manager to use
78 + */
79 + BgpSession(BgpSessionManager bgpSessionManager) {
80 + this.bgpSessionManager = bgpSessionManager;
81 + }
82 +
83 + /**
84 + * Gets the BGP RIB-IN routing entries.
85 + *
86 + * @return the BGP RIB-IN routing entries
87 + */
88 + public Collection<BgpRouteEntry> getBgpRibIn() {
89 + return bgpRibIn.values();
90 + }
91 +
92 + /**
93 + * Finds a BGP routing entry in the BGP RIB-IN.
94 + *
95 + * @param prefix the prefix of the route to search for
96 + * @return the BGP routing entry if found, otherwise null
97 + */
98 + public BgpRouteEntry findBgpRouteEntry(IpPrefix prefix) {
99 + return bgpRibIn.get(prefix);
100 + }
101 +
102 + /**
103 + * Gets the BGP session remote address.
104 + *
105 + * @return the BGP session remote address
106 + */
107 + public SocketAddress getRemoteAddress() {
108 + return remoteAddress;
109 + }
110 +
111 + /**
112 + * Gets the BGP session remote IPv4 address.
113 + *
114 + * @return the BGP session remote IPv4 address
115 + */
116 + public IpAddress getRemoteIp4Address() {
117 + return remoteIp4Address;
118 + }
119 +
120 + /**
121 + * Gets the BGP session remote BGP version.
122 + *
123 + * @return the BGP session remote BGP version
124 + */
125 + public int getRemoteBgpVersion() {
126 + return remoteBgpVersion;
127 + }
128 +
129 + /**
130 + * Gets the BGP session remote AS number.
131 + *
132 + * @return the BGP session remote AS number
133 + */
134 + public long getRemoteAs() {
135 + return remoteAs;
136 + }
137 +
138 + /**
139 + * Gets the BGP session remote Holdtime.
140 + *
141 + * @return the BGP session remote Holdtime
142 + */
143 + public long getRemoteHoldtime() {
144 + return remoteHoldtime;
145 + }
146 +
147 + /**
148 + * Gets the BGP session remote BGP Identifier as an IPv4 address.
149 + *
150 + * @return the BGP session remote BGP Identifier as an IPv4 address
151 + */
152 + public IpAddress getRemoteBgpId() {
153 + return remoteBgpId;
154 + }
155 +
156 + /**
157 + * Gets the BGP session local address.
158 + *
159 + * @return the BGP session local address
160 + */
161 + public SocketAddress getLocalAddress() {
162 + return localAddress;
163 + }
164 +
165 + /**
166 + * Gets the BGP session local BGP version.
167 + *
168 + * @return the BGP session local BGP version
169 + */
170 + public int getLocalBgpVersion() {
171 + return localBgpVersion;
172 + }
173 +
174 + /**
175 + * Gets the BGP session local AS number.
176 + *
177 + * @return the BGP session local AS number
178 + */
179 + public long getLocalAs() {
180 + return localAs;
181 + }
182 +
183 + /**
184 + * Gets the BGP session local Holdtime.
185 + *
186 + * @return the BGP session local Holdtime
187 + */
188 + public long getLocalHoldtime() {
189 + return localHoldtime;
190 + }
191 +
192 + /**
193 + * Gets the BGP session local BGP Identifier as an IPv4 address.
194 + *
195 + * @return the BGP session local BGP Identifier as an IPv4 address
196 + */
197 + public IpAddress getLocalBgpId() {
198 + return localBgpId;
199 + }
200 +
201 + /**
202 + * Tests whether the session is closed.
203 + * <p/>
204 + * NOTE: We use this method to avoid the Netty's asynchronous closing
205 + * of a channel.
206 + *
207 + * @param return true if the session is closed
208 + */
209 + boolean isClosed() {
210 + return isClosed;
211 + }
212 +
213 + /**
214 + * Closes the channel.
215 + *
216 + * @param ctx the Channel Handler Context
217 + */
218 + void closeChannel(ChannelHandlerContext ctx) {
219 + isClosed = true;
220 + timer.stop();
221 + ctx.getChannel().close();
222 + }
223 +
224 + @Override
225 + public void channelConnected(ChannelHandlerContext ctx,
226 + ChannelStateEvent channelEvent) {
227 + localAddress = ctx.getChannel().getLocalAddress();
228 + remoteAddress = ctx.getChannel().getRemoteAddress();
229 +
230 + // Assign the local and remote IPv4 addresses
231 + InetAddress inetAddr;
232 + if (localAddress instanceof InetSocketAddress) {
233 + inetAddr = ((InetSocketAddress) localAddress).getAddress();
234 + localIp4Address = IpAddress.valueOf(inetAddr.getAddress());
235 + }
236 + if (remoteAddress instanceof InetSocketAddress) {
237 + inetAddr = ((InetSocketAddress) remoteAddress).getAddress();
238 + remoteIp4Address = IpAddress.valueOf(inetAddr.getAddress());
239 + }
240 +
241 + log.debug("BGP Session Connected from {} on {}",
242 + remoteAddress, localAddress);
243 + if (!bgpSessionManager.peerConnected(this)) {
244 + log.debug("Cannot setup BGP Session Connection from {}. Closing...",
245 + remoteAddress);
246 + ctx.getChannel().close();
247 + }
248 + }
249 +
250 + @Override
251 + public void channelDisconnected(ChannelHandlerContext ctx,
252 + ChannelStateEvent channelEvent) {
253 + log.debug("BGP Session Disconnected from {} on {}",
254 + ctx.getChannel().getRemoteAddress(),
255 + ctx.getChannel().getLocalAddress());
256 +
257 + //
258 + // Withdraw the routes advertised by this BGP peer
259 + //
260 + // NOTE: We must initialize the RIB-IN before propagating the withdraws
261 + // for further processing. Otherwise, the BGP Decision Process
262 + // will use those routes again.
263 + //
264 + Collection<BgpRouteEntry> deletedRoutes = bgpRibIn.values();
265 + bgpRibIn = new ConcurrentHashMap<>();
266 +
267 + // Push the updates to the BGP Merged RIB
268 + BgpSessionManager.BgpRouteSelector bgpRouteSelector =
269 + bgpSessionManager.getBgpRouteSelector();
270 + Collection<BgpRouteEntry> addedRoutes = Collections.emptyList();
271 + bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes);
272 +
273 + bgpSessionManager.peerDisconnected(this);
274 + }
275 +
276 + /**
277 + * Processes BGP OPEN message.
278 + *
279 + * @param ctx the Channel Handler Context
280 + * @param message the message to process
281 + */
282 + void processBgpOpen(ChannelHandlerContext ctx, ChannelBuffer message) {
283 + int minLength =
284 + BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
285 + if (message.readableBytes() < minLength) {
286 + log.debug("BGP RX OPEN Error from {}: " +
287 + "Message length {} too short. Must be at least {}",
288 + remoteAddress, message.readableBytes(), minLength);
289 + //
290 + // ERROR: Bad Message Length
291 + //
292 + // Send NOTIFICATION and close the connection
293 + ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
294 + message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
295 + ctx.getChannel().write(txMessage);
296 + closeChannel(ctx);
297 + return;
298 + }
299 +
300 + //
301 + // Parse the OPEN message
302 + //
303 + // Remote BGP version
304 + remoteBgpVersion = message.readUnsignedByte();
305 + if (remoteBgpVersion != BgpConstants.BGP_VERSION) {
306 + log.debug("BGP RX OPEN Error from {}: " +
307 + "Unsupported BGP version {}. Should be {}",
308 + remoteAddress, remoteBgpVersion,
309 + BgpConstants.BGP_VERSION);
310 + //
311 + // ERROR: Unsupported Version Number
312 + //
313 + // Send NOTIFICATION and close the connection
314 + int errorCode = OpenMessageError.ERROR_CODE;
315 + int errorSubcode = OpenMessageError.UNSUPPORTED_VERSION_NUMBER;
316 + ChannelBuffer data = ChannelBuffers.buffer(2);
317 + data.writeShort(BgpConstants.BGP_VERSION);
318 + ChannelBuffer txMessage =
319 + prepareBgpNotification(errorCode, errorSubcode, data);
320 + ctx.getChannel().write(txMessage);
321 + closeChannel(ctx);
322 + return;
323 + }
324 +
325 + // Remote AS number
326 + remoteAs = message.readUnsignedShort();
327 + //
328 + // Verify that the AS number is same for all other BGP Sessions
329 + // NOTE: This check applies only for our use-case where all BGP
330 + // sessions are iBGP.
331 + //
332 + for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
333 + if (remoteAs != bgpSession.getRemoteAs()) {
334 + log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " +
335 + "Expected {}",
336 + remoteAddress, remoteAs, bgpSession.getRemoteAs());
337 + //
338 + // ERROR: Bad Peer AS
339 + //
340 + // Send NOTIFICATION and close the connection
341 + int errorCode = OpenMessageError.ERROR_CODE;
342 + int errorSubcode = OpenMessageError.BAD_PEER_AS;
343 + ChannelBuffer txMessage =
344 + prepareBgpNotification(errorCode, errorSubcode, null);
345 + ctx.getChannel().write(txMessage);
346 + closeChannel(ctx);
347 + return;
348 + }
349 + }
350 +
351 + // Remote Hold Time
352 + remoteHoldtime = message.readUnsignedShort();
353 + if ((remoteHoldtime != 0) &&
354 + (remoteHoldtime < BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME)) {
355 + log.debug("BGP RX OPEN Error from {}: " +
356 + "Unacceptable Hold Time field {}. " +
357 + "Should be 0 or at least {}",
358 + remoteAddress, remoteHoldtime,
359 + BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME);
360 + //
361 + // ERROR: Unacceptable Hold Time
362 + //
363 + // Send NOTIFICATION and close the connection
364 + int errorCode = OpenMessageError.ERROR_CODE;
365 + int errorSubcode = OpenMessageError.UNACCEPTABLE_HOLD_TIME;
366 + ChannelBuffer txMessage =
367 + prepareBgpNotification(errorCode, errorSubcode, null);
368 + ctx.getChannel().write(txMessage);
369 + closeChannel(ctx);
370 + return;
371 + }
372 +
373 + // Remote BGP Identifier
374 + remoteBgpId = IpAddress.valueOf((int) message.readUnsignedInt());
375 +
376 + // Optional Parameters
377 + int optParamLen = message.readUnsignedByte();
378 + if (message.readableBytes() < optParamLen) {
379 + log.debug("BGP RX OPEN Error from {}: " +
380 + "Invalid Optional Parameter Length field {}. " +
381 + "Remaining Optional Parameters {}",
382 + remoteAddress, optParamLen, message.readableBytes());
383 + //
384 + // ERROR: Invalid Optional Parameter Length field: Unspecific
385 + //
386 + // Send NOTIFICATION and close the connection
387 + int errorCode = OpenMessageError.ERROR_CODE;
388 + int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
389 + ChannelBuffer txMessage =
390 + prepareBgpNotification(errorCode, errorSubcode, null);
391 + ctx.getChannel().write(txMessage);
392 + closeChannel(ctx);
393 + return;
394 + }
395 + // TODO: Parse the optional parameters (if needed)
396 + message.readBytes(optParamLen); // NOTE: data ignored
397 +
398 + //
399 + // Copy some of the remote peer's state/setup to the local setup:
400 + // - BGP version
401 + // - AS number (NOTE: the peer setup is always iBGP)
402 + // - Holdtime
403 + // Also, assign the local BGP ID based on the local setup
404 + //
405 + localBgpVersion = remoteBgpVersion;
406 + localAs = remoteAs;
407 + localHoldtime = remoteHoldtime;
408 + localBgpId = bgpSessionManager.getMyBgpId();
409 +
410 + // Set the Keepalive interval
411 + if (localHoldtime == 0) {
412 + localKeepaliveInterval = 0;
413 + } else {
414 + localKeepaliveInterval = Math.max(localHoldtime /
415 + BgpConstants.BGP_KEEPALIVE_PER_HOLD_INTERVAL,
416 + BgpConstants.BGP_KEEPALIVE_MIN_INTERVAL);
417 + }
418 +
419 + log.debug("BGP RX OPEN message from {}: " +
420 + "BGPv{} AS {} BGP-ID {} Holdtime {}",
421 + remoteAddress, remoteBgpVersion, remoteAs,
422 + remoteBgpId, remoteHoldtime);
423 +
424 + // Send my OPEN followed by KEEPALIVE
425 + ChannelBuffer txMessage = prepareBgpOpen();
426 + ctx.getChannel().write(txMessage);
427 + //
428 + txMessage = prepareBgpKeepalive();
429 + ctx.getChannel().write(txMessage);
430 +
431 + // Start the KEEPALIVE timer
432 + restartKeepaliveTimer(ctx);
433 +
434 + // Start the Session Timeout timer
435 + restartSessionTimeoutTimer(ctx);
436 + }
437 +
438 + /**
439 + * Processes BGP UPDATE message.
440 + *
441 + * @param ctx the Channel Handler Context
442 + * @param message the message to process
443 + */
444 + void processBgpUpdate(ChannelHandlerContext ctx, ChannelBuffer message) {
445 + Collection<BgpRouteEntry> addedRoutes = null;
446 + Map<IpPrefix, BgpRouteEntry> deletedRoutes = new HashMap<>();
447 +
448 + int minLength =
449 + BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
450 + if (message.readableBytes() < minLength) {
451 + log.debug("BGP RX UPDATE Error from {}: " +
452 + "Message length {} too short. Must be at least {}",
453 + remoteAddress, message.readableBytes(), minLength);
454 + //
455 + // ERROR: Bad Message Length
456 + //
457 + // Send NOTIFICATION and close the connection
458 + ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
459 + message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
460 + ctx.getChannel().write(txMessage);
461 + closeChannel(ctx);
462 + return;
463 + }
464 +
465 + log.debug("BGP RX UPDATE message from {}", remoteAddress);
466 +
467 + //
468 + // Parse the UPDATE message
469 + //
470 +
471 + //
472 + // Parse the Withdrawn Routes
473 + //
474 + int withdrawnRoutesLength = message.readUnsignedShort();
475 + if (withdrawnRoutesLength > message.readableBytes()) {
476 + // ERROR: Malformed Attribute List
477 + actionsBgpUpdateMalformedAttributeList(ctx);
478 + return;
479 + }
480 + Collection<IpPrefix> withdrawnPrefixes = null;
481 + try {
482 + withdrawnPrefixes = parsePackedPrefixes(withdrawnRoutesLength,
483 + message);
484 + } catch (BgpParseException e) {
485 + // ERROR: Invalid Network Field
486 + log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ",
487 + remoteBgpId, e);
488 + actionsBgpUpdateInvalidNetworkField(ctx);
489 + return;
490 + }
491 + for (IpPrefix prefix : withdrawnPrefixes) {
492 + log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}",
493 + remoteAddress, prefix);
494 + BgpRouteEntry bgpRouteEntry = bgpRibIn.get(prefix);
495 + if (bgpRouteEntry != null) {
496 + deletedRoutes.put(prefix, bgpRouteEntry);
497 + }
498 + }
499 +
500 + //
501 + // Parse the Path Attributes
502 + //
503 + try {
504 + addedRoutes = parsePathAttributes(ctx, message);
505 + } catch (BgpParseException e) {
506 + log.debug("Exception parsing Path Attributes from BGP peer {}: ",
507 + remoteBgpId, e);
508 + // NOTE: The session was already closed, so nothing else to do
509 + return;
510 + }
511 + // Ignore WITHDRAWN routes that are ADDED
512 + for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
513 + deletedRoutes.remove(bgpRouteEntry.prefix());
514 + }
515 +
516 + // Update the BGP RIB-IN
517 + for (BgpRouteEntry bgpRouteEntry : deletedRoutes.values()) {
518 + bgpRibIn.remove(bgpRouteEntry.prefix());
519 + }
520 + for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
521 + bgpRibIn.put(bgpRouteEntry.prefix(), bgpRouteEntry);
522 + }
523 +
524 + // Push the updates to the BGP Merged RIB
525 + BgpSessionManager.BgpRouteSelector bgpRouteSelector =
526 + bgpSessionManager.getBgpRouteSelector();
527 + bgpRouteSelector.routeUpdates(this, addedRoutes,
528 + deletedRoutes.values());
529 +
530 + // Start the Session Timeout timer
531 + restartSessionTimeoutTimer(ctx);
532 + }
533 +
534 + /**
535 + * Parse BGP Path Attributes from the BGP UPDATE message.
536 + *
537 + * @param ctx the Channel Handler Context
538 + * @param message the message to parse
539 + * @return a collection of the result BGP Route Entries
540 + * @throws BgpParseException
541 + */
542 + private Collection<BgpRouteEntry> parsePathAttributes(
543 + ChannelHandlerContext ctx,
544 + ChannelBuffer message)
545 + throws BgpParseException {
546 + Map<IpPrefix, BgpRouteEntry> addedRoutes = new HashMap<>();
547 +
548 + //
549 + // Parsed values
550 + //
551 + Short origin = -1; // Mandatory
552 + BgpRouteEntry.AsPath asPath = null; // Mandatory
553 + IpAddress nextHop = null; // Mandatory
554 + long multiExitDisc = // Optional
555 + BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
556 + Long localPref = null; // Mandatory
557 + Long aggregatorAsNumber = null; // Optional: unused
558 + IpAddress aggregatorIpAddress = null; // Optional: unused
559 +
560 + //
561 + // Get and verify the Path Attributes Length
562 + //
563 + int pathAttributeLength = message.readUnsignedShort();
564 + if (pathAttributeLength > message.readableBytes()) {
565 + // ERROR: Malformed Attribute List
566 + actionsBgpUpdateMalformedAttributeList(ctx);
567 + String errorMsg = "Malformed Attribute List";
568 + throw new BgpParseException(errorMsg);
569 + }
570 + if (pathAttributeLength == 0) {
571 + return addedRoutes.values();
572 + }
573 +
574 + //
575 + // Parse the Path Attributes
576 + //
577 + int pathAttributeEnd = message.readerIndex() + pathAttributeLength;
578 + while (message.readerIndex() < pathAttributeEnd) {
579 + int attrFlags = message.readUnsignedByte();
580 + if (message.readerIndex() >= pathAttributeEnd) {
581 + // ERROR: Malformed Attribute List
582 + actionsBgpUpdateMalformedAttributeList(ctx);
583 + String errorMsg = "Malformed Attribute List";
584 + throw new BgpParseException(errorMsg);
585 + }
586 + int attrTypeCode = message.readUnsignedByte();
587 +
588 + // The Attribute Flags
589 + boolean optionalBit = ((0x80 & attrFlags) != 0);
590 + boolean transitiveBit = ((0x40 & attrFlags) != 0);
591 + boolean partialBit = ((0x20 & attrFlags) != 0);
592 + boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
593 +
594 + // The Attribute Length
595 + int attrLen = 0;
596 + int attrLenOctets = 1;
597 + if (extendedLengthBit) {
598 + attrLenOctets = 2;
599 + }
600 + if (message.readerIndex() + attrLenOctets > pathAttributeEnd) {
601 + // ERROR: Malformed Attribute List
602 + actionsBgpUpdateMalformedAttributeList(ctx);
603 + String errorMsg = "Malformed Attribute List";
604 + throw new BgpParseException(errorMsg);
605 + }
606 + if (extendedLengthBit) {
607 + attrLen = message.readUnsignedShort();
608 + } else {
609 + attrLen = message.readUnsignedByte();
610 + }
611 + if (message.readerIndex() + attrLen > pathAttributeEnd) {
612 + // ERROR: Malformed Attribute List
613 + actionsBgpUpdateMalformedAttributeList(ctx);
614 + String errorMsg = "Malformed Attribute List";
615 + throw new BgpParseException(errorMsg);
616 + }
617 +
618 + //
619 + // Verify the Attribute Flags
620 + //
621 + verifyBgpUpdateAttributeFlags(ctx, attrTypeCode, attrLen,
622 + attrFlags, message);
623 +
624 + //
625 + // Extract the Attribute Value based on the Attribute Type Code
626 + //
627 + switch (attrTypeCode) {
628 +
629 + case BgpConstants.Update.Origin.TYPE:
630 + // Attribute Type Code ORIGIN
631 + origin = parseAttributeTypeOrigin(ctx, attrTypeCode, attrLen,
632 + attrFlags, message);
633 + break;
634 +
635 + case BgpConstants.Update.AsPath.TYPE:
636 + // Attribute Type Code AS_PATH
637 + asPath = parseAttributeTypeAsPath(ctx, attrTypeCode, attrLen,
638 + attrFlags, message);
639 + break;
640 +
641 + case BgpConstants.Update.NextHop.TYPE:
642 + // Attribute Type Code NEXT_HOP
643 + nextHop = parseAttributeTypeNextHop(ctx, attrTypeCode, attrLen,
644 + attrFlags, message);
645 + break;
646 +
647 + case BgpConstants.Update.MultiExitDisc.TYPE:
648 + // Attribute Type Code MULTI_EXIT_DISC
649 + multiExitDisc =
650 + parseAttributeTypeMultiExitDisc(ctx, attrTypeCode, attrLen,
651 + attrFlags, message);
652 + break;
653 +
654 + case BgpConstants.Update.LocalPref.TYPE:
655 + // Attribute Type Code LOCAL_PREF
656 + localPref =
657 + parseAttributeTypeLocalPref(ctx, attrTypeCode, attrLen,
658 + attrFlags, message);
659 + break;
660 +
661 + case BgpConstants.Update.AtomicAggregate.TYPE:
662 + // Attribute Type Code ATOMIC_AGGREGATE
663 + parseAttributeTypeAtomicAggregate(ctx, attrTypeCode, attrLen,
664 + attrFlags, message);
665 + // Nothing to do: this attribute is primarily informational
666 + break;
667 +
668 + case BgpConstants.Update.Aggregator.TYPE:
669 + // Attribute Type Code AGGREGATOR
670 + Pair<Long, IpAddress> aggregator =
671 + parseAttributeTypeAggregator(ctx, attrTypeCode, attrLen,
672 + attrFlags, message);
673 + aggregatorAsNumber = aggregator.getLeft();
674 + aggregatorIpAddress = aggregator.getRight();
675 + break;
676 +
677 + default:
678 + // TODO: Parse any new Attribute Types if needed
679 + if (!optionalBit) {
680 + // ERROR: Unrecognized Well-known Attribute
681 + actionsBgpUpdateUnrecognizedWellKnownAttribute(
682 + ctx, attrTypeCode, attrLen, attrFlags, message);
683 + String errorMsg = "Unrecognized Well-known Attribute: " +
684 + attrTypeCode;
685 + throw new BgpParseException(errorMsg);
686 + }
687 +
688 + // Skip the data from the unrecognized attribute
689 + log.debug("BGP RX UPDATE message from {}: " +
690 + "Unrecognized Attribute Type {}",
691 + remoteAddress, attrTypeCode);
692 + message.skipBytes(attrLen);
693 + break;
694 + }
695 + }
696 +
697 + //
698 + // Verify the Well-known Attributes
699 + //
700 + verifyBgpUpdateWellKnownAttributes(ctx, origin, asPath, nextHop,
701 + localPref);
702 +
703 + //
704 + // Parse the NLRI (Network Layer Reachability Information)
705 + //
706 + Collection<IpPrefix> addedPrefixes = null;
707 + int nlriLength = message.readableBytes();
708 + try {
709 + addedPrefixes = parsePackedPrefixes(nlriLength, message);
710 + } catch (BgpParseException e) {
711 + // ERROR: Invalid Network Field
712 + log.debug("Exception parsing NLRI from BGP peer {}: ",
713 + remoteBgpId, e);
714 + actionsBgpUpdateInvalidNetworkField(ctx);
715 + // Rethrow the exception
716 + throw e;
717 + }
718 +
719 + // Generate the added routes
720 + for (IpPrefix prefix : addedPrefixes) {
721 + BgpRouteEntry bgpRouteEntry =
722 + new BgpRouteEntry(this, prefix, nextHop,
723 + origin.byteValue(), asPath, localPref);
724 + bgpRouteEntry.setMultiExitDisc(multiExitDisc);
725 + if (bgpRouteEntry.hasAsPathLoop(localAs)) {
726 + log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
727 + "nextHop {}: contains AS Path loop",
728 + remoteAddress, prefix, nextHop);
729 + continue;
730 + } else {
731 + log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
732 + remoteAddress, prefix, nextHop);
733 + }
734 + addedRoutes.put(prefix, bgpRouteEntry);
735 + }
736 +
737 + return addedRoutes.values();
738 + }
739 +
740 + /**
741 + * Verifies BGP UPDATE Well-known Attributes.
742 + *
743 + * @param ctx the Channel Handler Context
744 + * @param origin the ORIGIN well-known mandatory attribute
745 + * @param asPath the AS_PATH well-known mandatory attribute
746 + * @param nextHop the NEXT_HOP well-known mandatory attribute
747 + * @param localPref the LOCAL_PREF required attribute
748 + * @throws BgpParseException
749 + */
750 + private void verifyBgpUpdateWellKnownAttributes(
751 + ChannelHandlerContext ctx,
752 + Short origin,
753 + BgpRouteEntry.AsPath asPath,
754 + IpAddress nextHop,
755 + Long localPref)
756 + throws BgpParseException {
757 + //
758 + // Check for Missing Well-known Attributes
759 + //
760 + if ((origin == null) || (origin == -1)) {
761 + // Missing Attribute Type Code ORIGIN
762 + int type = BgpConstants.Update.Origin.TYPE;
763 + actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
764 + String errorMsg = "Missing Well-known Attribute: ORIGIN";
765 + throw new BgpParseException(errorMsg);
766 + }
767 + if (asPath == null) {
768 + // Missing Attribute Type Code AS_PATH
769 + int type = BgpConstants.Update.AsPath.TYPE;
770 + actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
771 + String errorMsg = "Missing Well-known Attribute: AS_PATH";
772 + throw new BgpParseException(errorMsg);
773 + }
774 + if (nextHop == null) {
775 + // Missing Attribute Type Code NEXT_HOP
776 + int type = BgpConstants.Update.NextHop.TYPE;
777 + actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
778 + String errorMsg = "Missing Well-known Attribute: NEXT_HOP";
779 + throw new BgpParseException(errorMsg);
780 + }
781 + if (localPref == null) {
782 + // Missing Attribute Type Code LOCAL_PREF
783 + // NOTE: Required for iBGP
784 + int type = BgpConstants.Update.LocalPref.TYPE;
785 + actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
786 + String errorMsg = "Missing Well-known Attribute: LOCAL_PREF";
787 + throw new BgpParseException(errorMsg);
788 + }
789 + }
790 +
791 + /**
792 + * Verifies the BGP UPDATE Attribute Flags.
793 + *
794 + * @param ctx the Channel Handler Context
795 + * @param attrTypeCode the attribute type code
796 + * @param attrLen the attribute length (in octets)
797 + * @param attrFlags the attribute flags
798 + * @param message the message to parse
799 + * @throws BgpParseException
800 + */
801 + private void verifyBgpUpdateAttributeFlags(
802 + ChannelHandlerContext ctx,
803 + int attrTypeCode,
804 + int attrLen,
805 + int attrFlags,
806 + ChannelBuffer message)
807 + throws BgpParseException {
808 +
809 + //
810 + // Assign the Attribute Type Name and the Well-known flag
811 + //
812 + String typeName = "UNKNOWN";
813 + boolean isWellKnown = false;
814 + switch (attrTypeCode) {
815 + case BgpConstants.Update.Origin.TYPE:
816 + isWellKnown = true;
817 + typeName = "ORIGIN";
818 + break;
819 + case BgpConstants.Update.AsPath.TYPE:
820 + isWellKnown = true;
821 + typeName = "AS_PATH";
822 + break;
823 + case BgpConstants.Update.NextHop.TYPE:
824 + isWellKnown = true;
825 + typeName = "NEXT_HOP";
826 + break;
827 + case BgpConstants.Update.MultiExitDisc.TYPE:
828 + isWellKnown = false;
829 + typeName = "MULTI_EXIT_DISC";
830 + break;
831 + case BgpConstants.Update.LocalPref.TYPE:
832 + isWellKnown = true;
833 + typeName = "LOCAL_PREF";
834 + break;
835 + case BgpConstants.Update.AtomicAggregate.TYPE:
836 + isWellKnown = true;
837 + typeName = "ATOMIC_AGGREGATE";
838 + break;
839 + case BgpConstants.Update.Aggregator.TYPE:
840 + isWellKnown = false;
841 + typeName = "AGGREGATOR";
842 + break;
843 + default:
844 + isWellKnown = false;
845 + typeName = "UNKNOWN(" + attrTypeCode + ")";
846 + break;
847 + }
848 +
849 + //
850 + // Verify the Attribute Flags
851 + //
852 + boolean optionalBit = ((0x80 & attrFlags) != 0);
853 + boolean transitiveBit = ((0x40 & attrFlags) != 0);
854 + boolean partialBit = ((0x20 & attrFlags) != 0);
855 + if ((isWellKnown && optionalBit) ||
856 + (isWellKnown && (!transitiveBit)) ||
857 + (isWellKnown && partialBit) ||
858 + (optionalBit && (!transitiveBit) && partialBit)) {
859 + //
860 + // ERROR: The Optional bit cannot be set for Well-known attributes
861 + // ERROR: The Transtive bit MUST be 1 for well-known attributes
862 + // ERROR: The Partial bit MUST be 0 for well-known attributes
863 + // ERROR: The Partial bit MUST be 0 for optional non-transitive
864 + // attributes
865 + //
866 + actionsBgpUpdateAttributeFlagsError(
867 + ctx, attrTypeCode, attrLen, attrFlags, message);
868 + String errorMsg = "Attribute Flags Error for " + typeName + ": " +
869 + attrFlags;
870 + throw new BgpParseException(errorMsg);
871 + }
872 + }
873 +
874 + /**
875 + * Parses BGP UPDATE Attribute Type ORIGIN.
876 + *
877 + * @param ctx the Channel Handler Context
878 + * @param attrTypeCode the attribute type code
879 + * @param attrLen the attribute length (in octets)
880 + * @param attrFlags the attribute flags
881 + * @param message the message to parse
882 + * @return the parsed ORIGIN value
883 + * @throws BgpParseException
884 + */
885 + private short parseAttributeTypeOrigin(
886 + ChannelHandlerContext ctx,
887 + int attrTypeCode,
888 + int attrLen,
889 + int attrFlags,
890 + ChannelBuffer message)
891 + throws BgpParseException {
892 +
893 + // Check the Attribute Length
894 + if (attrLen != BgpConstants.Update.Origin.LENGTH) {
895 + // ERROR: Attribute Length Error
896 + actionsBgpUpdateAttributeLengthError(
897 + ctx, attrTypeCode, attrLen, attrFlags, message);
898 + String errorMsg = "Attribute Length Error";
899 + throw new BgpParseException(errorMsg);
900 + }
901 +
902 + message.markReaderIndex();
903 + short origin = message.readUnsignedByte();
904 + switch (origin) {
905 + case BgpConstants.Update.Origin.IGP:
906 + // FALLTHROUGH
907 + case BgpConstants.Update.Origin.EGP:
908 + // FALLTHROUGH
909 + case BgpConstants.Update.Origin.INCOMPLETE:
910 + break;
911 + default:
912 + // ERROR: Invalid ORIGIN Attribute
913 + message.resetReaderIndex();
914 + actionsBgpUpdateInvalidOriginAttribute(
915 + ctx, attrTypeCode, attrLen, attrFlags, message, origin);
916 + String errorMsg = "Invalid ORIGIN Attribute: " + origin;
917 + throw new BgpParseException(errorMsg);
918 + }
919 +
920 + return origin;
921 + }
922 +
923 + /**
924 + * Parses BGP UPDATE Attribute AS Path.
925 + *
926 + * @param ctx the Channel Handler Context
927 + * @param attrTypeCode the attribute type code
928 + * @param attrLen the attribute length (in octets)
929 + * @param attrFlags the attribute flags
930 + * @param message the message to parse
931 + * @return the parsed AS Path
932 + * @throws BgpParseException
933 + */
934 + private BgpRouteEntry.AsPath parseAttributeTypeAsPath(
935 + ChannelHandlerContext ctx,
936 + int attrTypeCode,
937 + int attrLen,
938 + int attrFlags,
939 + ChannelBuffer message)
940 + throws BgpParseException {
941 + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
942 +
943 + //
944 + // Parse the message
945 + //
946 + while (attrLen > 0) {
947 + if (attrLen < 2) {
948 + // ERROR: Malformed AS_PATH
949 + actionsBgpUpdateMalformedAsPath(ctx);
950 + String errorMsg = "Malformed AS Path";
951 + throw new BgpParseException(errorMsg);
952 + }
953 + // Get the Path Segment Type and Length (in number of ASes)
954 + short pathSegmentType = message.readUnsignedByte();
955 + short pathSegmentLength = message.readUnsignedByte();
956 + attrLen -= 2;
957 +
958 + // Verify the Path Segment Type
959 + switch (pathSegmentType) {
960 + case BgpConstants.Update.AsPath.AS_SET:
961 + // FALLTHROUGH
962 + case BgpConstants.Update.AsPath.AS_SEQUENCE:
963 + break;
964 + default:
965 + // ERROR: Invalid Path Segment Type
966 + //
967 + // NOTE: The BGP Spec (RFC 4271) doesn't contain Error Subcode
968 + // for "Invalid Path Segment Type", hence we return
969 + // the error as "Malformed AS_PATH".
970 + //
971 + actionsBgpUpdateMalformedAsPath(ctx);
972 + String errorMsg =
973 + "Invalid AS Path Segment Type: " + pathSegmentType;
974 + throw new BgpParseException(errorMsg);
975 + }
976 +
977 + // Parse the AS numbers
978 + if (2 * pathSegmentLength > attrLen) {
979 + // ERROR: Malformed AS_PATH
980 + actionsBgpUpdateMalformedAsPath(ctx);
981 + String errorMsg = "Malformed AS Path";
982 + throw new BgpParseException(errorMsg);
983 + }
984 + attrLen -= (2 * pathSegmentLength);
985 + ArrayList<Long> segmentAsNumbers = new ArrayList<>();
986 + while (pathSegmentLength-- > 0) {
987 + long asNumber = message.readUnsignedShort();
988 + segmentAsNumbers.add(asNumber);
989 + }
990 +
991 + BgpRouteEntry.PathSegment pathSegment =
992 + new BgpRouteEntry.PathSegment((byte) pathSegmentType,
993 + segmentAsNumbers);
994 + pathSegments.add(pathSegment);
995 + }
996 +
997 + return new BgpRouteEntry.AsPath(pathSegments);
998 + }
999 +
1000 + /**
1001 + * Parses BGP UPDATE Attribute Type NEXT_HOP.
1002 + *
1003 + * @param ctx the Channel Handler Context
1004 + * @param attrTypeCode the attribute type code
1005 + * @param attrLen the attribute length (in octets)
1006 + * @param attrFlags the attribute flags
1007 + * @param message the message to parse
1008 + * @return the parsed NEXT_HOP value
1009 + * @throws BgpParseException
1010 + */
1011 + private IpAddress parseAttributeTypeNextHop(
1012 + ChannelHandlerContext ctx,
1013 + int attrTypeCode,
1014 + int attrLen,
1015 + int attrFlags,
1016 + ChannelBuffer message)
1017 + throws BgpParseException {
1018 +
1019 + // Check the Attribute Length
1020 + if (attrLen != BgpConstants.Update.NextHop.LENGTH) {
1021 + // ERROR: Attribute Length Error
1022 + actionsBgpUpdateAttributeLengthError(
1023 + ctx, attrTypeCode, attrLen, attrFlags, message);
1024 + String errorMsg = "Attribute Length Error";
1025 + throw new BgpParseException(errorMsg);
1026 + }
1027 +
1028 + message.markReaderIndex();
1029 + long address = message.readUnsignedInt();
1030 + IpAddress nextHopAddress = IpAddress.valueOf((int) address);
1031 + //
1032 + // Check whether the NEXT_HOP IP address is semantically correct.
1033 + // As per RFC 4271, Section 6.3:
1034 + //
1035 + // a) It MUST NOT be the IP address of the receiving speaker
1036 + // b) In the case of an EBGP ....
1037 + //
1038 + // Here we check only (a), because (b) doesn't apply for us: all our
1039 + // peers are iBGP.
1040 + //
1041 + if (nextHopAddress.equals(localIp4Address)) {
1042 + // ERROR: Invalid NEXT_HOP Attribute
1043 + message.resetReaderIndex();
1044 + actionsBgpUpdateInvalidNextHopAttribute(
1045 + ctx, attrTypeCode, attrLen, attrFlags, message,
1046 + nextHopAddress);
1047 + String errorMsg = "Invalid NEXT_HOP Attribute: " + nextHopAddress;
1048 + throw new BgpParseException(errorMsg);
1049 + }
1050 +
1051 + return nextHopAddress;
1052 + }
1053 +
1054 + /**
1055 + * Parses BGP UPDATE Attribute Type MULTI_EXIT_DISC.
1056 + *
1057 + * @param ctx the Channel Handler Context
1058 + * @param attrTypeCode the attribute type code
1059 + * @param attrLen the attribute length (in octets)
1060 + * @param attrFlags the attribute flags
1061 + * @param message the message to parse
1062 + * @return the parsed MULTI_EXIT_DISC value
1063 + * @throws BgpParseException
1064 + */
1065 + private long parseAttributeTypeMultiExitDisc(
1066 + ChannelHandlerContext ctx,
1067 + int attrTypeCode,
1068 + int attrLen,
1069 + int attrFlags,
1070 + ChannelBuffer message)
1071 + throws BgpParseException {
1072 +
1073 + // Check the Attribute Length
1074 + if (attrLen != BgpConstants.Update.MultiExitDisc.LENGTH) {
1075 + // ERROR: Attribute Length Error
1076 + actionsBgpUpdateAttributeLengthError(
1077 + ctx, attrTypeCode, attrLen, attrFlags, message);
1078 + String errorMsg = "Attribute Length Error";
1079 + throw new BgpParseException(errorMsg);
1080 + }
1081 +
1082 + long multiExitDisc = message.readUnsignedInt();
1083 + return multiExitDisc;
1084 + }
1085 +
1086 + /**
1087 + * Parses BGP UPDATE Attribute Type LOCAL_PREF.
1088 + *
1089 + * @param ctx the Channel Handler Context
1090 + * @param attrTypeCode the attribute type code
1091 + * @param attrLen the attribute length (in octets)
1092 + * @param attrFlags the attribute flags
1093 + * @param message the message to parse
1094 + * @return the parsed LOCAL_PREF value
1095 + * @throws BgpParseException
1096 + */
1097 + private long parseAttributeTypeLocalPref(
1098 + ChannelHandlerContext ctx,
1099 + int attrTypeCode,
1100 + int attrLen,
1101 + int attrFlags,
1102 + ChannelBuffer message)
1103 + throws BgpParseException {
1104 +
1105 + // Check the Attribute Length
1106 + if (attrLen != BgpConstants.Update.LocalPref.LENGTH) {
1107 + // ERROR: Attribute Length Error
1108 + actionsBgpUpdateAttributeLengthError(
1109 + ctx, attrTypeCode, attrLen, attrFlags, message);
1110 + String errorMsg = "Attribute Length Error";
1111 + throw new BgpParseException(errorMsg);
1112 + }
1113 +
1114 + long localPref = message.readUnsignedInt();
1115 + return localPref;
1116 + }
1117 +
1118 + /**
1119 + * Parses BGP UPDATE Attribute Type ATOMIC_AGGREGATE.
1120 + *
1121 + * @param ctx the Channel Handler Context
1122 + * @param attrTypeCode the attribute type code
1123 + * @param attrLen the attribute length (in octets)
1124 + * @param attrFlags the attribute flags
1125 + * @param message the message to parse
1126 + * @throws BgpParseException
1127 + */
1128 + private void parseAttributeTypeAtomicAggregate(
1129 + ChannelHandlerContext ctx,
1130 + int attrTypeCode,
1131 + int attrLen,
1132 + int attrFlags,
1133 + ChannelBuffer message)
1134 + throws BgpParseException {
1135 +
1136 + // Check the Attribute Length
1137 + if (attrLen != BgpConstants.Update.AtomicAggregate.LENGTH) {
1138 + // ERROR: Attribute Length Error
1139 + actionsBgpUpdateAttributeLengthError(
1140 + ctx, attrTypeCode, attrLen, attrFlags, message);
1141 + String errorMsg = "Attribute Length Error";
1142 + throw new BgpParseException(errorMsg);
1143 + }
1144 +
1145 + // Nothing to do: this attribute is primarily informational
1146 + }
1147 +
1148 + /**
1149 + * Parses BGP UPDATE Attribute Type AGGREGATOR.
1150 + *
1151 + * @param ctx the Channel Handler Context
1152 + * @param attrTypeCode the attribute type code
1153 + * @param attrLen the attribute length (in octets)
1154 + * @param attrFlags the attribute flags
1155 + * @param message the message to parse
1156 + * @return the parsed AGGREGATOR value: a tuple of <AS-Number, IP-Address>
1157 + * @throws BgpParseException
1158 + */
1159 + private Pair<Long, IpAddress> parseAttributeTypeAggregator(
1160 + ChannelHandlerContext ctx,
1161 + int attrTypeCode,
1162 + int attrLen,
1163 + int attrFlags,
1164 + ChannelBuffer message)
1165 + throws BgpParseException {
1166 +
1167 + // Check the Attribute Length
1168 + if (attrLen != BgpConstants.Update.Aggregator.LENGTH) {
1169 + // ERROR: Attribute Length Error
1170 + actionsBgpUpdateAttributeLengthError(
1171 + ctx, attrTypeCode, attrLen, attrFlags, message);
1172 + String errorMsg = "Attribute Length Error";
1173 + throw new BgpParseException(errorMsg);
1174 + }
1175 +
1176 + // The AGGREGATOR AS number
1177 + long aggregatorAsNumber = message.readUnsignedShort();
1178 + // The AGGREGATOR IP address
1179 + long aggregatorAddress = message.readUnsignedInt();
1180 + IpAddress aggregatorIpAddress =
1181 + IpAddress.valueOf((int) aggregatorAddress);
1182 +
1183 + Pair<Long, IpAddress> aggregator = Pair.of(aggregatorAsNumber,
1184 + aggregatorIpAddress);
1185 + return aggregator;
1186 + }
1187 +
1188 + /**
1189 + * Parses a message that contains encoded IPv4 network prefixes.
1190 + * <p>
1191 + * The IPv4 prefixes are encoded in the form:
1192 + * <Length, Prefix> where Length is the length in bits of the IPv4 prefix,
1193 + * and Prefix is the IPv4 prefix (padded with trailing bits to the end
1194 + * of an octet).
1195 + *
1196 + * @param totalLength the total length of the data to parse
1197 + * @param message the message with data to parse
1198 + * @return a collection of parsed IPv4 network prefixes
1199 + * @throws BgpParseException
1200 + */
1201 + private Collection<IpPrefix> parsePackedPrefixes(int totalLength,
1202 + ChannelBuffer message)
1203 + throws BgpParseException {
1204 + Collection<IpPrefix> result = new ArrayList<>();
1205 +
1206 + if (totalLength == 0) {
1207 + return result;
1208 + }
1209 +
1210 + // Parse the data
1211 + int dataEnd = message.readerIndex() + totalLength;
1212 + while (message.readerIndex() < dataEnd) {
1213 + int prefixBitlen = message.readUnsignedByte();
1214 + int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up
1215 + if (message.readerIndex() + prefixBytelen > dataEnd) {
1216 + String errorMsg = "Malformed Network Prefixes";
1217 + throw new BgpParseException(errorMsg);
1218 + }
1219 +
1220 + long address = 0;
1221 + long extraShift = (4 - prefixBytelen) * 8;
1222 + while (prefixBytelen > 0) {
1223 + address <<= 8;
1224 + address |= message.readUnsignedByte();
1225 + prefixBytelen--;
1226 + }
1227 + address <<= extraShift;
1228 + IpPrefix prefix =
1229 + IpPrefix.valueOf(IpAddress.valueOf((int) address).toInt(),
1230 + (short) prefixBitlen);
1231 + result.add(prefix);
1232 + }
1233 +
1234 + return result;
1235 + }
1236 +
1237 + /**
1238 + * Applies the appropriate actions after detecting BGP UPDATE
1239 + * Invalid Network Field Error: send NOTIFICATION and close the channel.
1240 + *
1241 + * @param ctx the Channel Handler Context
1242 + */
1243 + private void actionsBgpUpdateInvalidNetworkField(
1244 + ChannelHandlerContext ctx) {
1245 + log.debug("BGP RX UPDATE Error from {}: Invalid Network Field",
1246 + remoteAddress);
1247 +
1248 + //
1249 + // ERROR: Invalid Network Field
1250 + //
1251 + // Send NOTIFICATION and close the connection
1252 + int errorCode = UpdateMessageError.ERROR_CODE;
1253 + int errorSubcode = UpdateMessageError.INVALID_NETWORK_FIELD;
1254 + ChannelBuffer txMessage =
1255 + prepareBgpNotification(errorCode, errorSubcode, null);
1256 + ctx.getChannel().write(txMessage);
1257 + closeChannel(ctx);
1258 + }
1259 +
1260 + /**
1261 + * Applies the appropriate actions after detecting BGP UPDATE
1262 + * Malformed Attribute List Error: send NOTIFICATION and close the channel.
1263 + *
1264 + * @param ctx the Channel Handler Context
1265 + */
1266 + private void actionsBgpUpdateMalformedAttributeList(
1267 + ChannelHandlerContext ctx) {
1268 + log.debug("BGP RX UPDATE Error from {}: Malformed Attribute List",
1269 + remoteAddress);
1270 +
1271 + //
1272 + // ERROR: Malformed Attribute List
1273 + //
1274 + // Send NOTIFICATION and close the connection
1275 + int errorCode = UpdateMessageError.ERROR_CODE;
1276 + int errorSubcode = UpdateMessageError.MALFORMED_ATTRIBUTE_LIST;
1277 + ChannelBuffer txMessage =
1278 + prepareBgpNotification(errorCode, errorSubcode, null);
1279 + ctx.getChannel().write(txMessage);
1280 + closeChannel(ctx);
1281 + }
1282 +
1283 + /**
1284 + * Applies the appropriate actions after detecting BGP UPDATE
1285 + * Missing Well-known Attribute Error: send NOTIFICATION and close the
1286 + * channel.
1287 + *
1288 + * @param ctx the Channel Handler Context
1289 + * @param missingAttrTypeCode the missing attribute type code
1290 + */
1291 + private void actionsBgpUpdateMissingWellKnownAttribute(
1292 + ChannelHandlerContext ctx,
1293 + int missingAttrTypeCode) {
1294 + log.debug("BGP RX UPDATE Error from {}: Missing Well-known Attribute: {}",
1295 + remoteAddress, missingAttrTypeCode);
1296 +
1297 + //
1298 + // ERROR: Missing Well-known Attribute
1299 + //
1300 + // Send NOTIFICATION and close the connection
1301 + int errorCode = UpdateMessageError.ERROR_CODE;
1302 + int errorSubcode = UpdateMessageError.MISSING_WELL_KNOWN_ATTRIBUTE;
1303 + ChannelBuffer data = ChannelBuffers.buffer(1);
1304 + data.writeByte(missingAttrTypeCode);
1305 + ChannelBuffer txMessage =
1306 + prepareBgpNotification(errorCode, errorSubcode, data);
1307 + ctx.getChannel().write(txMessage);
1308 + closeChannel(ctx);
1309 + }
1310 +
1311 + /**
1312 + * Applies the appropriate actions after detecting BGP UPDATE
1313 + * Invalid ORIGIN Attribute Error: send NOTIFICATION and close the channel.
1314 + *
1315 + * @param ctx the Channel Handler Context
1316 + * @param attrTypeCode the attribute type code
1317 + * @param attrLen the attribute length (in octets)
1318 + * @param attrFlags the attribute flags
1319 + * @param message the message with the data
1320 + * @param origin the ORIGIN attribute value
1321 + */
1322 + private void actionsBgpUpdateInvalidOriginAttribute(
1323 + ChannelHandlerContext ctx,
1324 + int attrTypeCode,
1325 + int attrLen,
1326 + int attrFlags,
1327 + ChannelBuffer message,
1328 + short origin) {
1329 + log.debug("BGP RX UPDATE Error from {}: Invalid ORIGIN Attribute",
1330 + remoteAddress);
1331 +
1332 + //
1333 + // ERROR: Invalid ORIGIN Attribute
1334 + //
1335 + // Send NOTIFICATION and close the connection
1336 + int errorCode = UpdateMessageError.ERROR_CODE;
1337 + int errorSubcode = UpdateMessageError.INVALID_ORIGIN_ATTRIBUTE;
1338 + ChannelBuffer data =
1339 + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
1340 + attrFlags, message);
1341 + ChannelBuffer txMessage =
1342 + prepareBgpNotification(errorCode, errorSubcode, data);
1343 + ctx.getChannel().write(txMessage);
1344 + closeChannel(ctx);
1345 + }
1346 +
1347 + /**
1348 + * Applies the appropriate actions after detecting BGP UPDATE
1349 + * Attribute Flags Error: send NOTIFICATION and close the channel.
1350 + *
1351 + * @param ctx the Channel Handler Context
1352 + * @param attrTypeCode the attribute type code
1353 + * @param attrLen the attribute length (in octets)
1354 + * @param attrFlags the attribute flags
1355 + * @param message the message with the data
1356 + */
1357 + private void actionsBgpUpdateAttributeFlagsError(
1358 + ChannelHandlerContext ctx,
1359 + int attrTypeCode,
1360 + int attrLen,
1361 + int attrFlags,
1362 + ChannelBuffer message) {
1363 + log.debug("BGP RX UPDATE Error from {}: Attribute Flags Error",
1364 + remoteAddress);
1365 +
1366 + //
1367 + // ERROR: Attribute Flags Error
1368 + //
1369 + // Send NOTIFICATION and close the connection
1370 + int errorCode = UpdateMessageError.ERROR_CODE;
1371 + int errorSubcode = UpdateMessageError.ATTRIBUTE_FLAGS_ERROR;
1372 + ChannelBuffer data =
1373 + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
1374 + attrFlags, message);
1375 + ChannelBuffer txMessage =
1376 + prepareBgpNotification(errorCode, errorSubcode, data);
1377 + ctx.getChannel().write(txMessage);
1378 + closeChannel(ctx);
1379 + }
1380 +
1381 + /**
1382 + * Applies the appropriate actions after detecting BGP UPDATE
1383 + * Invalid NEXT_HOP Attribute Error: send NOTIFICATION and close the
1384 + * channel.
1385 + *
1386 + * @param ctx the Channel Handler Context
1387 + * @param attrTypeCode the attribute type code
1388 + * @param attrLen the attribute length (in octets)
1389 + * @param attrFlags the attribute flags
1390 + * @param message the message with the data
1391 + * @param nextHop the NEXT_HOP attribute value
1392 + */
1393 + private void actionsBgpUpdateInvalidNextHopAttribute(
1394 + ChannelHandlerContext ctx,
1395 + int attrTypeCode,
1396 + int attrLen,
1397 + int attrFlags,
1398 + ChannelBuffer message,
1399 + IpAddress nextHop) {
1400 + log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}",
1401 + remoteAddress, nextHop);
1402 +
1403 + //
1404 + // ERROR: Invalid ORIGIN Attribute
1405 + //
1406 + // Send NOTIFICATION and close the connection
1407 + int errorCode = UpdateMessageError.ERROR_CODE;
1408 + int errorSubcode = UpdateMessageError.INVALID_NEXT_HOP_ATTRIBUTE;
1409 + ChannelBuffer data =
1410 + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
1411 + attrFlags, message);
1412 + ChannelBuffer txMessage =
1413 + prepareBgpNotification(errorCode, errorSubcode, data);
1414 + ctx.getChannel().write(txMessage);
1415 + closeChannel(ctx);
1416 + }
1417 +
1418 + /**
1419 + * Applies the appropriate actions after detecting BGP UPDATE
1420 + * Unrecognized Well-known Attribute Error: send NOTIFICATION and close
1421 + * the channel.
1422 + *
1423 + * @param ctx the Channel Handler Context
1424 + * @param attrTypeCode the attribute type code
1425 + * @param attrLen the attribute length (in octets)
1426 + * @param attrFlags the attribute flags
1427 + * @param message the message with the data
1428 + */
1429 + private void actionsBgpUpdateUnrecognizedWellKnownAttribute(
1430 + ChannelHandlerContext ctx,
1431 + int attrTypeCode,
1432 + int attrLen,
1433 + int attrFlags,
1434 + ChannelBuffer message) {
1435 + log.debug("BGP RX UPDATE Error from {}: " +
1436 + "Unrecognized Well-known Attribute Error: {}",
1437 + remoteAddress, attrTypeCode);
1438 +
1439 + //
1440 + // ERROR: Unrecognized Well-known Attribute
1441 + //
1442 + // Send NOTIFICATION and close the connection
1443 + int errorCode = UpdateMessageError.ERROR_CODE;
1444 + int errorSubcode =
1445 + UpdateMessageError.UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE;
1446 + ChannelBuffer data =
1447 + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
1448 + attrFlags, message);
1449 + ChannelBuffer txMessage =
1450 + prepareBgpNotification(errorCode, errorSubcode, data);
1451 + ctx.getChannel().write(txMessage);
1452 + closeChannel(ctx);
1453 + }
1454 +
1455 + /**
1456 + * Applies the appropriate actions after detecting BGP UPDATE
1457 + * Attribute Length Error: send NOTIFICATION and close the channel.
1458 + *
1459 + * @param ctx the Channel Handler Context
1460 + * @param attrTypeCode the attribute type code
1461 + * @param attrLen the attribute length (in octets)
1462 + * @param attrFlags the attribute flags
1463 + * @param message the message with the data
1464 + */
1465 + private void actionsBgpUpdateAttributeLengthError(
1466 + ChannelHandlerContext ctx,
1467 + int attrTypeCode,
1468 + int attrLen,
1469 + int attrFlags,
1470 + ChannelBuffer message) {
1471 + log.debug("BGP RX UPDATE Error from {}: Attribute Length Error",
1472 + remoteAddress);
1473 +
1474 + //
1475 + // ERROR: Attribute Length Error
1476 + //
1477 + // Send NOTIFICATION and close the connection
1478 + int errorCode = UpdateMessageError.ERROR_CODE;
1479 + int errorSubcode = UpdateMessageError.ATTRIBUTE_LENGTH_ERROR;
1480 + ChannelBuffer data =
1481 + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
1482 + attrFlags, message);
1483 + ChannelBuffer txMessage =
1484 + prepareBgpNotification(errorCode, errorSubcode, data);
1485 + ctx.getChannel().write(txMessage);
1486 + closeChannel(ctx);
1487 + }
1488 +
1489 + /**
1490 + * Applies the appropriate actions after detecting BGP UPDATE
1491 + * Malformed AS_PATH Error: send NOTIFICATION and close the channel.
1492 + *
1493 + * @param ctx the Channel Handler Context
1494 + */
1495 + private void actionsBgpUpdateMalformedAsPath(
1496 + ChannelHandlerContext ctx) {
1497 + log.debug("BGP RX UPDATE Error from {}: Malformed AS Path",
1498 + remoteAddress);
1499 +
1500 + //
1501 + // ERROR: Malformed AS_PATH
1502 + //
1503 + // Send NOTIFICATION and close the connection
1504 + int errorCode = UpdateMessageError.ERROR_CODE;
1505 + int errorSubcode = UpdateMessageError.MALFORMED_AS_PATH;
1506 + ChannelBuffer txMessage =
1507 + prepareBgpNotification(errorCode, errorSubcode, null);
1508 + ctx.getChannel().write(txMessage);
1509 + closeChannel(ctx);
1510 + }
1511 +
1512 + /**
1513 + * Processes BGP NOTIFICATION message.
1514 + *
1515 + * @param ctx the Channel Handler Context
1516 + * @param message the message to process
1517 + */
1518 + void processBgpNotification(ChannelHandlerContext ctx,
1519 + ChannelBuffer message) {
1520 + int minLength =
1521 + BgpConstants.BGP_NOTIFICATION_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
1522 + if (message.readableBytes() < minLength) {
1523 + log.debug("BGP RX NOTIFICATION Error from {}: " +
1524 + "Message length {} too short. Must be at least {}",
1525 + remoteAddress, message.readableBytes(), minLength);
1526 + //
1527 + // ERROR: Bad Message Length
1528 + //
1529 + // NOTE: We do NOT send NOTIFICATION in response to a notification
1530 + return;
1531 + }
1532 +
1533 + //
1534 + // Parse the NOTIFICATION message
1535 + //
1536 + int errorCode = message.readUnsignedByte();
1537 + int errorSubcode = message.readUnsignedByte();
1538 + int dataLength = message.readableBytes();
1539 +
1540 + log.debug("BGP RX NOTIFICATION message from {}: Error Code {} " +
1541 + "Error Subcode {} Data Length {}",
1542 + remoteAddress, errorCode, errorSubcode, dataLength);
1543 +
1544 + //
1545 + // NOTE: If the peer sent a NOTIFICATION, we leave it to the peer to
1546 + // close the connection.
1547 + //
1548 +
1549 + // Start the Session Timeout timer
1550 + restartSessionTimeoutTimer(ctx);
1551 + }
1552 +
1553 + /**
1554 + * Processes BGP KEEPALIVE message.
1555 + *
1556 + * @param ctx the Channel Handler Context
1557 + * @param message the message to process
1558 + */
1559 + void processBgpKeepalive(ChannelHandlerContext ctx,
1560 + ChannelBuffer message) {
1561 + if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
1562 + BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
1563 + log.debug("BGP RX KEEPALIVE Error from {}: " +
1564 + "Invalid total message length {}. Expected {}",
1565 + remoteAddress,
1566 + message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH,
1567 + BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH);
1568 + //
1569 + // ERROR: Bad Message Length
1570 + //
1571 + // Send NOTIFICATION and close the connection
1572 + ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
1573 + message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
1574 + ctx.getChannel().write(txMessage);
1575 + closeChannel(ctx);
1576 + return;
1577 + }
1578 +
1579 + //
1580 + // Parse the KEEPALIVE message: nothing to do
1581 + //
1582 + log.debug("BGP RX KEEPALIVE message from {}", remoteAddress);
1583 +
1584 + // Start the Session Timeout timer
1585 + restartSessionTimeoutTimer(ctx);
1586 + }
1587 +
1588 + /**
1589 + * Prepares BGP OPEN message.
1590 + *
1591 + * @return the message to transmit (BGP header included)
1592 + */
1593 + private ChannelBuffer prepareBgpOpen() {
1594 + ChannelBuffer message =
1595 + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
1596 +
1597 + //
1598 + // Prepare the OPEN message payload
1599 + //
1600 + message.writeByte(localBgpVersion);
1601 + message.writeShort((int) localAs);
1602 + message.writeShort((int) localHoldtime);
1603 + message.writeInt(bgpSessionManager.getMyBgpId().toInt());
1604 + message.writeByte(0); // No Optional Parameters
1605 + return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message);
1606 + }
1607 +
1608 + /**
1609 + * Prepares BGP KEEPALIVE message.
1610 + *
1611 + * @return the message to transmit (BGP header included)
1612 + */
1613 + private ChannelBuffer prepareBgpKeepalive() {
1614 + ChannelBuffer message =
1615 + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
1616 +
1617 + //
1618 + // Prepare the KEEPALIVE message payload: nothing to do
1619 + //
1620 + return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message);
1621 + }
1622 +
1623 + /**
1624 + * Prepares BGP NOTIFICATION message.
1625 + *
1626 + * @param errorCode the BGP NOTIFICATION Error Code
1627 + * @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable,
1628 + * otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC
1629 + * @param payload the BGP NOTIFICATION Data if applicable, otherwise null
1630 + * @return the message to transmit (BGP header included)
1631 + */
1632 + ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode,
1633 + ChannelBuffer data) {
1634 + ChannelBuffer message =
1635 + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
1636 +
1637 + //
1638 + // Prepare the NOTIFICATION message payload
1639 + //
1640 + message.writeByte(errorCode);
1641 + message.writeByte(errorSubcode);
1642 + if (data != null) {
1643 + message.writeBytes(data);
1644 + }
1645 + return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message);
1646 + }
1647 +
1648 + /**
1649 + * Prepares BGP NOTIFICATION message: Bad Message Length.
1650 + *
1651 + * @param length the erroneous Length field
1652 + * @return the message to transmit (BGP header included)
1653 + */
1654 + ChannelBuffer prepareBgpNotificationBadMessageLength(int length) {
1655 + int errorCode = MessageHeaderError.ERROR_CODE;
1656 + int errorSubcode = MessageHeaderError.BAD_MESSAGE_LENGTH;
1657 + ChannelBuffer data = ChannelBuffers.buffer(2);
1658 + data.writeShort(length);
1659 +
1660 + return prepareBgpNotification(errorCode, errorSubcode, data);
1661 + }
1662 +
1663 + /**
1664 + * Prepares BGP UPDATE Notification data payload.
1665 + *
1666 + * @param attrTypeCode the attribute type code
1667 + * @param attrLen the attribute length (in octets)
1668 + * @param attrFlags the attribute flags
1669 + * @param message the message with the data
1670 + * @return the buffer with the data payload for the BGP UPDATE Notification
1671 + */
1672 + private ChannelBuffer prepareBgpUpdateNotificationDataPayload(
1673 + int attrTypeCode,
1674 + int attrLen,
1675 + int attrFlags,
1676 + ChannelBuffer message) {
1677 + // Compute the attribute length field octets
1678 + boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
1679 + int attrLenOctets = 1;
1680 + if (extendedLengthBit) {
1681 + attrLenOctets = 2;
1682 + }
1683 + ChannelBuffer data =
1684 + ChannelBuffers.buffer(attrLen + attrLenOctets + 1);
1685 + data.writeByte(attrTypeCode);
1686 + if (extendedLengthBit) {
1687 + data.writeShort(attrLen);
1688 + } else {
1689 + data.writeByte(attrLen);
1690 + }
1691 + data.writeBytes(message, attrLen);
1692 + return data;
1693 + }
1694 +
1695 + /**
1696 + * Prepares BGP message.
1697 + *
1698 + * @param type the BGP message type
1699 + * @param payload the message payload to transmit (BGP header excluded)
1700 + * @return the message to transmit (BGP header included)
1701 + */
1702 + private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) {
1703 + ChannelBuffer message =
1704 + ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH +
1705 + payload.readableBytes());
1706 +
1707 + // Write the marker
1708 + for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) {
1709 + message.writeByte(0xff);
1710 + }
1711 +
1712 + // Write the rest of the BGP header
1713 + message.writeShort(BgpConstants.BGP_HEADER_LENGTH +
1714 + payload.readableBytes());
1715 + message.writeByte(type);
1716 +
1717 + // Write the payload
1718 + message.writeBytes(payload);
1719 + return message;
1720 + }
1721 +
1722 + /**
1723 + * Restarts the BGP KeepaliveTimer.
1724 + */
1725 + private void restartKeepaliveTimer(ChannelHandlerContext ctx) {
1726 + if (localKeepaliveInterval == 0) {
1727 + return; // Nothing to do
1728 + }
1729 + keepaliveTimeout = timer.newTimeout(new TransmitKeepaliveTask(ctx),
1730 + localKeepaliveInterval,
1731 + TimeUnit.SECONDS);
1732 + }
1733 +
1734 + /**
1735 + * Task class for transmitting KEEPALIVE messages.
1736 + */
1737 + private final class TransmitKeepaliveTask implements TimerTask {
1738 + private final ChannelHandlerContext ctx;
1739 +
1740 + /**
1741 + * Constructor for given Channel Handler Context.
1742 + *
1743 + * @param ctx the Channel Handler Context to use
1744 + */
1745 + TransmitKeepaliveTask(ChannelHandlerContext ctx) {
1746 + this.ctx = ctx;
1747 + }
1748 +
1749 + @Override
1750 + public void run(Timeout timeout) throws Exception {
1751 + if (timeout.isCancelled()) {
1752 + return;
1753 + }
1754 + if (!ctx.getChannel().isOpen()) {
1755 + return;
1756 + }
1757 +
1758 + // Transmit the KEEPALIVE
1759 + ChannelBuffer txMessage = prepareBgpKeepalive();
1760 + ctx.getChannel().write(txMessage);
1761 +
1762 + // Restart the KEEPALIVE timer
1763 + restartKeepaliveTimer(ctx);
1764 + }
1765 + }
1766 +
1767 + /**
1768 + * Restarts the BGP Session Timeout Timer.
1769 + */
1770 + private void restartSessionTimeoutTimer(ChannelHandlerContext ctx) {
1771 + if (remoteHoldtime == 0) {
1772 + return; // Nothing to do
1773 + }
1774 + if (sessionTimeout != null) {
1775 + sessionTimeout.cancel();
1776 + }
1777 + sessionTimeout = timer.newTimeout(new SessionTimeoutTask(ctx),
1778 + remoteHoldtime,
1779 + TimeUnit.SECONDS);
1780 + }
1781 +
1782 + /**
1783 + * Task class for BGP Session timeout.
1784 + */
1785 + private final class SessionTimeoutTask implements TimerTask {
1786 + private final ChannelHandlerContext ctx;
1787 +
1788 + /**
1789 + * Constructor for given Channel Handler Context.
1790 + *
1791 + * @param ctx the Channel Handler Context to use
1792 + */
1793 + SessionTimeoutTask(ChannelHandlerContext ctx) {
1794 + this.ctx = ctx;
1795 + }
1796 +
1797 + @Override
1798 + public void run(Timeout timeout) throws Exception {
1799 + if (timeout.isCancelled()) {
1800 + return;
1801 + }
1802 + if (!ctx.getChannel().isOpen()) {
1803 + return;
1804 + }
1805 +
1806 + log.debug("BGP Session Timeout: peer {}", remoteAddress);
1807 + //
1808 + // ERROR: Invalid Optional Parameter Length field: Unspecific
1809 + //
1810 + // Send NOTIFICATION and close the connection
1811 + int errorCode = HoldTimerExpired.ERROR_CODE;
1812 + int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
1813 + ChannelBuffer txMessage =
1814 + prepareBgpNotification(errorCode, errorSubcode, null);
1815 + ctx.getChannel().write(txMessage);
1816 + closeChannel(ctx);
1817 + }
1818 + }
1819 +
1820 + /**
1821 + * An exception indicating a parsing error of the BGP message.
1822 + */
1823 + private static class BgpParseException extends Exception {
1824 + /**
1825 + * Default constructor.
1826 + */
1827 + public BgpParseException() {
1828 + super();
1829 + }
1830 +
1831 + /**
1832 + * Constructor for a specific exception details message.
1833 + *
1834 + * @param message the message with the exception details
1835 + */
1836 + public BgpParseException(String message) {
1837 + super(message);
1838 + }
1839 + }
1840 +}
1 +package org.onlab.onos.sdnip.bgp;
2 +
3 +import static com.google.common.base.Preconditions.checkNotNull;
4 +
5 +import java.net.InetAddress;
6 +import java.net.InetSocketAddress;
7 +import java.net.SocketAddress;
8 +import java.util.Collection;
9 +import java.util.concurrent.ConcurrentHashMap;
10 +import java.util.concurrent.ConcurrentMap;
11 +import java.util.concurrent.Executors;
12 +
13 +import org.jboss.netty.bootstrap.ServerBootstrap;
14 +import org.jboss.netty.channel.Channel;
15 +import org.jboss.netty.channel.ChannelException;
16 +import org.jboss.netty.channel.ChannelFactory;
17 +import org.jboss.netty.channel.ChannelPipeline;
18 +import org.jboss.netty.channel.ChannelPipelineFactory;
19 +import org.jboss.netty.channel.Channels;
20 +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
21 +import org.onlab.onos.sdnip.RouteListener;
22 +import org.onlab.onos.sdnip.RouteUpdate;
23 +import org.onlab.packet.IpAddress;
24 +import org.onlab.packet.IpPrefix;
25 +import org.slf4j.Logger;
26 +import org.slf4j.LoggerFactory;
27 +
28 +/**
29 + * BGP Session Manager class.
30 + */
31 +public class BgpSessionManager {
32 + private static final Logger log =
33 + LoggerFactory.getLogger(BgpSessionManager.class);
34 + private Channel serverChannel; // Listener for incoming BGP connections
35 + private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
36 + new ConcurrentHashMap<>();
37 + private IpAddress myBgpId; // Same BGP ID for all peers
38 +
39 + private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
40 + private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRoutes =
41 + new ConcurrentHashMap<>();
42 +
43 + private final RouteListener routeListener;
44 +
45 + /**
46 + * Constructor for given route listener.
47 + *
48 + * @param routeListener the route listener to use
49 + */
50 + public BgpSessionManager(RouteListener routeListener) {
51 + this.routeListener = checkNotNull(routeListener);
52 + }
53 +
54 + /**
55 + * Gets the BGP sessions.
56 + *
57 + * @return the BGP sessions
58 + */
59 + public Collection<BgpSession> getBgpSessions() {
60 + return bgpSessions.values();
61 + }
62 +
63 + /**
64 + * Gets the BGP routes.
65 + *
66 + * @return the BGP routes
67 + */
68 + public Collection<BgpRouteEntry> getBgpRoutes() {
69 + return bgpRoutes.values();
70 + }
71 +
72 + /**
73 + * Processes the connection from a BGP peer.
74 + *
75 + * @param bgpSession the BGP session for the peer
76 + * @return true if the connection can be established, otherwise false
77 + */
78 + boolean peerConnected(BgpSession bgpSession) {
79 +
80 + // Test whether there is already a session from the same remote
81 + if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) {
82 + return false; // Duplicate BGP session
83 + }
84 + bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession);
85 +
86 + //
87 + // If the first connection, set my BGP ID to the local address
88 + // of the socket.
89 + //
90 + if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
91 + InetAddress inetAddr =
92 + ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
93 + IpAddress ip4Address = IpAddress.valueOf(inetAddr.getAddress());
94 + updateMyBgpId(ip4Address);
95 + }
96 + return true;
97 + }
98 +
99 + /**
100 + * Processes the disconnection from a BGP peer.
101 + *
102 + * @param bgpSession the BGP session for the peer
103 + */
104 + void peerDisconnected(BgpSession bgpSession) {
105 + bgpSessions.remove(bgpSession.getRemoteAddress());
106 + }
107 +
108 + /**
109 + * Conditionally updates the local BGP ID if it wasn't set already.
110 + * <p/>
111 + * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
112 + *
113 + * @param ip4Address the IPv4 address to use as BGP ID
114 + */
115 + private synchronized void updateMyBgpId(IpAddress ip4Address) {
116 + if (myBgpId == null) {
117 + myBgpId = ip4Address;
118 + log.debug("BGP: My BGP ID is {}", myBgpId);
119 + }
120 + }
121 +
122 + /**
123 + * Gets the local BGP Identifier as an IPv4 address.
124 + *
125 + * @return the local BGP Identifier as an IPv4 address
126 + */
127 + IpAddress getMyBgpId() {
128 + return myBgpId;
129 + }
130 +
131 + /**
132 + * Gets the BGP Route Selector.
133 + *
134 + * @return the BGP Route Selector
135 + */
136 + BgpRouteSelector getBgpRouteSelector() {
137 + return bgpRouteSelector;
138 + }
139 +
140 + /**
141 + * Starts up BGP Session Manager operation.
142 + *
143 + * @param listenPortNumber the port number to listen on. By default
144 + * it should be BgpConstants.BGP_PORT (179)
145 + */
146 + public void startUp(int listenPortNumber) {
147 + log.debug("BGP Session Manager startUp()");
148 +
149 + ChannelFactory channelFactory =
150 + new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
151 + Executors.newCachedThreadPool());
152 + ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
153 + @Override
154 + public ChannelPipeline getPipeline() throws Exception {
155 + // Allocate a new session per connection
156 + BgpSession bgpSessionHandler =
157 + new BgpSession(BgpSessionManager.this);
158 + BgpFrameDecoder bgpFrameDecoder =
159 + new BgpFrameDecoder(bgpSessionHandler);
160 +
161 + // Setup the processing pipeline
162 + ChannelPipeline pipeline = Channels.pipeline();
163 + pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
164 + pipeline.addLast("BgpSession", bgpSessionHandler);
165 + return pipeline;
166 + }
167 + };
168 + InetSocketAddress listenAddress =
169 + new InetSocketAddress(listenPortNumber);
170 +
171 + ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory);
172 + // serverBootstrap.setOptions("reuseAddr", true);
173 + serverBootstrap.setOption("child.keepAlive", true);
174 + serverBootstrap.setOption("child.tcpNoDelay", true);
175 + serverBootstrap.setPipelineFactory(pipelineFactory);
176 + try {
177 + serverChannel = serverBootstrap.bind(listenAddress);
178 + } catch (ChannelException e) {
179 + log.debug("Exception binding to BGP port {}: ",
180 + listenAddress.getPort(), e);
181 + }
182 + }
183 +
184 + /**
185 + * Shuts down the BGP Session Manager operation.
186 + */
187 + public void shutDown() {
188 + // TODO: Complete the implementation: remove routes, etc.
189 + if (serverChannel != null) {
190 + serverChannel.close();
191 + }
192 + }
193 +
194 + /**
195 + * Class to receive and process the BGP routes from each BGP Session/Peer.
196 + */
197 + class BgpRouteSelector {
198 + /**
199 + * Processes route entry updates: added/updated and deleted route
200 + * entries.
201 + *
202 + * @param bgpSession the BGP session the route entry updates were
203 + * received on
204 + * @param addedBgpRouteEntries the added/updated route entries to
205 + * process
206 + * @param deletedBgpRouteEntries the deleted route entries to process
207 + */
208 + synchronized void routeUpdates(BgpSession bgpSession,
209 + Collection<BgpRouteEntry> addedBgpRouteEntries,
210 + Collection<BgpRouteEntry> deletedBgpRouteEntries) {
211 + //
212 + // TODO: Merge the updates from different BGP Peers,
213 + // by choosing the best route.
214 + //
215 +
216 + // Process the deleted route entries
217 + for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
218 + processDeletedRoute(bgpSession, bgpRouteEntry);
219 + }
220 +
221 + // Process the added/updated route entries
222 + for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
223 + processAddedRoute(bgpSession, bgpRouteEntry);
224 + }
225 + }
226 +
227 + /**
228 + * Processes an added/updated route entry.
229 + *
230 + * @param bgpSession the BGP session the route entry update was
231 + * received on
232 + * @param bgpRouteEntry the added/updated route entry
233 + */
234 + private void processAddedRoute(BgpSession bgpSession,
235 + BgpRouteEntry bgpRouteEntry) {
236 + RouteUpdate routeUpdate;
237 + BgpRouteEntry bestBgpRouteEntry =
238 + bgpRoutes.get(bgpRouteEntry.prefix());
239 +
240 + //
241 + // Install the new route entry if it is better than the
242 + // current best route.
243 + //
244 + if ((bestBgpRouteEntry == null) ||
245 + bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
246 + bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
247 + routeUpdate =
248 + new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
249 + // Forward the result route updates to the Route Listener
250 + routeListener.update(routeUpdate);
251 + return;
252 + }
253 +
254 + //
255 + // If the route entry arrived on the same BGP Session as
256 + // the current best route, then elect the next best route
257 + // and install it.
258 + //
259 + if (bestBgpRouteEntry.getBgpSession() !=
260 + bgpRouteEntry.getBgpSession()) {
261 + return;
262 + }
263 +
264 + // Find the next best route
265 + bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
266 + if (bestBgpRouteEntry == null) {
267 + //
268 + // TODO: Shouldn't happen. Install the new route as a
269 + // pre-caution.
270 + //
271 + log.debug("BGP next best route for prefix {} is missing. " +
272 + "Adding the route that is currently processed.",
273 + bgpRouteEntry.prefix());
274 + bestBgpRouteEntry = bgpRouteEntry;
275 + }
276 + // Install the next best route
277 + bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
278 + routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
279 + bestBgpRouteEntry);
280 + // Forward the result route updates to the Route Listener
281 + routeListener.update(routeUpdate);
282 + }
283 +
284 + /**
285 + * Processes a deleted route entry.
286 + *
287 + * @param bgpSession the BGP session the route entry update was
288 + * received on
289 + * @param bgpRouteEntry the deleted route entry
290 + */
291 + private void processDeletedRoute(BgpSession bgpSession,
292 + BgpRouteEntry bgpRouteEntry) {
293 + RouteUpdate routeUpdate;
294 + BgpRouteEntry bestBgpRouteEntry =
295 + bgpRoutes.get(bgpRouteEntry.prefix());
296 +
297 + //
298 + // Remove the route entry only if it was the best one.
299 + // Install the the next best route if it exists.
300 + //
301 + // NOTE: We intentionally use "==" instead of method equals(),
302 + // because we need to check whether this is same object.
303 + //
304 + if (bgpRouteEntry != bestBgpRouteEntry) {
305 + return; // Nothing to do
306 + }
307 +
308 + //
309 + // Find the next best route
310 + //
311 + bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
312 + if (bestBgpRouteEntry != null) {
313 + // Install the next best route
314 + bgpRoutes.put(bestBgpRouteEntry.prefix(),
315 + bestBgpRouteEntry);
316 + routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
317 + bestBgpRouteEntry);
318 + // Forward the result route updates to the Route Listener
319 + routeListener.update(routeUpdate);
320 + return;
321 + }
322 +
323 + //
324 + // No route found. Remove the route entry
325 + //
326 + bgpRoutes.remove(bgpRouteEntry.prefix());
327 + routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
328 + bgpRouteEntry);
329 + // Forward the result route updates to the Route Listener
330 + routeListener.update(routeUpdate);
331 + }
332 +
333 + /**
334 + * Finds the best route entry among all BGP Sessions.
335 + *
336 + * @param prefix the prefix of the route
337 + * @return the best route if found, otherwise null
338 + */
339 + private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
340 + BgpRouteEntry bestRoute = null;
341 +
342 + // Iterate across all BGP Sessions and select the best route
343 + for (BgpSession bgpSession : bgpSessions.values()) {
344 + BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
345 + if (route == null) {
346 + continue;
347 + }
348 + if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
349 + bestRoute = route;
350 + }
351 + }
352 + return bestRoute;
353 + }
354 + }
355 +}
1 +/**
2 + * Implementation of the BGP protocol.
3 + */
4 +package org.onlab.onos.sdnip.bgp;
...\ No newline at end of file ...\ No newline at end of file
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 +}
...@@ -26,6 +26,16 @@ ...@@ -26,6 +26,16 @@
26 <groupId>org.onlab.onos</groupId> 26 <groupId>org.onlab.onos</groupId>
27 <artifactId>onlab-osgi</artifactId> 27 <artifactId>onlab-osgi</artifactId>
28 </dependency> 28 </dependency>
29 +
30 + <dependency>
31 + <groupId>com.fasterxml.jackson.core</groupId>
32 + <artifactId>jackson-databind</artifactId>
33 + </dependency>
34 + <dependency>
35 + <groupId>com.fasterxml.jackson.core</groupId>
36 + <artifactId>jackson-annotations</artifactId>
37 + </dependency>
38 +
29 <dependency> 39 <dependency>
30 <groupId>org.osgi</groupId> 40 <groupId>org.osgi</groupId>
31 <artifactId>org.osgi.core</artifactId> 41 <artifactId>org.osgi.core</artifactId>
......
1 package org.onlab.onos.cli; 1 package org.onlab.onos.cli;
2 2
3 +import org.apache.karaf.shell.commands.Option;
3 import org.apache.karaf.shell.console.OsgiCommandSupport; 4 import org.apache.karaf.shell.console.OsgiCommandSupport;
4 import org.onlab.osgi.DefaultServiceDirectory; 5 import org.onlab.osgi.DefaultServiceDirectory;
5 import org.onlab.osgi.ServiceNotFoundException; 6 import org.onlab.osgi.ServiceNotFoundException;
...@@ -9,6 +10,10 @@ import org.onlab.osgi.ServiceNotFoundException; ...@@ -9,6 +10,10 @@ import org.onlab.osgi.ServiceNotFoundException;
9 */ 10 */
10 public abstract class AbstractShellCommand extends OsgiCommandSupport { 11 public abstract class AbstractShellCommand extends OsgiCommandSupport {
11 12
13 + @Option(name = "-j", aliases = "--json", description = "Output JSON",
14 + required = false, multiValued = false)
15 + private boolean json = false;
16 +
12 /** 17 /**
13 * Returns the reference to the implementation of the specified service. 18 * Returns the reference to the implementation of the specified service.
14 * 19 *
...@@ -46,6 +51,15 @@ public abstract class AbstractShellCommand extends OsgiCommandSupport { ...@@ -46,6 +51,15 @@ public abstract class AbstractShellCommand extends OsgiCommandSupport {
46 */ 51 */
47 protected abstract void execute(); 52 protected abstract void execute();
48 53
54 + /**
55 + * Indicates whether JSON format should be output.
56 + *
57 + * @return true if JSON is requested
58 + */
59 + protected boolean outputJson() {
60 + return json;
61 + }
62 +
49 @Override 63 @Override
50 protected Object doExecute() throws Exception { 64 protected Object doExecute() throws Exception {
51 try { 65 try {
......
1 package org.onlab.onos.cli; 1 package org.onlab.onos.cli;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
3 import com.google.common.collect.Lists; 6 import com.google.common.collect.Lists;
4 -
5 import org.apache.karaf.shell.commands.Command; 7 import org.apache.karaf.shell.commands.Command;
6 import org.onlab.onos.cluster.ClusterService; 8 import org.onlab.onos.cluster.ClusterService;
7 import org.onlab.onos.cluster.ControllerNode; 9 import org.onlab.onos.cluster.ControllerNode;
...@@ -26,15 +28,50 @@ public class MastersListCommand extends AbstractShellCommand { ...@@ -26,15 +28,50 @@ public class MastersListCommand extends AbstractShellCommand {
26 MastershipService mastershipService = get(MastershipService.class); 28 MastershipService mastershipService = get(MastershipService.class);
27 List<ControllerNode> nodes = newArrayList(service.getNodes()); 29 List<ControllerNode> nodes = newArrayList(service.getNodes());
28 Collections.sort(nodes, Comparators.NODE_COMPARATOR); 30 Collections.sort(nodes, Comparators.NODE_COMPARATOR);
31 +
32 + if (outputJson()) {
33 + print("%s", json(service, mastershipService, nodes));
34 + } else {
35 + for (ControllerNode node : nodes) {
36 + List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id()));
37 + Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR);
38 + print("%s: %d devices", node.id(), ids.size());
39 + for (DeviceId deviceId : ids) {
40 + print(" %s", deviceId);
41 + }
42 + }
43 + }
44 + }
45 +
46 + // Produces JSON structure.
47 + private JsonNode json(ClusterService service, MastershipService mastershipService,
48 + List<ControllerNode> nodes) {
49 + ObjectMapper mapper = new ObjectMapper();
50 + ArrayNode result = mapper.createArrayNode();
29 ControllerNode self = service.getLocalNode(); 51 ControllerNode self = service.getLocalNode();
30 for (ControllerNode node : nodes) { 52 for (ControllerNode node : nodes) {
31 List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id())); 53 List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id()));
32 - Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR); 54 + result.add(mapper.createObjectNode()
33 - print("%s: %d devices", node.id(), ids.size()); 55 + .put("id", node.id().toString())
34 - for (DeviceId deviceId : ids) { 56 + .put("size", ids.size())
35 - print(" %s", deviceId); 57 + .set("devices", json(mapper, ids)));
36 - } 58 + }
59 + return result;
60 + }
61 +
62 + /**
63 + * Produces a JSON array containing the specified device identifiers.
64 + *
65 + * @param mapper object mapper
66 + * @param ids collection of device identifiers
67 + * @return JSON array
68 + */
69 + public static JsonNode json(ObjectMapper mapper, Iterable<DeviceId> ids) {
70 + ArrayNode result = mapper.createArrayNode();
71 + for (DeviceId deviceId : ids) {
72 + result.add(deviceId.toString());
37 } 73 }
74 + return result;
38 } 75 }
39 76
40 } 77 }
......
1 package org.onlab.onos.cli; 1 package org.onlab.onos.cli;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
3 import org.apache.karaf.shell.commands.Command; 6 import org.apache.karaf.shell.commands.Command;
4 import org.onlab.onos.cluster.ClusterService; 7 import org.onlab.onos.cluster.ClusterService;
5 import org.onlab.onos.cluster.ControllerNode; 8 import org.onlab.onos.cluster.ControllerNode;
...@@ -24,12 +27,32 @@ public class NodesListCommand extends AbstractShellCommand { ...@@ -24,12 +27,32 @@ public class NodesListCommand extends AbstractShellCommand {
24 ClusterService service = get(ClusterService.class); 27 ClusterService service = get(ClusterService.class);
25 List<ControllerNode> nodes = newArrayList(service.getNodes()); 28 List<ControllerNode> nodes = newArrayList(service.getNodes());
26 Collections.sort(nodes, Comparators.NODE_COMPARATOR); 29 Collections.sort(nodes, Comparators.NODE_COMPARATOR);
30 + if (outputJson()) {
31 + print("%s", json(service, nodes));
32 + } else {
33 + ControllerNode self = service.getLocalNode();
34 + for (ControllerNode node : nodes) {
35 + print(FMT, node.id(), node.ip(), node.tcpPort(),
36 + service.getState(node.id()),
37 + node.equals(self) ? "*" : "");
38 + }
39 + }
40 + }
41 +
42 + // Produces JSON structure.
43 + private JsonNode json(ClusterService service, List<ControllerNode> nodes) {
44 + ObjectMapper mapper = new ObjectMapper();
45 + ArrayNode result = mapper.createArrayNode();
27 ControllerNode self = service.getLocalNode(); 46 ControllerNode self = service.getLocalNode();
28 for (ControllerNode node : nodes) { 47 for (ControllerNode node : nodes) {
29 - print(FMT, node.id(), node.ip(), node.tcpPort(), 48 + result.add(mapper.createObjectNode()
30 - service.getState(node.id()), 49 + .put("id", node.id().toString())
31 - node.equals(self) ? "*" : ""); 50 + .put("ip", node.ip().toString())
51 + .put("tcpPort", node.tcpPort())
52 + .put("state", service.getState(node.id()).toString())
53 + .put("self", node.equals(self)));
32 } 54 }
55 + return result;
33 } 56 }
34 57
35 } 58 }
......
1 package org.onlab.onos.cli; 1 package org.onlab.onos.cli;
2 2
3 +import com.fasterxml.jackson.databind.ObjectMapper;
3 import org.apache.karaf.shell.commands.Command; 4 import org.apache.karaf.shell.commands.Command;
4 import org.onlab.onos.CoreService; 5 import org.onlab.onos.CoreService;
5 import org.onlab.onos.cluster.ClusterService; 6 import org.onlab.onos.cluster.ClusterService;
...@@ -22,18 +23,32 @@ public class SummaryCommand extends AbstractShellCommand { ...@@ -22,18 +23,32 @@ public class SummaryCommand extends AbstractShellCommand {
22 protected void execute() { 23 protected void execute() {
23 TopologyService topologyService = get(TopologyService.class); 24 TopologyService topologyService = get(TopologyService.class);
24 Topology topology = topologyService.currentTopology(); 25 Topology topology = topologyService.currentTopology();
25 - print("node=%s, version=%s", 26 + if (outputJson()) {
26 - get(ClusterService.class).getLocalNode().ip(), 27 + print("%s", new ObjectMapper().createObjectNode()
27 - get(CoreService.class).version().toString()); 28 + .put("node", get(ClusterService.class).getLocalNode().ip().toString())
28 - print("nodes=%d, devices=%d, links=%d, hosts=%d, clusters=%s, paths=%d, flows=%d, intents=%d", 29 + .put("version", get(CoreService.class).version().toString())
29 - get(ClusterService.class).getNodes().size(), 30 + .put("nodes", get(ClusterService.class).getNodes().size())
30 - get(DeviceService.class).getDeviceCount(), 31 + .put("devices", get(DeviceService.class).getDeviceCount())
31 - get(LinkService.class).getLinkCount(), 32 + .put("links", get(LinkService.class).getLinkCount())
32 - get(HostService.class).getHostCount(), 33 + .put("hosts", get(HostService.class).getHostCount())
33 - topologyService.getClusters(topology).size(), 34 + .put("clusters", topologyService.getClusters(topology).size())
34 - topology.pathCount(), 35 + .put("paths", topology.pathCount())
35 - get(FlowRuleService.class).getFlowRuleCount(), 36 + .put("flows", get(FlowRuleService.class).getFlowRuleCount())
36 - get(IntentService.class).getIntentCount()); 37 + .put("intents", get(IntentService.class).getIntentCount()));
38 + } else {
39 + print("node=%s, version=%s",
40 + get(ClusterService.class).getLocalNode().ip(),
41 + get(CoreService.class).version().toString());
42 + print("nodes=%d, devices=%d, links=%d, hosts=%d, clusters=%s, paths=%d, flows=%d, intents=%d",
43 + get(ClusterService.class).getNodes().size(),
44 + get(DeviceService.class).getDeviceCount(),
45 + get(LinkService.class).getLinkCount(),
46 + get(HostService.class).getHostCount(),
47 + topologyService.getClusters(topology).size(),
48 + topology.pathCount(),
49 + get(FlowRuleService.class).getFlowRuleCount(),
50 + get(IntentService.class).getIntentCount());
51 + }
37 } 52 }
38 53
39 } 54 }
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.ObjectMapper;
3 import com.google.common.collect.Lists; 4 import com.google.common.collect.Lists;
4 import org.apache.karaf.shell.commands.Argument; 5 import org.apache.karaf.shell.commands.Argument;
5 import org.apache.karaf.shell.commands.Command; 6 import org.apache.karaf.shell.commands.Command;
...@@ -10,6 +11,7 @@ import org.onlab.onos.net.topology.TopologyCluster; ...@@ -10,6 +11,7 @@ import org.onlab.onos.net.topology.TopologyCluster;
10 import java.util.Collections; 11 import java.util.Collections;
11 import java.util.List; 12 import java.util.List;
12 13
14 +import static org.onlab.onos.cli.MastersListCommand.json;
13 import static org.onlab.onos.net.topology.ClusterId.clusterId; 15 import static org.onlab.onos.net.topology.ClusterId.clusterId;
14 16
15 /** 17 /**
...@@ -33,11 +35,14 @@ public class ClusterDevicesCommand extends ClustersListCommand { ...@@ -33,11 +35,14 @@ public class ClusterDevicesCommand extends ClustersListCommand {
33 } else { 35 } else {
34 List<DeviceId> ids = Lists.newArrayList(service.getClusterDevices(topology, cluster)); 36 List<DeviceId> ids = Lists.newArrayList(service.getClusterDevices(topology, cluster));
35 Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR); 37 Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR);
36 - for (DeviceId deviceId : ids) { 38 + if (outputJson()) {
37 - print("%s", deviceId); 39 + print("%s", json(new ObjectMapper(), ids));
40 + } else {
41 + for (DeviceId deviceId : ids) {
42 + print("%s", deviceId);
43 + }
38 } 44 }
39 } 45 }
40 } 46 }
41 47
42 -
43 } 48 }
......
...@@ -5,6 +5,7 @@ import org.apache.karaf.shell.commands.Command; ...@@ -5,6 +5,7 @@ import org.apache.karaf.shell.commands.Command;
5 import org.onlab.onos.net.Link; 5 import org.onlab.onos.net.Link;
6 import org.onlab.onos.net.topology.TopologyCluster; 6 import org.onlab.onos.net.topology.TopologyCluster;
7 7
8 +import static org.onlab.onos.cli.net.LinksListCommand.json;
8 import static org.onlab.onos.cli.net.LinksListCommand.linkString; 9 import static org.onlab.onos.cli.net.LinksListCommand.linkString;
9 import static org.onlab.onos.net.topology.ClusterId.clusterId; 10 import static org.onlab.onos.net.topology.ClusterId.clusterId;
10 11
...@@ -26,6 +27,8 @@ public class ClusterLinksCommand extends ClustersListCommand { ...@@ -26,6 +27,8 @@ public class ClusterLinksCommand extends ClustersListCommand {
26 TopologyCluster cluster = service.getCluster(topology, clusterId(cid)); 27 TopologyCluster cluster = service.getCluster(topology, clusterId(cid));
27 if (cluster == null) { 28 if (cluster == null) {
28 error("No such cluster %s", cid); 29 error("No such cluster %s", cid);
30 + } else if (outputJson()) {
31 + print("%s", json(service.getClusterLinks(topology, cluster)));
29 } else { 32 } else {
30 for (Link link : service.getClusterLinks(topology, cluster)) { 33 for (Link link : service.getClusterLinks(topology, cluster)) {
31 print(linkString(link)); 34 print(linkString(link));
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
3 import com.google.common.collect.Lists; 6 import com.google.common.collect.Lists;
4 import org.apache.karaf.shell.commands.Command; 7 import org.apache.karaf.shell.commands.Command;
5 import org.onlab.onos.cli.Comparators; 8 import org.onlab.onos.cli.Comparators;
...@@ -24,9 +27,26 @@ public class ClustersListCommand extends TopologyCommand { ...@@ -24,9 +27,26 @@ public class ClustersListCommand extends TopologyCommand {
24 List<TopologyCluster> clusters = Lists.newArrayList(service.getClusters(topology)); 27 List<TopologyCluster> clusters = Lists.newArrayList(service.getClusters(topology));
25 Collections.sort(clusters, Comparators.CLUSTER_COMPARATOR); 28 Collections.sort(clusters, Comparators.CLUSTER_COMPARATOR);
26 29
30 + if (outputJson()) {
31 + print("%s", json(clusters));
32 + } else {
33 + for (TopologyCluster cluster : clusters) {
34 + print(FMT, cluster.id().index(), cluster.deviceCount(), cluster.linkCount());
35 + }
36 + }
37 + }
38 +
39 + // Produces a JSON result.
40 + private JsonNode json(Iterable<TopologyCluster> clusters) {
41 + ObjectMapper mapper = new ObjectMapper();
42 + ArrayNode result = mapper.createArrayNode();
27 for (TopologyCluster cluster : clusters) { 43 for (TopologyCluster cluster : clusters) {
28 - print(FMT, cluster.id().index(), cluster.deviceCount(), cluster.linkCount()); 44 + result.add(mapper.createObjectNode()
45 + .put("id", cluster.id().index())
46 + .put("deviceCount", cluster.deviceCount())
47 + .put("linkCount", cluster.linkCount()));
29 } 48 }
49 + return result;
30 } 50 }
31 51
32 } 52 }
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
6 +import com.fasterxml.jackson.databind.node.ObjectNode;
3 import org.apache.karaf.shell.commands.Argument; 7 import org.apache.karaf.shell.commands.Argument;
4 import org.apache.karaf.shell.commands.Command; 8 import org.apache.karaf.shell.commands.Command;
9 +import org.apache.karaf.shell.commands.Option;
5 import org.onlab.onos.cli.Comparators; 10 import org.onlab.onos.cli.Comparators;
6 import org.onlab.onos.net.Device; 11 import org.onlab.onos.net.Device;
7 import org.onlab.onos.net.Port; 12 import org.onlab.onos.net.Port;
...@@ -22,6 +27,14 @@ public class DevicePortsListCommand extends DevicesListCommand { ...@@ -22,6 +27,14 @@ public class DevicePortsListCommand extends DevicesListCommand {
22 27
23 private static final String FMT = " port=%s, state=%s"; 28 private static final String FMT = " port=%s, state=%s";
24 29
30 + @Option(name = "-e", aliases = "--enabled", description = "Show only enabled ports",
31 + required = false, multiValued = false)
32 + private boolean enabled = false;
33 +
34 + @Option(name = "-d", aliases = "--disabled", description = "Show only disabled ports",
35 + required = false, multiValued = false)
36 + private boolean disabled = false;
37 +
25 @Argument(index = 0, name = "uri", description = "Device ID", 38 @Argument(index = 0, name = "uri", description = "Device ID",
26 required = false, multiValued = false) 39 required = false, multiValued = false)
27 String uri = null; 40 String uri = null;
...@@ -30,26 +43,78 @@ public class DevicePortsListCommand extends DevicesListCommand { ...@@ -30,26 +43,78 @@ public class DevicePortsListCommand extends DevicesListCommand {
30 protected void execute() { 43 protected void execute() {
31 DeviceService service = get(DeviceService.class); 44 DeviceService service = get(DeviceService.class);
32 if (uri == null) { 45 if (uri == null) {
33 - for (Device device : getSortedDevices(service)) { 46 + if (outputJson()) {
34 - printDevice(service, device); 47 + print("%s", jsonPorts(service, getSortedDevices(service)));
48 + } else {
49 + for (Device device : getSortedDevices(service)) {
50 + printDevice(service, device);
51 + }
35 } 52 }
53 +
36 } else { 54 } else {
37 Device device = service.getDevice(deviceId(uri)); 55 Device device = service.getDevice(deviceId(uri));
38 if (device == null) { 56 if (device == null) {
39 error("No such device %s", uri); 57 error("No such device %s", uri);
58 + } else if (outputJson()) {
59 + print("%s", jsonPorts(service, new ObjectMapper(), device));
40 } else { 60 } else {
41 printDevice(service, device); 61 printDevice(service, device);
42 } 62 }
43 } 63 }
44 } 64 }
45 65
66 + /**
67 + * Produces JSON array containing ports of the specified devices.
68 + *
69 + * @param service device service
70 + * @param devices collection of devices
71 + * @return JSON array
72 + */
73 + public JsonNode jsonPorts(DeviceService service, Iterable<Device> devices) {
74 + ObjectMapper mapper = new ObjectMapper();
75 + ArrayNode result = mapper.createArrayNode();
76 + for (Device device : devices) {
77 + result.add(jsonPorts(service, mapper, device));
78 + }
79 + return result;
80 + }
81 +
82 + /**
83 + * Produces JSON array containing ports of the specified device.
84 + *
85 + * @param service device service
86 + * @param mapper object mapper
87 + * @param device infrastructure devices
88 + * @return JSON array
89 + */
90 + public JsonNode jsonPorts(DeviceService service, ObjectMapper mapper, Device device) {
91 + ObjectNode result = mapper.createObjectNode();
92 + ArrayNode ports = mapper.createArrayNode();
93 + for (Port port : service.getPorts(device.id())) {
94 + if (isIncluded(port)) {
95 + ports.add(mapper.createObjectNode()
96 + .put("port", port.number().toString())
97 + .put("isEnabled", port.isEnabled()));
98 + }
99 + }
100 + return result.put("device", device.id().toString()).set("ports", ports);
101 + }
102 +
103 + // Determines if a port should be included in output.
104 + private boolean isIncluded(Port port) {
105 + return enabled && port.isEnabled() || disabled && !port.isEnabled() ||
106 + !enabled && !disabled;
107 + }
108 +
46 @Override 109 @Override
47 protected void printDevice(DeviceService service, Device device) { 110 protected void printDevice(DeviceService service, Device device) {
48 super.printDevice(service, device); 111 super.printDevice(service, device);
49 List<Port> ports = new ArrayList<>(service.getPorts(device.id())); 112 List<Port> ports = new ArrayList<>(service.getPorts(device.id()));
50 Collections.sort(ports, Comparators.PORT_COMPARATOR); 113 Collections.sort(ports, Comparators.PORT_COMPARATOR);
51 for (Port port : ports) { 114 for (Port port : ports) {
52 - print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled"); 115 + if (isIncluded(port)) {
116 + print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled");
117 + }
53 } 118 }
54 } 119 }
55 120
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
6 +import com.fasterxml.jackson.databind.node.ObjectNode;
3 import org.apache.karaf.shell.commands.Command; 7 import org.apache.karaf.shell.commands.Command;
4 import org.onlab.onos.cli.AbstractShellCommand; 8 import org.onlab.onos.cli.AbstractShellCommand;
5 import org.onlab.onos.cli.Comparators; 9 import org.onlab.onos.cli.Comparators;
...@@ -24,12 +28,55 @@ public class DevicesListCommand extends AbstractShellCommand { ...@@ -24,12 +28,55 @@ public class DevicesListCommand extends AbstractShellCommand {
24 @Override 28 @Override
25 protected void execute() { 29 protected void execute() {
26 DeviceService service = get(DeviceService.class); 30 DeviceService service = get(DeviceService.class);
27 - for (Device device : getSortedDevices(service)) { 31 + if (outputJson()) {
28 - printDevice(service, device); 32 + print("%s", json(service, getSortedDevices(service)));
33 + } else {
34 + for (Device device : getSortedDevices(service)) {
35 + printDevice(service, device);
36 + }
29 } 37 }
30 } 38 }
31 39
32 /** 40 /**
41 + * Returns JSON node representing the specified devices.
42 + *
43 + * @param service device service
44 + * @param devices collection of devices
45 + * @return JSON node
46 + */
47 + public static JsonNode json(DeviceService service, Iterable<Device> devices) {
48 + ObjectMapper mapper = new ObjectMapper();
49 + ArrayNode result = mapper.createArrayNode();
50 + for (Device device : devices) {
51 + result.add(json(service, mapper, device));
52 + }
53 + return result;
54 + }
55 +
56 + /**
57 + * Returns JSON node representing the specified device.
58 + *
59 + * @param service device service
60 + * @param mapper object mapper
61 + * @param device infrastructure device
62 + * @return JSON node
63 + */
64 + public static ObjectNode json(DeviceService service, ObjectMapper mapper,
65 + Device device) {
66 + ObjectNode result = mapper.createObjectNode();
67 + if (device != null) {
68 + result.put("id", device.id().toString())
69 + .put("available", service.isAvailable(device.id()))
70 + .put("role", service.getRole(device.id()).toString())
71 + .put("mfr", device.manufacturer())
72 + .put("hw", device.hwVersion())
73 + .put("sw", device.swVersion())
74 + .put("serial", device.serialNumber());
75 + }
76 + return result;
77 + }
78 +
79 + /**
33 * Returns the list of devices sorted using the device ID URIs. 80 * Returns the list of devices sorted using the device ID URIs.
34 * 81 *
35 * @param service device service 82 * @param service device service
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
6 +import com.fasterxml.jackson.databind.node.ObjectNode;
3 import com.google.common.collect.Maps; 7 import com.google.common.collect.Maps;
4 import org.apache.karaf.shell.commands.Argument; 8 import org.apache.karaf.shell.commands.Argument;
5 import org.apache.karaf.shell.commands.Command; 9 import org.apache.karaf.shell.commands.Command;
...@@ -12,6 +16,8 @@ import org.onlab.onos.net.device.DeviceService; ...@@ -12,6 +16,8 @@ import org.onlab.onos.net.device.DeviceService;
12 import org.onlab.onos.net.flow.FlowEntry; 16 import org.onlab.onos.net.flow.FlowEntry;
13 import org.onlab.onos.net.flow.FlowEntry.FlowEntryState; 17 import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
14 import org.onlab.onos.net.flow.FlowRuleService; 18 import org.onlab.onos.net.flow.FlowRuleService;
19 +import org.onlab.onos.net.flow.criteria.Criterion;
20 +import org.onlab.onos.net.flow.instructions.Instruction;
15 21
16 import java.util.Collections; 22 import java.util.Collections;
17 import java.util.List; 23 import java.util.List;
...@@ -48,9 +54,73 @@ public class FlowsListCommand extends AbstractShellCommand { ...@@ -48,9 +54,73 @@ public class FlowsListCommand extends AbstractShellCommand {
48 DeviceService deviceService = get(DeviceService.class); 54 DeviceService deviceService = get(DeviceService.class);
49 FlowRuleService service = get(FlowRuleService.class); 55 FlowRuleService service = get(FlowRuleService.class);
50 Map<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service); 56 Map<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service);
51 - for (Device d : getSortedDevices(deviceService)) { 57 +
52 - printFlows(d, flows.get(d), coreService); 58 + if (outputJson()) {
59 + print("%s", json(coreService, getSortedDevices(deviceService), flows));
60 + } else {
61 + for (Device d : getSortedDevices(deviceService)) {
62 + printFlows(d, flows.get(d), coreService);
63 + }
64 + }
65 + }
66 +
67 + /**
68 + * Produces a JSON array of flows grouped by the each device.
69 + *
70 + * @param coreService core service
71 + * @param devices collection of devices to group flow by
72 + * @param flows collection of flows per each device
73 + * @return JSON array
74 + */
75 + private JsonNode json(CoreService coreService, Iterable<Device> devices,
76 + Map<Device, List<FlowEntry>> flows) {
77 + ObjectMapper mapper = new ObjectMapper();
78 + ArrayNode result = mapper.createArrayNode();
79 + for (Device device : devices) {
80 + result.add(json(coreService, mapper, device, flows.get(device)));
53 } 81 }
82 + return result;
83 + }
84 +
85 + // Produces JSON object with the flows of the given device.
86 + private ObjectNode json(CoreService coreService, ObjectMapper mapper,
87 + Device device, List<FlowEntry> flows) {
88 + ObjectNode result = mapper.createObjectNode();
89 + ArrayNode array = mapper.createArrayNode();
90 +
91 + for (FlowEntry flow : flows) {
92 + array.add(json(coreService, mapper, flow));
93 + }
94 +
95 + result.put("device", device.id().toString())
96 + .put("flowCount", flows.size())
97 + .put("flows", array);
98 + return result;
99 + }
100 +
101 + // Produces JSON structure with the specified flow data.
102 + private ObjectNode json(CoreService coreService, ObjectMapper mapper,
103 + FlowEntry flow) {
104 + ObjectNode result = mapper.createObjectNode();
105 + ArrayNode crit = mapper.createArrayNode();
106 + for (Criterion c : flow.selector().criteria()) {
107 + crit.add(c.toString());
108 + }
109 +
110 + ArrayNode instr = mapper.createArrayNode();
111 + for (Instruction i : flow.treatment().instructions()) {
112 + instr.add(i.toString());
113 + }
114 +
115 + result.put("flowId", Long.toHexString(flow.id().value()))
116 + .put("state", flow.state().toString())
117 + .put("bytes", flow.bytes())
118 + .put("packets", flow.packets())
119 + .put("life", flow.life())
120 + .put("appId", coreService.getAppId(flow.appId()).name());
121 + result.set("selector", crit);
122 + result.set("treatment", instr);
123 + return result;
54 } 124 }
55 125
56 /** 126 /**
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
6 +import com.fasterxml.jackson.databind.node.ObjectNode;
3 import org.apache.karaf.shell.commands.Command; 7 import org.apache.karaf.shell.commands.Command;
4 import org.onlab.onos.cli.AbstractShellCommand; 8 import org.onlab.onos.cli.AbstractShellCommand;
5 import org.onlab.onos.cli.Comparators; 9 import org.onlab.onos.cli.Comparators;
6 import org.onlab.onos.net.Host; 10 import org.onlab.onos.net.Host;
7 import org.onlab.onos.net.host.HostService; 11 import org.onlab.onos.net.host.HostService;
12 +import org.onlab.packet.IpPrefix;
8 13
9 import java.util.Collections; 14 import java.util.Collections;
10 import java.util.List; 15 import java.util.List;
...@@ -15,7 +20,7 @@ import static com.google.common.collect.Lists.newArrayList; ...@@ -15,7 +20,7 @@ import static com.google.common.collect.Lists.newArrayList;
15 * Lists all currently-known hosts. 20 * Lists all currently-known hosts.
16 */ 21 */
17 @Command(scope = "onos", name = "hosts", 22 @Command(scope = "onos", name = "hosts",
18 - description = "Lists all currently-known hosts.") 23 + description = "Lists all currently-known hosts.")
19 public class HostsListCommand extends AbstractShellCommand { 24 public class HostsListCommand extends AbstractShellCommand {
20 25
21 private static final String FMT = 26 private static final String FMT =
...@@ -24,11 +29,42 @@ public class HostsListCommand extends AbstractShellCommand { ...@@ -24,11 +29,42 @@ public class HostsListCommand extends AbstractShellCommand {
24 @Override 29 @Override
25 protected void execute() { 30 protected void execute() {
26 HostService service = get(HostService.class); 31 HostService service = get(HostService.class);
27 - for (Host host : getSortedHosts(service)) { 32 + if (outputJson()) {
28 - printHost(host); 33 + print("%s", json(getSortedHosts(service)));
34 + } else {
35 + for (Host host : getSortedHosts(service)) {
36 + printHost(host);
37 + }
29 } 38 }
30 } 39 }
31 40
41 + // Produces JSON structure.
42 + private static JsonNode json(Iterable<Host> hosts) {
43 + ObjectMapper mapper = new ObjectMapper();
44 + ArrayNode result = mapper.createArrayNode();
45 + for (Host host : hosts) {
46 + result.add(json(mapper, host));
47 + }
48 + return result;
49 + }
50 +
51 + // Produces JSON structure.
52 + private static JsonNode json(ObjectMapper mapper, Host host) {
53 + ObjectNode loc = LinksListCommand.json(mapper, host.location())
54 + .put("time", host.location().time());
55 + ArrayNode ips = mapper.createArrayNode();
56 + for (IpPrefix ip : host.ipAddresses()) {
57 + ips.add(ip.toString());
58 + }
59 + ObjectNode result = mapper.createObjectNode()
60 + .put("id", host.id().toString())
61 + .put("mac", host.mac().toString())
62 + .put("vlan", host.vlan().toString());
63 + result.set("location", loc);
64 + result.set("ips", ips);
65 + return result;
66 + }
67 +
32 /** 68 /**
33 * Returns the list of devices sorted using the device ID URIs. 69 * Returns the list of devices sorted using the device ID URIs.
34 * 70 *
...@@ -44,14 +80,14 @@ public class HostsListCommand extends AbstractShellCommand { ...@@ -44,14 +80,14 @@ public class HostsListCommand extends AbstractShellCommand {
44 /** 80 /**
45 * Prints information about a host. 81 * Prints information about a host.
46 * 82 *
47 - * @param host 83 + * @param host end-station host
48 */ 84 */
49 protected void printHost(Host host) { 85 protected void printHost(Host host) {
50 if (host != null) { 86 if (host != null) {
51 print(FMT, host.id(), host.mac(), 87 print(FMT, host.id(), host.mac(),
52 - host.location().deviceId(), 88 + host.location().deviceId(),
53 - host.location().port(), 89 + host.location().port(),
54 - host.vlan(), host.ipAddresses()); 90 + host.vlan(), host.ipAddresses());
55 } 91 }
56 } 92 }
57 - } 93 +}
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
6 +import com.fasterxml.jackson.databind.node.ObjectNode;
3 import org.apache.karaf.shell.commands.Argument; 7 import org.apache.karaf.shell.commands.Argument;
4 import org.apache.karaf.shell.commands.Command; 8 import org.apache.karaf.shell.commands.Command;
5 import org.onlab.onos.cli.AbstractShellCommand; 9 import org.onlab.onos.cli.AbstractShellCommand;
10 +import org.onlab.onos.net.ConnectPoint;
6 import org.onlab.onos.net.Link; 11 import org.onlab.onos.net.Link;
7 import org.onlab.onos.net.link.LinkService; 12 import org.onlab.onos.net.link.LinkService;
8 13
...@@ -27,9 +32,55 @@ public class LinksListCommand extends AbstractShellCommand { ...@@ -27,9 +32,55 @@ public class LinksListCommand extends AbstractShellCommand {
27 LinkService service = get(LinkService.class); 32 LinkService service = get(LinkService.class);
28 Iterable<Link> links = uri != null ? 33 Iterable<Link> links = uri != null ?
29 service.getDeviceLinks(deviceId(uri)) : service.getLinks(); 34 service.getDeviceLinks(deviceId(uri)) : service.getLinks();
35 + if (outputJson()) {
36 + print("%s", json(links));
37 + } else {
38 + for (Link link : links) {
39 + print(linkString(link));
40 + }
41 + }
42 + }
43 +
44 + /**
45 + * Produces a JSON array containing the specified links.
46 + *
47 + * @param links collection of links
48 + * @return JSON array
49 + */
50 + public static JsonNode json(Iterable<Link> links) {
51 + ObjectMapper mapper = new ObjectMapper();
52 + ArrayNode result = mapper.createArrayNode();
30 for (Link link : links) { 53 for (Link link : links) {
31 - print(linkString(link)); 54 + result.add(json(mapper, link));
32 } 55 }
56 + return result;
57 + }
58 +
59 + /**
60 + * Produces a JSON object for the specified link.
61 + *
62 + * @param mapper object mapper
63 + * @param link link to encode
64 + * @return JSON object
65 + */
66 + public static ObjectNode json(ObjectMapper mapper, Link link) {
67 + ObjectNode result = mapper.createObjectNode();
68 + result.set("src", json(mapper, link.src()));
69 + result.set("dst", json(mapper, link.dst()));
70 + return result;
71 + }
72 +
73 + /**
74 + * Produces a JSON object for the specified connect point.
75 + *
76 + * @param mapper object mapper
77 + * @param connectPoint connection point to encode
78 + * @return JSON object
79 + */
80 + public static ObjectNode json(ObjectMapper mapper, ConnectPoint connectPoint) {
81 + return mapper.createObjectNode()
82 + .put("device", connectPoint.deviceId().toString())
83 + .put("port", connectPoint.port().toString());
33 } 84 }
34 85
35 /** 86 /**
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.node.ArrayNode;
3 import org.apache.karaf.shell.commands.Argument; 6 import org.apache.karaf.shell.commands.Argument;
4 import org.apache.karaf.shell.commands.Command; 7 import org.apache.karaf.shell.commands.Command;
5 import org.onlab.onos.net.Link; 8 import org.onlab.onos.net.Link;
...@@ -32,9 +35,30 @@ public class PathListCommand extends TopologyCommand { ...@@ -32,9 +35,30 @@ public class PathListCommand extends TopologyCommand {
32 protected void execute() { 35 protected void execute() {
33 init(); 36 init();
34 Set<Path> paths = service.getPaths(topology, deviceId(src), deviceId(dst)); 37 Set<Path> paths = service.getPaths(topology, deviceId(src), deviceId(dst));
38 + if (outputJson()) {
39 + print("%s", json(paths));
40 + } else {
41 + for (Path path : paths) {
42 + print(pathString(path));
43 + }
44 + }
45 + }
46 +
47 + /**
48 + * Produces a JSON array containing the specified paths.
49 + *
50 + * @param paths collection of paths
51 + * @return JSON array
52 + */
53 + public static JsonNode json(Iterable<Path> paths) {
54 + ObjectMapper mapper = new ObjectMapper();
55 + ArrayNode result = mapper.createArrayNode();
35 for (Path path : paths) { 56 for (Path path : paths) {
36 - print(pathString(path)); 57 + result.add(LinksListCommand.json(mapper, path)
58 + .put("cost", path.cost())
59 + .set("links", LinksListCommand.json(path.links())));
37 } 60 }
61 + return result;
38 } 62 }
39 63
40 /** 64 /**
......
1 package org.onlab.onos.cli.net; 1 package org.onlab.onos.cli.net;
2 2
3 +import com.fasterxml.jackson.databind.ObjectMapper;
3 import org.apache.karaf.shell.commands.Command; 4 import org.apache.karaf.shell.commands.Command;
4 import org.onlab.onos.cli.AbstractShellCommand; 5 import org.onlab.onos.cli.AbstractShellCommand;
5 import org.onlab.onos.net.topology.Topology; 6 import org.onlab.onos.net.topology.Topology;
...@@ -30,8 +31,17 @@ public class TopologyCommand extends AbstractShellCommand { ...@@ -30,8 +31,17 @@ public class TopologyCommand extends AbstractShellCommand {
30 @Override 31 @Override
31 protected void execute() { 32 protected void execute() {
32 init(); 33 init();
33 - print(FMT, topology.time(), topology.deviceCount(), topology.linkCount(), 34 + if (outputJson()) {
34 - topology.clusterCount(), topology.pathCount()); 35 + print("%s", new ObjectMapper().createObjectNode()
36 + .put("time", topology.time())
37 + .put("deviceCount", topology.deviceCount())
38 + .put("linkCount", topology.linkCount())
39 + .put("clusterCount", topology.clusterCount())
40 + .put("pathCount", topology.pathCount()));
41 + } else {
42 + print(FMT, topology.time(), topology.deviceCount(), topology.linkCount(),
43 + topology.clusterCount(), topology.pathCount());
44 + }
35 } 45 }
36 46
37 } 47 }
......
...@@ -12,7 +12,11 @@ public final class ControllerNodeToNodeId ...@@ -12,7 +12,11 @@ public final class ControllerNodeToNodeId
12 12
13 @Override 13 @Override
14 public NodeId apply(ControllerNode input) { 14 public NodeId apply(ControllerNode input) {
15 - return input.id(); 15 + if (input == null) {
16 + return null;
17 + } else {
18 + return input.id();
19 + }
16 } 20 }
17 21
18 /** 22 /**
......
1 package org.onlab.onos.net.host; 1 package org.onlab.onos.net.host;
2 2
3 +import java.util.Collections;
4 +import java.util.Set;
5 +
3 import org.onlab.onos.net.AbstractDescription; 6 import org.onlab.onos.net.AbstractDescription;
4 import org.onlab.onos.net.HostLocation; 7 import org.onlab.onos.net.HostLocation;
5 import org.onlab.onos.net.SparseAnnotations; 8 import org.onlab.onos.net.SparseAnnotations;
...@@ -7,6 +10,8 @@ import org.onlab.packet.IpPrefix; ...@@ -7,6 +10,8 @@ import org.onlab.packet.IpPrefix;
7 import org.onlab.packet.MacAddress; 10 import org.onlab.packet.MacAddress;
8 import org.onlab.packet.VlanId; 11 import org.onlab.packet.VlanId;
9 12
13 +import com.google.common.collect.ImmutableSet;
14 +
10 import static com.google.common.base.MoreObjects.toStringHelper; 15 import static com.google.common.base.MoreObjects.toStringHelper;
11 16
12 /** 17 /**
...@@ -18,7 +23,7 @@ public class DefaultHostDescription extends AbstractDescription ...@@ -18,7 +23,7 @@ public class DefaultHostDescription extends AbstractDescription
18 private final MacAddress mac; 23 private final MacAddress mac;
19 private final VlanId vlan; 24 private final VlanId vlan;
20 private final HostLocation location; 25 private final HostLocation location;
21 - private final IpPrefix ip; 26 + private final Set<IpPrefix> ip;
22 27
23 /** 28 /**
24 * Creates a host description using the supplied information. 29 * Creates a host description using the supplied information.
...@@ -31,7 +36,7 @@ public class DefaultHostDescription extends AbstractDescription ...@@ -31,7 +36,7 @@ public class DefaultHostDescription extends AbstractDescription
31 public DefaultHostDescription(MacAddress mac, VlanId vlan, 36 public DefaultHostDescription(MacAddress mac, VlanId vlan,
32 HostLocation location, 37 HostLocation location,
33 SparseAnnotations... annotations) { 38 SparseAnnotations... annotations) {
34 - this(mac, vlan, location, null, annotations); 39 + this(mac, vlan, location, Collections.<IpPrefix>emptySet(), annotations);
35 } 40 }
36 41
37 /** 42 /**
...@@ -46,11 +51,26 @@ public class DefaultHostDescription extends AbstractDescription ...@@ -46,11 +51,26 @@ public class DefaultHostDescription extends AbstractDescription
46 public DefaultHostDescription(MacAddress mac, VlanId vlan, 51 public DefaultHostDescription(MacAddress mac, VlanId vlan,
47 HostLocation location, IpPrefix ip, 52 HostLocation location, IpPrefix ip,
48 SparseAnnotations... annotations) { 53 SparseAnnotations... annotations) {
54 + this(mac, vlan, location, ImmutableSet.of(ip), annotations);
55 + }
56 +
57 + /**
58 + * Creates a host description using the supplied information.
59 + *
60 + * @param mac host MAC address
61 + * @param vlan host VLAN identifier
62 + * @param location host location
63 + * @param ip host IP addresses
64 + * @param annotations optional key/value annotations map
65 + */
66 + public DefaultHostDescription(MacAddress mac, VlanId vlan,
67 + HostLocation location, Set<IpPrefix> ip,
68 + SparseAnnotations... annotations) {
49 super(annotations); 69 super(annotations);
50 this.mac = mac; 70 this.mac = mac;
51 this.vlan = vlan; 71 this.vlan = vlan;
52 this.location = location; 72 this.location = location;
53 - this.ip = ip; 73 + this.ip = ImmutableSet.copyOf(ip);
54 } 74 }
55 75
56 @Override 76 @Override
...@@ -69,7 +89,7 @@ public class DefaultHostDescription extends AbstractDescription ...@@ -69,7 +89,7 @@ public class DefaultHostDescription extends AbstractDescription
69 } 89 }
70 90
71 @Override 91 @Override
72 - public IpPrefix ipAddress() { 92 + public Set<IpPrefix> ipAddress() {
73 return ip; 93 return ip;
74 } 94 }
75 95
......
1 package org.onlab.onos.net.host; 1 package org.onlab.onos.net.host;
2 2
3 +import java.util.Set;
4 +
3 import org.onlab.onos.net.Description; 5 import org.onlab.onos.net.Description;
4 import org.onlab.onos.net.HostLocation; 6 import org.onlab.onos.net.HostLocation;
5 import org.onlab.packet.IpPrefix; 7 import org.onlab.packet.IpPrefix;
...@@ -38,6 +40,6 @@ public interface HostDescription extends Description { ...@@ -38,6 +40,6 @@ public interface HostDescription extends Description {
38 * @return host IP address 40 * @return host IP address
39 */ 41 */
40 // FIXME: Switch to IpAddress 42 // FIXME: Switch to IpAddress
41 - IpPrefix ipAddress(); 43 + Set<IpPrefix> ipAddress();
42 44
43 } 45 }
......
1 package org.onlab.onos.cluster; 1 package org.onlab.onos.cluster;
2 2
3 +import static com.google.common.base.Predicates.notNull;
3 import static org.junit.Assert.*; 4 import static org.junit.Assert.*;
4 import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId; 5 import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
5 6
...@@ -30,12 +31,13 @@ public class ControllerNodeToNodeIdTest { ...@@ -30,12 +31,13 @@ public class ControllerNodeToNodeIdTest {
30 @Test 31 @Test
31 public final void testToNodeId() { 32 public final void testToNodeId() {
32 33
33 - final Iterable<ControllerNode> nodes = Arrays.asList(CN1, CN2, CN3); 34 + final Iterable<ControllerNode> nodes = Arrays.asList(CN1, CN2, CN3, null);
34 final List<NodeId> nodeIds = Arrays.asList(NID1, NID2, NID3); 35 final List<NodeId> nodeIds = Arrays.asList(NID1, NID2, NID3);
35 36
36 assertEquals(nodeIds, 37 assertEquals(nodeIds,
37 FluentIterable.from(nodes) 38 FluentIterable.from(nodes)
38 .transform(toNodeId()) 39 .transform(toNodeId())
40 + .filter(notNull())
39 .toList()); 41 .toList());
40 } 42 }
41 43
......
...@@ -8,6 +8,8 @@ import org.onlab.packet.IpPrefix; ...@@ -8,6 +8,8 @@ import org.onlab.packet.IpPrefix;
8 import org.onlab.packet.MacAddress; 8 import org.onlab.packet.MacAddress;
9 import org.onlab.packet.VlanId; 9 import org.onlab.packet.VlanId;
10 10
11 +import com.google.common.collect.ImmutableSet;
12 +
11 import static org.junit.Assert.assertEquals; 13 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertTrue; 14 import static org.junit.Assert.assertTrue;
13 15
...@@ -33,7 +35,7 @@ public class DefualtHostDecriptionTest { ...@@ -33,7 +35,7 @@ public class DefualtHostDecriptionTest {
33 assertEquals("incorrect mac", MAC, host.hwAddress()); 35 assertEquals("incorrect mac", MAC, host.hwAddress());
34 assertEquals("incorrect vlan", VLAN, host.vlan()); 36 assertEquals("incorrect vlan", VLAN, host.vlan());
35 assertEquals("incorrect location", LOC, host.location()); 37 assertEquals("incorrect location", LOC, host.location());
36 - assertEquals("incorrect ip's", IP, host.ipAddress()); 38 + assertEquals("incorrect ip's", ImmutableSet.of(IP), host.ipAddress());
37 assertTrue("incorrect toString", host.toString().contains("vlan=10")); 39 assertTrue("incorrect toString", host.toString().contains("vlan=10"));
38 } 40 }
39 41
......
1 /** 1 /**
2 - * 2 + * Miscellaneous core system implementations.
3 */ 3 */
4 package org.onlab.onos.impl; 4 package org.onlab.onos.impl;
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -355,7 +355,7 @@ public class ProxyArpManager implements ProxyArpService { ...@@ -355,7 +355,7 @@ public class ProxyArpManager implements ProxyArpService {
355 355
356 arp.setTargetProtocolAddress(((ARP) request.getPayload()) 356 arp.setTargetProtocolAddress(((ARP) request.getPayload())
357 .getSenderProtocolAddress()); 357 .getSenderProtocolAddress());
358 - arp.setSenderProtocolAddress(srcIp.toRealInt()); 358 + arp.setSenderProtocolAddress(srcIp.toInt());
359 eth.setPayload(arp); 359 eth.setPayload(arp);
360 return eth; 360 return eth;
361 } 361 }
......
...@@ -515,12 +515,12 @@ public class GossipDeviceStore ...@@ -515,12 +515,12 @@ public class GossipDeviceStore
515 Map<PortNumber, Port> ports, 515 Map<PortNumber, Port> ports,
516 Set<PortNumber> processed) { 516 Set<PortNumber> processed) {
517 List<DeviceEvent> events = new ArrayList<>(); 517 List<DeviceEvent> events = new ArrayList<>();
518 - Iterator<PortNumber> iterator = ports.keySet().iterator(); 518 + Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator();
519 while (iterator.hasNext()) { 519 while (iterator.hasNext()) {
520 - PortNumber portNumber = iterator.next(); 520 + Entry<PortNumber, Port> e = iterator.next();
521 + PortNumber portNumber = e.getKey();
521 if (!processed.contains(portNumber)) { 522 if (!processed.contains(portNumber)) {
522 - events.add(new DeviceEvent(PORT_REMOVED, device, 523 + events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue()));
523 - ports.get(portNumber)));
524 iterator.remove(); 524 iterator.remove();
525 } 525 }
526 } 526 }
......
1 package org.onlab.onos.store.host.impl; 1 package org.onlab.onos.store.host.impl;
2 2
3 +import com.google.common.collect.FluentIterable;
3 import com.google.common.collect.HashMultimap; 4 import com.google.common.collect.HashMultimap;
5 +import com.google.common.collect.ImmutableList;
4 import com.google.common.collect.ImmutableSet; 6 import com.google.common.collect.ImmutableSet;
5 import com.google.common.collect.Multimap; 7 import com.google.common.collect.Multimap;
6 import com.google.common.collect.Sets; 8 import com.google.common.collect.Sets;
7 9
10 +import org.apache.commons.lang3.RandomUtils;
8 import org.apache.felix.scr.annotations.Activate; 11 import org.apache.felix.scr.annotations.Activate;
9 import org.apache.felix.scr.annotations.Component; 12 import org.apache.felix.scr.annotations.Component;
10 import org.apache.felix.scr.annotations.Deactivate; 13 import org.apache.felix.scr.annotations.Deactivate;
...@@ -12,6 +15,8 @@ import org.apache.felix.scr.annotations.Reference; ...@@ -12,6 +15,8 @@ import org.apache.felix.scr.annotations.Reference;
12 import org.apache.felix.scr.annotations.ReferenceCardinality; 15 import org.apache.felix.scr.annotations.ReferenceCardinality;
13 import org.apache.felix.scr.annotations.Service; 16 import org.apache.felix.scr.annotations.Service;
14 import org.onlab.onos.cluster.ClusterService; 17 import org.onlab.onos.cluster.ClusterService;
18 +import org.onlab.onos.cluster.ControllerNode;
19 +import org.onlab.onos.cluster.NodeId;
15 import org.onlab.onos.net.Annotations; 20 import org.onlab.onos.net.Annotations;
16 import org.onlab.onos.net.ConnectPoint; 21 import org.onlab.onos.net.ConnectPoint;
17 import org.onlab.onos.net.DefaultHost; 22 import org.onlab.onos.net.DefaultHost;
...@@ -19,6 +24,7 @@ import org.onlab.onos.net.DeviceId; ...@@ -19,6 +24,7 @@ import org.onlab.onos.net.DeviceId;
19 import org.onlab.onos.net.Host; 24 import org.onlab.onos.net.Host;
20 import org.onlab.onos.net.HostId; 25 import org.onlab.onos.net.HostId;
21 import org.onlab.onos.net.HostLocation; 26 import org.onlab.onos.net.HostLocation;
27 +import org.onlab.onos.net.host.DefaultHostDescription;
22 import org.onlab.onos.net.host.HostClockService; 28 import org.onlab.onos.net.host.HostClockService;
23 import org.onlab.onos.net.host.HostDescription; 29 import org.onlab.onos.net.host.HostDescription;
24 import org.onlab.onos.net.host.HostEvent; 30 import org.onlab.onos.net.host.HostEvent;
...@@ -42,12 +48,19 @@ import org.onlab.util.KryoPool; ...@@ -42,12 +48,19 @@ import org.onlab.util.KryoPool;
42 import org.slf4j.Logger; 48 import org.slf4j.Logger;
43 49
44 import java.io.IOException; 50 import java.io.IOException;
51 +import java.util.HashMap;
45 import java.util.HashSet; 52 import java.util.HashSet;
46 import java.util.Map; 53 import java.util.Map;
47 import java.util.Set; 54 import java.util.Set;
55 +import java.util.Map.Entry;
48 import java.util.concurrent.ConcurrentHashMap; 56 import java.util.concurrent.ConcurrentHashMap;
57 +import java.util.concurrent.ScheduledExecutorService;
58 +import java.util.concurrent.TimeUnit;
49 59
60 +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
61 +import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
50 import static org.onlab.onos.net.host.HostEvent.Type.*; 62 import static org.onlab.onos.net.host.HostEvent.Type.*;
63 +import static org.onlab.util.Tools.namedThreads;
51 import static org.slf4j.LoggerFactory.getLogger; 64 import static org.slf4j.LoggerFactory.getLogger;
52 65
53 //TODO: multi-provider, annotation not supported. 66 //TODO: multi-provider, annotation not supported.
...@@ -88,24 +101,58 @@ public class GossipHostStore ...@@ -88,24 +101,58 @@ public class GossipHostStore
88 protected void setupKryoPool() { 101 protected void setupKryoPool() {
89 serializerPool = KryoPool.newBuilder() 102 serializerPool = KryoPool.newBuilder()
90 .register(DistributedStoreSerializers.COMMON) 103 .register(DistributedStoreSerializers.COMMON)
104 + .register(InternalHostEvent.class)
91 .register(InternalHostRemovedEvent.class) 105 .register(InternalHostRemovedEvent.class)
106 + .register(HostFragmentId.class)
107 + .register(HostAntiEntropyAdvertisement.class)
92 .build() 108 .build()
93 .populate(1); 109 .populate(1);
94 } 110 }
95 }; 111 };
96 112
113 + private ScheduledExecutorService executor;
114 +
97 @Activate 115 @Activate
98 public void activate() { 116 public void activate() {
99 clusterCommunicator.addSubscriber( 117 clusterCommunicator.addSubscriber(
100 - GossipHostStoreMessageSubjects.HOST_UPDATED, new InternalHostEventListener()); 118 + GossipHostStoreMessageSubjects.HOST_UPDATED,
119 + new InternalHostEventListener());
120 + clusterCommunicator.addSubscriber(
121 + GossipHostStoreMessageSubjects.HOST_REMOVED,
122 + new InternalHostRemovedEventListener());
101 clusterCommunicator.addSubscriber( 123 clusterCommunicator.addSubscriber(
102 - GossipHostStoreMessageSubjects.HOST_REMOVED, new InternalHostRemovedEventListener()); 124 + GossipHostStoreMessageSubjects.HOST_ANTI_ENTROPY_ADVERTISEMENT,
125 + new InternalHostAntiEntropyAdvertisementListener());
126 +
127 + executor =
128 + newSingleThreadScheduledExecutor(namedThreads("link-anti-entropy-%d"));
129 +
130 + // TODO: Make these configurable
131 + long initialDelaySec = 5;
132 + long periodSec = 5;
133 + // start anti-entropy thread
134 + executor.scheduleAtFixedRate(new SendAdvertisementTask(),
135 + initialDelaySec, periodSec, TimeUnit.SECONDS);
103 136
104 log.info("Started"); 137 log.info("Started");
105 } 138 }
106 139
107 @Deactivate 140 @Deactivate
108 public void deactivate() { 141 public void deactivate() {
142 + executor.shutdownNow();
143 + try {
144 + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
145 + log.error("Timeout during executor shutdown");
146 + }
147 + } catch (InterruptedException e) {
148 + log.error("Error during executor shutdown", e);
149 + }
150 +
151 + hosts.clear();
152 + removedHosts.clear();
153 + locations.clear();
154 + portAddresses.clear();
155 +
109 log.info("Stopped"); 156 log.info("Stopped");
110 } 157 }
111 158
...@@ -153,7 +200,7 @@ public class GossipHostStore ...@@ -153,7 +200,7 @@ public class GossipHostStore
153 descr.hwAddress(), 200 descr.hwAddress(),
154 descr.vlan(), 201 descr.vlan(),
155 new Timestamped<>(descr.location(), timestamp), 202 new Timestamped<>(descr.location(), timestamp),
156 - ImmutableSet.of(descr.ipAddress())); 203 + ImmutableSet.copyOf(descr.ipAddress()));
157 hosts.put(hostId, newhost); 204 hosts.put(hostId, newhost);
158 locations.put(descr.location(), newhost); 205 locations.put(descr.location(), newhost);
159 return new HostEvent(HOST_ADDED, newhost); 206 return new HostEvent(HOST_ADDED, newhost);
...@@ -169,12 +216,12 @@ public class GossipHostStore ...@@ -169,12 +216,12 @@ public class GossipHostStore
169 return new HostEvent(HOST_MOVED, host); 216 return new HostEvent(HOST_MOVED, host);
170 } 217 }
171 218
172 - if (host.ipAddresses().contains(descr.ipAddress())) { 219 + if (host.ipAddresses().containsAll(descr.ipAddress())) {
173 return null; 220 return null;
174 } 221 }
175 222
176 Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses()); 223 Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
177 - addresses.add(descr.ipAddress()); 224 + addresses.addAll(descr.ipAddress());
178 StoredHost updated = new StoredHost(providerId, host.id(), 225 StoredHost updated = new StoredHost(providerId, host.id(),
179 host.mac(), host.vlan(), 226 host.mac(), host.vlan(),
180 host.location, addresses); 227 host.location, addresses);
...@@ -381,6 +428,10 @@ public class GossipHostStore ...@@ -381,6 +428,10 @@ public class GossipHostStore
381 public HostLocation location() { 428 public HostLocation location() {
382 return location.value(); 429 return location.value();
383 } 430 }
431 +
432 + public Timestamp timestamp() {
433 + return location.timestamp();
434 + }
384 } 435 }
385 436
386 private void notifyPeers(InternalHostRemovedEvent event) throws IOException { 437 private void notifyPeers(InternalHostRemovedEvent event) throws IOException {
...@@ -399,6 +450,16 @@ public class GossipHostStore ...@@ -399,6 +450,16 @@ public class GossipHostStore
399 clusterCommunicator.broadcast(message); 450 clusterCommunicator.broadcast(message);
400 } 451 }
401 452
453 + private void unicastMessage(NodeId peer,
454 + MessageSubject subject,
455 + Object event) throws IOException {
456 + ClusterMessage message = new ClusterMessage(
457 + clusterService.getLocalNode().id(),
458 + subject,
459 + SERIALIZER.encode(event));
460 + clusterCommunicator.unicast(message, peer);
461 + }
462 +
402 private void notifyDelegateIfNotNull(HostEvent event) { 463 private void notifyDelegateIfNotNull(HostEvent event) {
403 if (event != null) { 464 if (event != null) {
404 notifyDelegate(event); 465 notifyDelegate(event);
...@@ -434,4 +495,165 @@ public class GossipHostStore ...@@ -434,4 +495,165 @@ public class GossipHostStore
434 notifyDelegateIfNotNull(removeHostInternal(hostId, timestamp)); 495 notifyDelegateIfNotNull(removeHostInternal(hostId, timestamp));
435 } 496 }
436 } 497 }
498 +
499 + private final class SendAdvertisementTask implements Runnable {
500 +
501 + @Override
502 + public void run() {
503 + if (Thread.currentThread().isInterrupted()) {
504 + log.info("Interrupted, quitting");
505 + return;
506 + }
507 +
508 + try {
509 + final NodeId self = clusterService.getLocalNode().id();
510 + Set<ControllerNode> nodes = clusterService.getNodes();
511 +
512 + ImmutableList<NodeId> nodeIds = FluentIterable.from(nodes)
513 + .transform(toNodeId())
514 + .toList();
515 +
516 + if (nodeIds.size() == 1 && nodeIds.get(0).equals(self)) {
517 + log.debug("No other peers in the cluster.");
518 + return;
519 + }
520 +
521 + NodeId peer;
522 + do {
523 + int idx = RandomUtils.nextInt(0, nodeIds.size());
524 + peer = nodeIds.get(idx);
525 + } while (peer.equals(self));
526 +
527 + HostAntiEntropyAdvertisement ad = createAdvertisement();
528 +
529 + if (Thread.currentThread().isInterrupted()) {
530 + log.info("Interrupted, quitting");
531 + return;
532 + }
533 +
534 + try {
535 + unicastMessage(peer, GossipHostStoreMessageSubjects.HOST_ANTI_ENTROPY_ADVERTISEMENT, ad);
536 + } catch (IOException e) {
537 + log.debug("Failed to send anti-entropy advertisement", e);
538 + return;
539 + }
540 + } catch (Exception e) {
541 + // catch all Exception to avoid Scheduled task being suppressed.
542 + log.error("Exception thrown while sending advertisement", e);
543 + }
544 + }
545 + }
546 +
547 + private HostAntiEntropyAdvertisement createAdvertisement() {
548 + final NodeId self = clusterService.getLocalNode().id();
549 +
550 + Map<HostFragmentId, Timestamp> timestamps = new HashMap<>(hosts.size());
551 + Map<HostId, Timestamp> tombstones = new HashMap<>(removedHosts.size());
552 +
553 + for (Entry<HostId, StoredHost> e : hosts.entrySet()) {
554 +
555 + final HostId hostId = e.getKey();
556 + final StoredHost hostInfo = e.getValue();
557 + final ProviderId providerId = hostInfo.providerId();
558 + timestamps.put(new HostFragmentId(hostId, providerId), hostInfo.timestamp());
559 + }
560 +
561 + for (Entry<HostId, Timestamped<Host>> e : removedHosts.entrySet()) {
562 + tombstones.put(e.getKey(), e.getValue().timestamp());
563 + }
564 +
565 + return new HostAntiEntropyAdvertisement(self, timestamps, tombstones);
566 + }
567 +
568 + private synchronized void handleAntiEntropyAdvertisement(HostAntiEntropyAdvertisement ad) {
569 +
570 + final NodeId sender = ad.sender();
571 +
572 + for (Entry<HostId, StoredHost> host : hosts.entrySet()) {
573 + // for each locally live Hosts...
574 + final HostId hostId = host.getKey();
575 + final StoredHost localHost = host.getValue();
576 + final ProviderId providerId = localHost.providerId();
577 + final HostFragmentId hostFragId = new HostFragmentId(hostId, providerId);
578 + final Timestamp localLiveTimestamp = localHost.timestamp();
579 +
580 + Timestamp remoteTimestamp = ad.timestamps().get(hostFragId);
581 + if (remoteTimestamp == null) {
582 + remoteTimestamp = ad.tombstones().get(hostId);
583 + }
584 + if (remoteTimestamp == null ||
585 + localLiveTimestamp.compareTo(remoteTimestamp) > 0) {
586 +
587 + // local is more recent, push
588 + // TODO: annotation is lost
589 + final HostDescription desc = new DefaultHostDescription(
590 + localHost.mac(),
591 + localHost.vlan(),
592 + localHost.location(),
593 + localHost.ipAddresses());
594 + try {
595 + unicastMessage(sender, GossipHostStoreMessageSubjects.HOST_UPDATED,
596 + new InternalHostEvent(providerId, hostId, desc, localHost.timestamp()));
597 + } catch (IOException e1) {
598 + log.debug("Failed to send advertisement response", e1);
599 + }
600 + }
601 +
602 + final Timestamp remoteDeadTimestamp = ad.tombstones().get(hostId);
603 + if (remoteDeadTimestamp != null &&
604 + remoteDeadTimestamp.compareTo(localLiveTimestamp) > 0) {
605 + // sender has recent remove
606 + notifyDelegateIfNotNull(removeHostInternal(hostId, remoteDeadTimestamp));
607 + }
608 + }
609 +
610 + for (Entry<HostId, Timestamped<Host>> dead : removedHosts.entrySet()) {
611 + // for each locally dead Hosts
612 + final HostId hostId = dead.getKey();
613 + final Timestamp localDeadTimestamp = dead.getValue().timestamp();
614 +
615 + // TODO: pick proper ProviderId, when supporting multi-provider
616 + final ProviderId providerId = dead.getValue().value().providerId();
617 + final HostFragmentId hostFragId = new HostFragmentId(hostId, providerId);
618 +
619 + final Timestamp remoteLiveTimestamp = ad.timestamps().get(hostFragId);
620 + if (remoteLiveTimestamp != null &&
621 + localDeadTimestamp.compareTo(remoteLiveTimestamp) > 0) {
622 + // sender has zombie, push
623 + try {
624 + unicastMessage(sender, GossipHostStoreMessageSubjects.HOST_REMOVED,
625 + new InternalHostRemovedEvent(hostId, localDeadTimestamp));
626 + } catch (IOException e1) {
627 + log.debug("Failed to send advertisement response", e1);
628 + }
629 + }
630 + }
631 +
632 +
633 + for (Entry<HostId, Timestamp> e : ad.tombstones().entrySet()) {
634 + // for each remote tombstone advertisement...
635 + final HostId hostId = e.getKey();
636 + final Timestamp adRemoveTimestamp = e.getValue();
637 +
638 + final StoredHost storedHost = hosts.get(hostId);
639 + if (storedHost == null) {
640 + continue;
641 + }
642 + if (adRemoveTimestamp.compareTo(storedHost.timestamp()) > 0) {
643 + // sender has recent remove info, locally remove
644 + notifyDelegateIfNotNull(removeHostInternal(hostId, adRemoveTimestamp));
645 + }
646 + }
647 + }
648 +
649 + private final class InternalHostAntiEntropyAdvertisementListener implements
650 + ClusterMessageHandler {
651 +
652 + @Override
653 + public void handle(ClusterMessage message) {
654 + log.debug("Received Host Anti-Entropy advertisement from peer: {}", message.sender());
655 + HostAntiEntropyAdvertisement advertisement = SERIALIZER.decode(message.payload());
656 + handleAntiEntropyAdvertisement(advertisement);
657 + }
658 + }
437 } 659 }
......
...@@ -4,6 +4,11 @@ import org.onlab.onos.store.cluster.messaging.MessageSubject; ...@@ -4,6 +4,11 @@ import org.onlab.onos.store.cluster.messaging.MessageSubject;
4 4
5 public final class GossipHostStoreMessageSubjects { 5 public final class GossipHostStoreMessageSubjects {
6 private GossipHostStoreMessageSubjects() {} 6 private GossipHostStoreMessageSubjects() {}
7 - public static final MessageSubject HOST_UPDATED = new MessageSubject("peer-host-updated"); 7 +
8 - public static final MessageSubject HOST_REMOVED = new MessageSubject("peer-host-removed"); 8 + public static final MessageSubject HOST_UPDATED
9 + = new MessageSubject("peer-host-updated");
10 + public static final MessageSubject HOST_REMOVED
11 + = new MessageSubject("peer-host-removed");
12 + public static final MessageSubject HOST_ANTI_ENTROPY_ADVERTISEMENT
13 + = new MessageSubject("host-enti-entropy-advertisement");;
9 } 14 }
......
1 +package org.onlab.onos.store.host.impl;
2 +
3 +import static com.google.common.base.Preconditions.checkNotNull;
4 +
5 +import java.util.Map;
6 +
7 +import org.onlab.onos.cluster.NodeId;
8 +import org.onlab.onos.net.HostId;
9 +import org.onlab.onos.store.Timestamp;
10 +
11 +/**
12 + * Host AE Advertisement message.
13 + */
14 +public final class HostAntiEntropyAdvertisement {
15 +
16 + private final NodeId sender;
17 + private final Map<HostFragmentId, Timestamp> timestamps;
18 + private final Map<HostId, Timestamp> tombstones;
19 +
20 +
21 + public HostAntiEntropyAdvertisement(NodeId sender,
22 + Map<HostFragmentId, Timestamp> timestamps,
23 + Map<HostId, Timestamp> tombstones) {
24 + this.sender = checkNotNull(sender);
25 + this.timestamps = checkNotNull(timestamps);
26 + this.tombstones = checkNotNull(tombstones);
27 + }
28 +
29 + public NodeId sender() {
30 + return sender;
31 + }
32 +
33 + public Map<HostFragmentId, Timestamp> timestamps() {
34 + return timestamps;
35 + }
36 +
37 + public Map<HostId, Timestamp> tombstones() {
38 + return tombstones;
39 + }
40 +
41 + // For serializer
42 + @SuppressWarnings("unused")
43 + private HostAntiEntropyAdvertisement() {
44 + this.sender = null;
45 + this.timestamps = null;
46 + this.tombstones = null;
47 + }
48 +}
1 +package org.onlab.onos.store.host.impl;
2 +
3 +import java.util.Objects;
4 +
5 +import org.onlab.onos.net.HostId;
6 +import org.onlab.onos.net.provider.ProviderId;
7 +
8 +import com.google.common.base.MoreObjects;
9 +
10 +/**
11 + * Identifier for HostDescription from a Provider.
12 + */
13 +public final class HostFragmentId {
14 + public final ProviderId providerId;
15 + public final HostId hostId;
16 +
17 + public HostFragmentId(HostId hostId, ProviderId providerId) {
18 + this.providerId = providerId;
19 + this.hostId = hostId;
20 + }
21 +
22 + public HostId hostId() {
23 + return hostId;
24 + }
25 +
26 + public ProviderId providerId() {
27 + return providerId;
28 + }
29 +
30 + @Override
31 + public int hashCode() {
32 + return Objects.hash(providerId, hostId);
33 + }
34 +
35 + @Override
36 + public boolean equals(Object obj) {
37 + if (this == obj) {
38 + return true;
39 + }
40 + if (!(obj instanceof HostFragmentId)) {
41 + return false;
42 + }
43 + HostFragmentId that = (HostFragmentId) obj;
44 + return Objects.equals(this.hostId, that.hostId) &&
45 + Objects.equals(this.providerId, that.providerId);
46 + }
47 +
48 + @Override
49 + public String toString() {
50 + return MoreObjects.toStringHelper(getClass())
51 + .add("providerId", providerId)
52 + .add("hostId", hostId)
53 + .toString();
54 + }
55 +
56 + // for serializer
57 + @SuppressWarnings("unused")
58 + private HostFragmentId() {
59 + this.providerId = null;
60 + this.hostId = null;
61 + }
62 +}
1 +/**
2 + * Implementation of host store using distributed p2p synchronization protocol.
3 + */
4 +package org.onlab.onos.store.host.impl;
1 package org.onlab.onos.store.link.impl; 1 package org.onlab.onos.store.link.impl;
2 2
3 import com.google.common.base.Function; 3 import com.google.common.base.Function;
4 -import com.google.common.base.Predicate;
5 import com.google.common.collect.FluentIterable; 4 import com.google.common.collect.FluentIterable;
6 import com.google.common.collect.HashMultimap; 5 import com.google.common.collect.HashMultimap;
7 import com.google.common.collect.ImmutableList; 6 import com.google.common.collect.ImmutableList;
...@@ -27,7 +26,6 @@ import org.onlab.onos.net.Link; ...@@ -27,7 +26,6 @@ import org.onlab.onos.net.Link;
27 import org.onlab.onos.net.SparseAnnotations; 26 import org.onlab.onos.net.SparseAnnotations;
28 import org.onlab.onos.net.Link.Type; 27 import org.onlab.onos.net.Link.Type;
29 import org.onlab.onos.net.LinkKey; 28 import org.onlab.onos.net.LinkKey;
30 -import org.onlab.onos.net.Provided;
31 import org.onlab.onos.net.device.DeviceClockService; 29 import org.onlab.onos.net.device.DeviceClockService;
32 import org.onlab.onos.net.link.DefaultLinkDescription; 30 import org.onlab.onos.net.link.DefaultLinkDescription;
33 import org.onlab.onos.net.link.LinkDescription; 31 import org.onlab.onos.net.link.LinkDescription;
...@@ -70,7 +68,9 @@ import static org.onlab.onos.net.link.LinkEvent.Type.*; ...@@ -70,7 +68,9 @@ import static org.onlab.onos.net.link.LinkEvent.Type.*;
70 import static org.onlab.util.Tools.namedThreads; 68 import static org.onlab.util.Tools.namedThreads;
71 import static org.slf4j.LoggerFactory.getLogger; 69 import static org.slf4j.LoggerFactory.getLogger;
72 import static com.google.common.collect.Multimaps.synchronizedSetMultimap; 70 import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
71 +import static com.google.common.base.Preconditions.checkNotNull;
73 import static com.google.common.base.Predicates.notNull; 72 import static com.google.common.base.Predicates.notNull;
73 +import static org.onlab.onos.store.link.impl.GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT;
74 74
75 /** 75 /**
76 * Manages inventory of infrastructure links in distributed data store 76 * Manages inventory of infrastructure links in distributed data store
...@@ -239,9 +239,9 @@ public class GossipLinkStore ...@@ -239,9 +239,9 @@ public class GossipLinkStore
239 LinkKey key = linkKey(linkDescription.src(), linkDescription.dst()); 239 LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
240 final LinkEvent event; 240 final LinkEvent event;
241 final Timestamped<LinkDescription> mergedDesc; 241 final Timestamped<LinkDescription> mergedDesc;
242 - synchronized (getLinkDescriptions(key)) { 242 + synchronized (getOrCreateLinkDescriptions(key)) {
243 event = createOrUpdateLinkInternal(providerId, deltaDesc); 243 event = createOrUpdateLinkInternal(providerId, deltaDesc);
244 - mergedDesc = getLinkDescriptions(key).get(providerId); 244 + mergedDesc = getOrCreateLinkDescriptions(key).get(providerId);
245 } 245 }
246 246
247 if (event != null) { 247 if (event != null) {
...@@ -265,7 +265,7 @@ public class GossipLinkStore ...@@ -265,7 +265,7 @@ public class GossipLinkStore
265 265
266 LinkKey key = linkKey(linkDescription.value().src(), 266 LinkKey key = linkKey(linkDescription.value().src(),
267 linkDescription.value().dst()); 267 linkDescription.value().dst());
268 - Map<ProviderId, Timestamped<LinkDescription>> descs = getLinkDescriptions(key); 268 + Map<ProviderId, Timestamped<LinkDescription>> descs = getOrCreateLinkDescriptions(key);
269 269
270 synchronized (descs) { 270 synchronized (descs) {
271 // if the link was previously removed, we should proceed if and 271 // if the link was previously removed, we should proceed if and
...@@ -296,7 +296,7 @@ public class GossipLinkStore ...@@ -296,7 +296,7 @@ public class GossipLinkStore
296 ProviderId providerId, 296 ProviderId providerId,
297 Timestamped<LinkDescription> linkDescription) { 297 Timestamped<LinkDescription> linkDescription) {
298 298
299 - // merge existing attributes and merge 299 + // merge existing annotations
300 Timestamped<LinkDescription> existingLinkDescription = descs.get(providerId); 300 Timestamped<LinkDescription> existingLinkDescription = descs.get(providerId);
301 if (existingLinkDescription != null && existingLinkDescription.isNewer(linkDescription)) { 301 if (existingLinkDescription != null && existingLinkDescription.isNewer(linkDescription)) {
302 return null; 302 return null;
...@@ -377,14 +377,54 @@ public class GossipLinkStore ...@@ -377,14 +377,54 @@ public class GossipLinkStore
377 return event; 377 return event;
378 } 378 }
379 379
380 + private static Timestamped<LinkDescription> getPrimaryDescription(
381 + Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
382 +
383 + synchronized (linkDescriptions) {
384 + for (Entry<ProviderId, Timestamped<LinkDescription>>
385 + e : linkDescriptions.entrySet()) {
386 +
387 + if (!e.getKey().isAncillary()) {
388 + return e.getValue();
389 + }
390 + }
391 + }
392 + return null;
393 + }
394 +
395 +
396 + // TODO: consider slicing out as Timestamp utils
397 + /**
398 + * Checks is timestamp is more recent than timestamped object.
399 + *
400 + * @param timestamp to check if this is more recent then other
401 + * @param timestamped object to be tested against
402 + * @return true if {@code timestamp} is more recent than {@code timestamped}
403 + * or {@code timestamped is null}
404 + */
405 + private static boolean isMoreRecent(Timestamp timestamp, Timestamped<?> timestamped) {
406 + checkNotNull(timestamp);
407 + if (timestamped == null) {
408 + return true;
409 + }
410 + return timestamp.compareTo(timestamped.timestamp()) > 0;
411 + }
412 +
380 private LinkEvent removeLinkInternal(LinkKey key, Timestamp timestamp) { 413 private LinkEvent removeLinkInternal(LinkKey key, Timestamp timestamp) {
381 - Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions = 414 + Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions
382 - getLinkDescriptions(key); 415 + = getOrCreateLinkDescriptions(key);
416 +
383 synchronized (linkDescriptions) { 417 synchronized (linkDescriptions) {
418 + if (linkDescriptions.isEmpty()) {
419 + // never seen such link before. keeping timestamp for record
420 + removedLinks.put(key, timestamp);
421 + return null;
422 + }
384 // accept removal request if given timestamp is newer than 423 // accept removal request if given timestamp is newer than
385 // the latest Timestamp from Primary provider 424 // the latest Timestamp from Primary provider
386 - ProviderId primaryProviderId = pickPrimaryProviderId(linkDescriptions); 425 + Timestamped<LinkDescription> prim = getPrimaryDescription(linkDescriptions);
387 - if (linkDescriptions.get(primaryProviderId).isNewer(timestamp)) { 426 + if (!isMoreRecent(timestamp, prim)) {
427 + // outdated remove request, ignore
388 return null; 428 return null;
389 } 429 }
390 removedLinks.put(key, timestamp); 430 removedLinks.put(key, timestamp);
...@@ -406,12 +446,13 @@ public class GossipLinkStore ...@@ -406,12 +446,13 @@ public class GossipLinkStore
406 /** 446 /**
407 * @return primary ProviderID, or randomly chosen one if none exists 447 * @return primary ProviderID, or randomly chosen one if none exists
408 */ 448 */
409 - private ProviderId pickPrimaryProviderId( 449 + private static ProviderId pickBaseProviderId(
410 Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) { 450 Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
411 451
412 ProviderId fallBackPrimary = null; 452 ProviderId fallBackPrimary = null;
413 for (Entry<ProviderId, Timestamped<LinkDescription>> e : linkDescriptions.entrySet()) { 453 for (Entry<ProviderId, Timestamped<LinkDescription>> e : linkDescriptions.entrySet()) {
414 if (!e.getKey().isAncillary()) { 454 if (!e.getKey().isAncillary()) {
455 + // found primary
415 return e.getKey(); 456 return e.getKey();
416 } else if (fallBackPrimary == null) { 457 } else if (fallBackPrimary == null) {
417 // pick randomly as a fallback in case there is no primary 458 // pick randomly as a fallback in case there is no primary
...@@ -421,9 +462,10 @@ public class GossipLinkStore ...@@ -421,9 +462,10 @@ public class GossipLinkStore
421 return fallBackPrimary; 462 return fallBackPrimary;
422 } 463 }
423 464
465 + // Guarded by linkDescs value (=locking each Link)
424 private Link composeLink(Map<ProviderId, Timestamped<LinkDescription>> descs) { 466 private Link composeLink(Map<ProviderId, Timestamped<LinkDescription>> descs) {
425 - ProviderId primaryProviderId = pickPrimaryProviderId(descs); 467 + ProviderId baseProviderId = pickBaseProviderId(descs);
426 - Timestamped<LinkDescription> base = descs.get(primaryProviderId); 468 + Timestamped<LinkDescription> base = descs.get(baseProviderId);
427 469
428 ConnectPoint src = base.value().src(); 470 ConnectPoint src = base.value().src();
429 ConnectPoint dst = base.value().dst(); 471 ConnectPoint dst = base.value().dst();
...@@ -432,7 +474,7 @@ public class GossipLinkStore ...@@ -432,7 +474,7 @@ public class GossipLinkStore
432 annotations = merge(annotations, base.value().annotations()); 474 annotations = merge(annotations, base.value().annotations());
433 475
434 for (Entry<ProviderId, Timestamped<LinkDescription>> e : descs.entrySet()) { 476 for (Entry<ProviderId, Timestamped<LinkDescription>> e : descs.entrySet()) {
435 - if (primaryProviderId.equals(e.getKey())) { 477 + if (baseProviderId.equals(e.getKey())) {
436 continue; 478 continue;
437 } 479 }
438 480
...@@ -445,10 +487,10 @@ public class GossipLinkStore ...@@ -445,10 +487,10 @@ public class GossipLinkStore
445 annotations = merge(annotations, e.getValue().value().annotations()); 487 annotations = merge(annotations, e.getValue().value().annotations());
446 } 488 }
447 489
448 - return new DefaultLink(primaryProviderId , src, dst, type, annotations); 490 + return new DefaultLink(baseProviderId, src, dst, type, annotations);
449 } 491 }
450 492
451 - private Map<ProviderId, Timestamped<LinkDescription>> getLinkDescriptions(LinkKey key) { 493 + private Map<ProviderId, Timestamped<LinkDescription>> getOrCreateLinkDescriptions(LinkKey key) {
452 Map<ProviderId, Timestamped<LinkDescription>> r; 494 Map<ProviderId, Timestamped<LinkDescription>> r;
453 r = linkDescs.get(key); 495 r = linkDescs.get(key);
454 if (r != null) { 496 if (r != null) {
...@@ -464,11 +506,11 @@ public class GossipLinkStore ...@@ -464,11 +506,11 @@ public class GossipLinkStore
464 } 506 }
465 } 507 }
466 508
467 - private Timestamped<LinkDescription> getLinkDescription(LinkKey key, ProviderId providerId) {
468 - return getLinkDescriptions(key).get(providerId);
469 - }
470 -
471 private final Function<LinkKey, Link> lookupLink = new LookupLink(); 509 private final Function<LinkKey, Link> lookupLink = new LookupLink();
510 + /**
511 + * Returns a Function to lookup Link instance using LinkKey from cache.
512 + * @return
513 + */
472 private Function<LinkKey, Link> lookupLink() { 514 private Function<LinkKey, Link> lookupLink() {
473 return lookupLink; 515 return lookupLink;
474 } 516 }
...@@ -476,20 +518,11 @@ public class GossipLinkStore ...@@ -476,20 +518,11 @@ public class GossipLinkStore
476 private final class LookupLink implements Function<LinkKey, Link> { 518 private final class LookupLink implements Function<LinkKey, Link> {
477 @Override 519 @Override
478 public Link apply(LinkKey input) { 520 public Link apply(LinkKey input) {
479 - return links.get(input); 521 + if (input == null) {
480 - } 522 + return null;
481 - } 523 + } else {
482 - 524 + return links.get(input);
483 - private static final class IsPrimary implements Predicate<Provided> { 525 + }
484 -
485 - private static final Predicate<Provided> IS_PRIMARY = new IsPrimary();
486 - public static final Predicate<Provided> isPrimary() {
487 - return IS_PRIMARY;
488 - }
489 -
490 - @Override
491 - public boolean apply(Provided input) {
492 - return !input.providerId().isAncillary();
493 } 526 }
494 } 527 }
495 528
...@@ -499,7 +532,6 @@ public class GossipLinkStore ...@@ -499,7 +532,6 @@ public class GossipLinkStore
499 } 532 }
500 } 533 }
501 534
502 - // TODO: should we be throwing exception?
503 private void broadcastMessage(MessageSubject subject, Object event) throws IOException { 535 private void broadcastMessage(MessageSubject subject, Object event) throws IOException {
504 ClusterMessage message = new ClusterMessage( 536 ClusterMessage message = new ClusterMessage(
505 clusterService.getLocalNode().id(), 537 clusterService.getLocalNode().id(),
...@@ -508,17 +540,12 @@ public class GossipLinkStore ...@@ -508,17 +540,12 @@ public class GossipLinkStore
508 clusterCommunicator.broadcast(message); 540 clusterCommunicator.broadcast(message);
509 } 541 }
510 542
511 - // TODO: should we be throwing exception? 543 + private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) throws IOException {
512 - private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) { 544 + ClusterMessage message = new ClusterMessage(
513 - try { 545 + clusterService.getLocalNode().id(),
514 - ClusterMessage message = new ClusterMessage( 546 + subject,
515 - clusterService.getLocalNode().id(), 547 + SERIALIZER.encode(event));
516 - subject, 548 + clusterCommunicator.unicast(message, recipient);
517 - SERIALIZER.encode(event));
518 - clusterCommunicator.unicast(message, recipient);
519 - } catch (IOException e) {
520 - log.error("Failed to send a {} message to {}", subject.value(), recipient);
521 - }
522 } 549 }
523 550
524 private void notifyPeers(InternalLinkEvent event) throws IOException { 551 private void notifyPeers(InternalLinkEvent event) throws IOException {
...@@ -529,12 +556,22 @@ public class GossipLinkStore ...@@ -529,12 +556,22 @@ public class GossipLinkStore
529 broadcastMessage(GossipLinkStoreMessageSubjects.LINK_REMOVED, event); 556 broadcastMessage(GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
530 } 557 }
531 558
559 + // notify peer, silently ignoring error
532 private void notifyPeer(NodeId peer, InternalLinkEvent event) { 560 private void notifyPeer(NodeId peer, InternalLinkEvent event) {
533 - unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_UPDATE, event); 561 + try {
562 + unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_UPDATE, event);
563 + } catch (IOException e) {
564 + log.debug("Failed to notify peer {} with message {}", peer, event);
565 + }
534 } 566 }
535 567
568 + // notify peer, silently ignoring error
536 private void notifyPeer(NodeId peer, InternalLinkRemovedEvent event) { 569 private void notifyPeer(NodeId peer, InternalLinkRemovedEvent event) {
537 - unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_REMOVED, event); 570 + try {
571 + unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
572 + } catch (IOException e) {
573 + log.debug("Failed to notify peer {} with message {}", peer, event);
574 + }
538 } 575 }
539 576
540 private final class SendAdvertisementTask implements Runnable { 577 private final class SendAdvertisementTask implements Runnable {
...@@ -573,9 +610,9 @@ public class GossipLinkStore ...@@ -573,9 +610,9 @@ public class GossipLinkStore
573 } 610 }
574 611
575 try { 612 try {
576 - unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT, ad); 613 + unicastMessage(peer, LINK_ANTI_ENTROPY_ADVERTISEMENT, ad);
577 - } catch (Exception e) { 614 + } catch (IOException e) {
578 - log.error("Failed to send anti-entropy advertisement", e); 615 + log.debug("Failed to send anti-entropy advertisement to {}", peer);
579 return; 616 return;
580 } 617 }
581 } catch (Exception e) { 618 } catch (Exception e) {
...@@ -608,42 +645,75 @@ public class GossipLinkStore ...@@ -608,42 +645,75 @@ public class GossipLinkStore
608 return new LinkAntiEntropyAdvertisement(self, linkTimestamps, linkTombstones); 645 return new LinkAntiEntropyAdvertisement(self, linkTimestamps, linkTombstones);
609 } 646 }
610 647
611 - private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement advertisement) { 648 + private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement ad) {
612 -
613 - NodeId peer = advertisement.sender();
614 -
615 - Map<LinkFragmentId, Timestamp> linkTimestamps = advertisement.linkTimestamps();
616 - Map<LinkKey, Timestamp> linkTombstones = advertisement.linkTombstones();
617 - for (Map.Entry<LinkFragmentId, Timestamp> entry : linkTimestamps.entrySet()) {
618 - LinkFragmentId linkFragmentId = entry.getKey();
619 - Timestamp peerTimestamp = entry.getValue();
620 649
621 - LinkKey key = linkFragmentId.linkKey(); 650 + final NodeId sender = ad.sender();
622 - ProviderId providerId = linkFragmentId.providerId(); 651 + boolean localOutdated = false;
623 652
624 - Timestamped<LinkDescription> linkDescription = getLinkDescription(key, providerId); 653 + for (Entry<LinkKey, Map<ProviderId, Timestamped<LinkDescription>>>
625 - if (linkDescription.isNewer(peerTimestamp)) { 654 + l : linkDescs.entrySet()) {
626 - // I have more recent link description. update peer. 655 +
627 - notifyPeer(peer, new InternalLinkEvent(providerId, linkDescription)); 656 + final LinkKey key = l.getKey();
628 - } 657 + final Map<ProviderId, Timestamped<LinkDescription>> link = l.getValue();
629 - // else TODO: Peer has more recent link description. request it. 658 + synchronized (link) {
630 - 659 + Timestamp localLatest = removedLinks.get(key);
631 - Timestamp linkRemovedTimestamp = removedLinks.get(key); 660 +
632 - if (linkRemovedTimestamp != null && linkRemovedTimestamp.compareTo(peerTimestamp) > 0) { 661 + for (Entry<ProviderId, Timestamped<LinkDescription>> p : link.entrySet()) {
633 - // peer has a zombie link. update peer. 662 + final ProviderId providerId = p.getKey();
634 - notifyPeer(peer, new InternalLinkRemovedEvent(key, linkRemovedTimestamp)); 663 + final Timestamped<LinkDescription> pDesc = p.getValue();
664 +
665 + final LinkFragmentId fragId = new LinkFragmentId(key, providerId);
666 + // remote
667 + Timestamp remoteTimestamp = ad.linkTimestamps().get(fragId);
668 + if (remoteTimestamp == null) {
669 + remoteTimestamp = ad.linkTombstones().get(key);
670 + }
671 + if (remoteTimestamp == null ||
672 + pDesc.isNewer(remoteTimestamp)) {
673 + // I have more recent link description. update peer.
674 + notifyPeer(sender, new InternalLinkEvent(providerId, pDesc));
675 + } else {
676 + final Timestamp remoteLive = ad.linkTimestamps().get(fragId);
677 + if (remoteLive != null &&
678 + remoteLive.compareTo(pDesc.timestamp()) > 0) {
679 + // I have something outdated
680 + localOutdated = true;
681 + }
682 + }
683 +
684 + // search local latest along the way
685 + if (localLatest == null ||
686 + pDesc.isNewer(localLatest)) {
687 + localLatest = pDesc.timestamp();
688 + }
689 + }
690 + // Tests if remote remove is more recent then local latest.
691 + final Timestamp remoteRemove = ad.linkTombstones().get(key);
692 + if (remoteRemove != null) {
693 + if (localLatest != null &&
694 + localLatest.compareTo(remoteRemove) < 0) {
695 + // remote remove is more recent
696 + notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
697 + }
698 + }
635 } 699 }
636 } 700 }
637 701
638 - for (Map.Entry<LinkKey, Timestamp> entry : linkTombstones.entrySet()) { 702 + // populate remove info if not known locally
639 - LinkKey key = entry.getKey(); 703 + for (Entry<LinkKey, Timestamp> remoteRm : ad.linkTombstones().entrySet()) {
640 - Timestamp peerTimestamp = entry.getValue(); 704 + final LinkKey key = remoteRm.getKey();
705 + final Timestamp remoteRemove = remoteRm.getValue();
706 + // relying on removeLinkInternal to ignore stale info
707 + notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
708 + }
641 709
642 - ProviderId primaryProviderId = pickPrimaryProviderId(getLinkDescriptions(key)); 710 + if (localOutdated) {
643 - if (primaryProviderId != null) { 711 + // send back advertisement to speed up convergence
644 - if (!getLinkDescription(key, primaryProviderId).isNewer(peerTimestamp)) { 712 + try {
645 - notifyDelegateIfNotNull(removeLinkInternal(key, peerTimestamp)); 713 + unicastMessage(sender, LINK_ANTI_ENTROPY_ADVERTISEMENT,
646 - } 714 + createAdvertisement());
715 + } catch (IOException e) {
716 + log.debug("Failed to send back active advertisement");
647 } 717 }
648 } 718 }
649 } 719 }
......
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>onos-core-hz</artifactId>
10 - <version>1.0.0-SNAPSHOT</version>
11 - <relativePath>../pom.xml</relativePath>
12 - </parent>
13 -
14 - <artifactId>onos-core-hz-net</artifactId>
15 - <packaging>bundle</packaging>
16 -
17 - <description>ONOS Hazelcast based distributed store subsystems</description>
18 -
19 - <dependencies>
20 - <dependency>
21 - <groupId>org.onlab.onos</groupId>
22 - <artifactId>onos-api</artifactId>
23 - </dependency>
24 - <dependency>
25 - <groupId>org.onlab.onos</groupId>
26 - <artifactId>onos-core-hz-common</artifactId>
27 - <version>${project.version}</version>
28 - </dependency>
29 - <dependency>
30 - <groupId>org.onlab.onos</groupId>
31 - <artifactId>onos-core-hz-common</artifactId>
32 - <classifier>tests</classifier>
33 - <scope>test</scope>
34 - <version>${project.version}</version>
35 - </dependency>
36 - <dependency>
37 - <groupId>org.apache.felix</groupId>
38 - <artifactId>org.apache.felix.scr.annotations</artifactId>
39 - </dependency>
40 - <dependency>
41 - <groupId>com.hazelcast</groupId>
42 - <artifactId>hazelcast</artifactId>
43 - </dependency>
44 - </dependencies>
45 -
46 - <build>
47 - <plugins>
48 - <plugin>
49 - <groupId>org.apache.felix</groupId>
50 - <artifactId>maven-scr-plugin</artifactId>
51 - </plugin>
52 - </plugins>
53 - </build>
54 -
55 -</project>
1 -package org.onlab.onos.store.device.impl;
2 -
3 -import static com.google.common.base.Predicates.notNull;
4 -
5 -import com.google.common.base.Optional;
6 -import com.google.common.cache.LoadingCache;
7 -import com.google.common.collect.FluentIterable;
8 -import com.google.common.collect.ImmutableList;
9 -import com.google.common.collect.ImmutableSet;
10 -import com.google.common.collect.ImmutableSet.Builder;
11 -import com.hazelcast.core.IMap;
12 -import com.hazelcast.core.ISet;
13 -
14 -import org.apache.felix.scr.annotations.Activate;
15 -import org.apache.felix.scr.annotations.Component;
16 -import org.apache.felix.scr.annotations.Deactivate;
17 -import org.apache.felix.scr.annotations.Service;
18 -import org.onlab.onos.net.DefaultDevice;
19 -import org.onlab.onos.net.DefaultPort;
20 -import org.onlab.onos.net.Device;
21 -import org.onlab.onos.net.DeviceId;
22 -import org.onlab.onos.net.Port;
23 -import org.onlab.onos.net.PortNumber;
24 -import org.onlab.onos.net.device.DeviceDescription;
25 -import org.onlab.onos.net.device.DeviceEvent;
26 -import org.onlab.onos.net.device.DeviceStore;
27 -import org.onlab.onos.net.device.DeviceStoreDelegate;
28 -import org.onlab.onos.net.device.PortDescription;
29 -import org.onlab.onos.net.provider.ProviderId;
30 -import org.onlab.onos.store.common.AbsentInvalidatingLoadingCache;
31 -import org.onlab.onos.store.common.AbstractHazelcastStore;
32 -import org.onlab.onos.store.common.OptionalCacheLoader;
33 -import org.slf4j.Logger;
34 -
35 -import java.util.ArrayList;
36 -import java.util.Collections;
37 -import java.util.HashMap;
38 -import java.util.HashSet;
39 -import java.util.Iterator;
40 -import java.util.List;
41 -import java.util.Map;
42 -import java.util.Objects;
43 -import java.util.Set;
44 -
45 -import static com.google.common.base.Preconditions.checkArgument;
46 -import static com.google.common.cache.CacheBuilder.newBuilder;
47 -import static org.onlab.onos.net.device.DeviceEvent.Type.*;
48 -import static org.slf4j.LoggerFactory.getLogger;
49 -
50 -//TODO: Add support for multiple provider and annotations
51 -/**
52 - * Manages inventory of infrastructure devices using Hazelcast-backed map.
53 - */
54 -@Component(immediate = true)
55 -@Service
56 -public class DistributedDeviceStore
57 - extends AbstractHazelcastStore<DeviceEvent, DeviceStoreDelegate>
58 - implements DeviceStore {
59 -
60 - private final Logger log = getLogger(getClass());
61 -
62 - public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
63 -
64 - // private IMap<DeviceId, DefaultDevice> cache;
65 - private IMap<byte[], byte[]> rawDevices;
66 - private LoadingCache<DeviceId, Optional<DefaultDevice>> devices;
67 -
68 - // private ISet<DeviceId> availableDevices;
69 - private ISet<byte[]> availableDevices;
70 -
71 - // TODO DevicePorts is very inefficient consider restructuring.
72 - // private IMap<DeviceId, Map<PortNumber, Port>> devicePorts;
73 - private IMap<byte[], byte[]> rawDevicePorts;
74 - private LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> devicePorts;
75 -
76 - private String devicesListener;
77 -
78 - private String portsListener;
79 -
80 - @Override
81 - @Activate
82 - public void activate() {
83 - super.activate();
84 -
85 - // IMap event handler needs value
86 - final boolean includeValue = true;
87 -
88 - // TODO decide on Map name scheme to avoid collision
89 - rawDevices = theInstance.getMap("devices");
90 - final OptionalCacheLoader<DeviceId, DefaultDevice> deviceLoader
91 - = new OptionalCacheLoader<>(serializer, rawDevices);
92 - devices = new AbsentInvalidatingLoadingCache<>(newBuilder().build(deviceLoader));
93 - // refresh/populate cache based on notification from other instance
94 - devicesListener = rawDevices.addEntryListener(new RemoteDeviceEventHandler(devices), includeValue);
95 -
96 - // TODO cache availableDevices
97 - availableDevices = theInstance.getSet("availableDevices");
98 -
99 - rawDevicePorts = theInstance.getMap("devicePorts");
100 - final OptionalCacheLoader<DeviceId, Map<PortNumber, Port>> devicePortLoader
101 - = new OptionalCacheLoader<>(serializer, rawDevicePorts);
102 - devicePorts = new AbsentInvalidatingLoadingCache<>(newBuilder().build(devicePortLoader));
103 - // refresh/populate cache based on notification from other instance
104 - portsListener = rawDevicePorts.addEntryListener(new RemotePortEventHandler(devicePorts), includeValue);
105 -
106 - loadDeviceCache();
107 - loadDevicePortsCache();
108 -
109 - log.info("Started");
110 - }
111 -
112 - @Deactivate
113 - public void deactivate() {
114 - rawDevicePorts.removeEntryListener(portsListener);
115 - rawDevices.removeEntryListener(devicesListener);
116 - log.info("Stopped");
117 - }
118 -
119 - @Override
120 - public int getDeviceCount() {
121 - return devices.asMap().size();
122 - }
123 -
124 - @Override
125 - public Iterable<Device> getDevices() {
126 - // TODO builder v.s. copyOf. Guava semms to be using copyOf?
127 - Builder<Device> builder = ImmutableSet.builder();
128 - for (Optional<DefaultDevice> e : devices.asMap().values()) {
129 - if (e.isPresent()) {
130 - builder.add(e.get());
131 - }
132 - }
133 - return builder.build();
134 - }
135 -
136 - private void loadDeviceCache() {
137 - for (byte[] keyBytes : rawDevices.keySet()) {
138 - final DeviceId id = deserialize(keyBytes);
139 - devices.refresh(id);
140 - }
141 - }
142 -
143 - private void loadDevicePortsCache() {
144 - for (byte[] keyBytes : rawDevicePorts.keySet()) {
145 - final DeviceId id = deserialize(keyBytes);
146 - devicePorts.refresh(id);
147 - }
148 - }
149 -
150 - @Override
151 - public Device getDevice(DeviceId deviceId) {
152 - // TODO revisit if ignoring exception is safe.
153 - return devices.getUnchecked(deviceId).orNull();
154 - }
155 -
156 - @Override
157 - public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
158 - DeviceDescription deviceDescription) {
159 - DefaultDevice device = devices.getUnchecked(deviceId).orNull();
160 - if (device == null) {
161 - return createDevice(providerId, deviceId, deviceDescription);
162 - }
163 - return updateDevice(providerId, device, deviceDescription);
164 - }
165 -
166 - // Creates the device and returns the appropriate event if necessary.
167 - private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
168 - DeviceDescription desc) {
169 - DefaultDevice device = new DefaultDevice(providerId, deviceId, desc.type(),
170 - desc.manufacturer(),
171 - desc.hwVersion(), desc.swVersion(),
172 - desc.serialNumber());
173 -
174 - synchronized (this) {
175 - final byte[] deviceIdBytes = serialize(deviceId);
176 - rawDevices.put(deviceIdBytes, serialize(device));
177 - devices.put(deviceId, Optional.of(device));
178 -
179 - availableDevices.add(deviceIdBytes);
180 - }
181 - return new DeviceEvent(DEVICE_ADDED, device, null);
182 - }
183 -
184 - // Updates the device and returns the appropriate event if necessary.
185 - private DeviceEvent updateDevice(ProviderId providerId, DefaultDevice device,
186 - DeviceDescription desc) {
187 - // We allow only certain attributes to trigger update
188 - if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
189 - !Objects.equals(device.swVersion(), desc.swVersion())) {
190 -
191 - DefaultDevice updated = new DefaultDevice(providerId, device.id(),
192 - desc.type(),
193 - desc.manufacturer(),
194 - desc.hwVersion(),
195 - desc.swVersion(),
196 - desc.serialNumber());
197 - synchronized (this) {
198 - final byte[] deviceIdBytes = serialize(device.id());
199 - rawDevices.put(deviceIdBytes, serialize(updated));
200 - devices.put(device.id(), Optional.of(updated));
201 - availableDevices.add(serialize(device.id()));
202 - }
203 - return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, updated, null);
204 - }
205 -
206 - // Otherwise merely attempt to change availability
207 - synchronized (this) {
208 - boolean added = availableDevices.add(serialize(device.id()));
209 - return !added ? null :
210 - new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
211 - }
212 - }
213 -
214 - @Override
215 - public DeviceEvent markOffline(DeviceId deviceId) {
216 - synchronized (this) {
217 - Device device = devices.getUnchecked(deviceId).orNull();
218 - boolean removed = device != null && availableDevices.remove(serialize(deviceId));
219 - return !removed ? null :
220 - new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
221 - }
222 - }
223 -
224 - @Override
225 - public List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
226 - List<PortDescription> portDescriptions) {
227 - List<DeviceEvent> events = new ArrayList<>();
228 - synchronized (this) {
229 - Device device = devices.getUnchecked(deviceId).orNull();
230 - checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
231 - Map<PortNumber, Port> ports = getPortMap(deviceId);
232 -
233 - // Add new ports
234 - Set<PortNumber> processed = new HashSet<>();
235 - for (PortDescription portDescription : portDescriptions) {
236 - Port port = ports.get(portDescription.portNumber());
237 - events.add(port == null ?
238 - createPort(device, portDescription, ports) :
239 - updatePort(device, port, portDescription, ports));
240 - processed.add(portDescription.portNumber());
241 - }
242 -
243 - updatePortMap(deviceId, ports);
244 -
245 - events.addAll(pruneOldPorts(device, ports, processed));
246 - }
247 - return FluentIterable.from(events).filter(notNull()).toList();
248 - }
249 -
250 - // Creates a new port based on the port description adds it to the map and
251 - // Returns corresponding event.
252 - //@GuardedBy("this")
253 - private DeviceEvent createPort(Device device, PortDescription portDescription,
254 - Map<PortNumber, Port> ports) {
255 - DefaultPort port = new DefaultPort(device, portDescription.portNumber(),
256 - portDescription.isEnabled());
257 - ports.put(port.number(), port);
258 - updatePortMap(device.id(), ports);
259 - return new DeviceEvent(PORT_ADDED, device, port);
260 - }
261 -
262 - // Checks if the specified port requires update and if so, it replaces the
263 - // existing entry in the map and returns corresponding event.
264 - //@GuardedBy("this")
265 - private DeviceEvent updatePort(Device device, Port port,
266 - PortDescription portDescription,
267 - Map<PortNumber, Port> ports) {
268 - if (port.isEnabled() != portDescription.isEnabled()) {
269 - DefaultPort updatedPort =
270 - new DefaultPort(device, portDescription.portNumber(),
271 - portDescription.isEnabled());
272 - ports.put(port.number(), updatedPort);
273 - updatePortMap(device.id(), ports);
274 - return new DeviceEvent(PORT_UPDATED, device, updatedPort);
275 - }
276 - return null;
277 - }
278 -
279 - // Prunes the specified list of ports based on which ports are in the
280 - // processed list and returns list of corresponding events.
281 - //@GuardedBy("this")
282 - private List<DeviceEvent> pruneOldPorts(Device device,
283 - Map<PortNumber, Port> ports,
284 - Set<PortNumber> processed) {
285 - List<DeviceEvent> events = new ArrayList<>();
286 - Iterator<PortNumber> iterator = ports.keySet().iterator();
287 - while (iterator.hasNext()) {
288 - PortNumber portNumber = iterator.next();
289 - if (!processed.contains(portNumber)) {
290 - events.add(new DeviceEvent(PORT_REMOVED, device,
291 - ports.get(portNumber)));
292 - iterator.remove();
293 - }
294 - }
295 - if (!events.isEmpty()) {
296 - updatePortMap(device.id(), ports);
297 - }
298 - return events;
299 - }
300 -
301 - // Gets the map of ports for the specified device; if one does not already
302 - // exist, it creates and registers a new one.
303 - // WARN: returned value is a copy, changes made to the Map
304 - // needs to be written back using updatePortMap
305 - //@GuardedBy("this")
306 - private Map<PortNumber, Port> getPortMap(DeviceId deviceId) {
307 - Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
308 - if (ports == null) {
309 - ports = new HashMap<>();
310 - // this probably is waste of time in most cases.
311 - updatePortMap(deviceId, ports);
312 - }
313 - return ports;
314 - }
315 -
316 - //@GuardedBy("this")
317 - private void updatePortMap(DeviceId deviceId, Map<PortNumber, Port> ports) {
318 - rawDevicePorts.put(serialize(deviceId), serialize(ports));
319 - devicePorts.put(deviceId, Optional.of(ports));
320 - }
321 -
322 - @Override
323 - public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
324 - PortDescription portDescription) {
325 - synchronized (this) {
326 - Device device = devices.getUnchecked(deviceId).orNull();
327 - checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
328 - Map<PortNumber, Port> ports = getPortMap(deviceId);
329 - Port port = ports.get(portDescription.portNumber());
330 - return updatePort(device, port, portDescription, ports);
331 - }
332 - }
333 -
334 - @Override
335 - public List<Port> getPorts(DeviceId deviceId) {
336 - Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
337 - return ports == null ? Collections.<Port>emptyList() : ImmutableList.copyOf(ports.values());
338 - }
339 -
340 - @Override
341 - public Port getPort(DeviceId deviceId, PortNumber portNumber) {
342 - Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
343 - return ports == null ? null : ports.get(portNumber);
344 - }
345 -
346 - @Override
347 - public boolean isAvailable(DeviceId deviceId) {
348 - return availableDevices.contains(serialize(deviceId));
349 - }
350 -
351 - @Override
352 - public DeviceEvent removeDevice(DeviceId deviceId) {
353 - synchronized (this) {
354 - byte[] deviceIdBytes = serialize(deviceId);
355 -
356 - // TODO conditional remove?
357 - Device device = deserialize(rawDevices.remove(deviceIdBytes));
358 - devices.invalidate(deviceId);
359 - return device == null ? null :
360 - new DeviceEvent(DEVICE_REMOVED, device, null);
361 - }
362 - }
363 -
364 - private class RemoteDeviceEventHandler extends RemoteCacheEventHandler<DeviceId, DefaultDevice> {
365 - public RemoteDeviceEventHandler(LoadingCache<DeviceId, Optional<DefaultDevice>> cache) {
366 - super(cache);
367 - }
368 -
369 - @Override
370 - protected void onAdd(DeviceId deviceId, DefaultDevice device) {
371 - notifyDelegate(new DeviceEvent(DEVICE_ADDED, device));
372 - }
373 -
374 - @Override
375 - protected void onRemove(DeviceId deviceId, DefaultDevice device) {
376 - notifyDelegate(new DeviceEvent(DEVICE_REMOVED, device));
377 - }
378 -
379 - @Override
380 - protected void onUpdate(DeviceId deviceId, DefaultDevice oldDevice, DefaultDevice device) {
381 - notifyDelegate(new DeviceEvent(DEVICE_UPDATED, device));
382 - }
383 - }
384 -
385 - private class RemotePortEventHandler extends RemoteCacheEventHandler<DeviceId, Map<PortNumber, Port>> {
386 - public RemotePortEventHandler(LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> cache) {
387 - super(cache);
388 - }
389 -
390 - @Override
391 - protected void onAdd(DeviceId deviceId, Map<PortNumber, Port> ports) {
392 -// notifyDelegate(new DeviceEvent(PORT_ADDED, getDevice(deviceId)));
393 - }
394 -
395 - @Override
396 - protected void onRemove(DeviceId deviceId, Map<PortNumber, Port> ports) {
397 -// notifyDelegate(new DeviceEvent(PORT_REMOVED, getDevice(deviceId)));
398 - }
399 -
400 - @Override
401 - protected void onUpdate(DeviceId deviceId, Map<PortNumber, Port> oldPorts, Map<PortNumber, Port> ports) {
402 -// notifyDelegate(new DeviceEvent(PORT_UPDATED, getDevice(deviceId)));
403 - }
404 - }
405 -
406 -
407 - // TODO cache serialized DeviceID if we suffer from serialization cost
408 -}
1 -package org.onlab.onos.store.device.impl;
2 -
3 -import org.apache.felix.scr.annotations.Component;
4 -import org.apache.felix.scr.annotations.Service;
5 -import org.onlab.onos.mastership.MastershipTerm;
6 -import org.onlab.onos.net.DeviceId;
7 -import org.onlab.onos.net.device.DeviceClockProviderService;
8 -
9 -// FIXME: Code clone in onos-core-trivial, onos-core-hz-net
10 -/**
11 - * Dummy implementation of {@link DeviceClockProviderService}.
12 - */
13 -@Component(immediate = true)
14 -@Service
15 -public class NoOpClockProviderService implements DeviceClockProviderService {
16 -
17 - @Override
18 - public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
19 - }
20 -}
1 -package org.onlab.onos.store.flow.impl;
2 -
3 -import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
4 -import static org.slf4j.LoggerFactory.getLogger;
5 -
6 -import java.util.Collection;
7 -import java.util.Collections;
8 -
9 -import org.apache.felix.scr.annotations.Activate;
10 -import org.apache.felix.scr.annotations.Component;
11 -import org.apache.felix.scr.annotations.Deactivate;
12 -import org.apache.felix.scr.annotations.Service;
13 -import org.onlab.onos.ApplicationId;
14 -import org.onlab.onos.net.DeviceId;
15 -import org.onlab.onos.net.flow.DefaultFlowEntry;
16 -import org.onlab.onos.net.flow.FlowEntry;
17 -import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
18 -import org.onlab.onos.net.flow.FlowRule;
19 -import org.onlab.onos.net.flow.FlowRuleEvent;
20 -import org.onlab.onos.net.flow.FlowRuleEvent.Type;
21 -import org.onlab.onos.net.flow.FlowRuleStore;
22 -import org.onlab.onos.net.flow.FlowRuleStoreDelegate;
23 -import org.onlab.onos.store.AbstractStore;
24 -import org.slf4j.Logger;
25 -
26 -import com.google.common.collect.ArrayListMultimap;
27 -import com.google.common.collect.ImmutableSet;
28 -import com.google.common.collect.Multimap;
29 -
30 -/**
31 - * Manages inventory of flow rules using trivial in-memory implementation.
32 - */
33 -//FIXME I LIE. I AIN'T DISTRIBUTED
34 -@Component(immediate = true)
35 -@Service
36 -public class DistributedFlowRuleStore
37 - extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
38 - implements FlowRuleStore {
39 -
40 - private final Logger log = getLogger(getClass());
41 -
42 - // store entries as a pile of rules, no info about device tables
43 - private final Multimap<DeviceId, FlowEntry> flowEntries =
44 - ArrayListMultimap.<DeviceId, FlowEntry>create();
45 -
46 - private final Multimap<Short, FlowRule> flowEntriesById =
47 - ArrayListMultimap.<Short, FlowRule>create();
48 -
49 - @Activate
50 - public void activate() {
51 - log.info("Started");
52 - }
53 -
54 - @Deactivate
55 - public void deactivate() {
56 - log.info("Stopped");
57 - }
58 -
59 -
60 - @Override
61 - public int getFlowRuleCount() {
62 - return flowEntries.size();
63 - }
64 -
65 - @Override
66 - public synchronized FlowEntry getFlowEntry(FlowRule rule) {
67 - for (FlowEntry f : flowEntries.get(rule.deviceId())) {
68 - if (f.equals(rule)) {
69 - return f;
70 - }
71 - }
72 - return null;
73 - }
74 -
75 - @Override
76 - public synchronized Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
77 - Collection<FlowEntry> rules = flowEntries.get(deviceId);
78 - if (rules == null) {
79 - return Collections.emptyList();
80 - }
81 - return ImmutableSet.copyOf(rules);
82 - }
83 -
84 - @Override
85 - public synchronized Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
86 - Collection<FlowRule> rules = flowEntriesById.get(appId.id());
87 - if (rules == null) {
88 - return Collections.emptyList();
89 - }
90 - return ImmutableSet.copyOf(rules);
91 - }
92 -
93 - @Override
94 - public synchronized void storeFlowRule(FlowRule rule) {
95 - FlowEntry f = new DefaultFlowEntry(rule);
96 - DeviceId did = f.deviceId();
97 - if (!flowEntries.containsEntry(did, f)) {
98 - flowEntries.put(did, f);
99 - flowEntriesById.put(rule.appId(), f);
100 - }
101 - }
102 -
103 - @Override
104 - public synchronized void deleteFlowRule(FlowRule rule) {
105 - FlowEntry entry = getFlowEntry(rule);
106 - if (entry == null) {
107 - return;
108 - }
109 - entry.setState(FlowEntryState.PENDING_REMOVE);
110 - }
111 -
112 - @Override
113 - public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
114 - DeviceId did = rule.deviceId();
115 -
116 - // check if this new rule is an update to an existing entry
117 - FlowEntry stored = getFlowEntry(rule);
118 - if (stored != null) {
119 - stored.setBytes(rule.bytes());
120 - stored.setLife(rule.life());
121 - stored.setPackets(rule.packets());
122 - if (stored.state() == FlowEntryState.PENDING_ADD) {
123 - stored.setState(FlowEntryState.ADDED);
124 - return new FlowRuleEvent(Type.RULE_ADDED, rule);
125 - }
126 - return new FlowRuleEvent(Type.RULE_UPDATED, rule);
127 - }
128 -
129 - flowEntries.put(did, rule);
130 - return null;
131 - }
132 -
133 - @Override
134 - public synchronized FlowRuleEvent removeFlowRule(FlowEntry rule) {
135 - // This is where one could mark a rule as removed and still keep it in the store.
136 - if (flowEntries.remove(rule.deviceId(), rule)) {
137 - return new FlowRuleEvent(RULE_REMOVED, rule);
138 - } else {
139 - return null;
140 - }
141 - }
142 -}
1 -package org.onlab.onos.store.host.impl;
2 -
3 -import com.google.common.collect.HashMultimap;
4 -import com.google.common.collect.ImmutableSet;
5 -import com.google.common.collect.Multimap;
6 -import com.google.common.collect.Sets;
7 -import org.apache.felix.scr.annotations.Activate;
8 -import org.apache.felix.scr.annotations.Component;
9 -import org.apache.felix.scr.annotations.Deactivate;
10 -import org.apache.felix.scr.annotations.Service;
11 -import org.onlab.onos.net.Annotations;
12 -import org.onlab.onos.net.ConnectPoint;
13 -import org.onlab.onos.net.DefaultHost;
14 -import org.onlab.onos.net.DeviceId;
15 -import org.onlab.onos.net.Host;
16 -import org.onlab.onos.net.HostId;
17 -import org.onlab.onos.net.HostLocation;
18 -import org.onlab.onos.net.host.HostDescription;
19 -import org.onlab.onos.net.host.HostEvent;
20 -import org.onlab.onos.net.host.HostStore;
21 -import org.onlab.onos.net.host.HostStoreDelegate;
22 -import org.onlab.onos.net.host.PortAddresses;
23 -import org.onlab.onos.net.provider.ProviderId;
24 -import org.onlab.onos.store.AbstractStore;
25 -import org.onlab.packet.IpPrefix;
26 -import org.onlab.packet.MacAddress;
27 -import org.onlab.packet.VlanId;
28 -import org.slf4j.Logger;
29 -
30 -import java.util.HashSet;
31 -import java.util.Map;
32 -import java.util.Set;
33 -import java.util.concurrent.ConcurrentHashMap;
34 -
35 -import static org.onlab.onos.net.host.HostEvent.Type.*;
36 -import static org.slf4j.LoggerFactory.getLogger;
37 -
38 -/**
39 - * TEMPORARY: Manages inventory of end-station hosts using distributed
40 - * structures implementation.
41 - */
42 -//FIXME: I LIE I AM NOT DISTRIBUTED
43 -@Component(immediate = true)
44 -@Service
45 -public class DistributedHostStore
46 - extends AbstractStore<HostEvent, HostStoreDelegate>
47 - implements HostStore {
48 -
49 - private final Logger log = getLogger(getClass());
50 -
51 - // Host inventory
52 - private final Map<HostId, StoredHost> hosts = new ConcurrentHashMap<>(2000000, 0.75f, 16);
53 -
54 - // Hosts tracked by their location
55 - private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
56 -
57 - private final Map<ConnectPoint, PortAddresses> portAddresses =
58 - new ConcurrentHashMap<>();
59 -
60 - @Activate
61 - public void activate() {
62 - log.info("Started");
63 - }
64 -
65 - @Deactivate
66 - public void deactivate() {
67 - log.info("Stopped");
68 - }
69 -
70 - @Override
71 - public HostEvent createOrUpdateHost(ProviderId providerId, HostId hostId,
72 - HostDescription hostDescription) {
73 - StoredHost host = hosts.get(hostId);
74 - if (host == null) {
75 - return createHost(providerId, hostId, hostDescription);
76 - }
77 - return updateHost(providerId, host, hostDescription);
78 - }
79 -
80 - // creates a new host and sends HOST_ADDED
81 - private HostEvent createHost(ProviderId providerId, HostId hostId,
82 - HostDescription descr) {
83 - StoredHost newhost = new StoredHost(providerId, hostId,
84 - descr.hwAddress(),
85 - descr.vlan(),
86 - descr.location(),
87 - ImmutableSet.of(descr.ipAddress()));
88 - synchronized (this) {
89 - hosts.put(hostId, newhost);
90 - locations.put(descr.location(), newhost);
91 - }
92 - return new HostEvent(HOST_ADDED, newhost);
93 - }
94 -
95 - // checks for type of update to host, sends appropriate event
96 - private HostEvent updateHost(ProviderId providerId, StoredHost host,
97 - HostDescription descr) {
98 - HostEvent event;
99 - if (!host.location().equals(descr.location())) {
100 - host.setLocation(descr.location());
101 - return new HostEvent(HOST_MOVED, host);
102 - }
103 -
104 - if (host.ipAddresses().contains(descr.ipAddress())) {
105 - return null;
106 - }
107 -
108 - Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
109 - addresses.add(descr.ipAddress());
110 - StoredHost updated = new StoredHost(providerId, host.id(),
111 - host.mac(), host.vlan(),
112 - descr.location(), addresses);
113 - event = new HostEvent(HOST_UPDATED, updated);
114 - synchronized (this) {
115 - hosts.put(host.id(), updated);
116 - locations.remove(host.location(), host);
117 - locations.put(updated.location(), updated);
118 - }
119 - return event;
120 - }
121 -
122 - @Override
123 - public HostEvent removeHost(HostId hostId) {
124 - synchronized (this) {
125 - Host host = hosts.remove(hostId);
126 - if (host != null) {
127 - locations.remove((host.location()), host);
128 - return new HostEvent(HOST_REMOVED, host);
129 - }
130 - return null;
131 - }
132 - }
133 -
134 - @Override
135 - public int getHostCount() {
136 - return hosts.size();
137 - }
138 -
139 - @Override
140 - public Iterable<Host> getHosts() {
141 - return ImmutableSet.<Host>copyOf(hosts.values());
142 - }
143 -
144 - @Override
145 - public Host getHost(HostId hostId) {
146 - return hosts.get(hostId);
147 - }
148 -
149 - @Override
150 - public Set<Host> getHosts(VlanId vlanId) {
151 - Set<Host> vlanset = new HashSet<>();
152 - for (Host h : hosts.values()) {
153 - if (h.vlan().equals(vlanId)) {
154 - vlanset.add(h);
155 - }
156 - }
157 - return vlanset;
158 - }
159 -
160 - @Override
161 - public Set<Host> getHosts(MacAddress mac) {
162 - Set<Host> macset = new HashSet<>();
163 - for (Host h : hosts.values()) {
164 - if (h.mac().equals(mac)) {
165 - macset.add(h);
166 - }
167 - }
168 - return macset;
169 - }
170 -
171 - @Override
172 - public Set<Host> getHosts(IpPrefix ip) {
173 - Set<Host> ipset = new HashSet<>();
174 - for (Host h : hosts.values()) {
175 - if (h.ipAddresses().contains(ip)) {
176 - ipset.add(h);
177 - }
178 - }
179 - return ipset;
180 - }
181 -
182 - @Override
183 - public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
184 - return ImmutableSet.copyOf(locations.get(connectPoint));
185 - }
186 -
187 - @Override
188 - public Set<Host> getConnectedHosts(DeviceId deviceId) {
189 - Set<Host> hostset = new HashSet<>();
190 - for (ConnectPoint p : locations.keySet()) {
191 - if (p.deviceId().equals(deviceId)) {
192 - hostset.addAll(locations.get(p));
193 - }
194 - }
195 - return hostset;
196 - }
197 -
198 - @Override
199 - public void updateAddressBindings(PortAddresses addresses) {
200 - synchronized (portAddresses) {
201 - PortAddresses existing = portAddresses.get(addresses.connectPoint());
202 - if (existing == null) {
203 - portAddresses.put(addresses.connectPoint(), addresses);
204 - } else {
205 - Set<IpPrefix> union = Sets.union(existing.ips(), addresses.ips())
206 - .immutableCopy();
207 -
208 - MacAddress newMac = (addresses.mac() == null) ? existing.mac()
209 - : addresses.mac();
210 -
211 - PortAddresses newAddresses =
212 - new PortAddresses(addresses.connectPoint(), union, newMac);
213 -
214 - portAddresses.put(newAddresses.connectPoint(), newAddresses);
215 - }
216 - }
217 - }
218 -
219 - @Override
220 - public void removeAddressBindings(PortAddresses addresses) {
221 - synchronized (portAddresses) {
222 - PortAddresses existing = portAddresses.get(addresses.connectPoint());
223 - if (existing != null) {
224 - Set<IpPrefix> difference =
225 - Sets.difference(existing.ips(), addresses.ips()).immutableCopy();
226 -
227 - // If they removed the existing mac, set the new mac to null.
228 - // Otherwise, keep the existing mac.
229 - MacAddress newMac = existing.mac();
230 - if (addresses.mac() != null && addresses.mac().equals(existing.mac())) {
231 - newMac = null;
232 - }
233 -
234 - PortAddresses newAddresses =
235 - new PortAddresses(addresses.connectPoint(), difference, newMac);
236 -
237 - portAddresses.put(newAddresses.connectPoint(), newAddresses);
238 - }
239 - }
240 - }
241 -
242 - @Override
243 - public void clearAddressBindings(ConnectPoint connectPoint) {
244 - synchronized (portAddresses) {
245 - portAddresses.remove(connectPoint);
246 - }
247 - }
248 -
249 - @Override
250 - public Set<PortAddresses> getAddressBindings() {
251 - synchronized (portAddresses) {
252 - return new HashSet<>(portAddresses.values());
253 - }
254 - }
255 -
256 - @Override
257 - public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
258 - PortAddresses addresses;
259 -
260 - synchronized (portAddresses) {
261 - addresses = portAddresses.get(connectPoint);
262 - }
263 -
264 - if (addresses == null) {
265 - addresses = new PortAddresses(connectPoint, null, null);
266 - }
267 -
268 - return addresses;
269 - }
270 -
271 - // Auxiliary extension to allow location to mutate.
272 - private class StoredHost extends DefaultHost {
273 - private HostLocation location;
274 -
275 - /**
276 - * Creates an end-station host using the supplied information.
277 - *
278 - * @param providerId provider identity
279 - * @param id host identifier
280 - * @param mac host MAC address
281 - * @param vlan host VLAN identifier
282 - * @param location host location
283 - * @param ips host IP addresses
284 - * @param annotations optional key/value annotations
285 - */
286 - public StoredHost(ProviderId providerId, HostId id,
287 - MacAddress mac, VlanId vlan, HostLocation location,
288 - Set<IpPrefix> ips, Annotations... annotations) {
289 - super(providerId, id, mac, vlan, location, ips, annotations);
290 - this.location = location;
291 - }
292 -
293 - void setLocation(HostLocation location) {
294 - this.location = location;
295 - }
296 -
297 - @Override
298 - public HostLocation location() {
299 - return location;
300 - }
301 - }
302 -}
1 -package org.onlab.onos.store.link.impl;
2 -
3 -import static com.google.common.cache.CacheBuilder.newBuilder;
4 -import static org.onlab.onos.net.Link.Type.DIRECT;
5 -import static org.onlab.onos.net.Link.Type.INDIRECT;
6 -import static org.onlab.onos.net.LinkKey.linkKey;
7 -import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
8 -import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
9 -import static org.onlab.onos.net.link.LinkEvent.Type.LINK_UPDATED;
10 -import static org.slf4j.LoggerFactory.getLogger;
11 -
12 -import java.util.HashSet;
13 -import java.util.Set;
14 -
15 -import org.apache.felix.scr.annotations.Activate;
16 -import org.apache.felix.scr.annotations.Component;
17 -import org.apache.felix.scr.annotations.Deactivate;
18 -import org.apache.felix.scr.annotations.Service;
19 -import org.onlab.onos.net.ConnectPoint;
20 -import org.onlab.onos.net.DefaultLink;
21 -import org.onlab.onos.net.DeviceId;
22 -import org.onlab.onos.net.Link;
23 -import org.onlab.onos.net.LinkKey;
24 -import org.onlab.onos.net.link.LinkDescription;
25 -import org.onlab.onos.net.link.LinkEvent;
26 -import org.onlab.onos.net.link.LinkStore;
27 -import org.onlab.onos.net.link.LinkStoreDelegate;
28 -import org.onlab.onos.net.provider.ProviderId;
29 -import org.onlab.onos.store.common.AbsentInvalidatingLoadingCache;
30 -import org.onlab.onos.store.common.AbstractHazelcastStore;
31 -import org.onlab.onos.store.common.OptionalCacheLoader;
32 -import org.slf4j.Logger;
33 -
34 -import com.google.common.base.Optional;
35 -import com.google.common.cache.LoadingCache;
36 -import com.google.common.collect.HashMultimap;
37 -import com.google.common.collect.ImmutableSet;
38 -import com.google.common.collect.Multimap;
39 -import com.google.common.collect.ImmutableSet.Builder;
40 -import com.hazelcast.core.IMap;
41 -
42 -//TODO: Add support for multiple provider and annotations
43 -/**
44 - * Manages inventory of infrastructure links using Hazelcast-backed map.
45 - */
46 -@Component(immediate = true)
47 -@Service
48 -public class DistributedLinkStore
49 - extends AbstractHazelcastStore<LinkEvent, LinkStoreDelegate>
50 - implements LinkStore {
51 -
52 - private final Logger log = getLogger(getClass());
53 -
54 - // Link inventory
55 - private IMap<byte[], byte[]> rawLinks;
56 - private LoadingCache<LinkKey, Optional<DefaultLink>> links;
57 -
58 - // TODO synchronize?
59 - // Egress and ingress link sets
60 - private final Multimap<DeviceId, Link> srcLinks = HashMultimap.create();
61 - private final Multimap<DeviceId, Link> dstLinks = HashMultimap.create();
62 -
63 - private String linksListener;
64 -
65 - @Override
66 - @Activate
67 - public void activate() {
68 - super.activate();
69 -
70 - boolean includeValue = true;
71 -
72 - // TODO decide on Map name scheme to avoid collision
73 - rawLinks = theInstance.getMap("links");
74 - final OptionalCacheLoader<LinkKey, DefaultLink> linkLoader
75 - = new OptionalCacheLoader<>(serializer, rawLinks);
76 - links = new AbsentInvalidatingLoadingCache<>(newBuilder().build(linkLoader));
77 - // refresh/populate cache based on notification from other instance
78 - linksListener = rawLinks.addEntryListener(new RemoteLinkEventHandler(links), includeValue);
79 -
80 - loadLinkCache();
81 -
82 - log.info("Started");
83 - }
84 -
85 - @Deactivate
86 - public void deactivate() {
87 - rawLinks.removeEntryListener(linksListener);
88 - log.info("Stopped");
89 - }
90 -
91 - private void loadLinkCache() {
92 - for (byte[] keyBytes : rawLinks.keySet()) {
93 - final LinkKey id = deserialize(keyBytes);
94 - links.refresh(id);
95 - }
96 - }
97 -
98 - @Override
99 - public int getLinkCount() {
100 - return links.asMap().size();
101 - }
102 -
103 - @Override
104 - public Iterable<Link> getLinks() {
105 - Builder<Link> builder = ImmutableSet.builder();
106 - for (Optional<DefaultLink> e : links.asMap().values()) {
107 - if (e.isPresent()) {
108 - builder.add(e.get());
109 - }
110 - }
111 - return builder.build();
112 - }
113 -
114 - @Override
115 - public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
116 - return ImmutableSet.copyOf(srcLinks.get(deviceId));
117 - }
118 -
119 - @Override
120 - public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
121 - return ImmutableSet.copyOf(dstLinks.get(deviceId));
122 - }
123 -
124 - @Override
125 - public Link getLink(ConnectPoint src, ConnectPoint dst) {
126 - return links.getUnchecked(linkKey(src, dst)).orNull();
127 - }
128 -
129 - @Override
130 - public Set<Link> getEgressLinks(ConnectPoint src) {
131 - Set<Link> egress = new HashSet<>();
132 - for (Link link : srcLinks.get(src.deviceId())) {
133 - if (link.src().equals(src)) {
134 - egress.add(link);
135 - }
136 - }
137 - return egress;
138 - }
139 -
140 - @Override
141 - public Set<Link> getIngressLinks(ConnectPoint dst) {
142 - Set<Link> ingress = new HashSet<>();
143 - for (Link link : dstLinks.get(dst.deviceId())) {
144 - if (link.dst().equals(dst)) {
145 - ingress.add(link);
146 - }
147 - }
148 - return ingress;
149 - }
150 -
151 - @Override
152 - public LinkEvent createOrUpdateLink(ProviderId providerId,
153 - LinkDescription linkDescription) {
154 - LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
155 - Optional<DefaultLink> link = links.getUnchecked(key);
156 - if (!link.isPresent()) {
157 - return createLink(providerId, key, linkDescription);
158 - }
159 - return updateLink(providerId, link.get(), key, linkDescription);
160 - }
161 -
162 - // Creates and stores the link and returns the appropriate event.
163 - private LinkEvent createLink(ProviderId providerId, LinkKey key,
164 - LinkDescription linkDescription) {
165 - DefaultLink link = new DefaultLink(providerId, key.src(), key.dst(),
166 - linkDescription.type());
167 - synchronized (this) {
168 - final byte[] keyBytes = serialize(key);
169 - rawLinks.put(keyBytes, serialize(link));
170 - links.asMap().putIfAbsent(key, Optional.of(link));
171 -
172 - addNewLink(link);
173 - }
174 - return new LinkEvent(LINK_ADDED, link);
175 - }
176 -
177 - // update Egress and ingress link sets
178 - private void addNewLink(DefaultLink link) {
179 - synchronized (this) {
180 - srcLinks.put(link.src().deviceId(), link);
181 - dstLinks.put(link.dst().deviceId(), link);
182 - }
183 - }
184 -
185 - // Updates, if necessary the specified link and returns the appropriate event.
186 - private LinkEvent updateLink(ProviderId providerId, DefaultLink link,
187 - LinkKey key, LinkDescription linkDescription) {
188 - // FIXME confirm Link update condition is OK
189 - if (link.type() == INDIRECT && linkDescription.type() == DIRECT) {
190 - synchronized (this) {
191 -
192 - DefaultLink updated =
193 - new DefaultLink(providerId, link.src(), link.dst(),
194 - linkDescription.type());
195 - final byte[] keyBytes = serialize(key);
196 - rawLinks.put(keyBytes, serialize(updated));
197 - links.asMap().replace(key, Optional.of(link), Optional.of(updated));
198 -
199 - replaceLink(link, updated);
200 - return new LinkEvent(LINK_UPDATED, updated);
201 - }
202 - }
203 - return null;
204 - }
205 -
206 - // update Egress and ingress link sets
207 - private void replaceLink(DefaultLink link, DefaultLink updated) {
208 - synchronized (this) {
209 - srcLinks.remove(link.src().deviceId(), link);
210 - dstLinks.remove(link.dst().deviceId(), link);
211 -
212 - srcLinks.put(link.src().deviceId(), updated);
213 - dstLinks.put(link.dst().deviceId(), updated);
214 - }
215 - }
216 -
217 - @Override
218 - public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
219 - synchronized (this) {
220 - LinkKey key = linkKey(src, dst);
221 - byte[] keyBytes = serialize(key);
222 - Link link = deserialize(rawLinks.remove(keyBytes));
223 - links.invalidate(key);
224 - if (link != null) {
225 - removeLink(link);
226 - return new LinkEvent(LINK_REMOVED, link);
227 - }
228 - return null;
229 - }
230 - }
231 -
232 - // update Egress and ingress link sets
233 - private void removeLink(Link link) {
234 - synchronized (this) {
235 - srcLinks.remove(link.src().deviceId(), link);
236 - dstLinks.remove(link.dst().deviceId(), link);
237 - }
238 - }
239 -
240 - private class RemoteLinkEventHandler extends RemoteCacheEventHandler<LinkKey, DefaultLink> {
241 - public RemoteLinkEventHandler(LoadingCache<LinkKey, Optional<DefaultLink>> cache) {
242 - super(cache);
243 - }
244 -
245 - @Override
246 - protected void onAdd(LinkKey key, DefaultLink newVal) {
247 - addNewLink(newVal);
248 - notifyDelegate(new LinkEvent(LINK_ADDED, newVal));
249 - }
250 -
251 - @Override
252 - protected void onUpdate(LinkKey key, DefaultLink oldVal, DefaultLink newVal) {
253 - replaceLink(oldVal, newVal);
254 - notifyDelegate(new LinkEvent(LINK_UPDATED, newVal));
255 - }
256 -
257 - @Override
258 - protected void onRemove(LinkKey key, DefaultLink val) {
259 - removeLink(val);
260 - notifyDelegate(new LinkEvent(LINK_REMOVED, val));
261 - }
262 - }
263 -}
1 -package org.onlab.onos.store.topology.impl;
2 -
3 -import com.google.common.collect.ImmutableMap;
4 -import com.google.common.collect.ImmutableSet;
5 -import com.google.common.collect.ImmutableSetMultimap;
6 -import org.onlab.graph.DijkstraGraphSearch;
7 -import org.onlab.graph.GraphPathSearch;
8 -import org.onlab.graph.TarjanGraphSearch;
9 -import org.onlab.onos.net.AbstractModel;
10 -import org.onlab.onos.net.ConnectPoint;
11 -import org.onlab.onos.net.DefaultPath;
12 -import org.onlab.onos.net.DeviceId;
13 -import org.onlab.onos.net.Link;
14 -import org.onlab.onos.net.Path;
15 -import org.onlab.onos.net.provider.ProviderId;
16 -import org.onlab.onos.net.topology.ClusterId;
17 -import org.onlab.onos.net.topology.DefaultTopologyCluster;
18 -import org.onlab.onos.net.topology.DefaultTopologyVertex;
19 -import org.onlab.onos.net.topology.GraphDescription;
20 -import org.onlab.onos.net.topology.LinkWeight;
21 -import org.onlab.onos.net.topology.Topology;
22 -import org.onlab.onos.net.topology.TopologyCluster;
23 -import org.onlab.onos.net.topology.TopologyEdge;
24 -import org.onlab.onos.net.topology.TopologyGraph;
25 -import org.onlab.onos.net.topology.TopologyVertex;
26 -
27 -import java.util.ArrayList;
28 -import java.util.List;
29 -import java.util.Map;
30 -import java.util.Set;
31 -
32 -import static com.google.common.base.MoreObjects.toStringHelper;
33 -import static com.google.common.collect.ImmutableSetMultimap.Builder;
34 -import static org.onlab.graph.GraphPathSearch.Result;
35 -import static org.onlab.graph.TarjanGraphSearch.SCCResult;
36 -import static org.onlab.onos.net.Link.Type.INDIRECT;
37 -
38 -/**
39 - * Default implementation of the topology descriptor. This carries the
40 - * backing topology data.
41 - */
42 -public class DefaultTopology extends AbstractModel implements Topology {
43 -
44 - private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA =
45 - new DijkstraGraphSearch<>();
46 - private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN =
47 - new TarjanGraphSearch<>();
48 -
49 - private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.net");
50 -
51 - private final long time;
52 - private final TopologyGraph graph;
53 -
54 - private final SCCResult<TopologyVertex, TopologyEdge> clusterResults;
55 - private final ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> results;
56 - private final ImmutableSetMultimap<PathKey, Path> paths;
57 -
58 - private final ImmutableMap<ClusterId, TopologyCluster> clusters;
59 - private final ImmutableSet<ConnectPoint> infrastructurePoints;
60 - private final ImmutableSetMultimap<ClusterId, ConnectPoint> broadcastSets;
61 -
62 - private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
63 - private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
64 - private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
65 -
66 -
67 - /**
68 - * Creates a topology descriptor attributed to the specified provider.
69 - *
70 - * @param providerId identity of the provider
71 - * @param description data describing the new topology
72 - */
73 - DefaultTopology(ProviderId providerId, GraphDescription description) {
74 - super(providerId);
75 - this.time = description.timestamp();
76 -
77 - // Build the graph
78 - this.graph = new DefaultTopologyGraph(description.vertexes(),
79 - description.edges());
80 -
81 - this.results = searchForShortestPaths();
82 - this.paths = buildPaths();
83 -
84 - this.clusterResults = searchForClusters();
85 - this.clusters = buildTopologyClusters();
86 -
87 - buildIndexes();
88 -
89 - this.broadcastSets = buildBroadcastSets();
90 - this.infrastructurePoints = findInfrastructurePoints();
91 - }
92 -
93 - @Override
94 - public long time() {
95 - return time;
96 - }
97 -
98 - @Override
99 - public int clusterCount() {
100 - return clusters.size();
101 - }
102 -
103 - @Override
104 - public int deviceCount() {
105 - return graph.getVertexes().size();
106 - }
107 -
108 - @Override
109 - public int linkCount() {
110 - return graph.getEdges().size();
111 - }
112 -
113 - @Override
114 - public int pathCount() {
115 - return paths.size();
116 - }
117 -
118 - /**
119 - * Returns the backing topology graph.
120 - *
121 - * @return topology graph
122 - */
123 - TopologyGraph getGraph() {
124 - return graph;
125 - }
126 -
127 - /**
128 - * Returns the set of topology clusters.
129 - *
130 - * @return set of clusters
131 - */
132 - Set<TopologyCluster> getClusters() {
133 - return ImmutableSet.copyOf(clusters.values());
134 - }
135 -
136 - /**
137 - * Returns the specified topology cluster.
138 - *
139 - * @param clusterId cluster identifier
140 - * @return topology cluster
141 - */
142 - TopologyCluster getCluster(ClusterId clusterId) {
143 - return clusters.get(clusterId);
144 - }
145 -
146 - /**
147 - * Returns the topology cluster that contains the given device.
148 - *
149 - * @param deviceId device identifier
150 - * @return topology cluster
151 - */
152 - TopologyCluster getCluster(DeviceId deviceId) {
153 - return clustersByDevice.get(deviceId);
154 - }
155 -
156 - /**
157 - * Returns the set of cluster devices.
158 - *
159 - * @param cluster topology cluster
160 - * @return cluster devices
161 - */
162 - Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
163 - return devicesByCluster.get(cluster);
164 - }
165 -
166 - /**
167 - * Returns the set of cluster links.
168 - *
169 - * @param cluster topology cluster
170 - * @return cluster links
171 - */
172 - Set<Link> getClusterLinks(TopologyCluster cluster) {
173 - return linksByCluster.get(cluster);
174 - }
175 -
176 - /**
177 - * Indicates whether the given point is an infrastructure link end-point.
178 - *
179 - * @param connectPoint connection point
180 - * @return true if infrastructure
181 - */
182 - boolean isInfrastructure(ConnectPoint connectPoint) {
183 - return infrastructurePoints.contains(connectPoint);
184 - }
185 -
186 - /**
187 - * Indicates whether the given point is part of a broadcast set.
188 - *
189 - * @param connectPoint connection point
190 - * @return true if in broadcast set
191 - */
192 - boolean isBroadcastPoint(ConnectPoint connectPoint) {
193 - // Any non-infrastructure, i.e. edge points are assumed to be OK.
194 - if (!isInfrastructure(connectPoint)) {
195 - return true;
196 - }
197 -
198 - // Find the cluster to which the device belongs.
199 - TopologyCluster cluster = clustersByDevice.get(connectPoint.deviceId());
200 - if (cluster == null) {
201 - throw new IllegalArgumentException("No cluster found for device " + connectPoint.deviceId());
202 - }
203 -
204 - // If the broadcast set is null or empty, or if the point explicitly
205 - // belongs to it, return true;
206 - Set<ConnectPoint> points = broadcastSets.get(cluster.id());
207 - return points == null || points.isEmpty() || points.contains(connectPoint);
208 - }
209 -
210 - /**
211 - * Returns the size of the cluster broadcast set.
212 - *
213 - * @param clusterId cluster identifier
214 - * @return size of the cluster broadcast set
215 - */
216 - int broadcastSetSize(ClusterId clusterId) {
217 - return broadcastSets.get(clusterId).size();
218 - }
219 -
220 - /**
221 - * Returns the set of pre-computed shortest paths between source and
222 - * destination devices.
223 - *
224 - * @param src source device
225 - * @param dst destination device
226 - * @return set of shortest paths
227 - */
228 - Set<Path> getPaths(DeviceId src, DeviceId dst) {
229 - return paths.get(new PathKey(src, dst));
230 - }
231 -
232 - /**
233 - * Computes on-demand the set of shortest paths between source and
234 - * destination devices.
235 - *
236 - * @param src source device
237 - * @param dst destination device
238 - * @return set of shortest paths
239 - */
240 - Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
241 - GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
242 - DIJKSTRA.search(graph, new DefaultTopologyVertex(src),
243 - new DefaultTopologyVertex(dst), weight);
244 - ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
245 - for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
246 - builder.add(networkPath(path));
247 - }
248 - return builder.build();
249 - }
250 -
251 -
252 - // Searches the graph for all shortest paths and returns the search results.
253 - private ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> searchForShortestPaths() {
254 - ImmutableMap.Builder<DeviceId, Result<TopologyVertex, TopologyEdge>> builder = ImmutableMap.builder();
255 -
256 - // Search graph paths for each source to all destinations.
257 - LinkWeight weight = new HopCountLinkWeight(graph.getVertexes().size());
258 - for (TopologyVertex src : graph.getVertexes()) {
259 - builder.put(src.deviceId(), DIJKSTRA.search(graph, src, null, weight));
260 - }
261 - return builder.build();
262 - }
263 -
264 - // Builds network paths from the graph path search results
265 - private ImmutableSetMultimap<PathKey, Path> buildPaths() {
266 - Builder<PathKey, Path> builder = ImmutableSetMultimap.builder();
267 - for (DeviceId deviceId : results.keySet()) {
268 - Result<TopologyVertex, TopologyEdge> result = results.get(deviceId);
269 - for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
270 - builder.put(new PathKey(path.src().deviceId(), path.dst().deviceId()),
271 - networkPath(path));
272 - }
273 - }
274 - return builder.build();
275 - }
276 -
277 - // Converts graph path to a network path with the same cost.
278 - private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
279 - List<Link> links = new ArrayList<>();
280 - for (TopologyEdge edge : path.edges()) {
281 - links.add(edge.link());
282 - }
283 - return new DefaultPath(PID, links, path.cost());
284 - }
285 -
286 -
287 - // Searches for SCC clusters in the network topology graph using Tarjan
288 - // algorithm.
289 - private SCCResult<TopologyVertex, TopologyEdge> searchForClusters() {
290 - return TARJAN.search(graph, new NoIndirectLinksWeight());
291 - }
292 -
293 - // Builds the topology clusters and returns the id-cluster bindings.
294 - private ImmutableMap<ClusterId, TopologyCluster> buildTopologyClusters() {
295 - ImmutableMap.Builder<ClusterId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
296 - SCCResult<TopologyVertex, TopologyEdge> result =
297 - TARJAN.search(graph, new NoIndirectLinksWeight());
298 -
299 - // Extract both vertexes and edges from the results; the lists form
300 - // pairs along the same index.
301 - List<Set<TopologyVertex>> clusterVertexes = result.clusterVertexes();
302 - List<Set<TopologyEdge>> clusterEdges = result.clusterEdges();
303 -
304 - // Scan over the lists and create a cluster from the results.
305 - for (int i = 0, n = result.clusterCount(); i < n; i++) {
306 - Set<TopologyVertex> vertexSet = clusterVertexes.get(i);
307 - Set<TopologyEdge> edgeSet = clusterEdges.get(i);
308 -
309 - ClusterId cid = ClusterId.clusterId(i);
310 - DefaultTopologyCluster cluster =
311 - new DefaultTopologyCluster(cid, vertexSet.size(), edgeSet.size(),
312 - findRoot(vertexSet).deviceId());
313 - clusterBuilder.put(cid, cluster);
314 - }
315 - return clusterBuilder.build();
316 - }
317 -
318 - // Finds the vertex whose device id is the lexicographical minimum in the
319 - // specified set.
320 - private TopologyVertex findRoot(Set<TopologyVertex> vertexSet) {
321 - TopologyVertex minVertex = null;
322 - for (TopologyVertex vertex : vertexSet) {
323 - if (minVertex == null ||
324 - minVertex.deviceId().toString()
325 - .compareTo(minVertex.deviceId().toString()) < 0) {
326 - minVertex = vertex;
327 - }
328 - }
329 - return minVertex;
330 - }
331 -
332 - // Processes a map of broadcast sets for each cluster.
333 - private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
334 - Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap.builder();
335 - for (TopologyCluster cluster : clusters.values()) {
336 - addClusterBroadcastSet(cluster, builder);
337 - }
338 - return builder.build();
339 - }
340 -
341 - // Finds all broadcast points for the cluster. These are those connection
342 - // points which lie along the shortest paths between the cluster root and
343 - // all other devices within the cluster.
344 - private void addClusterBroadcastSet(TopologyCluster cluster,
345 - Builder<ClusterId, ConnectPoint> builder) {
346 - // Use the graph root search results to build the broadcast set.
347 - Result<TopologyVertex, TopologyEdge> result = results.get(cluster.root());
348 - for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
349 - TopologyVertex vertex = entry.getKey();
350 -
351 - // Ignore any parents that lead outside the cluster.
352 - if (clustersByDevice.get(vertex.deviceId()) != cluster) {
353 - continue;
354 - }
355 -
356 - // Ignore any back-link sets that are empty.
357 - Set<TopologyEdge> parents = entry.getValue();
358 - if (parents.isEmpty()) {
359 - continue;
360 - }
361 -
362 - // Use the first back-link source and destinations to add to the
363 - // broadcast set.
364 - Link link = parents.iterator().next().link();
365 - builder.put(cluster.id(), link.src());
366 - builder.put(cluster.id(), link.dst());
367 - }
368 - }
369 -
370 - // Collects and returns an set of all infrastructure link end-points.
371 - private ImmutableSet<ConnectPoint> findInfrastructurePoints() {
372 - ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
373 - for (TopologyEdge edge : graph.getEdges()) {
374 - builder.add(edge.link().src());
375 - builder.add(edge.link().dst());
376 - }
377 - return builder.build();
378 - }
379 -
380 - // Builds cluster-devices, cluster-links and device-cluster indexes.
381 - private void buildIndexes() {
382 - // Prepare the index builders
383 - ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
384 - ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder = ImmutableSetMultimap.builder();
385 - ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder = ImmutableSetMultimap.builder();
386 -
387 - // Now scan through all the clusters
388 - for (TopologyCluster cluster : clusters.values()) {
389 - int i = cluster.id().index();
390 -
391 - // Scan through all the cluster vertexes.
392 - for (TopologyVertex vertex : clusterResults.clusterVertexes().get(i)) {
393 - devicesBuilder.put(cluster, vertex.deviceId());
394 - clusterBuilder.put(vertex.deviceId(), cluster);
395 - }
396 -
397 - // Scan through all the cluster edges.
398 - for (TopologyEdge edge : clusterResults.clusterEdges().get(i)) {
399 - linksBuilder.put(cluster, edge.link());
400 - }
401 - }
402 -
403 - // Finalize all indexes.
404 - clustersByDevice = clusterBuilder.build();
405 - devicesByCluster = devicesBuilder.build();
406 - linksByCluster = linksBuilder.build();
407 - }
408 -
409 - // Link weight for measuring link cost as hop count with indirect links
410 - // being as expensive as traversing the entire graph to assume the worst.
411 - private static class HopCountLinkWeight implements LinkWeight {
412 - private final int indirectLinkCost;
413 -
414 - HopCountLinkWeight(int indirectLinkCost) {
415 - this.indirectLinkCost = indirectLinkCost;
416 - }
417 -
418 - @Override
419 - public double weight(TopologyEdge edge) {
420 - // To force preference to use direct paths first, make indirect
421 - // links as expensive as the linear vertex traversal.
422 - return edge.link().type() == INDIRECT ? indirectLinkCost : 1;
423 - }
424 - }
425 -
426 - // Link weight for preventing traversal over indirect links.
427 - private static class NoIndirectLinksWeight implements LinkWeight {
428 - @Override
429 - public double weight(TopologyEdge edge) {
430 - return edge.link().type() == INDIRECT ? -1 : 1;
431 - }
432 - }
433 -
434 - @Override
435 - public String toString() {
436 - return toStringHelper(this)
437 - .add("time", time)
438 - .add("clusters", clusterCount())
439 - .add("devices", deviceCount())
440 - .add("links", linkCount())
441 - .add("pathCount", pathCount())
442 - .toString();
443 - }
444 -}
1 -package org.onlab.onos.store.topology.impl;
2 -
3 -import org.onlab.graph.AdjacencyListsGraph;
4 -import org.onlab.onos.net.topology.TopologyEdge;
5 -import org.onlab.onos.net.topology.TopologyGraph;
6 -import org.onlab.onos.net.topology.TopologyVertex;
7 -
8 -import java.util.Set;
9 -
10 -/**
11 - * Default implementation of an immutable topology graph based on a generic
12 - * implementation of adjacency lists graph.
13 - */
14 -public class DefaultTopologyGraph
15 - extends AdjacencyListsGraph<TopologyVertex, TopologyEdge>
16 - implements TopologyGraph {
17 -
18 - /**
19 - * Creates a topology graph comprising of the specified vertexes and edges.
20 - *
21 - * @param vertexes set of graph vertexes
22 - * @param edges set of graph edges
23 - */
24 - public DefaultTopologyGraph(Set<TopologyVertex> vertexes, Set<TopologyEdge> edges) {
25 - super(vertexes, edges);
26 - }
27 -
28 -}
1 -package org.onlab.onos.store.topology.impl;
2 -
3 -import static org.slf4j.LoggerFactory.getLogger;
4 -
5 -import java.util.List;
6 -import java.util.Set;
7 -
8 -import org.apache.felix.scr.annotations.Activate;
9 -import org.apache.felix.scr.annotations.Component;
10 -import org.apache.felix.scr.annotations.Deactivate;
11 -import org.apache.felix.scr.annotations.Service;
12 -import org.onlab.onos.event.Event;
13 -import org.onlab.onos.net.ConnectPoint;
14 -import org.onlab.onos.net.DeviceId;
15 -import org.onlab.onos.net.Link;
16 -import org.onlab.onos.net.Path;
17 -import org.onlab.onos.net.provider.ProviderId;
18 -import org.onlab.onos.net.topology.ClusterId;
19 -import org.onlab.onos.net.topology.GraphDescription;
20 -import org.onlab.onos.net.topology.LinkWeight;
21 -import org.onlab.onos.net.topology.Topology;
22 -import org.onlab.onos.net.topology.TopologyCluster;
23 -import org.onlab.onos.net.topology.TopologyEvent;
24 -import org.onlab.onos.net.topology.TopologyGraph;
25 -import org.onlab.onos.net.topology.TopologyStore;
26 -import org.onlab.onos.net.topology.TopologyStoreDelegate;
27 -import org.onlab.onos.store.AbstractStore;
28 -import org.slf4j.Logger;
29 -
30 -/**
31 - * TEMPORARY: Manages inventory of topology snapshots using distributed
32 - * structures implementation.
33 - */
34 -//FIXME: I LIE I AM NOT DISTRIBUTED
35 -@Component(immediate = true)
36 -@Service
37 -public class DistributedTopologyStore
38 -extends AbstractStore<TopologyEvent, TopologyStoreDelegate>
39 -implements TopologyStore {
40 -
41 - private final Logger log = getLogger(getClass());
42 -
43 - private volatile DefaultTopology current;
44 -
45 - @Activate
46 - public void activate() {
47 - log.info("Started");
48 - }
49 -
50 - @Deactivate
51 - public void deactivate() {
52 - log.info("Stopped");
53 - }
54 - @Override
55 - public Topology currentTopology() {
56 - return current;
57 - }
58 -
59 - @Override
60 - public boolean isLatest(Topology topology) {
61 - // Topology is current only if it is the same as our current topology
62 - return topology == current;
63 - }
64 -
65 - @Override
66 - public TopologyGraph getGraph(Topology topology) {
67 - return defaultTopology(topology).getGraph();
68 - }
69 -
70 - @Override
71 - public Set<TopologyCluster> getClusters(Topology topology) {
72 - return defaultTopology(topology).getClusters();
73 - }
74 -
75 - @Override
76 - public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
77 - return defaultTopology(topology).getCluster(clusterId);
78 - }
79 -
80 - @Override
81 - public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
82 - return defaultTopology(topology).getClusterDevices(cluster);
83 - }
84 -
85 - @Override
86 - public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
87 - return defaultTopology(topology).getClusterLinks(cluster);
88 - }
89 -
90 - @Override
91 - public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
92 - return defaultTopology(topology).getPaths(src, dst);
93 - }
94 -
95 - @Override
96 - public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
97 - LinkWeight weight) {
98 - return defaultTopology(topology).getPaths(src, dst, weight);
99 - }
100 -
101 - @Override
102 - public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
103 - return defaultTopology(topology).isInfrastructure(connectPoint);
104 - }
105 -
106 - @Override
107 - public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) {
108 - return defaultTopology(topology).isBroadcastPoint(connectPoint);
109 - }
110 -
111 - @Override
112 - public TopologyEvent updateTopology(ProviderId providerId,
113 - GraphDescription graphDescription,
114 - List<Event> reasons) {
115 - // First off, make sure that what we're given is indeed newer than
116 - // what we already have.
117 - if (current != null && graphDescription.timestamp() < current.time()) {
118 - return null;
119 - }
120 -
121 - // Have the default topology construct self from the description data.
122 - DefaultTopology newTopology =
123 - new DefaultTopology(providerId, graphDescription);
124 -
125 - // Promote the new topology to current and return a ready-to-send event.
126 - synchronized (this) {
127 - current = newTopology;
128 - return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED,
129 - current, reasons);
130 - }
131 - }
132 -
133 - // Validates the specified topology and returns it as a default
134 - private DefaultTopology defaultTopology(Topology topology) {
135 - if (topology instanceof DefaultTopology) {
136 - return (DefaultTopology) topology;
137 - }
138 - throw new IllegalArgumentException("Topology class " + topology.getClass() +
139 - " not supported");
140 - }
141 -
142 -}
1 -package org.onlab.onos.store.topology.impl;
2 -
3 -import org.onlab.onos.net.DeviceId;
4 -
5 -import java.util.Objects;
6 -
7 -/**
8 - * Key for filing pre-computed paths between source and destination devices.
9 - */
10 -class PathKey {
11 - private final DeviceId src;
12 - private final DeviceId dst;
13 -
14 - /**
15 - * Creates a path key from the given source/dest pair.
16 - * @param src source device
17 - * @param dst destination device
18 - */
19 - PathKey(DeviceId src, DeviceId dst) {
20 - this.src = src;
21 - this.dst = dst;
22 - }
23 -
24 - @Override
25 - public int hashCode() {
26 - return Objects.hash(src, dst);
27 - }
28 -
29 - @Override
30 - public boolean equals(Object obj) {
31 - if (this == obj) {
32 - return true;
33 - }
34 - if (obj instanceof PathKey) {
35 - final PathKey other = (PathKey) obj;
36 - return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst);
37 - }
38 - return false;
39 - }
40 -}
1 -/**
2 - *
3 - */
4 -package org.onlab.onos.store.device.impl;
5 -
6 -import static org.junit.Assert.*;
7 -import static org.onlab.onos.net.Device.Type.SWITCH;
8 -import static org.onlab.onos.net.DeviceId.deviceId;
9 -import static org.onlab.onos.net.device.DeviceEvent.Type.*;
10 -
11 -import java.util.Arrays;
12 -import java.util.HashMap;
13 -import java.util.List;
14 -import java.util.Map;
15 -import java.util.Set;
16 -import java.util.concurrent.CountDownLatch;
17 -import java.util.concurrent.TimeUnit;
18 -
19 -import org.junit.After;
20 -import org.junit.AfterClass;
21 -import org.junit.Before;
22 -import org.junit.BeforeClass;
23 -import org.junit.Ignore;
24 -import org.junit.Test;
25 -import org.onlab.onos.net.Device;
26 -import org.onlab.onos.net.DeviceId;
27 -import org.onlab.onos.net.Port;
28 -import org.onlab.onos.net.PortNumber;
29 -import org.onlab.onos.net.device.DefaultDeviceDescription;
30 -import org.onlab.onos.net.device.DefaultPortDescription;
31 -import org.onlab.onos.net.device.DeviceDescription;
32 -import org.onlab.onos.net.device.DeviceEvent;
33 -import org.onlab.onos.net.device.DeviceStoreDelegate;
34 -import org.onlab.onos.net.device.PortDescription;
35 -import org.onlab.onos.net.provider.ProviderId;
36 -import org.onlab.onos.store.common.StoreManager;
37 -import org.onlab.onos.store.common.StoreService;
38 -import org.onlab.onos.store.common.TestStoreManager;
39 -import com.google.common.collect.Iterables;
40 -import com.google.common.collect.Sets;
41 -import com.hazelcast.config.Config;
42 -import com.hazelcast.core.Hazelcast;
43 -
44 -/**
45 - * Test of the Hazelcast based distributed DeviceStore implementation.
46 - */
47 -public class DistributedDeviceStoreTest {
48 -
49 - private static final ProviderId PID = new ProviderId("of", "foo");
50 - private static final DeviceId DID1 = deviceId("of:foo");
51 - private static final DeviceId DID2 = deviceId("of:bar");
52 - private static final String MFR = "whitebox";
53 - private static final String HW = "1.1.x";
54 - private static final String SW1 = "3.8.1";
55 - private static final String SW2 = "3.9.5";
56 - private static final String SN = "43311-12345";
57 -
58 - private static final PortNumber P1 = PortNumber.portNumber(1);
59 - private static final PortNumber P2 = PortNumber.portNumber(2);
60 - private static final PortNumber P3 = PortNumber.portNumber(3);
61 -
62 - private DistributedDeviceStore deviceStore;
63 -
64 - private StoreManager storeManager;
65 -
66 -
67 - @BeforeClass
68 - public static void setUpBeforeClass() throws Exception {
69 - }
70 -
71 - @AfterClass
72 - public static void tearDownAfterClass() throws Exception {
73 - }
74 -
75 -
76 - @Before
77 - public void setUp() throws Exception {
78 - // TODO should find a way to clean Hazelcast instance without shutdown.
79 - Config config = TestStoreManager.getTestConfig();
80 -
81 - storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
82 - storeManager.activate();
83 -
84 - deviceStore = new TestDistributedDeviceStore(storeManager);
85 - deviceStore.activate();
86 - }
87 -
88 - @After
89 - public void tearDown() throws Exception {
90 - deviceStore.deactivate();
91 -
92 - storeManager.deactivate();
93 - }
94 -
95 - private void putDevice(DeviceId deviceId, String swVersion) {
96 - DeviceDescription description =
97 - new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
98 - HW, swVersion, SN);
99 - deviceStore.createOrUpdateDevice(PID, deviceId, description);
100 - }
101 -
102 - private static void assertDevice(DeviceId id, String swVersion, Device device) {
103 - assertNotNull(device);
104 - assertEquals(id, device.id());
105 - assertEquals(MFR, device.manufacturer());
106 - assertEquals(HW, device.hwVersion());
107 - assertEquals(swVersion, device.swVersion());
108 - assertEquals(SN, device.serialNumber());
109 - }
110 -
111 - @Test
112 - public final void testGetDeviceCount() {
113 - assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
114 -
115 - putDevice(DID1, SW1);
116 - putDevice(DID2, SW2);
117 - putDevice(DID1, SW1);
118 -
119 - assertEquals("expect 2 uniq devices", 2, deviceStore.getDeviceCount());
120 - }
121 -
122 - @Test
123 - public final void testGetDevices() {
124 - assertEquals("initialy empty", 0, Iterables.size(deviceStore.getDevices()));
125 -
126 - putDevice(DID1, SW1);
127 - putDevice(DID2, SW2);
128 - putDevice(DID1, SW1);
129 -
130 - assertEquals("expect 2 uniq devices",
131 - 2, Iterables.size(deviceStore.getDevices()));
132 -
133 - Map<DeviceId, Device> devices = new HashMap<>();
134 - for (Device device : deviceStore.getDevices()) {
135 - devices.put(device.id(), device);
136 - }
137 -
138 - assertDevice(DID1, SW1, devices.get(DID1));
139 - assertDevice(DID2, SW2, devices.get(DID2));
140 -
141 - // add case for new node?
142 - }
143 -
144 - @Test
145 - public final void testGetDevice() {
146 -
147 - putDevice(DID1, SW1);
148 -
149 - assertDevice(DID1, SW1, deviceStore.getDevice(DID1));
150 - assertNull("DID2 shouldn't be there", deviceStore.getDevice(DID2));
151 - }
152 -
153 - @Test
154 - public final void testCreateOrUpdateDevice() {
155 - DeviceDescription description =
156 - new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
157 - HW, SW1, SN);
158 - DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
159 - assertEquals(DEVICE_ADDED, event.type());
160 - assertDevice(DID1, SW1, event.subject());
161 -
162 - DeviceDescription description2 =
163 - new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
164 - HW, SW2, SN);
165 - DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
166 - assertEquals(DEVICE_UPDATED, event2.type());
167 - assertDevice(DID1, SW2, event2.subject());
168 -
169 - assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
170 - }
171 -
172 - @Test
173 - public final void testMarkOffline() {
174 -
175 - putDevice(DID1, SW1);
176 - assertTrue(deviceStore.isAvailable(DID1));
177 -
178 - DeviceEvent event = deviceStore.markOffline(DID1);
179 - assertEquals(DEVICE_AVAILABILITY_CHANGED, event.type());
180 - assertDevice(DID1, SW1, event.subject());
181 - assertFalse(deviceStore.isAvailable(DID1));
182 -
183 - DeviceEvent event2 = deviceStore.markOffline(DID1);
184 - assertNull("No change, no event", event2);
185 -}
186 -
187 - @Test
188 - public final void testUpdatePorts() {
189 - putDevice(DID1, SW1);
190 - List<PortDescription> pds = Arrays.<PortDescription>asList(
191 - new DefaultPortDescription(P1, true),
192 - new DefaultPortDescription(P2, true)
193 - );
194 -
195 - List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
196 -
197 - Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
198 - for (DeviceEvent event : events) {
199 - assertEquals(PORT_ADDED, event.type());
200 - assertDevice(DID1, SW1, event.subject());
201 - assertTrue("PortNumber is one of expected",
202 - expectedPorts.remove(event.port().number()));
203 - assertTrue("Port is enabled", event.port().isEnabled());
204 - }
205 - assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
206 -
207 -
208 - List<PortDescription> pds2 = Arrays.<PortDescription>asList(
209 - new DefaultPortDescription(P1, false),
210 - new DefaultPortDescription(P2, true),
211 - new DefaultPortDescription(P3, true)
212 - );
213 -
214 - events = deviceStore.updatePorts(PID, DID1, pds2);
215 - assertFalse("event should be triggered", events.isEmpty());
216 - for (DeviceEvent event : events) {
217 - PortNumber num = event.port().number();
218 - if (P1.equals(num)) {
219 - assertEquals(PORT_UPDATED, event.type());
220 - assertDevice(DID1, SW1, event.subject());
221 - assertFalse("Port is disabled", event.port().isEnabled());
222 - } else if (P2.equals(num)) {
223 - fail("P2 event not expected.");
224 - } else if (P3.equals(num)) {
225 - assertEquals(PORT_ADDED, event.type());
226 - assertDevice(DID1, SW1, event.subject());
227 - assertTrue("Port is enabled", event.port().isEnabled());
228 - } else {
229 - fail("Unknown port number encountered: " + num);
230 - }
231 - }
232 -
233 - List<PortDescription> pds3 = Arrays.<PortDescription>asList(
234 - new DefaultPortDescription(P1, false),
235 - new DefaultPortDescription(P2, true)
236 - );
237 - events = deviceStore.updatePorts(PID, DID1, pds3);
238 - assertFalse("event should be triggered", events.isEmpty());
239 - for (DeviceEvent event : events) {
240 - PortNumber num = event.port().number();
241 - if (P1.equals(num)) {
242 - fail("P1 event not expected.");
243 - } else if (P2.equals(num)) {
244 - fail("P2 event not expected.");
245 - } else if (P3.equals(num)) {
246 - assertEquals(PORT_REMOVED, event.type());
247 - assertDevice(DID1, SW1, event.subject());
248 - assertTrue("Port was enabled", event.port().isEnabled());
249 - } else {
250 - fail("Unknown port number encountered: " + num);
251 - }
252 - }
253 -
254 - }
255 -
256 - @Test
257 - public final void testUpdatePortStatus() {
258 - putDevice(DID1, SW1);
259 - List<PortDescription> pds = Arrays.<PortDescription>asList(
260 - new DefaultPortDescription(P1, true)
261 - );
262 - deviceStore.updatePorts(PID, DID1, pds);
263 -
264 - DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
265 - new DefaultPortDescription(P1, false));
266 - assertEquals(PORT_UPDATED, event.type());
267 - assertDevice(DID1, SW1, event.subject());
268 - assertEquals(P1, event.port().number());
269 - assertFalse("Port is disabled", event.port().isEnabled());
270 - }
271 -
272 - @Test
273 - public final void testGetPorts() {
274 - putDevice(DID1, SW1);
275 - putDevice(DID2, SW1);
276 - List<PortDescription> pds = Arrays.<PortDescription>asList(
277 - new DefaultPortDescription(P1, true),
278 - new DefaultPortDescription(P2, true)
279 - );
280 - deviceStore.updatePorts(PID, DID1, pds);
281 -
282 - Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
283 - List<Port> ports = deviceStore.getPorts(DID1);
284 - for (Port port : ports) {
285 - assertTrue("Port is enabled", port.isEnabled());
286 - assertTrue("PortNumber is one of expected",
287 - expectedPorts.remove(port.number()));
288 - }
289 - assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
290 -
291 -
292 - assertTrue("DID2 has no ports", deviceStore.getPorts(DID2).isEmpty());
293 - }
294 -
295 - @Test
296 - public final void testGetPort() {
297 - putDevice(DID1, SW1);
298 - putDevice(DID2, SW1);
299 - List<PortDescription> pds = Arrays.<PortDescription>asList(
300 - new DefaultPortDescription(P1, true),
301 - new DefaultPortDescription(P2, false)
302 - );
303 - deviceStore.updatePorts(PID, DID1, pds);
304 -
305 - Port port1 = deviceStore.getPort(DID1, P1);
306 - assertEquals(P1, port1.number());
307 - assertTrue("Port is enabled", port1.isEnabled());
308 -
309 - Port port2 = deviceStore.getPort(DID1, P2);
310 - assertEquals(P2, port2.number());
311 - assertFalse("Port is disabled", port2.isEnabled());
312 -
313 - Port port3 = deviceStore.getPort(DID1, P3);
314 - assertNull("P3 not expected", port3);
315 - }
316 -
317 - @Test
318 - public final void testRemoveDevice() {
319 - putDevice(DID1, SW1);
320 - putDevice(DID2, SW1);
321 -
322 - assertEquals(2, deviceStore.getDeviceCount());
323 -
324 - DeviceEvent event = deviceStore.removeDevice(DID1);
325 - assertEquals(DEVICE_REMOVED, event.type());
326 - assertDevice(DID1, SW1, event.subject());
327 -
328 - assertEquals(1, deviceStore.getDeviceCount());
329 - }
330 -
331 - // TODO add test for Port events when we have them
332 - @Ignore("Ignore until Delegate spec. is clear.")
333 - @Test
334 - public final void testEvents() throws InterruptedException {
335 - final CountDownLatch addLatch = new CountDownLatch(1);
336 - DeviceStoreDelegate checkAdd = new DeviceStoreDelegate() {
337 - @Override
338 - public void notify(DeviceEvent event) {
339 - assertEquals(DEVICE_ADDED, event.type());
340 - assertDevice(DID1, SW1, event.subject());
341 - addLatch.countDown();
342 - }
343 - };
344 - final CountDownLatch updateLatch = new CountDownLatch(1);
345 - DeviceStoreDelegate checkUpdate = new DeviceStoreDelegate() {
346 - @Override
347 - public void notify(DeviceEvent event) {
348 - assertEquals(DEVICE_UPDATED, event.type());
349 - assertDevice(DID1, SW2, event.subject());
350 - updateLatch.countDown();
351 - }
352 - };
353 - final CountDownLatch removeLatch = new CountDownLatch(1);
354 - DeviceStoreDelegate checkRemove = new DeviceStoreDelegate() {
355 - @Override
356 - public void notify(DeviceEvent event) {
357 - assertEquals(DEVICE_REMOVED, event.type());
358 - assertDevice(DID1, SW2, event.subject());
359 - removeLatch.countDown();
360 - }
361 - };
362 -
363 - DeviceDescription description =
364 - new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
365 - HW, SW1, SN);
366 - deviceStore.setDelegate(checkAdd);
367 - deviceStore.createOrUpdateDevice(PID, DID1, description);
368 - assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
369 -
370 -
371 - DeviceDescription description2 =
372 - new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
373 - HW, SW2, SN);
374 - deviceStore.unsetDelegate(checkAdd);
375 - deviceStore.setDelegate(checkUpdate);
376 - deviceStore.createOrUpdateDevice(PID, DID1, description2);
377 - assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
378 -
379 - deviceStore.unsetDelegate(checkUpdate);
380 - deviceStore.setDelegate(checkRemove);
381 - deviceStore.removeDevice(DID1);
382 - assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
383 - }
384 -
385 - private class TestDistributedDeviceStore extends DistributedDeviceStore {
386 - public TestDistributedDeviceStore(StoreService storeService) {
387 - this.storeService = storeService;
388 - }
389 - }
390 -}
1 -package org.onlab.onos.store.link.impl;
2 -
3 -import static org.junit.Assert.*;
4 -import static org.onlab.onos.net.DeviceId.deviceId;
5 -import static org.onlab.onos.net.Link.Type.*;
6 -import static org.onlab.onos.net.LinkKey.linkKey;
7 -import static org.onlab.onos.net.link.LinkEvent.Type.*;
8 -
9 -import java.util.HashMap;
10 -import java.util.Map;
11 -import java.util.Set;
12 -import java.util.concurrent.CountDownLatch;
13 -import java.util.concurrent.TimeUnit;
14 -
15 -import org.junit.After;
16 -import org.junit.AfterClass;
17 -import org.junit.Before;
18 -import org.junit.BeforeClass;
19 -import org.junit.Ignore;
20 -import org.junit.Test;
21 -import org.onlab.onos.net.ConnectPoint;
22 -import org.onlab.onos.net.DeviceId;
23 -import org.onlab.onos.net.Link;
24 -import org.onlab.onos.net.LinkKey;
25 -import org.onlab.onos.net.PortNumber;
26 -import org.onlab.onos.net.Link.Type;
27 -import org.onlab.onos.net.link.DefaultLinkDescription;
28 -import org.onlab.onos.net.link.LinkEvent;
29 -import org.onlab.onos.net.link.LinkStoreDelegate;
30 -import org.onlab.onos.net.provider.ProviderId;
31 -import org.onlab.onos.store.common.StoreManager;
32 -import org.onlab.onos.store.common.StoreService;
33 -import org.onlab.onos.store.common.TestStoreManager;
34 -import com.google.common.collect.Iterables;
35 -import com.hazelcast.config.Config;
36 -import com.hazelcast.core.Hazelcast;
37 -
38 -/**
39 - * Test of the Hazelcast based distributed LinkStore implementation.
40 - */
41 -public class DistributedLinkStoreTest {
42 -
43 - private static final ProviderId PID = new ProviderId("of", "foo");
44 - private static final DeviceId DID1 = deviceId("of:foo");
45 - private static final DeviceId DID2 = deviceId("of:bar");
46 -
47 - private static final PortNumber P1 = PortNumber.portNumber(1);
48 - private static final PortNumber P2 = PortNumber.portNumber(2);
49 - private static final PortNumber P3 = PortNumber.portNumber(3);
50 -
51 - private StoreManager storeManager;
52 -
53 - private DistributedLinkStore linkStore;
54 -
55 - @BeforeClass
56 - public static void setUpBeforeClass() throws Exception {
57 - }
58 -
59 - @AfterClass
60 - public static void tearDownAfterClass() throws Exception {
61 - }
62 -
63 - @Before
64 - public void setUp() throws Exception {
65 - // TODO should find a way to clean Hazelcast instance without shutdown.
66 - Config config = TestStoreManager.getTestConfig();
67 -
68 - storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
69 - storeManager.activate();
70 -
71 - linkStore = new TestDistributedLinkStore(storeManager);
72 - linkStore.activate();
73 - }
74 -
75 - @After
76 - public void tearDown() throws Exception {
77 - linkStore.deactivate();
78 - storeManager.deactivate();
79 - }
80 -
81 - private void putLink(DeviceId srcId, PortNumber srcNum,
82 - DeviceId dstId, PortNumber dstNum, Type type) {
83 - ConnectPoint src = new ConnectPoint(srcId, srcNum);
84 - ConnectPoint dst = new ConnectPoint(dstId, dstNum);
85 - linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type));
86 - }
87 -
88 - private void putLink(LinkKey key, Type type) {
89 - putLink(key.src().deviceId(), key.src().port(),
90 - key.dst().deviceId(), key.dst().port(),
91 - type);
92 - }
93 -
94 - private static void assertLink(DeviceId srcId, PortNumber srcNum,
95 - DeviceId dstId, PortNumber dstNum, Type type,
96 - Link link) {
97 - assertEquals(srcId, link.src().deviceId());
98 - assertEquals(srcNum, link.src().port());
99 - assertEquals(dstId, link.dst().deviceId());
100 - assertEquals(dstNum, link.dst().port());
101 - assertEquals(type, link.type());
102 - }
103 -
104 - private static void assertLink(LinkKey key, Type type, Link link) {
105 - assertLink(key.src().deviceId(), key.src().port(),
106 - key.dst().deviceId(), key.dst().port(),
107 - type, link);
108 - }
109 -
110 - @Test
111 - public final void testGetLinkCount() {
112 - assertEquals("initialy empty", 0, linkStore.getLinkCount());
113 -
114 - putLink(DID1, P1, DID2, P2, DIRECT);
115 - putLink(DID2, P2, DID1, P1, DIRECT);
116 - putLink(DID1, P1, DID2, P2, DIRECT);
117 -
118 - assertEquals("expecting 2 unique link", 2, linkStore.getLinkCount());
119 - }
120 -
121 - @Test
122 - public final void testGetLinks() {
123 - assertEquals("initialy empty", 0,
124 - Iterables.size(linkStore.getLinks()));
125 -
126 - LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
127 - LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
128 -
129 - putLink(linkId1, DIRECT);
130 - putLink(linkId2, DIRECT);
131 - putLink(linkId1, DIRECT);
132 -
133 - assertEquals("expecting 2 unique link", 2,
134 - Iterables.size(linkStore.getLinks()));
135 -
136 - Map<LinkKey, Link> links = new HashMap<>();
137 - for (Link link : linkStore.getLinks()) {
138 - links.put(linkKey(link), link);
139 - }
140 -
141 - assertLink(linkId1, DIRECT, links.get(linkId1));
142 - assertLink(linkId2, DIRECT, links.get(linkId2));
143 - }
144 -
145 - @Test
146 - public final void testGetDeviceEgressLinks() {
147 - LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
148 - LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
149 - LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
150 -
151 - putLink(linkId1, DIRECT);
152 - putLink(linkId2, DIRECT);
153 - putLink(linkId3, DIRECT);
154 -
155 - // DID1,P1 => DID2,P2
156 - // DID2,P2 => DID1,P1
157 - // DID1,P2 => DID2,P3
158 -
159 - Set<Link> links1 = linkStore.getDeviceEgressLinks(DID1);
160 - assertEquals(2, links1.size());
161 - // check
162 -
163 - Set<Link> links2 = linkStore.getDeviceEgressLinks(DID2);
164 - assertEquals(1, links2.size());
165 - assertLink(linkId2, DIRECT, links2.iterator().next());
166 - }
167 -
168 - @Test
169 - public final void testGetDeviceIngressLinks() {
170 - LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
171 - LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
172 - LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
173 -
174 - putLink(linkId1, DIRECT);
175 - putLink(linkId2, DIRECT);
176 - putLink(linkId3, DIRECT);
177 -
178 - // DID1,P1 => DID2,P2
179 - // DID2,P2 => DID1,P1
180 - // DID1,P2 => DID2,P3
181 -
182 - Set<Link> links1 = linkStore.getDeviceIngressLinks(DID2);
183 - assertEquals(2, links1.size());
184 - // check
185 -
186 - Set<Link> links2 = linkStore.getDeviceIngressLinks(DID1);
187 - assertEquals(1, links2.size());
188 - assertLink(linkId2, DIRECT, links2.iterator().next());
189 - }
190 -
191 - @Test
192 - public final void testGetLink() {
193 - ConnectPoint src = new ConnectPoint(DID1, P1);
194 - ConnectPoint dst = new ConnectPoint(DID2, P2);
195 - LinkKey linkId1 = linkKey(src, dst);
196 -
197 - putLink(linkId1, DIRECT);
198 -
199 - Link link = linkStore.getLink(src, dst);
200 - assertLink(linkId1, DIRECT, link);
201 -
202 - assertNull("There shouldn't be reverese link",
203 - linkStore.getLink(dst, src));
204 - }
205 -
206 - @Test
207 - public final void testGetEgressLinks() {
208 - final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
209 - final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
210 - LinkKey linkId1 = linkKey(d1P1, d2P2);
211 - LinkKey linkId2 = linkKey(d2P2, d1P1);
212 - LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
213 -
214 - putLink(linkId1, DIRECT);
215 - putLink(linkId2, DIRECT);
216 - putLink(linkId3, DIRECT);
217 -
218 - // DID1,P1 => DID2,P2
219 - // DID2,P2 => DID1,P1
220 - // DID1,P2 => DID2,P3
221 -
222 - Set<Link> links1 = linkStore.getEgressLinks(d1P1);
223 - assertEquals(1, links1.size());
224 - assertLink(linkId1, DIRECT, links1.iterator().next());
225 -
226 - Set<Link> links2 = linkStore.getEgressLinks(d2P2);
227 - assertEquals(1, links2.size());
228 - assertLink(linkId2, DIRECT, links2.iterator().next());
229 - }
230 -
231 - @Test
232 - public final void testGetIngressLinks() {
233 - final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
234 - final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
235 - LinkKey linkId1 = linkKey(d1P1, d2P2);
236 - LinkKey linkId2 = linkKey(d2P2, d1P1);
237 - LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
238 -
239 - putLink(linkId1, DIRECT);
240 - putLink(linkId2, DIRECT);
241 - putLink(linkId3, DIRECT);
242 -
243 - // DID1,P1 => DID2,P2
244 - // DID2,P2 => DID1,P1
245 - // DID1,P2 => DID2,P3
246 -
247 - Set<Link> links1 = linkStore.getIngressLinks(d2P2);
248 - assertEquals(1, links1.size());
249 - assertLink(linkId1, DIRECT, links1.iterator().next());
250 -
251 - Set<Link> links2 = linkStore.getIngressLinks(d1P1);
252 - assertEquals(1, links2.size());
253 - assertLink(linkId2, DIRECT, links2.iterator().next());
254 - }
255 -
256 - @Test
257 - public final void testCreateOrUpdateLink() {
258 - ConnectPoint src = new ConnectPoint(DID1, P1);
259 - ConnectPoint dst = new ConnectPoint(DID2, P2);
260 -
261 - // add link
262 - LinkEvent event = linkStore.createOrUpdateLink(PID,
263 - new DefaultLinkDescription(src, dst, INDIRECT));
264 -
265 - assertLink(DID1, P1, DID2, P2, INDIRECT, event.subject());
266 - assertEquals(LINK_ADDED, event.type());
267 -
268 - // update link type
269 - LinkEvent event2 = linkStore.createOrUpdateLink(PID,
270 - new DefaultLinkDescription(src, dst, DIRECT));
271 -
272 - assertLink(DID1, P1, DID2, P2, DIRECT, event2.subject());
273 - assertEquals(LINK_UPDATED, event2.type());
274 -
275 - // no change
276 - LinkEvent event3 = linkStore.createOrUpdateLink(PID,
277 - new DefaultLinkDescription(src, dst, DIRECT));
278 -
279 - assertNull("No change event expected", event3);
280 - }
281 -
282 - @Test
283 - public final void testRemoveLink() {
284 - final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
285 - final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
286 - LinkKey linkId1 = linkKey(d1P1, d2P2);
287 - LinkKey linkId2 = linkKey(d2P2, d1P1);
288 -
289 - putLink(linkId1, DIRECT);
290 - putLink(linkId2, DIRECT);
291 -
292 - // DID1,P1 => DID2,P2
293 - // DID2,P2 => DID1,P1
294 - // DID1,P2 => DID2,P3
295 -
296 - LinkEvent event = linkStore.removeLink(d1P1, d2P2);
297 - assertEquals(LINK_REMOVED, event.type());
298 - LinkEvent event2 = linkStore.removeLink(d1P1, d2P2);
299 - assertNull(event2);
300 -
301 - assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
302 - }
303 -
304 - @Ignore("Ignore until Delegate spec. is clear.")
305 - @Test
306 - public final void testEvents() throws InterruptedException {
307 -
308 - final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
309 - final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
310 - final LinkKey linkId1 = linkKey(d1P1, d2P2);
311 -
312 - final CountDownLatch addLatch = new CountDownLatch(1);
313 - LinkStoreDelegate checkAdd = new LinkStoreDelegate() {
314 - @Override
315 - public void notify(LinkEvent event) {
316 - assertEquals(LINK_ADDED, event.type());
317 - assertLink(linkId1, INDIRECT, event.subject());
318 - addLatch.countDown();
319 - }
320 - };
321 - final CountDownLatch updateLatch = new CountDownLatch(1);
322 - LinkStoreDelegate checkUpdate = new LinkStoreDelegate() {
323 - @Override
324 - public void notify(LinkEvent event) {
325 - assertEquals(LINK_UPDATED, event.type());
326 - assertLink(linkId1, DIRECT, event.subject());
327 - updateLatch.countDown();
328 - }
329 - };
330 - final CountDownLatch removeLatch = new CountDownLatch(1);
331 - LinkStoreDelegate checkRemove = new LinkStoreDelegate() {
332 - @Override
333 - public void notify(LinkEvent event) {
334 - assertEquals(LINK_REMOVED, event.type());
335 - assertLink(linkId1, DIRECT, event.subject());
336 - removeLatch.countDown();
337 - }
338 - };
339 -
340 - linkStore.setDelegate(checkAdd);
341 - putLink(linkId1, INDIRECT);
342 - assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
343 -
344 - linkStore.unsetDelegate(checkAdd);
345 - linkStore.setDelegate(checkUpdate);
346 - putLink(linkId1, DIRECT);
347 - assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
348 -
349 - linkStore.unsetDelegate(checkUpdate);
350 - linkStore.setDelegate(checkRemove);
351 - linkStore.removeLink(d1P1, d2P2);
352 - assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
353 - }
354 -
355 -
356 - class TestDistributedLinkStore extends DistributedLinkStore {
357 - TestDistributedLinkStore(StoreService storeService) {
358 - this.storeService = storeService;
359 - }
360 - }
361 -}
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
17 <modules> 17 <modules>
18 <module>common</module> 18 <module>common</module>
19 <module>cluster</module> 19 <module>cluster</module>
20 - <module>net</module>
21 </modules> 20 </modules>
22 21
23 <dependencies> 22 <dependencies>
......
1 +package org.onlab.onos.store.serializers;
2 +
3 +import org.onlab.onos.net.DeviceId;
4 +import org.onlab.onos.net.HostLocation;
5 +import org.onlab.onos.net.PortNumber;
6 +
7 +import com.esotericsoftware.kryo.Kryo;
8 +import com.esotericsoftware.kryo.Serializer;
9 +import com.esotericsoftware.kryo.io.Input;
10 +import com.esotericsoftware.kryo.io.Output;
11 +
12 +/**
13 +* Kryo Serializer for {@link HostLocation}.
14 +*/
15 +public class HostLocationSerializer extends Serializer<HostLocation> {
16 +
17 + /**
18 + * Creates {@link HostLocation} serializer instance.
19 + */
20 + public HostLocationSerializer() {
21 + // non-null, immutable
22 + super(false, true);
23 + }
24 +
25 + @Override
26 + public void write(Kryo kryo, Output output, HostLocation object) {
27 + kryo.writeClassAndObject(output, object.deviceId());
28 + kryo.writeClassAndObject(output, object.port());
29 + output.writeLong(object.time());
30 + }
31 +
32 + @Override
33 + public HostLocation read(Kryo kryo, Input input, Class<HostLocation> type) {
34 + DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
35 + PortNumber portNumber = (PortNumber) kryo.readClassAndObject(input);
36 + long time = input.readLong();
37 + return new HostLocation(deviceId, portNumber, time);
38 + }
39 +
40 +}
...@@ -20,8 +20,7 @@ public class IpAddressSerializer extends Serializer<IpAddress> { ...@@ -20,8 +20,7 @@ public class IpAddressSerializer extends Serializer<IpAddress> {
20 } 20 }
21 21
22 @Override 22 @Override
23 - public void write(Kryo kryo, Output output, 23 + public void write(Kryo kryo, Output output, IpAddress object) {
24 - IpAddress object) {
25 byte[] octs = object.toOctets(); 24 byte[] octs = object.toOctets();
26 output.writeInt(octs.length); 25 output.writeInt(octs.length);
27 output.writeBytes(octs); 26 output.writeBytes(octs);
...@@ -29,11 +28,10 @@ public class IpAddressSerializer extends Serializer<IpAddress> { ...@@ -29,11 +28,10 @@ public class IpAddressSerializer extends Serializer<IpAddress> {
29 } 28 }
30 29
31 @Override 30 @Override
32 - public IpAddress read(Kryo kryo, Input input, 31 + public IpAddress read(Kryo kryo, Input input, Class<IpAddress> type) {
33 - Class<IpAddress> type) { 32 + final int octLen = input.readInt();
34 - int octLen = input.readInt();
35 byte[] octs = new byte[octLen]; 33 byte[] octs = new byte[octLen];
36 - input.read(octs); 34 + input.readBytes(octs);
37 int prefLen = input.readInt(); 35 int prefLen = input.readInt();
38 return IpAddress.valueOf(octs, prefLen); 36 return IpAddress.valueOf(octs, prefLen);
39 } 37 }
......
...@@ -34,7 +34,7 @@ public final class IpPrefixSerializer extends Serializer<IpPrefix> { ...@@ -34,7 +34,7 @@ public final class IpPrefixSerializer extends Serializer<IpPrefix> {
34 Class<IpPrefix> type) { 34 Class<IpPrefix> type) {
35 int octLen = input.readInt(); 35 int octLen = input.readInt();
36 byte[] octs = new byte[octLen]; 36 byte[] octs = new byte[octLen];
37 - input.read(octs); 37 + input.readBytes(octs);
38 int prefLen = input.readInt(); 38 int prefLen = input.readInt();
39 return IpPrefix.valueOf(octs, prefLen); 39 return IpPrefix.valueOf(octs, prefLen);
40 } 40 }
......
...@@ -17,21 +17,28 @@ import org.onlab.onos.net.DefaultPort; ...@@ -17,21 +17,28 @@ import org.onlab.onos.net.DefaultPort;
17 import org.onlab.onos.net.Device; 17 import org.onlab.onos.net.Device;
18 import org.onlab.onos.net.DeviceId; 18 import org.onlab.onos.net.DeviceId;
19 import org.onlab.onos.net.Element; 19 import org.onlab.onos.net.Element;
20 +import org.onlab.onos.net.HostId;
21 +import org.onlab.onos.net.HostLocation;
20 import org.onlab.onos.net.Link; 22 import org.onlab.onos.net.Link;
21 import org.onlab.onos.net.LinkKey; 23 import org.onlab.onos.net.LinkKey;
22 import org.onlab.onos.net.Port; 24 import org.onlab.onos.net.Port;
23 import org.onlab.onos.net.PortNumber; 25 import org.onlab.onos.net.PortNumber;
24 import org.onlab.onos.net.device.DefaultDeviceDescription; 26 import org.onlab.onos.net.device.DefaultDeviceDescription;
25 import org.onlab.onos.net.device.DefaultPortDescription; 27 import org.onlab.onos.net.device.DefaultPortDescription;
28 +import org.onlab.onos.net.host.DefaultHostDescription;
29 +import org.onlab.onos.net.host.HostDescription;
26 import org.onlab.onos.net.link.DefaultLinkDescription; 30 import org.onlab.onos.net.link.DefaultLinkDescription;
27 import org.onlab.onos.net.provider.ProviderId; 31 import org.onlab.onos.net.provider.ProviderId;
28 import org.onlab.onos.store.Timestamp; 32 import org.onlab.onos.store.Timestamp;
29 import org.onlab.packet.IpAddress; 33 import org.onlab.packet.IpAddress;
30 import org.onlab.packet.IpPrefix; 34 import org.onlab.packet.IpPrefix;
35 +import org.onlab.packet.MacAddress;
36 +import org.onlab.packet.VlanId;
31 import org.onlab.util.KryoPool; 37 import org.onlab.util.KryoPool;
32 38
33 import com.google.common.collect.ImmutableList; 39 import com.google.common.collect.ImmutableList;
34 import com.google.common.collect.ImmutableMap; 40 import com.google.common.collect.ImmutableMap;
41 +import com.google.common.collect.ImmutableSet;
35 42
36 public final class KryoPoolUtil { 43 public final class KryoPoolUtil {
37 44
...@@ -41,6 +48,8 @@ public final class KryoPoolUtil { ...@@ -41,6 +48,8 @@ public final class KryoPoolUtil {
41 public static final KryoPool MISC = KryoPool.newBuilder() 48 public static final KryoPool MISC = KryoPool.newBuilder()
42 .register(IpPrefix.class, new IpPrefixSerializer()) 49 .register(IpPrefix.class, new IpPrefixSerializer())
43 .register(IpAddress.class, new IpAddressSerializer()) 50 .register(IpAddress.class, new IpAddressSerializer())
51 + .register(MacAddress.class, new MacAddressSerializer())
52 + .register(VlanId.class)
44 .build(); 53 .build();
45 54
46 // TODO: Populate other classes 55 // TODO: Populate other classes
...@@ -51,6 +60,7 @@ public final class KryoPoolUtil { ...@@ -51,6 +60,7 @@ public final class KryoPoolUtil {
51 .register(MISC) 60 .register(MISC)
52 .register(ImmutableMap.class, new ImmutableMapSerializer()) 61 .register(ImmutableMap.class, new ImmutableMapSerializer())
53 .register(ImmutableList.class, new ImmutableListSerializer()) 62 .register(ImmutableList.class, new ImmutableListSerializer())
63 + .register(ImmutableSet.class, new ImmutableSetSerializer())
54 .register( 64 .register(
55 // 65 //
56 ArrayList.class, 66 ArrayList.class,
...@@ -69,8 +79,10 @@ public final class KryoPoolUtil { ...@@ -69,8 +79,10 @@ public final class KryoPoolUtil {
69 DefaultPortDescription.class, 79 DefaultPortDescription.class,
70 Element.class, 80 Element.class,
71 Link.Type.class, 81 Link.Type.class,
72 - Timestamp.class 82 + Timestamp.class,
73 - 83 + HostId.class,
84 + HostDescription.class,
85 + DefaultHostDescription.class
74 ) 86 )
75 .register(URI.class, new URISerializer()) 87 .register(URI.class, new URISerializer())
76 .register(NodeId.class, new NodeIdSerializer()) 88 .register(NodeId.class, new NodeIdSerializer())
...@@ -82,6 +94,8 @@ public final class KryoPoolUtil { ...@@ -82,6 +94,8 @@ public final class KryoPoolUtil {
82 .register(ConnectPoint.class, new ConnectPointSerializer()) 94 .register(ConnectPoint.class, new ConnectPointSerializer())
83 .register(DefaultLink.class, new DefaultLinkSerializer()) 95 .register(DefaultLink.class, new DefaultLinkSerializer())
84 .register(MastershipTerm.class, new MastershipTermSerializer()) 96 .register(MastershipTerm.class, new MastershipTermSerializer())
97 + .register(MastershipRole.class, new MastershipRoleSerializer())
98 + .register(HostLocation.class, new HostLocationSerializer())
85 99
86 .build(); 100 .build();
87 101
......
1 +package org.onlab.onos.store.serializers;
2 +
3 +import org.onlab.packet.MacAddress;
4 +
5 +import com.esotericsoftware.kryo.Kryo;
6 +import com.esotericsoftware.kryo.Serializer;
7 +import com.esotericsoftware.kryo.io.Input;
8 +import com.esotericsoftware.kryo.io.Output;
9 +
10 +/**
11 + * Kryo Serializer for {@link MacAddress}.
12 + */
13 +public class MacAddressSerializer extends Serializer<MacAddress> {
14 +
15 + /**
16 + * Creates {@link MacAddress} serializer instance.
17 + */
18 + public MacAddressSerializer() {
19 + super(false, true);
20 + }
21 +
22 + @Override
23 + public void write(Kryo kryo, Output output, MacAddress object) {
24 + output.writeBytes(object.getAddress());
25 + }
26 +
27 + @Override
28 + public MacAddress read(Kryo kryo, Input input, Class<MacAddress> type) {
29 + return MacAddress.valueOf(input.readBytes(MacAddress.MAC_ADDRESS_LENGTH));
30 + }
31 +
32 +}
...@@ -84,7 +84,7 @@ public class SimpleHostStore ...@@ -84,7 +84,7 @@ public class SimpleHostStore
84 descr.hwAddress(), 84 descr.hwAddress(),
85 descr.vlan(), 85 descr.vlan(),
86 descr.location(), 86 descr.location(),
87 - ImmutableSet.of(descr.ipAddress())); 87 + ImmutableSet.copyOf(descr.ipAddress()));
88 synchronized (this) { 88 synchronized (this) {
89 hosts.put(hostId, newhost); 89 hosts.put(hostId, newhost);
90 locations.put(descr.location(), newhost); 90 locations.put(descr.location(), newhost);
...@@ -101,12 +101,12 @@ public class SimpleHostStore ...@@ -101,12 +101,12 @@ public class SimpleHostStore
101 return new HostEvent(HOST_MOVED, host); 101 return new HostEvent(HOST_MOVED, host);
102 } 102 }
103 103
104 - if (host.ipAddresses().contains(descr.ipAddress())) { 104 + if (host.ipAddresses().containsAll(descr.ipAddress())) {
105 return null; 105 return null;
106 } 106 }
107 107
108 Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses()); 108 Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
109 - addresses.add(descr.ipAddress()); 109 + addresses.addAll(descr.ipAddress());
110 StoredHost updated = new StoredHost(providerId, host.id(), 110 StoredHost updated = new StoredHost(providerId, host.id(),
111 host.mac(), host.vlan(), 111 host.mac(), host.vlan(),
112 descr.location(), addresses); 112 descr.location(), addresses);
......
...@@ -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>
...@@ -164,6 +170,12 @@ ...@@ -164,6 +170,12 @@
164 <scope>provided</scope> 170 <scope>provided</scope>
165 </dependency> 171 </dependency>
166 <dependency> 172 <dependency>
173 + <groupId>org.osgi</groupId>
174 + <artifactId>org.osgi.compendium</artifactId>
175 + <version>4.3.1</version>
176 + <scope>provided</scope>
177 + </dependency>
178 + <dependency>
167 <groupId>org.apache.felix</groupId> 179 <groupId>org.apache.felix</groupId>
168 <artifactId>org.apache.felix.scr.annotations</artifactId> 180 <artifactId>org.apache.felix.scr.annotations</artifactId>
169 <version>1.9.8</version> 181 <version>1.9.8</version>
...@@ -260,6 +272,13 @@ ...@@ -260,6 +272,13 @@
260 <artifactId>onos-of-api</artifactId> 272 <artifactId>onos-of-api</artifactId>
261 <version>${project.version}</version> 273 <version>${project.version}</version>
262 </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 +
263 <dependency> 282 <dependency>
264 <groupId>org.onlab.onos</groupId> 283 <groupId>org.onlab.onos</groupId>
265 <artifactId>onos-of-api</artifactId> 284 <artifactId>onos-of-api</artifactId>
......
...@@ -161,10 +161,10 @@ public class FlowModBuilder { ...@@ -161,10 +161,10 @@ public class FlowModBuilder {
161 switch (l3m.subtype()) { 161 switch (l3m.subtype()) {
162 case IP_DST: 162 case IP_DST:
163 ip = (ModIPInstruction) i; 163 ip = (ModIPInstruction) i;
164 - return factory.actions().setNwDst(IPv4Address.of(ip.ip().toRealInt())); 164 + return factory.actions().setNwDst(IPv4Address.of(ip.ip().toInt()));
165 case IP_SRC: 165 case IP_SRC:
166 ip = (ModIPInstruction) i; 166 ip = (ModIPInstruction) i;
167 - return factory.actions().setNwSrc(IPv4Address.of(ip.ip().toRealInt())); 167 + return factory.actions().setNwSrc(IPv4Address.of(ip.ip().toInt()));
168 default: 168 default:
169 log.warn("Unimplemented action type {}.", l3m.subtype()); 169 log.warn("Unimplemented action type {}.", l3m.subtype());
170 break; 170 break;
...@@ -220,21 +220,21 @@ public class FlowModBuilder { ...@@ -220,21 +220,21 @@ public class FlowModBuilder {
220 case IPV4_DST: 220 case IPV4_DST:
221 ip = (IPCriterion) c; 221 ip = (IPCriterion) c;
222 if (ip.ip().isMasked()) { 222 if (ip.ip().isMasked()) {
223 - Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toRealInt()), 223 + Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toInt()),
224 - IPv4Address.of(ip.ip().netmask().toRealInt())); 224 + IPv4Address.of(ip.ip().netmask().toInt()));
225 mBuilder.setMasked(MatchField.IPV4_DST, maskedIp); 225 mBuilder.setMasked(MatchField.IPV4_DST, maskedIp);
226 } else { 226 } else {
227 - mBuilder.setExact(MatchField.IPV4_DST, IPv4Address.of(ip.ip().toRealInt())); 227 + mBuilder.setExact(MatchField.IPV4_DST, IPv4Address.of(ip.ip().toInt()));
228 } 228 }
229 break; 229 break;
230 case IPV4_SRC: 230 case IPV4_SRC:
231 ip = (IPCriterion) c; 231 ip = (IPCriterion) c;
232 if (ip.ip().isMasked()) { 232 if (ip.ip().isMasked()) {
233 - Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toRealInt()), 233 + Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toInt()),
234 - IPv4Address.of(ip.ip().netmask().toRealInt())); 234 + IPv4Address.of(ip.ip().netmask().toInt()));
235 mBuilder.setMasked(MatchField.IPV4_SRC, maskedIp); 235 mBuilder.setMasked(MatchField.IPV4_SRC, maskedIp);
236 } else { 236 } else {
237 - mBuilder.setExact(MatchField.IPV4_SRC, IPv4Address.of(ip.ip().toRealInt())); 237 + mBuilder.setExact(MatchField.IPV4_SRC, IPv4Address.of(ip.ip().toInt()));
238 } 238 }
239 break; 239 break;
240 case IP_PROTO: 240 case IP_PROTO:
......
...@@ -339,9 +339,14 @@ public class LinkDiscovery implements TimerTask { ...@@ -339,9 +339,14 @@ public class LinkDiscovery implements TimerTask {
339 final Iterator<Integer> fastIterator = this.fastPorts.iterator(); 339 final Iterator<Integer> fastIterator = this.fastPorts.iterator();
340 while (fastIterator.hasNext()) { 340 while (fastIterator.hasNext()) {
341 final Integer portNumber = fastIterator.next(); 341 final Integer portNumber = fastIterator.next();
342 + OFPortDesc port = findPort(portNumber);
343 + if (port == null) {
344 + // port can be null
345 + // #removePort modifies `ports` outside synchronized block
346 + continue;
347 + }
342 final int probeCount = this.portProbeCount.get(portNumber) 348 final int probeCount = this.portProbeCount.get(portNumber)
343 .getAndIncrement(); 349 .getAndIncrement();
344 - OFPortDesc port = findPort(portNumber);
345 if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) { 350 if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
346 this.log.debug("sending fast probe to port"); 351 this.log.debug("sending fast probe to port");
347 352
......
...@@ -25,6 +25,9 @@ export ONOS_STAGE=$ONOS_STAGE_ROOT/$ONOS_BITS ...@@ -25,6 +25,9 @@ export ONOS_STAGE=$ONOS_STAGE_ROOT/$ONOS_BITS
25 export ONOS_TAR=$ONOS_STAGE.tar.gz 25 export ONOS_TAR=$ONOS_STAGE.tar.gz
26 26
27 # Defaults for ONOS testing using remote machines. 27 # Defaults for ONOS testing using remote machines.
28 +if [ -n "${ONOS_CELL}" -a -f $ONOS_ROOT/tools/test/cells/${ONOS_CELL} ]; then
29 + . $ONOS_ROOT/tools/test/cells/${ONOS_CELL}
30 +fi
28 export ONOS_INSTALL_DIR="/opt/onos" # Installation directory on remote 31 export ONOS_INSTALL_DIR="/opt/onos" # Installation directory on remote
29 export OCI="${OCI:-192.168.56.101}" # ONOS Controller Instance 32 export OCI="${OCI:-192.168.56.101}" # ONOS Controller Instance
30 export ONOS_USER="sdn" # ONOS user on remote system 33 export ONOS_USER="sdn" # ONOS user on remote system
......
...@@ -6,7 +6,13 @@ ...@@ -6,7 +6,13 @@
6 export ONOS_ROOT=${ONOS_ROOT:-~/onos-next} 6 export ONOS_ROOT=${ONOS_ROOT:-~/onos-next}
7 7
8 # Setup some environmental context for developers 8 # Setup some environmental context for developers
9 -export JAVA_HOME=${JAVA_HOME:-$(/usr/libexec/java_home -v 1.7)} 9 +if [ -z "${JAVA_HOME}" ]; then
10 + if [ -x /usr/libexec/java_home ]; then
11 + export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
12 + elif [ -d /usr/lib/jvm/java-7-openjdk-amd64 ]; then
13 + export JAVA_HOME="/usr/lib/jvm/java-7-openjdk-amd64"
14 + fi
15 +fi
10 export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2} 16 export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2}
11 export KARAF=${KARAF:-~/Applications/apache-karaf-3.0.1} 17 export KARAF=${KARAF:-~/Applications/apache-karaf-3.0.1}
12 export KARAF_LOG=$KARAF/data/log/karaf.log 18 export KARAF_LOG=$KARAF/data/log/karaf.log
...@@ -15,7 +21,6 @@ export KARAF_LOG=$KARAF/data/log/karaf.log ...@@ -15,7 +21,6 @@ export KARAF_LOG=$KARAF/data/log/karaf.log
15 export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin" 21 export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin"
16 export PATH="$PATH:$ONOS_ROOT/tools/build" 22 export PATH="$PATH:$ONOS_ROOT/tools/build"
17 export PATH="$PATH:$MAVEN/bin:$KARAF/bin" 23 export PATH="$PATH:$MAVEN/bin:$KARAF/bin"
18 -export PATH="$PATH:."
19 24
20 # Convenience utility to warp to various ONOS source projects 25 # Convenience utility to warp to various ONOS source projects
21 # e.g. 'o api', 'o dev', 'o' 26 # e.g. 'o api', 'o dev', 'o'
......
...@@ -2,13 +2,15 @@ package org.onlab.packet; ...@@ -2,13 +2,15 @@ package org.onlab.packet;
2 2
3 import java.util.Arrays; 3 import java.util.Arrays;
4 4
5 +
6 +
5 /** 7 /**
6 * A class representing an IPv4 address. 8 * A class representing an IPv4 address.
7 * <p/> 9 * <p/>
8 * TODO this class is a clone of IpPrefix and still needs to be modified to 10 * TODO this class is a clone of IpPrefix and still needs to be modified to
9 * look more like an IpAddress. 11 * look more like an IpAddress.
10 */ 12 */
11 -public final class IpAddress { 13 +public final class IpAddress implements Comparable<IpAddress> {
12 14
13 // TODO a comparator for netmasks? E.g. for sorting by prefix match order. 15 // TODO a comparator for netmasks? E.g. for sorting by prefix match order.
14 16
...@@ -121,7 +123,7 @@ public final class IpAddress { ...@@ -121,7 +123,7 @@ public final class IpAddress {
121 123
122 int mask = DEFAULT_MASK; 124 int mask = DEFAULT_MASK;
123 if (parts.length == 2) { 125 if (parts.length == 2) {
124 - mask = Integer.valueOf(parts[1]); 126 + mask = Integer.parseInt(parts[1]);
125 if (mask > MAX_INET_MASK) { 127 if (mask > MAX_INET_MASK) {
126 throw new IllegalArgumentException( 128 throw new IllegalArgumentException(
127 "Value of subnet mask cannot exceed " 129 "Value of subnet mask cannot exceed "
...@@ -174,14 +176,6 @@ public final class IpAddress { ...@@ -174,14 +176,6 @@ public final class IpAddress {
174 * @return the IP address's value as an integer 176 * @return the IP address's value as an integer
175 */ 177 */
176 public int toInt() { 178 public int toInt() {
177 - int address = 0;
178 - for (int i = 0; i < INET_LEN; i++) {
179 - address |= octets[i] << ((INET_LEN - (i + 1)) * 8);
180 - }
181 - return address;
182 - }
183 -
184 - public int toRealInt() {
185 int val = 0; 179 int val = 0;
186 for (int i = 0; i < octets.length; i++) { 180 for (int i = 0; i < octets.length; i++) {
187 val <<= 8; 181 val <<= 8;
...@@ -191,6 +185,15 @@ public final class IpAddress { ...@@ -191,6 +185,15 @@ public final class IpAddress {
191 } 185 }
192 186
193 /** 187 /**
188 + * Converts the IP address to a /32 IP prefix.
189 + *
190 + * @return the new IP prefix
191 + */
192 + public IpPrefix toPrefix() {
193 + return IpPrefix.valueOf(octets, MAX_INET_MASK);
194 + }
195 +
196 + /**
194 * Helper for computing the mask value from CIDR. 197 * Helper for computing the mask value from CIDR.
195 * 198 *
196 * @return an integer bitmask 199 * @return an integer bitmask
...@@ -280,6 +283,13 @@ public final class IpAddress { ...@@ -280,6 +283,13 @@ public final class IpAddress {
280 } 283 }
281 284
282 @Override 285 @Override
286 + public int compareTo(IpAddress o) {
287 + Long lv = ((long) this.toInt()) & 0xffffffffL;
288 + Long rv = ((long) o.toInt()) & 0xffffffffL;
289 + return lv.compareTo(rv);
290 + }
291 +
292 + @Override
283 public int hashCode() { 293 public int hashCode() {
284 final int prime = 31; 294 final int prime = 31;
285 int result = 1; 295 int result = 1;
......
...@@ -120,7 +120,7 @@ public final class IpPrefix { ...@@ -120,7 +120,7 @@ public final class IpPrefix {
120 120
121 int mask = DEFAULT_MASK; 121 int mask = DEFAULT_MASK;
122 if (parts.length == 2) { 122 if (parts.length == 2) {
123 - mask = Integer.valueOf(parts[1]); 123 + mask = Integer.parseInt(parts[1]);
124 if (mask > MAX_INET_MASK) { 124 if (mask > MAX_INET_MASK) {
125 throw new IllegalArgumentException( 125 throw new IllegalArgumentException(
126 "Value of subnet mask cannot exceed " 126 "Value of subnet mask cannot exceed "
...@@ -173,14 +173,6 @@ public final class IpPrefix { ...@@ -173,14 +173,6 @@ public final class IpPrefix {
173 * @return the IP address's value as an integer 173 * @return the IP address's value as an integer
174 */ 174 */
175 public int toInt() { 175 public int toInt() {
176 - int address = 0;
177 - for (int i = 0; i < INET_LEN; i++) {
178 - address |= octets[i] << ((INET_LEN - (i + 1)) * 8);
179 - }
180 - return address;
181 - }
182 -
183 - public int toRealInt() {
184 int val = 0; 176 int val = 0;
185 for (int i = 0; i < octets.length; i++) { 177 for (int i = 0; i < octets.length; i++) {
186 val <<= 8; 178 val <<= 8;
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
23 <module>nio</module> 23 <module>nio</module>
24 <module>osgi</module> 24 <module>osgi</module>
25 <module>rest</module> 25 <module>rest</module>
26 + <module>thirdparty</module>
26 </modules> 27 </modules>
27 28
28 <dependencies> 29 <dependencies>
......
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>
1 +package org.onlab.thirdparty;
2 +
3 +
4 +/**
5 + * Empty class required to get the onlab-thirdparty module to build properly.
6 + * <p/>
7 + * TODO Figure out how to remove this.
8 + */
9 +public class OnlabThirdparty {
10 +
11 +}