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 | +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 | ... | ... |
... | @@ -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 | } | ... | ... |
core/store/dist/src/main/java/org/onlab/onos/store/host/impl/HostAntiEntropyAdvertisement.java
0 → 100644
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 | 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 | } | ... | ... |
core/store/hz/net/pom.xml
deleted
100644 → 0
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> |
core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
deleted
100644 → 0
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 | -} |
core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
deleted
100644 → 0
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 | -} |
core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/DistributedHostStore.java
deleted
100644 → 0
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 | -} |
core/store/hz/net/src/main/java/org/onlab/onos/store/link/impl/DistributedLinkStore.java
deleted
100644 → 0
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 | -} |
core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
deleted
100644 → 0
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 | -} |
core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopologyGraph.java
deleted
100644 → 0
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 | -} |
core/store/hz/net/src/test/java/org/onlab/onos/store/link/impl/DistributedLinkStoreTest.java
deleted
100644 → 0
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 | -} |
core/store/serializers/src/main/java/org/onlab/onos/store/serializers/HostLocationSerializer.java
0 → 100644
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 | ... | ... |
core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java
0 → 100644
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; | ... | ... |
utils/thirdparty/pom.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
5 | + <modelVersion>4.0.0</modelVersion> | ||
6 | + | ||
7 | + <parent> | ||
8 | + <groupId>org.onlab.onos</groupId> | ||
9 | + <artifactId>onlab-utils</artifactId> | ||
10 | + <version>1.0.0-SNAPSHOT</version> | ||
11 | + <relativePath>../pom.xml</relativePath> | ||
12 | + </parent> | ||
13 | + | ||
14 | + <artifactId>onlab-thirdparty</artifactId> | ||
15 | + <packaging>bundle</packaging> | ||
16 | + | ||
17 | + <description>ONLab third-party dependencies</description> | ||
18 | + | ||
19 | + <dependencies> | ||
20 | + <dependency> | ||
21 | + <groupId>com.googlecode.concurrent-trees</groupId> | ||
22 | + <artifactId>concurrent-trees</artifactId> | ||
23 | + <version>2.4.0</version> | ||
24 | + </dependency> | ||
25 | + </dependencies> | ||
26 | + | ||
27 | + <build> | ||
28 | + <plugins> | ||
29 | + <plugin> | ||
30 | + <groupId>org.apache.maven.plugins</groupId> | ||
31 | + <artifactId>maven-shade-plugin</artifactId> | ||
32 | + <version>2.3</version> | ||
33 | + <configuration> | ||
34 | + <filters> | ||
35 | + <filter> | ||
36 | + <artifact>com.googlecode.concurrent-trees:concurrent-trees</artifact> | ||
37 | + <includes> | ||
38 | + <include>com/googlecode/**</include> | ||
39 | + </includes> | ||
40 | + | ||
41 | + </filter> | ||
42 | + <filter> | ||
43 | + <artifact>com.google.guava:guava</artifact> | ||
44 | + <excludes> | ||
45 | + <exclude>**</exclude> | ||
46 | + </excludes> | ||
47 | + </filter> | ||
48 | + </filters> | ||
49 | + </configuration> | ||
50 | + <executions> | ||
51 | + <execution> | ||
52 | + <phase>package</phase> | ||
53 | + <goals> | ||
54 | + <goal>shade</goal> | ||
55 | + </goals> | ||
56 | + </execution> | ||
57 | + </executions> | ||
58 | + </plugin> | ||
59 | + <plugin> | ||
60 | + <groupId>org.apache.felix</groupId> | ||
61 | + <artifactId>maven-bundle-plugin</artifactId> | ||
62 | + <configuration> | ||
63 | + <instructions> | ||
64 | + <Export-Package> | ||
65 | + com.googlecode.concurrenttrees.* | ||
66 | + </Export-Package> | ||
67 | + </instructions> | ||
68 | + </configuration> | ||
69 | + </plugin> | ||
70 | + </plugins> | ||
71 | + </build> | ||
72 | + | ||
73 | +</project> |
-
Please register or login to post a comment